| 
				
					
						 | 
			||
|---|---|---|
| .. | ||
| doc | ||
| examples | ||
| src | ||
| LICENSE | ||
| README.md | ||
| keywords.txt | ||
| library.properties | ||
		
			
				
				README.md
			
		
		
			
			
				
				
			
		
	
	EncButton
Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки
- Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13)
 - Максимально лёгкий вес
 - Быстрые и лёгкие алгоритмы опроса кнопки и энкодера
 - Энкодер: обычный поворот, нажатый поворот, быстрый поворот, доступ к счётчику
 - Кнопка: антидребезг, клик, несколько кликов, счётчик кликов, удержание, режим импульсного удержания
 - Подключение - только с подтяжкой к питанию (внешней или внутренней)!
 - Опциональный режим с обработчиками callback (+24 байта SRAM на каждый экземпляр)
 - Виртуальный режим (кнопка, энк, энк с кнопкой)
 
Совместимость
Совместима со всеми Arduino платформами (используются Arduino-функции)
Содержание
Установка
- Библиотеку можно найти по названию EncButton и установить через менеджер библиотек в:
- Arduino IDE
 - Arduino IDE v2
 - PlatformIO
 
 - Скачать библиотеку .zip архивом для ручной установки:
- Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
 - Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
 - Распаковать и положить в Документы/Arduino/libraries/
 - (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
 
 - Читай более подробную инструкцию по установке библиотек здесь
 
Железо
Для работы по сценарию "энкодер с кнопкой" рекомендую вот такие (ссылка, ссылка) круглые китайские модули с распаянными цепями антидребезга:

Самостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC):

Производительность
Время холостого выполнения функции tick() при реальном устройстве (кнопка/энкодер подключены к пинам МК) на ATmega328, библиотека EncButton:
| Режим | Время, мкс | 
|---|---|
| Энкодер + кнопка | 3.8 | 
| Энкодер | 2.4 | 
| Кнопка | 1.9 | 
Для сравнения, стандартный digitalRead() на AVR выполняется 3.5 us
Сравнение с аналогами
- EncButton в режиме кнопки на 6 мкс быстрее, на ~450 байт Flash и 12 байт SRAM легче моей старой библиотеки GyverButton, имея при этом больше возможностей
 - EncButton в режиме энкодера с кнопкой на 6 мкс быстрее, на ~400 байт Flash и 18 байт SRAM легче моей старой библиотеки GyverEncoder, имея при этом больше возможностей
 
