ПОДКЛЮЧЕНИЕ ЭНКОДЕРА К ARDUINO
ПОДКЛЮЧЕНИЕ
Подключается модуль энкодера очень просто: питание на питание (GND и VCC), логические пины CLK, DT (тактовые выводы энкодера) и SW (вывод кнопки) на любые пины Arduino (D или A). У круглых модулей выводы энкодера подписаны как S1 и S2, а вывод кнопки как Key, подключаются точно так же. От порядка подключения тактовых выводов энкодера зависит “направление” его работы, но это можно поправить в программе.
У модулей энкодера тактовые выводы подтянуты к питанию и дают низкий сигнал при срабатывании, также на них стоят RC цепи для гашения дребезга. Вывод кнопки никуда не подтянут! Промышленный энкодер подключается точно так же, чёрный и красный провода у него питание, остальные – тактовые выходы.
У модулей энкодеров тактовые выходы и кнопка подтянуты к питанию, у круглого модуля также стоят RC цепи для аппаратного подавления дребезга контактов, у KY-40 (прямоугольный) распаяна только подтяжка. Если нужно подключить “голый” энкодер к плате – в целом можно подключить напрямую без обвязки, как на схеме ниже, моя библиотека отработает и подтяжку средствами микроконтроллера (INPUT_PULLUP), и программный антидребезг. Но рекомендуется всё-таки делать RC цепи для кнопки и для тактовых выходов энкодера.
Чем отличаются энкодеры на практике: если опрашивать одноимпульсный энкодер как двухимпульсный, то для отработки одного тика нужно повернуть рукоятку на два тика. Если опрашивать двухимпульсный как одноимпульсный, то для отработки одного тика нужно повернуть рукоятку на два тика. То есть при неправильном использовании причина сразу видна.
ПРОГРАММИРОВАНИЕ
Я не нашёл в интернете нормальных библиотек для энкодера с хорошей функциональностью, поэтому написал свою, GyverEncoder. Что умеет:
- Отработка поворота рукоятки энкодера
- Обычный поворот
- “Нажатый поворот”
- “Быстрый” поворот
- Три алгоритма опроса энкодера
- Быстрый – но не справляется с люфтами
- Бинарный – медленнее, лучше справляется с люфтами
- Высокоточный – ещё медленнее, но работает даже с убитым энкодером
- Возможность работы с “виртуальным” энкодером – через расширитель пинов или ещё как
- Работа с двумя типами энкодеров (тип 1 и 2, см. выше)
- Работа с кнопкой энкодера:
- Отработка нажатия
- Клика
- Двойного клика
- Удержания
- Антидребезг контактов
- Возможность полностью убрать код кнопки для быстродействия
Поддерживаемые платформы: все Arduino (используются стандартные Wiring-функции)
– Оптимизирован код
– Исправлены баги
– Добавлены другие алгоритмы опроса
– Добавлена возможность полностью убрать кнопку (экономия памяти)
– Добавлена возможность подключения внешнего энкодера
– Добавлена настройка подтяжки пинов
– 4.1
– Исправлено изменение подтяжек
– 4.2
– Добавлена поддержка TYPE1 для алгоритма PRECISE_ALGORITHM
– Добавлена отработка двойного клика: isSingle / isDouble
– 4.3: Исправлено ложное isSingle
– 4.4: Добавлен метод resetStates, сбрасывает все is-флаги и счётчики
– 4.5: Улучшен алгоритм BINARY_ALGORITHM (спасибо Ярославу Курусу)
– 4.6: BINARY_ALGORITHM пофикшен для TYPE1, добавлена isReleaseHold
– 4.7: Исправлен случайный нажатый поворот в BINARY_ALGORITHM
– 4.8: увеличена производительность для AVR Arduino
Инициализация
Объект энкодера может быть создан несколькими способами:
Encoder enc; // не привязан к пину (для виртуального энкодера, см. пример) Encoder enc(пин CLK, пин DT); // энкодер без кнопки (ускоренный опрос) Encoder enc(пин CLK, пин DT, пин SW); // энкодер с кнопкой Encoder enc(пин CLK, пин DT, пин SW, тип); // энкодер с кнопкой и указанием типа Encoder enc(пин CLK, пин DT, ENC_NO_BUTTON, тип); // энкодер без кнопки и с указанием типа
Опрос
Опрос энкодера происходит в методе .tick()
, после чего можно узнать состояние энкодера из методов is*. Сам .tick()
должен вызываться как можно чаще:
- В
loop()
– у вас должен быть “прозрачный”loop()
без задержек - В прерывании таймера – достаточно опрашивать энкодер каждые 5 мс (зависит от скорости поворота)
- В аппаратном прерывании (достаточно завести одну таковую ногу энкодера)
Для “расшифровки” состояния энкодера используются следующие методы:
isTurn();
// возвращает true при любом повороте, сама сбрасывается в falseisRight();
// возвращает true при повороте направо, сама сбрасывается в falseisLeft();
// возвращает true при повороте налево, сама сбрасывается в falseisRightH();
// возвращает true при удержании кнопки и повороте направо, сама сбрасывается в falseisLeftH();
// возвращает true при удержании кнопки и повороте налево, сама сбрасывается в falseisFastR();
// возвращает true при быстром поворотеisFastL();
// возвращает true при быстром повороте
Для кнопки энкодера:
isPress();
// возвращает true при нажатии кнопки, сама сбрасывается в falseisRelease();
// возвращает true при отпускании кнопки, сама сбрасывается в falseisClick();
// возвращает true при нажатии и отпускании кнопки, сама сбрасывается в falseisHolded();
// возвращает true при удержании кнопки, сама сбрасывается в falseisHold();
// возвращает true при удержании кнопки, НЕ СБРАСЫВАЕТСЯisSingle();
// возвращает true при одиночном клике (после таймаута), сама сбрасывается в falseisDouble();
// возвращает true при двойном клике, сама сбрасывается в false
Примечание: isClick()
возвращает true
сразу же после отпускания кнопки, в то время как isSingle()
возвращает true
после таймаута, во время которого можно сделать второй клик и поймать уже двойной клик при помощи isDouble()
.
В версии 4.4 появился метод resetStates()
, который принудительно сбрасывает все флаги is-методов
Настройки в скетче
Некоторые параметры работы энкодера можно настроить из программы:
setType(type);
// тип энкодераTYPE1
одношаговый,TYPE2
двухшаговый. Если ваш энкодер работает странно, смените типsetTickMode(tickMode);
//MANUAL
/AUTO
– ручной или автоматический опрос энкодера функциейtick()
(по умолчанию ручной)setDirection(direction);
//NORM
/REVERSE
– направление вращения энкодераsetFastTimeout(timeout);
// установка таймаута быстрого поворотаsetPinMode(mode);
// тип подключения пинов энкодера, подтяжкаHIGH_PULL
(внутренняя) илиLOW_PULL
(внешняя на GND)setBtnPinMode(mode);
// тип подключения кнопки, подтяжкаHIGH_PULL
(внутренняя) илиLOW_PULL
(внешняя на GND)
Настройки в библиотеке
В заголовочном файле библиотеки (GyverEncoder.h) есть несколько дополнительных настроек:
Время:
ENC_DEBOUNCE_TURN 1
// время антидребезга для энкодера, миллисекундENC_DEBOUNCE_BUTTON 80
// время антидребезга для кнопки, миллисекундENC_HOLD_TIMEOUT 700
// таймаут удержания кнопки, миллисекундENC_DOUBLE_TIMEOUT 300
// таймаут двойного клика
Использование кнопки:
#define ENC_WITH_BUTTON
// если закомментировать данную строку, опрос кнопки будет полностью “убран” из кода, что сделает его легче и чуть быстрее
Логика подключения:
#define DEFAULT_ENC_PULL LOW_PULL
// тип подключения энкодера по умолчанию (LOW_PULL или HIGH_PULL)#define DEFAULT_BTN_PULL HIGH_PULL
// тип подключения кнопки энкодера по умолчанию (LOW_PULL или HIGH_PULL)
Алгоритмы опроса энкодера
Алгоритм работы библиотеки можно выбрать в заголовочном файле библиотеки (GyverEncoder.h), для этого нужно раскомментировать одну из строк с дефайнами алгоритмов:
#define FAST_ALGORITHM
// быстрый, не справляется с люфтами#define BINARY_ALGORITHM
// медленнее, лучше справляется с люфтами#define PRECISE_ALGORITHM
// медленнее, но работает даже с убитым энкодером (по мотивам https://github.com/mathertel/RotaryEncoder)
Работа с “виртуальным” энкодером
Версия библиотеки 4+ поддерживает работу с виртуальным энкодером, т.е. алгоритм опрашивает не напрямую цифровой пин микроконтроллера, а логическую величину, которую ему передадут. Таким образом можно попробовать опрашивать несколько энкодеров, подключенных через расширитель пинов. Для работы с таким энкодером нужно инициализировать энкодер без указания пина:
Encoder enc; // не привязан к пину
Работа с таким энкодером ничем не отличается от обычного, кроме метода tick()
– в него нужно передать состояния тактовых пинов энкодера (CLK и DT), а также пина кнопки (опционально):
enc1.tick(stateCLK, stateDT, stateSW); // с кнопкой enc1.tick(stateCLK, stateDT); // без кнопки
Смотрите пример external_enc в папке с примерами
Encoder(); // для непривязанного к пинам энкодера Encoder(uint8_t clk, uint8_t dt, int8_t sw = -1, bool type = false); // CLK, DT, SW, тип (TYPE1 / TYPE2) TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип // Варианты инициализации: // Encoder enc; // не привязан к пину // Encoder enc(пин CLK, пин DT); // энкодер без кнопки (ускоренный опрос) // Encoder enc(пин CLK, пин DT, пин SW); // энкодер с кнопкой // Encoder enc(пин CLK, пин DT, пин SW, тип); // энкодер с кнопкой и указанием типа // Encoder enc(пин CLK, пин DT, ENC_NO_BUTTON, тип); // энкодер без кнопкой и с указанием типа void tick(); // опрос энкодера, нужно вызывать постоянно или в прерывании void setType(boolean type); // TYPE1 / TYPE2 - тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип void setTickMode(boolean tickMode); // MANUAL / AUTO - ручной или автоматический опрос энкодера функцией tick(). (по умолчанию ручной) void setDirection(boolean direction); // NORM / REVERSE - направление вращения энкодера void setFastTimeout(int timeout); // установка таймаута быстрого поворота void setPinMode(bool mode); // тип подключения энкодера, подтяжка HIGH_PULL (внутренняя) или LOW_PULL (внешняя на GND) void setBtnPinMode(bool mode); // тип подключения кнопки, подтяжка HIGH_PULL (внутренняя) или LOW_PULL (внешняя на GND) boolean isTurn(); // возвращает true при любом повороте, сама сбрасывается в false boolean isRight(); // возвращает true при повороте направо, сама сбрасывается в false boolean isLeft(); // возвращает true при повороте налево, сама сбрасывается в false boolean isRightH(); // возвращает true при удержании кнопки и повороте направо, сама сбрасывается в false boolean isLeftH(); // возвращает true при удержании кнопки и повороте налево, сама сбрасывается в false boolean isFastR(); // возвращает true при быстром повороте boolean isFastL(); // возвращает true при быстром повороте boolean isPress(); // возвращает true при нажатии кнопки, сама сбрасывается в false boolean isRelease(); // возвращает true при отпускании кнопки, сама сбрасывается в false boolean isClick(); // возвращает true при нажатии и отпускании кнопки, сама сбрасывается в false boolean isHolded(); // возвращает true при удержании кнопки, сама сбрасывается в false boolean isHold(); // возвращает true при удержании кнопки, НЕ СБРАСЫВАЕТСЯ boolean isSingle(); // возвращает true при одиночном клике (после таймаута), сама сбрасывается в false boolean isDouble(); // возвращает true при двойном клике, сама сбрасывается в false void resetStates(); // сбрасывает все is-флаги
ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
Библиотека GyverEncoder
#define CLK 2 #define DT 3 #define SW 4 #include "GyverEncoder.h" //Encoder enc1(CLK, DT); // для работы без кнопки Encoder enc1(CLK, DT, SW); // для работы c кнопкой //Encoder enc1(CLK, DT, SW, TYPE2); // для работы c кнопкой и сразу выбираем тип //Encoder enc1(CLK, DT, ENC_NO_BUTTON, TYPE2); // для работы без кнопки и сразу выбираем тип // Варианты инициализации: // Encoder enc; // не привязан к пину // Encoder enc(пин CLK, пин DT); // энкодер без кнопки (ускоренный опрос) // Encoder enc(пин CLK, пин DT, пин SW); // энкодер с кнопкой // Encoder enc(пин CLK, пин DT, пин SW, тип); // энкодер с кнопкой и указанием типа // Encoder enc(пин CLK, пин DT, ENC_NO_BUTTON, тип); // энкодер без кнопкой и с указанием типа void setup() { Serial.begin(9600); enc1.setType(TYPE2); } void loop() { // обязательная функция отработки. Должна постоянно опрашиваться enc1.tick(); if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону) // ваш код } if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс) if (enc1.isClick()) Serial.println("Click"); // отпускание кнопки (+ дебаунс) //if (enc1.isRelease()) Serial.println("Release"); // то же самое, что isClick if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался //if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки }
#define CLK 7 #define DT 8 #define SW 9 #include "GyverEncoder.h" Encoder enc1(CLK, DT, SW); int value = 0; void setup() { Serial.begin(9600); enc1.setType(TYPE2); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип } void loop() { // обязательная функция отработки. Должна постоянно опрашиваться enc1.tick(); if (enc1.isRight()) value++; // если был поворот направо, увеличиваем на 1 if (enc1.isLeft()) value--; // если был поворот налево, уменьшаем на 1 if (enc1.isRightH()) value += 5; // если было удержание + поворот направо, увеличиваем на 5 if (enc1.isLeftH()) value -= 5; // если было удержание + поворот налево, уменьшаем на 5 if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону) Serial.println(value); // выводим значение при повороте } }
#define CLK 2 #define DT 3 #define SW 4 #include "GyverEncoder.h" Encoder enc1(CLK, DT, SW); int value = 0; void setup() { Serial.begin(9600); enc1.setType(TYPE1); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип\= enc1.setFastTimeout(40); // таймаут на скорость isFastR. По умолч. 50 } void loop() { // обязательная функция отработки. Должна постоянно опрашиваться enc1.tick(); if (enc1.isRight()) value++; // если был поворот направо, увеличиваем на 1 if (enc1.isLeft()) value--; // если был поворот налево, уменьшаем на 1 if (enc1.isRightH()) value += 5; // если было удержание + поворот направо, увеличиваем на 5 if (enc1.isLeftH()) value -= 5; // если было удержание + поворот налево, уменьшаем на 5 if (enc1.isFastR()) value += 10; // если был быстрый поворот направо, увеличиваем на 10 if (enc1.isFastL()) value -= 10; // если был быстрый поворот налево, уменьшаем на 10 if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону) Serial.println(value); // выводим значение при повороте } }
// два энкодера #include "GyverEncoder.h" Encoder enc1(4, 3, 2); Encoder enc2(7, 6, 5); void setup() { Serial.begin(9600); } void loop() { // обязательная функция отработки. Должна постоянно опрашиваться enc1.tick(); enc2.tick(); if (enc1.isLeft()) Serial.println("enc 1 left"); if (enc1.isRight()) Serial.println("enc 1 right"); if (enc2.isLeft()) Serial.println("enc 2 left"); if (enc2.isRight()) Serial.println("enc 2 right"); }
/* В последнее время китайцы стали делать одинаковые модули (ку 40) с разными типами энкодеров - полный период и полпериода. Если ваш энкодер ведёт себя странно (один тик считает за два поворота), то смените тип энкодера */ #define CLK 4 #define DT 3 #define SW 2 #include "GyverEncoder.h" Encoder enc1(CLK, DT, SW); void setup() { Serial.begin(9600); enc1.setType(TYPE2); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип } void loop() { // обязательная функция отработки. Должна постоянно опрашиваться enc1.tick(); if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); }
#define CLK 6 #define DT 5 #define SW 4 #include "GyverEncoder.h" Encoder enc1(CLK, DT, SW); void setup() { Serial.begin(9600); enc1.setTickMode(AUTO); } void loop() { // enc1.tick(); // не нужна, в этом режиме (AUTO) она входит в каждую функцию! if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону) // ваш код } if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс) if (enc1.isRelease()) Serial.println("Release"); // отпускание кнопки (+ дебаунс) if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался //if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки }
/* Пример работы с энкодером с прерыванием. Максимальная чёткость работы в любом быдлокоде! */ #define CLK 2 #define DT 3 #define SW 4 #include "GyverEncoder.h" Encoder enc1(CLK, DT, SW); void setup() { Serial.begin(9600); attachInterrupt(0, isr, CHANGE); // прерывание на 2 пине! CLK у энка } void isr() { enc1.tick(); // отработка в прерывании } void loop() { enc1.tick(); // отработка if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); }
/* * Отработка по прерыванию таймера */ #define CLK 7 #define DT 8 #define SW 9 #include "GyverEncoder.h" #include "TimerOne.h" Encoder enc1(CLK, DT, SW); void setup() { Serial.begin(9600); enc1.setType(TYPE2); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип Timer1.initialize(1000); // установка таймера на каждые 1000 микросекунд (= 1 мс) Timer1.attachInterrupt(timerIsr); // запуск таймера } void timerIsr() { // прерывание таймера enc1.tick(); // отработка теперь находится здесь } void loop() { if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); }
// пример с прерываниями pinChangeInterrupt (прерывания на любом пине) // только для ATmega328 (UNO, Nano, Pro Mini) #define SW 0 #define DT 2 #define CLK 3 #include "GyverEncoder.h" Encoder enc1(CLK, DT, SW); void setup() { Serial.begin(9600); // настроить PCINT attachPCINT(CLK); attachPCINT(DT); } void loop() { enc1.tick(); // оставляем тут для работы "временных" функций и антидребезга if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); } // функция для настройки PCINT для ATmega328 (UNO, Nano, Pro Mini) uint8_t attachPCINT(uint8_t pin) { if (pin < 8) { // D0-D7 // PCINT2 PCICR |= (1 << PCIE2); PCMSK2 |= (1 << pin); return 2; } else if (pin > 13) { //A0-A5 // PCINT1 PCICR |= (1 << PCIE1); PCMSK1 |= (1 << pin - 14); return 1; } else { // D8-D13 // PCINT0 PCICR |= (1 << PCIE0); PCMSK0 |= (1 << pin - 8); return 0; } } // Векторы PCINT, нужно кинуть сюда тики // не обязательно в каждый вектор, достаточно в тот, который задействован // пины 0-7: PCINT2 // пины 8-13: PCINT0 // пины A0-A5: PCINT1 ISR(PCINT0_vect) { //enc1.tick(); } ISR(PCINT1_vect) { //enc1.tick(); } ISR(PCINT2_vect) { enc1.tick(); }
// подключаем "внешний" энкодер, для работы с расширителями пинов например #define SW 0 #define DT 2 #define CLK 3 #include "GyverEncoder.h" Encoder enc1; void setup() { Serial.begin(9600); enc1.setType(TYPE2); pinMode(SW, INPUT_PULLUP); pinMode(CLK, INPUT); pinMode(DT, INPUT); } void loop() { // подаём значения напрямую в tick() // можно подавать лог. величины с любых расширителей пинов!!! // Здесь в качестве примера digitalRead enc1.tick(digitalRead(CLK), digitalRead(DT), !digitalRead(SW)); if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону) // ваш код } if (enc1.isRight()) Serial.println("Right"); // если был поворот if (enc1.isLeft()) Serial.println("Left"); if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот if (enc1.isLeftH()) Serial.println("Left holded"); if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс) if (enc1.isClick()) Serial.println("Click"); // отпускание кнопки (+ дебаунс) //if (enc1.isRelease()) Serial.println("Release"); // то же самое, что isClick if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался //if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки }
Мини-библиотека
Также у меня есть мини-версия библиотеки энкодера, у неё меньше возможностей, но она гораздо “легче”. Скачать релиз можно вот по этой ссылке, в нём есть пример – encTest. “Библиотека” лежит рядом со скетчем, файл encMinim.h.
// пины энкодера #define SW 0 #define DT 2 #define CLK 3 #include "encMinim.h" encMinim enc(CLK, DT, SW, 1, 1); // пин clk, пин dt, пин sw, направление (0/1), тип (0/1) void setup() { Serial.begin(9600); } void loop() { enc.tick(); if (enc.isTurn()) Serial.println("turn"); if (enc.isLeft()) Serial.println("left"); if (enc.isRight()) Serial.println("right"); if (enc.isLeftH()) Serial.println("leftH"); if (enc.isRightH()) Serial.println("rightH"); if (enc.isClick()) Serial.println("click"); }
Лёгкий быстрый код
Если вам важна не функциональность библиотеки, а максимальная скорость работы с энкодером и минимальное время его опроса, предлагаю следующий код. Здесь используется аппаратное прерывание (одно на один энкодер), для увеличения скорости выполнения прерывания используется функция bitRead вместо digitalRead (скорость выполнения разнится в десятки раз). Также в этом коде предусмотрен выбор типа энкодера соответствующей настройкой. Отработка энкодера идёт параллельно выполнению скетча, переменная encCounter меняет своё значение при повороте рукоятки и выводится через порт.
/* Максимально быстрый универсальный код для обработки энкодера Работает на перывании (используется одно) Тут код построен на bitRead(PIND..) - только для Arduino NANO! */ #define ENC_A 2 // пин энкодера #define ENC_B 4 // пин энкодера #define ENC_TYPE 1 // тип энкодера, 0 или 1 volatile int encCounter; volatile boolean state0, lastState, turnFlag; void setup() { Serial.begin(9600); attachInterrupt(0, int0, CHANGE); } void int0() { state0 = bitRead(PIND, ENC_A); if (state0 != lastState) { #if (ENC_TYPE == 1) turnFlag = !turnFlag; if (turnFlag) encCounter += (bitRead(PIND, ENC_B) != lastState) ? -1 : 1; #else encCounter += (bitRead(PIND, ENC_B) != lastState) ? -1 : 1; #endif lastState = state0; } } void loop() { Serial.println(encCounter); delay(100); }
/* Максимально быстрый универсальный код для обработки энкодера Работает на перывании (используется одно) Тут код построен на digitalRead, что делает его универсальным для всех плат Arduino */ #define ENC_A 2 // пин энкодера #define ENC_B 4 // пин энкодера #define ENC_TYPE 1 // тип энкодера, 0 или 1 volatile int encCounter; volatile boolean state0, lastState, turnFlag; void setup() { Serial.begin(9600); attachInterrupt(0, int0, CHANGE); } void int0() { state0 = digitalRead(ENC_A); if (state0 != lastState) { #if (ENC_TYPE == 1) turnFlag = !turnFlag; if (turnFlag) encCounter += (digitalRead(ENC_B) != lastState) ? -1 : 1; #else encCounter += (digitalRead(ENC_B) != lastState) ? -1 : 1; #endif lastState = state0; } } void loop() { Serial.println(encCounter); delay(100); }
// алгоритм с "таблицей", позволяющий увеличить точность энкодера // в 4 раза, работает максимально чётко даже с плохими энкодерами. // Для увеличения скорости опроса используйте PCINT и чтение из PINn #define CLK 3 #define DT 2 long pos = 0; byte lastState = 0; const int8_t increment[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; void setup() { Serial.begin(9600); } void loop() { byte state = digitalRead(CLK) | (digitalRead(DT) << 1); if (state != lastState) { pos += increment[state | (lastState << 2)]; lastState = state; Serial.println(pos); } }
#define ENC_A 2 // пин энкодера #define ENC_B 3 // пин энкодера volatile int encCounter; volatile boolean flag, resetFlag; volatile byte curState, prevState; void setup() { Serial.begin(115200); attachInterrupt(0, int0, CHANGE); attachInterrupt(1, int1, CHANGE); } void int0() { encTick(); } void int1() { encTick(); } // алгоритм со сбросом от Ярослава Куруса void encTick() { curState = digitalRead(ENC_A) | digitalRead(ENC_B) << 1; // digitalRead хорошо бы заменить чем-нибудь более быстрым if (resetFlag && curState == 0b11) { if (prevState == 0b10) encCounter++; if (prevState == 0b01) encCounter--; resetFlag = 0; flag = true; } if (curState == 0b00) resetFlag = 1; prevState = curState; } void loop() { if (flag) { Serial.println(encCounter); flag = 0; } }
// тест энкодера на прерываниях pin change для ATmega328. Алгоритм со сбросом от Ярослава Куруса #define ENC_A 2 // пин энкодера #define ENC_B 3 // пин энкодера volatile int encCounter; volatile boolean resetFlag; volatile byte curState, prevState; void setup() { Serial.begin(9600); attachPCINT(ENC_A); attachPCINT(ENC_B); pinMode(ENC_A, INPUT_PULLUP); pinMode(ENC_B, INPUT_PULLUP); } void encTick() { curState = readPin(ENC_A) | readPin(ENC_B) << 1; if (resetFlag && curState == 0b11) { if (prevState == 0b10) encCounter++; if (prevState == 0b01) encCounter--; resetFlag = 0; } if (curState == 0b00) resetFlag = 1; prevState = curState; } void loop() { Serial.println(encCounter); delay(500); // задержка для теста. Энкодер опрашивается во время неё! } // функция для настройки PCINT для ATmega328 (UNO, Nano, Pro Mini) uint8_t attachPCINT(uint8_t pin) { if (pin < 8) { // D0-D7 // PCINT2 PCICR |= (1 << PCIE2); PCMSK2 |= (1 << pin); return 2; } else if (pin > 13) { //A0-A5 // PCINT1 PCICR |= (1 << PCIE1); PCMSK1 |= (1 << pin - 14); return 1; } else { // D8-D13 // PCINT0 PCICR |= (1 << PCIE0); PCMSK0 |= (1 << pin - 8); return 0; } } // быстрый аналог degetalRead boolean readPin(uint8_t pin) { if (pin < 8) return bitRead(PIND, pin); else if (pin < 14) return bitRead(PINB, pin - 8); else if (pin < 20) return bitRead(PINC, pin - 14); else return false; } // Векторы PCINT, нужно кинуть сюда тики // не обязательно в каждый вектор, достаточно в тот, который задействован // пины 0-7: PCINT2 // пины 8-13: PCINT0 // пины A0-A5: PCINT1 ISR(PCINT0_vect) { } ISR(PCINT1_vect) { } ISR(PCINT2_vect) { encTick(); }
УСТАНОВКА БИБЛИОТЕКИ
Если вы не знаете, как установить библиотеку – читайте отдельный урок по работе с библиотеками!
БАГИ И ОШИБКИ
Если вы нашли баг или ошибку в исходнике или примерах, или у вас есть идеи по доработке библиотеки – пишите пожалуйста на почту alex@alexgyver.ru. В комментарии на страницах я заглядываю очень редко, на форум – ещё реже.
ОСТАЛЬНЫЕ БИБЛИОТЕКИ

У меня есть ещё очень много всего интересного! Смотрите полный список библиотек вот здесь.