Давно хотел разобраться с "
крутилкой". Наконец появилось время и желание это сделать. В паутине нашел три метода обработки
кода грея для энкодера.
Первый самый примитивный. Использует тонны
if-else и програмный debounce на
millis(). Работает медленно. Вот скриншот такой копипасты.
счетчик энкодера у курильщика с ардуино головного мозга
С переходными процессами в этом примере разбираются без таймера. Тупо ждут микросекунду. Шах и мат перфекционисты. Вторая особенность - вешают функцию onA() на первое прерывание, а onB() на второе. Зачем? Никто не запрещает читать encoder_A_Pin вместе с encoder_B_Pin при срабатывании внешнего прерывания на onA(). Освободившийся interrupt можно использовать для кнопки или второго энкодера.
Второй метод на основе массива всех возможных состояний энкодера. А их не много не мало 16 штук. Достоинства. Простота кода. Чуть-чуть быстрее if-else. Автоматический debounce - все ложные состояния отбрасываются. Правда при сильном дребезге отбрасываются и истиные значения - энкодер кликает, а счетчик не срабатывает. Лечится
добавлением 100nF/0.1μF конденсаторов между ногами энкодера и землей.
таблица состояний энкодера
Самый продвинутый - это доработанный второй. Из 16 состояний удаляются бесполезные. Какая нам польза от знания где крутилка до/после клика? На основе оставшихся 4-х комбинаций с помощью
булевой алгебры и
switch-case делается простейший счетчик. Все! Правда есть нюанс. Функция
digitalRead() оказалась настолько медленной, что ATmega328 не успевал читать значения pinA и pinB при срабатывании внешнего прерывания с условием CHANGE на pin A. Поэтому для AVR пришлось использовать
прерывание по Timer1. Каждые 0.01 секунд срабатывает таймер и AVR не спеша читает состояние пинов и обновляет счетчик энкодера. Для быстрых STM32 и ESP8266 все работает на внешнем прерывании - как только энкодер начинает крутиться, срабатывает внешнее прерывание на pinA, считываются значения pinA и pinB и обновляется позиция энкодера. UDP: Версия библиотеки подросла до 1.4.1 - заменен тормозной digitalRead() для ATmega328 . Тепрь AVR работает без костыля Timer1.
счетчик энкодера здорового человека
Библиотека подсчитывает только физические клики, оставляя все лишнее за бортом. Внутренняя подтяжка включена и доплнительные резисторы не нужны. У популярного шилда KY-040 10КОм уже есть на плате и подключать их НЕ НАДО.
Чтобы подавить дребезг и избежать пропуск кликов нужно добавить конденсаторы:
- 100nF/0.1μF между A и землей
- 100nF/0.1μF между B и землей
- 100nF/0.1μF между кнопкой и землей
БЕЗ КОНДЕНСАТОРОВ БИБЛИОТЕКА РАБОТАТЬ НЕ БУДЕТ!!!
железный debounce
С теорией по методам устранения дребезга можно ознакомится
здесь. Калькулятор для подбора гасящего конденсатора
тут. Чем больше емкость конденсаторов, тем выше износ контактов энкодера.
расшифровка контактов KY-040
UDP: Переписал код. Теперь еще быстрее, меньше в размере и винарнее. Для кого-то недостаток, для кого-то достоинство, но теперь без аппаратных прерываний не работает. Спасибо товарищу
kotyamba за консультацию и знания.
UDP2: Пришлось опять переписать код. Как правильно заметил
ksergey9 в комментариях, наше с kotyamba творение более менее нормально работало на медленном AVR. Как только его запускали на быстрых Cortex все превращалось в тыкву. Тепрь все ОК. Лично проверил на Arduino Nano 16Mhz,
STM32 Blue Pill и ESP8266.
UDP3: Воспользовался
ООП и путем наследования сделал тяжелый класс с float - "RotaryEncoderAdvanced". В нем можно прописывать количество шагов на клик, минимальное и максимальное значение. Получился законченный велосипед. Естественно легкий класс "RotaryEncoder" никуда не делся и работает без изменений.
UDP4: Переделал "RotaryEncoderAdvanced" на
template, прощай float. Библиотека может занимать меньше памяти - все зависит от типа используемых переменных. Добавил возможность на лету менять - step per click, minimum value и maximum value. Управляем множеством различных значений с помощью одного энкодера!!!
UDP5: Тормозной digitalRead() для ATmega328 заменен на быстрый, теперь все работает без костыля Timer1.
Забирать как всегда
тут.