Инициализация
Если нужен массив кнопок/энкодеров, используй EncButton2!
Инициализация EncButton
// ============== БАЗОВАЯ =============
EncButton<MODE, A, B, KEY> enc;     // энкодер с кнопкой
EncButton<MODE, A, B> enc;          // просто энкодер
EncButton<MODE, KEY> btn;           // просто кнопка
// A, B, KEY: номера пинов
// MODE: EB_TICK или EB_CALLBACK - режим работы ручной или с обработчиками
// для изменения направления энкодера поменяй A и B при инициализации
// ============ ПОДКЛЮЧЕНИЕ ============
// По умолчанию пины настраиваются в INPUT_PULLUP
// Если используется внешняя подтяжка - лучше перевести в INPUT
EncButton<...> enc(INPUT);
// ========= ВИРТУАЛЬНЫЙ РЕЖИМ =========
EncButton<MODE, VIRT_BTN> enc;     // виртуальная кнопка
EncButton<MODE, VIRT_ENCBTN> enc;  // виртуальный энк с кнопкой
EncButton<MODE, VIRT_ENC> enc;     // виртуальный энк
// в tick нужно будет передавать виртуальное значение, см. пример
Инициализация EncButton2
Хранит пины НЕ в шаблоне, а как член класса. Всё кроме инициализации такое же как у EncButton!
// ================ TICK ===============
EncButton2<EB_ENCBTN> enc(INPUT, A, B, KEY);    // энкодер с кнопкой
EncButton2<EB_ENC> enc(INPUT, A, B);            // просто энкодер
EncButton2<EB_BTN> enc(INPUT, KEY);             // просто кнопка
// режим пинов INPUT/INPUT_PULLUP
// ============== CALLBACK =============
EncButton2<EB_ENCBTN, EB_CALLBACK> enc(INPUT, A, B, KEY);    // энкодер с кнопкой
EncButton2<EB_ENC, EB_CALLBACK> enc(INPUT, A, B);            // просто энкодер
EncButton2<EB_BTN, EB_CALLBACK> enc(INPUT, KEY);             // просто кнопка
// режим пинов INPUT/INPUT_PULLUP
// ============== VIRT TICK ============
EncButton2<VIRT_ENCBTN> enc;        // энкодер с кнопкой
EncButton2<VIRT_ENC> enc;           // просто энкодер
EncButton2<VIRT_BTN> enc;           // просто кнопка
// ============ VIRT CALLBACK ==========
EncButton2<VIRT_ENCBTN, EB_CALLBACK> enc;   // энкодер с кнопкой
EncButton2<VIRT_ENC, EB_CALLBACK> enc;      // просто энкодер
EncButton2<VIRT_BTN, EB_CALLBACK> enc;      // просто кнопка
Массив экземпляров EncButton2
EncButton2<EB_ENCBTN> enc[количество];
EncButton2<EB_ENC> enc[количество];
EncButton2<EB_BTN> enc[количество];
EncButton2<EB_ENCBTN, EB_CALLBACK> enc[количество];
EncButton2<EB_ENC, EB_CALLBACK> enc[количество];
EncButton2<EB_BTN, EB_CALLBACK> enc[количество];
// и так далее
// Задавать пины можно через setPins()
setPins(uint8_t mode, uint8_t P1, uint8_t P2, uint8_t P3);
// mode - INPUT/INPUT_PULLUP (для всех пинов)
// указываем только нужные для выбранного режима пины:
// EB_ENCBTN - A, B, KEY
// EB_ENC - A, B
// EB_BTN - KEY
// см. пример EucButton2_array
Документация
ПОЛНОЕ ОПИСАНИЕ КЛАССА
// =============== SETTINGS ==============
void pullUp();                      // подтянуть все пины внутренней подтяжкой
void holdEncButton(bool state);     // виртуально зажать кнопку энкодера
void setHoldTimeout(int tout);      // установить время удержания кнопки, мс (до 30 000)
void setButtonLevel(bool level);    // уровень кнопки: LOW - кнопка подключает GND (по умолч.), HIGH - кнопка подключает VCC
// ================= TICK ================
// тикер, вызывать как можно чаще или в прерывании
// вернёт отличное от нуля значение, если произошло какое то событие (см. пример optimisation)
uint8_t tick();
// tick(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0)
// может принимать виртуальный сигнал при режиме VIRT_xxx:
// (сигнал кнопки)
// (сигнал энкодера А, сигнал энкодера B)
// (сигнал энкодера А, сигнал энкодера B, сигнал кнопки)
// Тикер для прерывания в режиме callback. Не вызывает подключенные функции!
// Требует наличие обычного tick() в loop() (см. примеры tickISR и callbackISR)
uint8_t tickISR();
// проверяет и вызывает подключенные функции для режима callback
// Встроено в tick(), но вынесено отдельной функцией для нестандартных сценариев работы
void checkCallback();
// =============== STATUS ================
uint8_t getState(); // получить статус кнопки/энкодера
void resetState();  // сбросить статус
// =============== ENCODER ===============
bool turn();        // поворот на один щелчок в любую сторону
bool turnH();       // поворот на один щелчок в любую сторону с зажатой кнопкой
bool fast();        // быстрый поворот на один щелчок в любую сторону
bool right();       // поворот на один щелчок направо
bool left();        // поворот на один щелчок налево
bool rightH();      // поворот на один щелчок направо с зажатой кнопкой
bool leftH();       // поворот на один щелчок налево с зажатой кнопкой
int8_t getDir();    // направление последнего поворота, 1 или -1
int counter;        // доступ к счётчику энкодера
// ================ BUTTON ================
bool busy();        // вернёт true, если всё ещё нужно вызывать tick для опроса таймаутов
bool state();       // текущее состояние кнопки (true нажата, false не нажата)
bool press();       // кнопка была нажата [однократное срабатывание]
bool release();     // кнопка была отпущена [однократное срабатывание]
bool click();       // клик (нажата и отпущена) [однократное срабатывание]
bool held();        // кнопка была удержана [однократное срабатывание]
bool hold();        // кнопка удерживается [постоянное срабатывание]
bool step();        // режим импульсного удержания
bool step(uint8_t clicks);    // режим импульсного удержания с предварительным накликиванием
bool releaseStep(); // отпущена после режима step
bool releaseStep(uint8_t clicks); // отпущена после режима step с предварительным накликиванием
uint8_t clicks;     // доступ к счётчику кликов
uint8_t hasClicks();            // вернёт количество кликов, если они есть
bool hasClicks(uint8_t num);    // проверка на наличие указанного количества кликов
// =============== CALLBACK ===============
void attach(eb_callback type, void (*handler)());       // подключить обработчик
void detach(eb_callback type);                          // отключить обработчик
void attachClicks(uint8_t amount, void (*handler)());   // подключить обработчик на количество кликов (может быть только один!)
void detachClicks();                                    // отключить обработчик на количество кликов
// eb_callback может быть:
TURN_HANDLER
TURN_H_HANDLER
RIGHT_HANDLER
LEFT_HANDLER
RIGHT_H_HANDLER
LEFT_H_HANDLER
CLICK_HANDLER
HOLDED_HANDLER
STEP_HANDLER
HOLD_HANDLER
CLICKS_HANDLER
PRESS_HANDLER
RELEASE_HANDLER
Дополнительно у EncButton2
void pullUp();          // здесь не реализована!
void setPins(uint8_t mode, uint8_t P1, uint8_t P2, uint8_t P3);     // настроить пины
// mode - INPUT/INPUT_PULLUP (для всех пинов)
// указываем только нужные для выбранного режима пины:
// EB_ENCBTN - (A, B, KEY)
// EB_ENC - (A, B)
// EB_BTN - (KEY)
// см. пример EucButton2_array
Заметки
- Библиотека универсальная, но сделана с упором на максимальную оптимизацию памяти при работе во всех режимах внутри одного класса, поэтому используется шаблон и дефайны
- При создании объекта с разным количеством пинов (энкодер, кнопка, энкодер с кнопкой) библиотека будет компилироваться по разному, ненужный код будет вырезан. Это позволяет экономить Flash память.
 - То же самое касается режимов работы TICK/CALLBACK, при использовании TICK весь относящийся к CALLBACK код вырезается компилятором
 
 - Два алгоритма опроса энкодера, обычный и точный. Точный использует на 16 байт больше SRAM памяти (на всю библиотеку), но позволяет работать даже с низкокачественными и убитыми энкодерами
