ПОДКЛЮЧЕНИЕ ЭНКОДЕРА К ARDUINO

Энкодер (от англ. encode – преобразовывать) – это устройство для преобразования угловых положений или линейных перемещений в цифровой сигнал, т.е. энкодер – это датчик угла или линейного перемещения, соответственно есть крутильные и линейные энкодеры. Принцип работы энкодера заключается в преобразовании механического перемещения в электрические сигналы, у обычного инкрементального энкодера, который мы будем рассматривать, этот сигнал представляет собой два квадратных сигнала (при равномерном вращении), сдвинутых по фазе на 90 градусов.
Большая куча теории “как это работает” с примерами прошивок (работоспособность не гарантируется) – ссылка. Так как мы рассматриваем применение энкодера в паре с Arduino – логичнее будет рассматривать готовые энкодерные модули, коих у китайцев есть несколько разных.

Самый классический энкодерный модуль KY-040. Имеет 28 тиков на оборот, рукоятка является кнопкой (отдельный выход). Тип энкодера – 1 или 2 импульсный, китайцы могут прислать любой (о типах читайте ниже)

Более новый модуль, гораздо меньший процент брака. Имеет 28 тиков на оборот, рукоятка является кнопкой (отдельный выход). Тип энкодера – 2 импульсный.

Промышленный энкодер – надёжная и точная штука: металлический корпус, подшипниковый узел. Имеет 100, 200, 300, 360, 400, 600, 1000 тиков на оборот (на выбор). Тип энкодера – 2.

ПОДКЛЮЧЕНИЕ

Подключается модуль энкодера очень просто: питание на питание (GND и VCC), логические пины CLK, DT (тактовые выводы энкодера) и SW (вывод кнопки) на любые пины Arduino (D или A). У круглых модулей выводы энкодера подписаны как S1 и S2, а вывод кнопки как Key, подключаются точно так же. От порядка подключения тактовых выводов энкодера зависит “направление” его работы, но это можно поправить в программе.

У модулей энкодера тактовые выводы подтянуты к питанию и дают низкий сигнал при срабатывании, также на них стоят RC цепи для гашения дребезга. Вывод кнопки никуда не подтянут! Промышленный энкодер подключается точно так же, чёрный и красный провода у него питание, остальные – тактовые выходы.

У модулей энкодеров тактовые выходы и кнопка подтянуты к питанию, у круглого модуля также стоят RC цепи для аппаратного подавления дребезга контактов, у KY-40 (прямоугольный) распаяна только подтяжка. Если нужно подключить “голый” энкодер к плате – в целом можно подключить напрямую без обвязки, как на схеме ниже, моя библиотека отработает и подтяжку средствами микроконтроллера (INPUT_PULLUP), и программный антидребезг. Но рекомендуется всё-таки делать RC цепи для кнопки и для тактовых выходов энкодера.

Подключаем модуль

Схема KY-40

Голый энкодер без обвязки

Схема круглого модуля

RC цепь на выводы энка

Бывает два типа энкодеров, я назвал их одноимпульсные и двухимпульсные, тип энкодера можно определить по внешнему виду самого энкодера:

Чем отличаются энкодеры на практике: если опрашивать одноимпульсный энкодер как двухимпульсный, то для отработки одного тика нужно повернуть рукоятку на два тика. Если опрашивать двухимпульсный как одноимпульсный, то для отработки одного тика нужно повернуть рукоятку на два тика. То есть при неправильном использовании причина сразу видна.

ПРОГРАММИРОВАНИЕ

Я не нашёл в интернете нормальных библиотек для энкодера с хорошей функциональностью, поэтому написал свою, GyverEncoder. Что умеет:

  • Отработка поворота рукоятки энкодера
    • Обычный поворот
    • “Нажатый поворот”
    • “Быстрый” поворот
  • Три алгоритма опроса энкодера
    • Быстрый – но не справляется с люфтами
    • Бинарный – медленнее, лучше справляется с люфтами
    • Высокоточный – ещё медленнее, но работает даже с убитым энкодером
  • Возможность работы с “виртуальным” энкодером – через расширитель пинов или ещё как
  • Работа с двумя типами энкодеров (тип 1 и 2, см. выше)
  • Работа с кнопкой энкодера:
    • Отработка нажатия
    • Клика
    • Двойного клика
    • Удержания
    • Антидребезг контактов
    • Возможность полностью убрать код кнопки для быстродействия

Поддерживаемые платформы: все Arduino (используются стандартные Wiring-функции)

– 4.0 от 13.11.2019
– Оптимизирован код
– Исправлены баги
– Добавлены другие алгоритмы опроса
– Добавлена возможность полностью убрать кнопку (экономия памяти)
– Добавлена возможность подключения внешнего энкодера
– Добавлена настройка подтяжки пинов

– 4.1
– Исправлено изменение подтяжек

– 4.2
– Добавлена поддержка TYPE1 для алгоритма PRECISE_ALGORITHM
– Добавлена отработка двойного клика: isSingle / isDouble

Инициализация


Объект энкодера может быть создан несколькими способами:

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 при любом повороте, сама сбрасывается в false
  • isRight(); // возвращает true при повороте направо, сама сбрасывается в false
  • isLeft(); // возвращает true при повороте налево, сама сбрасывается в false
  • isRightH(); // возвращает true при удержании кнопки и повороте направо, сама сбрасывается в false
  • isLeftH(); // возвращает true при удержании кнопки и повороте налево, сама сбрасывается в false
  • isFastR(); // возвращает true при быстром повороте
  • isFastL(); // возвращает true при быстром повороте

Для кнопки энкодера:

  • isPress(); // возвращает true при нажатии кнопки, сама сбрасывается в false
  • isRelease(); // возвращает true при отпускании кнопки, сама сбрасывается в false
  • isClick(); // возвращает true при нажатии и отпускании кнопки, сама сбрасывается в false
  • isHolded(); // возвращает true при удержании кнопки, сама сбрасывается в false
  • isHold(); // возвращает true при удержании кнопки, НЕ СБРАСЫВАЕТСЯ
  • isSingle(); // возвращает true при одиночном клике (после таймаута), сама сбрасывается в false
  • isDouble(); // возвращает true при двойном клике, сама сбрасывается в false

Примечание: isClick() возвращает true сразу же после отпускания кнопки, в то время как isSingle() возвращает true после таймаута, во время которого можно сделать второй клик и поймать уже двойной клик при помощи isDouble().

Настройки в скетче


Некоторые параметры работы энкодера можно настроить из программы:

  • 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

ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ

Библиотека 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);
}

ОСТАЛЬНЫЕ БИБЛИОТЕКИ

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