- Точный алгоритм активируется добавлением 
#define EB_BETTER_ENCперед подключением библиотеки 
 - Точный алгоритм активируется добавлением 
 - Версия библиотеки EncButton2.h хранит номера пинов в классе. Используйте эту версию для создания массива объектов EncButton!
 
Особенности и сценарии использования
Дефайны настроек
// дефайнить ПЕРЕД ПОДКЛЮЧЕНИЕМ БИБЛИОТЕКИ, показаны значения по умолчанию (если они есть)
// энкодер
#define EB_FAST 30      // таймаут быстрого поворота, мс
#define EB_BETTER_ENC   // улучшенный алгоритм опроса энкодера. Добавит 16 байт SRAM при подключении библиотеки
#define EB_HALFSTEP_ENC // режим опроса полушагового энкодера (включи, если твой энкодер делает два тика за один)
// кнопка
#define EB_DEB 50       // дебаунс кнопки, мс
#define EB_STEP 500     // период срабатывания степ, мс
#define EB_CLICK 400    // таймаут накликивания, мс
#define EB_HOLD 1000    // таймаут удержания кнопки (можно переназначить setHoldTimeout() из программы), мс
Режим tick
- Опрос пинов энкодера/кнопки и расчёт таймаутов осуществляется внутри функции 
tick(). Эту функцию нужно однократно вызывать в основном цикле программы. - Для повышения качества обработки энкодера/кнопки в загруженной программе (чтобы не пропустить поворот или клик) рекомендуется продублировать опрос в прерывании по CHANGE: внутри обработчика прерывания вызываем специальный тикер 
tickISR(), и в основном цикле программы оставляем обычныйtick(). Он нужен для того, чтобы корректно считались все таймауты. tick()возвращает текущий статус энкодера/кнопки:- 0 - никаких действий не было
 - 1 - left + turn
 - 2 - right + turn
 - 3 - leftH + turnH
 - 4 - rightH + turnH
 - 5 - click
 - 6 - held
 - 7 - step
 - 8 - press
Это позволяет например производить дальнейший опрос действий кнопки/энкодера только по факту их совершения: можно поместить весь опрос в блокif (enc.tick()) {}. В конце рекомендуется вызватьresetState()для сборса неопрошенных флагов, чтобыtick()перестал "сигналить" о действии. Подробнее смотри в примере optimisation. 
- Основная идея работы: "тикнули", а затем вручную через условия опрашиваем нужные действия кнопки/энкодера. Почти все функции опроса имеют механизм "однократного срабатывания", то есть возвращают 
trueи автоматически сбрасываются вfalseдо наступления следующего события. Таким образом конструкцияif (btn.click())позволяет выполнить какой-то блок кода однократно по клику. Подробнее разберём ниже. 
Кнопка
press()- кнопка была нажата. [однократно вернёт true]release()- кнопка была отпущена. [однократно вернёт true]click()- кнопка была кликнута, т.е. нажата и отпущена до таймаута удержания. [однократно вернёт true]held()- кнопка была удержана дольше таймаута удержания. [однократно вернёт true]held(clicks)- то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: held() без аргумента перехватит вызов! См. пример preClicks. [однократно вернёт true]hold()- кнопка была удержана дольше таймаута удержания. [возвращает true, пока удерживается]hold(clicks)- то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: hold() без аргумента перехватит вызов! См. пример preClicks. [возвращает true, пока удерживается]step()- режим "импульсного удержания": после удержания кнопки дольше таймаута данная функция [возвращает true с периодом EB_STEP]. Удобно использовать для пошагового изменения переменных:if (btn.step()) val++;.step(clicks)- то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: step() без аргумента перехватит вызов! См. пример StepMode и preClicks.releaseStep()- кнопка была отпущена после импульсного удержания. Может использоваться для изменения знака инкремента переменной. См. пример StepMode. [однократно вернёт true]releaseStep(clicks)- то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: releaseStep() без аргумента перехватит вызов! См. пример StepMode и preClicks. [однократно вернёт true]hasClicks(clicks)- было сделано указанное количество кликов с периодом менее EB_CLICK. [однократно вернёт true]state()- возвращает теукщее состояние кнопки (сигнал с пина, без антидребезга):true- нажата,false- не нажата.busy()- вернётtrue, если всё ещё нужно вызывать tick для опроса таймаутовhasClicks()- вернёт количество кликов, сделанных с периодом менее EB_CLICK. В противном случае вернёт 0.uint8_t clicks- публичная переменная (член класса), хранит количество сделанных кликов с периодом менее EB_CLICK. Сбрасывается в 0 после нового клика.
Энкодер
turn()- поворот на один щелчок в любую сторону. [однократно вернёт true]turnH()- поворот на один щелчок в любую сторону с зажатой кнопкой. [однократно вернёт true]fast()- был совершён быстрый поворот (с периодом менее EB_FAST мс) на один щелчок в любую сторону. [возвращает true, пока энкодер крутится быстро]right()- поворот на один щелчок направо. [однократно вернёт true]left()- поворот на один щелчок налево. [однократно вернёт true]rightH()- поворот на один щелчок направо с зажатой кнопкой. [однократно вернёт true]leftH()- поворот на один щелчок налево с зажатой кнопкой. [однократно вернёт true]getDir()- направление последнего поворота, 1 или -1.int16_t counter- публичная переменная (член класса), хранит счётчик энкодера.
Режим callback
- В данном режиме можно подключить свою функцию-обработчик на любое действие кнопки/энкодера. Они будут автоматически вызываться при наступлении события.
 - Для работы нужно вызывать 
tick()в основном цикле программы, а также можно продублироватьtickISR()в прерывании по CHANGE для улучшения точности обработки энкодера. - При работе в прерывании подключенные функции вызываются из 
tick(), т.е. из основного цикла программы. ВtickISR()происходит только обработка алгоритмов библиотеки! - Смотри пример callbackMode
 
void attach(type, func);                   // подключить обработчик
void detach(type);                         // отключить обработчик
void attachClicks(uint8_t amount, func);   // подключить обработчик на количество кликов (может быть только один!)
void detachClicks();                       // отключить обработчик на количество кликов
Где type - тип события:
- TURN_HANDLER - поворот
 - TURN_H_HANDLER - нажатый поворот
 - RIGHT_HANDLER - поворот направо
 - LEFT_HANDLER - поворот налево
 - RIGHT_H_HANDLER - нажатый поворот направо
 - LEFT_H_HANDLER - нажатый поворот налево
 - PRESS_HANDLER - нажатие
 - RELEASE_HANDLER - отпускание
 - CLICK_HANDLER - клик
 - HOLDED_HANDLER - удержание (однократное срабатывание)
 - HOLD_HANDLER - удержание (постоянное срабатывание)
 - STEP_HANDLER - импульсное удержание
 - CLICKS_HANDLER - несколько кликов
 
Виртуальный режим
Виртуальный режим позволяет получить все возможности библиотеки EncButton в ситуациях, когда кнопка не подключена напрямую к микроконтроллеру, либо для её опроса используется другая библиотека:
- Аналоговая клавиатура (например через библиотеку AnalogKey). Смотри пример virtual_AnalogKey
 - Матричная клавиатура (например через библиотеку SimpleKeypad). Смотри пример virtual_SimpleKeypad и virtual_SimpleKeypad_array
 - Кнопки или энкодеры, подключенные через расширители пинов или сдвиговые регистры
 
Таким образом можно получить несколько нажатий с матричной клавиатуры, удержание кнопок матричной клавиатуры, импульсное удержание и прочие фишки EncButton.
Для работы нужно передать в tick() текущие состояния "пинов" кнопки/энкодера: tick(s1, s2, s3) в следующем порядке
- Кнопка - (сигнал кнопки)
 - Энкодер - (сигнал энкодера А, сигнал энкодера B)
 - Энкодер с кнопкой - (сигнал энкодера А, сигнал энкодера B, сигнал кнопки)
 
Настройки
void pullUp();                      // подтянуть все пины внутренней подтяжкой
void holdEncButton(bool state);     // виртуально зажать кнопку энкодера
void setHoldTimeout(int tout);      // установить время удержания кнопки, мс (до 30 000)
void setButtonLevel(bool level);    // уровень кнопки: LOW - кнопка подключает GND (по умолч.), HIGH - кнопка подключает VCC
Примеры
Полное демо tick
Остальные примеры смотри в examples!
// Пример с прямой работой библиотеки
#include <EncButton.h>
EncButton<EB_TICK, 2, 3, 4> enc;  // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_TICK, 2, 3> enc;     // просто энкодер <A, B>
//EncButton<EB_TICK, 4> enc;        // просто кнопка <KEY>
void setup() {
  Serial.begin(9600);
  // ещё настройки
  //enc.counter = 100;        // изменение счётчика энкодера
  //enc.setHoldTimeout(500);  // установка таймаута удержания кнопки
  //enc.setButtonLevel(HIGH); // LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
}
void loop() {
  enc.tick();                       // опрос происходит здесь
  // =============== ЭНКОДЕР ===============
  // обычный поворот
  if (enc.turn()) {
    Serial.println("turn");
    // можно опросить ещё:
    //Serial.println(enc.counter);  // вывести счётчик
    //Serial.println(enc.fast());   // проверить быстрый поворот
    Serial.println(enc.getDir()); // направление поворота
  }
  // "нажатый поворот"
  if (enc.turnH()) {
    Serial.println("hold + turn");
    // можно опросить ещё:
    //Serial.println(enc.counter);  // вывести счётчик
    //Serial.println(enc.fast());   // проверить быстрый поворот
    Serial.println(enc.getDir()); // направление поворота
  }
  if (enc.left()) Serial.println("left");     // поворот налево
  if (enc.right()) Serial.println("right");   // поворот направо
  if (enc.leftH()) Serial.println("leftH");   // нажатый поворот налево
  if (enc.rightH()) Serial.println("rightH"); // нажатый поворот направо
  // =============== КНОПКА ===============
  if (enc.press()) Serial.println("press");
  if (enc.click()) Serial.println("click");
  if (enc.release()) Serial.println("release");
  if (enc.held()) Serial.println("held");     // однократно вернёт true при удержании
  //if (enc.hold()) Serial.println("hold");   // будет постоянно возвращать true после удержания
  if (enc.step()) Serial.println("step");     // импульсное удержание
  if (enc.releaseStep()) Serial.println("release step");  // отпущена после импульсного удержания
  
  // проверка на количество кликов
  if (enc.hasClicks(1)) Serial.println("action 1 clicks");
  if (enc.hasClicks(2)) Serial.println("action 2 clicks");
  if (enc.hasClicks(3)) Serial.println("action 3 clicks");
  if (enc.hasClicks(5)) Serial.println("action 5 clicks");
  // вывести количество кликов
  if (enc.hasClicks()) {
    Serial.print("has clicks ");
    Serial.println(enc.clicks);
  }
}
Массив кнопок EncButton2
// объявляем массив кнопок
#define BTN_AMOUNT 5
#include <EncButton2.h>
EncButton2<EB_BTN> btn[BTN_AMOUNT];
void setup() {
  Serial.begin(9600);
  btn[0].setPins(INPUT_PULLUP, D3);
  btn[1].setPins(INPUT_PULLUP, D2);
}
void loop() {
  for (int i = 0; i < BTN_AMOUNT; i++) btn[i].tick();
  for (int i = 0; i < BTN_AMOUNT; i++) {
    if (btn[i].click()) {
      Serial.print("click btn: ");
      Serial.println(i);
    }
  }
}
Одна кнопка управляет несколькими переменными
// используем одну КНОПКУ для удобного изменения трёх переменных
// первая - один клик, затем удержание (нажал-отпустил-нажал-держим)
// вторая - два клика, затем удержание
// третья - три клика, затем удержание
// смотри монитор порта
#include <EncButton.h>
EncButton<EB_TICK, 3> btn;
// переменные для изменения
int val_a, val_b, val_c;
// шаги изменения (signed)
int8_t step_a = 1;
int8_t step_b = 5;
int8_t step_c = 10;
void setup() {
  Serial.begin(9600);
}
void loop() {
  btn.tick();
  // передаём количество предварительных кликов
  if (btn.step(1)) {
    val_a += step_a;
    Serial.print("val_a: ");
    Serial.println(val_a);
  }
  if (btn.step(2)) {
    val_b += step_b;
    Serial.print("val_b: ");
    Serial.println(val_b);
  }
  if (btn.step(3)) {
    val_c += step_c;
    Serial.print("val_c: ");
    Serial.println(val_c);
  }
  // разворачиваем шаг для изменения в обратную сторону
  // передаём количество предварительных кликов
  if (btn.releaseStep(1)) step_a = -step_a;
  if (btn.releaseStep(2)) step_b = -step_b;
  if (btn.releaseStep(3)) step_c = -step_c;
}
Версии
- v1.1 - пуллап отдельныи методом
 - v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч)
 - v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения
 - v1.4 - обработка нажатия и отпускания кнопки
 - v1.5 - добавлен виртуальный режим
 - v1.6 - оптимизация работы в прерывании
 - v1.6.1 - подтяжка по умолчанию INPUT_PULLUP
 - v1.7 - большая оптимизация памяти, переделан FastIO
 - v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех)
 - v1.8.1 - убран FastIO
 - v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления
 - v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги
 - v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки
 - v1.11.1 - совместимость Digispark
 - v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC
 - v1.13 - добавлен экспериментальный EncButton2
 - v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс
 - v1.15 - добавлен setPins() для EncButton2
 - v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров
 - v1.17 - добавлен step с предварительными кликами
 - v1.18 - не считаем клики после активации step. held() и hold() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс
 - v1.18.1 - исправлена ошибка в releaseStep() (не возвращала результат)
 - v1.18.2 - fix compiler warnings
 - v1.19 - оптимизация скорости, уменьшен вес в sram
 - v1.19.1 - ещё чутка увеличена производительность
 - v1.19.2 - ещё немного увеличена производительность, спасибо XRay3D
 - v1.19.3 - сделал высокий уровень кнопки по умолчанию в виртуальном режиме
 - v1.19.4 - фикс EncButton2
 - v1.20 - исправлена критическая ошибка в EncButton2
 
Баги и обратная связь
При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru
Библиотека открыта для доработки и ваших Pull Request'ов!