Новая версия v3 несовместима с предыдущими, смотри документацию, примеры и краткий гайд по миграции с v2 на v3! |
|---|
Лёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino
- Кнопка
- Обработка событий: нажатие, отпускание, клик, счётчик кликов, удержание, импульсное удержание, время удержания + предварительные клики для всех режимов
- Программное подавление дребезга
- Поддержка обработки двух одновременно нажимаемых кнопок как третьей кнопки
- Энкодер
- Обработка событий: обычный поворот, нажатый поворот, быстрый поворот
- Поддержка четырёх типов инкрементальных энкодеров
- Высокоточный алгоритм определения позиции
- Буферизация в прерывании
- Простое и понятное использование
- Огромное количество возможностей и их комбинаций для разных сценариев использования даже одной кнопки
- Виртуальный режим (например для работы с расширителем пинов)
- Оптимизирована для работы в прерывании
- Максимально быстрое чтение пинов для AVR, esp8266, esp32 (используется GyverIO)
- Быстрые асинхронные алгоритмы опроса действий с кнопки и энкодера
- Жёсткая оптимизация и небольшой вес во Flash и SRAM памяти: 5 байт SRAM (на экземпляр) и ~350 байт Flash на обработку кнопки
Примеры сценариев использования:
- Несколько кликов - включение режима (по кол-ву кликов)
- Несколько кликов + короткое удержание - ещё вариант включения режима (по кол-ву кликов)
- Несколько кликов + удержание - постепенное изменение значения выбранной переменной (по кол-ву кликов)
- Несколько кликов выбирают переменную, энкодер её изменяет
- Изменение шага изменения переменной при вращении энкодера - например уменьшение при зажатой кнопке и увеличение при быстром вращении
- Навигация по меню при вращении энкодера, изменение переменной при вращении зажатого энкодера
- Полноценная навигация по меню при использовании двух кнопок (одновременное удержание для перехода на следующий уровень, одновременное нажатие для возврата на предыдущий)
- И так далее
Вес в Байтах по сравнению с другими библиотеками (опрос пяти событий кнопки):
| uButton | EncButton | EncButton opti | GyverButton | OneButton | OneButtonTiny | JC_Button | AceButton | |
|---|---|---|---|---|---|---|---|---|
| Flash | 776 | 1006 | 610 | 1054 | 1838 | 1152 | 650 | 1482 |
| SRAM | 6 | 15 | 6 | 23 | 83 | 36 | 24 | 45 |
| Фич | Очень много (20+) | Очень много (20+) | Очень много (20+) | Много (13) | Средне (8) | Мало (4) | Крайне мало (6) | Средне (7) |
Совместима со всеми Arduino платформами (используются Arduino-функции)
- Для работы требуется библиотека GyverIO
- Библиотеку можно найти по названию 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 библиотеку… и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек здесь
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
- Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
Библиотека поддерживает все 4 типа инкрементальных энкодеров, тип можно настроить при помощи setEncType(тип):
EB_STEP4_LOW- активный низкий сигнал (подтяжка к VCC). Полный период (4 фазы) за один щелчок. Установлен по умолчаниюEB_STEP4_HIGH- активный высокий сигнал (подтяжка к GND). Полный период (4 фазы) за один щелчокEB_STEP2- половина периода (2 фазы) за один щелчокEB_STEP1- четверть периода (1 фаза) за один щелчок, а также энкодеры без фиксации
Для работы по сценарию "энкодер с кнопкой" рекомендую вот такие (ссылка, ссылка) круглые китайские модули с распаянными цепями антидребезга (имеют тип EB_STEP4_LOW по классификации выше):
Самостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC):
Примечание: по умолчанию в библиотеке пины энкодера настроены на
INPUTс расчётом на внешнюю подтяжку. Если у вас энкодер без подтяжки - можно использовать внутреннююINPUT_PULLUP, указав это при инициализации энкодера (см. документацию ниже).
Кнопка может быть подключена к микроконтроллеру двумя способами и давать при нажатии высокий или низкий сигнал. В библиотеке предусмотрена настройка setBtnLevel(уровень), где уровень - активный сигнал кнопки:
HIGH- кнопка подключает VCC. Установлен по умолчанию вVirt-библиотекахLOW- кнопка подключает GND. Установлен по умолчанию в основных библиотеках
В схемах с микроконтроллерами чаще всего используется подключение кнопки к GND с подтяжкой пина к VCC. Подтяжка может быть внешней (режим пина нужно поставить INPUT) или внутренней (режим пина INPUT_PULLUP). В "реальных" проектах рекомендуется внешняя подтяжка, т.к. она менее подвержена помехам - у внутренней слишком высокое сопротивление.
Объявлять до подключения библиотеки
// отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)
#defineEB_NO_FOR// отключить обработчик событий attach (экономит 2 байта оперативки)
#defineEB_NO_CALLBACK// отключить счётчик энкодера [VirtEncoder, Encoder, EncButton] (экономит 4 байта оперативки)
#defineEB_NO_COUNTER// отключить буферизацию энкодера (экономит 2 байта оперативки)
#defineEB_NO_BUFFER/* Настройка таймаутов для всех классов - Заменяет таймауты константами, изменить их из программы (SetXxxTimeout()) будет нельзя - Настройка влияет на все объявленные в программе кнопки/энкодеры - Экономит 1 байт оперативки на объект за каждый таймаут - Показаны значения по умолчанию в мс - Значения не ограничены 4000мс, как при установке из программы (SetXxxTimeout())*/
#defineEB_DEB_TIME50// таймаут гашения дребезга кнопки (кнопка)
#defineEB_CLICK_TIME500// таймаут ожидания кликов (кнопка)
#defineEB_HOLD_TIME600// таймаут удержания (кнопка)
#defineEB_STEP_TIME200// таймаут импульсного удержания (кнопка)
#defineEB_FAST_TIME30// таймаут быстрого поворота (энкодер)
#defineEB_TOUT_TIME1000// таймаут действия (кнопка и энкодер)Как работать с документацией: EncButton начиная с версии 3.0 представляет собой несколько библиотек (классов) для различных сценариев использования, они друг друга наследуют для расширения функциональности. Таким образом библиотека представляет собой "луковицу", каждый слой которой имеет доступ к функциям нижних слоёв:
- Базовые классы:
VirtButton- базовый класс виртуальной кнопки, обеспечивает все возможности кнопкиVirtEncoder- базовый класс виртуального энкодера, определяет факт и направление вращения энкодераVirtEncButton- базовый класс виртуального энкодера с кнопкой, обеспечивает опрос энкодера с учётом кнопки, наследует VirtButton и VirtEncoder
- Основные классы:
Button,ButtonT- класс кнопки, наследует VirtButtonEncoder,EncoderT- класс энкодера, наследует VirtEncoderEncButton,EncButtonT- класс энкодера с кнопкой, наследует VirtEncButton, VirtButton, VirtEncoder
Таким образом для изучения всех доступных функций конкретной библиотеки нужно смотреть не только её, но и то что она наследует. Например для обработки кнопки при помощи Button нужно открыть ниже описание Button и VirtButton.
Виртуальный - без указания пина микроконтроллера, работает напрямую с переданным значением, например для опроса кнопок-энкодеров через расширители пинов и сдвиговые регистры.
T-версии библиотек требуют указания пинов константами (цифрами). Номера пинов будут храниться в памяти программы, это ускоряет работу и делает код легче на 1 байт за каждый пин.
Примечание:
#include <EncButton.h>подключает все инструменты библиотеки!
Таблица функций кнопки
| VirtButton | VirtEncButton | Button | EncButton | |
|---|---|---|---|---|
| read | ✔ | |||
| readBtn | ✔ | |||
| tickRaw | ✔ | ✔ | ✔ | ✔ |
| setHoldTimeout | ✔ | ✔ | ✔ | ✔ |
| setStepTimeout | ✔ | ✔ | ✔ | ✔ |
| setClickTimeout | ✔ | ✔ | ✔ | ✔ |
| setDebTimeout | ✔ | ✔ | ✔ | ✔ |
| setTimeout | ✔ | ✔ | ✔ | ✔ |
| setBtnLevel | ✔ | ✔ | ✔ | ✔ |
| pressISR | ✔ | ✔ | ✔ | ✔ |
| reset | ✔ | ✔ | ✔ | ✔ |
| clear | ✔ | ✔ | ✔ | ✔ |
| skipEvents | ✔ | ✔ | ✔ | ✔ |
| attach | ✔ | ✔ | ✔ | ✔ |
| detach | ✔ | ✔ | ✔ | ✔ |
| press | ✔ | ✔ | ✔ | ✔ |
| release | ✔ | ✔ | ✔ | ✔ |
| click | ✔ | ✔ | ✔ | ✔ |
| pressing | ✔ | ✔ | ✔ | ✔ |
| hold | ✔ | ✔ | ✔ | ✔ |
| holding | ✔ | ✔ | ✔ | ✔ |
| step | ✔ | ✔ | ✔ | ✔ |
| hasClicks | ✔ | ✔ | ✔ | ✔ |
| getClicks | ✔ | ✔ | ✔ | ✔ |
| getSteps | ✔ | ✔ | ✔ | ✔ |
| releaseHold | ✔ | ✔ | ✔ | ✔ |
| releaseStep | ✔ | ✔ | ✔ | ✔ |
| releaseHoldStep | ✔ | ✔ | ✔ | ✔ |
| waiting | ✔ | ✔ | ✔ | ✔ |
| busy | ✔ | ✔ | ✔ | ✔ |
| action | ✔ | ✔ | ✔ | ✔ |
| getAction | ✔ | ✔ | ✔ | ✔ |
| timeout | ✔ | ✔ | ✔ | ✔ |
| pressFor | ✔ | ✔ | ✔ | ✔ |
| holdFor | ✔ | ✔ | ✔ | ✔ |
| stepFor | ✔ | ✔ | ✔ | ✔ |
Таблица функций энкодера
| VirtEncoder | Encoder | VirtEncButton | EncButton | |
|---|---|---|---|---|
| initEnc | ✔ | ✔ | ✔ | ✔ |
| setEncReverse | ✔ | ✔ | ✔ | ✔ |
| setEncType | ✔ | ✔ | ✔ | ✔ |
| setEncISR | ✔ | ✔ | ✔ | ✔ |
| clear | ✔ | ✔ | ✔ | ✔ |
| turn | ✔ | ✔ | ✔ | ✔ |
| dir | ✔ | ✔ | ✔ | ✔ |
| tickRaw | ✔ | ✔ | ✔ | ✔ |
| pollEnc | ✔ | ✔ | ✔ | ✔ |
| counter | ✔ | ✔ | ✔ | ✔ |
| setFastTimeout | ✔ | ✔ | ||
| turnH | ✔ | ✔ | ||
| fast | ✔ | ✔ | ||
| right | ✔ | ✔ | ||
| left | ✔ | ✔ | ||
| rightH | ✔ | ✔ | ||
| leftH | ✔ | ✔ | ||
| action | ✔ | ✔ | ||
| getAction | ✔ | ✔ | ||
| timeout | ✔ | ✔ | ||
| attach | ✔ | ✔ | ||
| detach | ✔ | ✔ |
VirtButton
// ================ НАСТРОЙКИ ================// установить таймаут удержания, умолч. 600 (макс. 4000 мс)voidsetHoldTimeout(uint16_t tout);
// установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс)voidsetStepTimeout(uint16_t tout);
// установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс)voidsetClickTimeout(uint16_t tout);
// установить таймаут антидребезга, умолч. 50 (макс. 255 мс)voidsetDebTimeout(uint8_t tout);
// установить время таймаута для timeout(), умолч. 1000 (макс. 4000 мс)voidsetTimeout(constuint16_t tout);
// установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND)// умолч. HIGH, то есть true - кнопка нажатаvoidsetBtnLevel(bool level);
// подключить функцию-обработчик событийvoidattach(void (*handler)());
// отключить функцию-обработчик событийvoiddetach();
// ================== СБРОС ==================// сбросить системные флаги (принудительно закончить обработку)voidreset();
// принудительно сбросить флаги событийvoidclear(bool resetTout = false);
// игнорировать все события до отпускания кнопкиvoidskipEvents();
// ================ ОБРАБОТКА ================// обработка кнопки значениемbooltick(bool s);
// обработка виртуальной кнопки как одновременное нажатие двух других кнопокbooltick(VirtButton& b0, VirtButton& b1);
// кнопка нажата в прерывании кнопкиvoidpressISR();
// обработка кнопки без сброса событий и вызова коллбэкаbooltickRaw(bool s);
// ================== ОПРОС ==================// кнопка нажата [событие]boolpress();
boolpress(uint8_t clicks);
// кнопка отпущена (в любом случае) [событие]boolrelease();
boolrelease(uint8_t clicks);
// клик по кнопке (отпущена без удержания) [событие]boolclick();
boolclick(uint8_t clicks);
// кнопка зажата (между press() и release()) [состояние]boolpressing();
boolpressing(uint8_t clicks);
// кнопка была удержана (больше таймаута) [событие]boolhold();
boolhold(uint8_t clicks);
// кнопка удерживается (больше таймаута) [состояние]boolholding();
boolholding(uint8_t clicks);
// импульсное удержание [событие]boolstep();
boolstep(uint8_t clicks);
// зафиксировано несколько кликов [событие]boolhasClicks();
boolhasClicks(uint8_t clicks);
// кнопка отпущена после удержания [событие]boolreleaseHold();
boolreleaseHold(uint8_t clicks);
// кнопка отпущена после импульсного удержания [событие]boolreleaseStep();
boolreleaseStep(uint8_t clicks);
// кнопка отпущена после удержания или импульсного удержания [событие]boolreleaseHoldStep();
boolreleaseHoldStep(uint8_t clicks);
// получить количество кликовuint8_tgetClicks();
// получить количество степовuint16_tgetSteps();
// кнопка ожидает повторных кликов (между click() и hasClicks()) [состояние]boolwaiting();
// идёт обработка (между первым нажатием и после ожидания кликов) [состояние]boolbusy();
// было действие с кнопки, вернёт код события [событие]uint16_taction();
EBAction getAction();
// ================== ВРЕМЯ ==================// после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [событие]booltimeout();
// после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [состояние]booltimeoutState();
// время, которое кнопка удерживается (с начала нажатия), мсuint16_tpressFor();
// кнопка удерживается дольше чем (с начала нажатия), мс [состояние]boolpressFor(uint16_t ms);
// время, которое кнопка удерживается (с начала удержания), мсuint16_tholdFor();
// кнопка удерживается дольше чем (с начала удержания), мс [состояние]boolholdFor(uint16_t ms);
// время, которое кнопка удерживается (с начала степа), мсuint16_tstepFor();
// кнопка удерживается дольше чем (с начала степа), мс [состояние]boolstepFor(uint16_t ms);VirtEncoder
// ==================== НАСТРОЙКИ ====================// инвертировать направление энкодера (умолч. 0)voidsetEncReverse(bool rev);
// установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1)voidsetEncType(uint8_t type);
// использовать обработку энкодера в прерыванииvoidsetEncISR(bool use);
// инициализация энкодераvoidinitEnc(boole0, boole1);
// инициализация энкодера совмещённым значениемvoidinitEnc(int8_t v);
// сбросить флаги событийvoidclear();
// ====================== ОПРОС ======================// был поворот [событие]boolturn();
// направление энкодера (1 или -1) [состояние]int8_tdir();
// счётчикint32_t counter;
// ==================== ОБРАБОТКА ====================// опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановкеint8_ttickISR(boole0, boole1);
// опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановкеint8_ttick(boole0, boole1);
int8_ttick(); // сама обработка в прерывании// опросить энкодер без сброса события поворота. Вернёт 1 или -1 при вращении, 0 при остановкеint8_ttickRaw(boole0, boole1);
int8_ttickRaw(); // сама обработка в прерывании// опросить энкодер без установки флагов на поворот (быстрее). Вернёт 1 или -1 при вращении, 0 при остановкеint8_tpollEnc(boole0, boole1);VirtEncButton
- Доступны функции из
VirtButton - Доступны функции из
VirtEncoder
// ================== НАСТРОЙКИ ==================// установить таймаут быстрого поворота, мсvoidsetFastTimeout(uint8_t tout);
// сбросить флаги энкодера и кнопкиvoidclear(bool resetTout = false);
// ==================== ОПРОС ====================// ЛЮБОЙ поворот энкодера [событие]boolturn();
// нажатый поворот энкодера [событие]boolturnH();
// быстрый поворот энкодера [состояние]boolfast();
// ненажатый поворот направо [событие]boolright();
// ненажатый поворот налево [событие]boolleft();
// нажатый поворот направо [событие]boolrightH();
// нажатый поворот налево [событие]boolleftH();
// было действие с кнопки или энкодера, вернёт код события [событие]uint16_taction();
EBAction getAction();
// ==================== ОБРАБОТКА ====================// обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при поворотеint8_ttickISR(boole0, boole1);
// обработка энкодера и кнопкиbooltick(boole0, boole1, bool btn);
booltick(bool btn); // энкодер в прерывании// обработка энкодера и кнопки без сброса флагов и вызова коллбэкаbooltickRaw(boole0, boole1, bool btn);
booltickRaw(bool btn); // энкодер в прерыванииButton
- Доступны функции из
VirtButton - Режим кнопки по умолчанию -
LOW
Button;
Button(uint8_t pin); // с указанием пинаButton(uint8_t npin, uint8_t mode); // + режим работы (умолч. INPUT_PULLUP)Button(uint8_t npin, uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)// указать пин и его режим работыvoidinit(uint8_t npin, uint8_t mode);
// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevelboolread();
// функция обработки, вызывать в loopbooltick();
// обработка кнопки без сброса событий и вызова коллбэкаbooltickRaw();
// получить пин кнопкиuint8_tgetPin();ButtonT
- Доступны функции из
VirtButton - Режим кнопки по умолчанию -
LOW
ButtonT<uint8_t pin>; // с указанием пина
ButtonT<uint8_t pin> (uint8_t mode); // + режим работы (умолч. INPUT_PULLUP)
ButtonT<uint8_t pin> (uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)// указать режим работыvoidinit(uint8_t mode);
// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevelboolread();
// функция обработки, вызывать в loopbooltick();Encoder
- Доступны функции из
VirtEncoder
Encoder;
Encoder(uint8_t encA, uint8_t encB); // с указанием пиновEncoder(uint8_t encA, uint8_t encB, uint8_t mode); // + режим работы (умолч. INPUT)// указать пины и их режим работыvoidinit(uint8_t encA, uint8_t encB, uint8_t mode);
// функция обработки для вызова в прерывании энкодераint8_ttickISR();
// функция обработки для вызова в loopint8_ttick();
// получить пин энкодераuint8_tgetPinA();
// получить пин энкодераuint8_tgetPinB();EncoderT
- Доступны функции из
VirtEncoder
EncoderT<uint8_t encA, uint8_t encB>; // с указанием пинов
EncoderT<uint8_t encA, uint8_t encB> (uint8_t mode); // + режим работы (умолч. INPUT)// указать режим работы пиновvoidinit(uint8_t mode);
// функция обработки для вызова в прерывании энкодераint8_ttickISR();
// функция обработки для вызова в loopint8_ttick();EncButton
- Доступны функции из
VirtButton - Доступны функции из
VirtEncoder - Доступны функции из
VirtEncButton
EncButton;
// настроить пины (энк, энк, кнопка)EncButton(uint8_t encA, uint8_t encB, uint8_t btn);
// настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки)EncButton(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);// настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки)voidinit(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);
// функция обработки для вызова в прерывании энкодераint8_ttickISR();
// функция обработки, вызывать в loopbooltick();
// прочитать значение кнопки с учётом setBtnLevelboolreadBtn();
// получить пин кнопкиuint8_tgetPin();
// получить пин энкодераuint8_tgetPinA();
// получить пин энкодераuint8_tgetPinB();EncButtonT
- Доступны функции из
VirtButton - Доступны функции из
VirtEncoder - Доступны функции из
VirtEncButton
// с указанием пинов
EncButtonT<uint8_t encA, uint8_t encB, uint8_t btn>;
// + режим работы пинов, уровень кнопки
EncButtonT<uint8_t encA, uint8_t encB, uint8_t btn> (uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);// настроить режим работы пинов, уровень кнопкиvoidinit(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);
// функция обработки для вызова в прерывании энкодераint8_ttickISR();
// функция обработки, вызывать в loopbooltick();
// прочитать значение кнопкиboolreadBtn();Во всех библиотеках есть общая функция обработки (тикер tick), которая получает текущий сигнал с кнопки и энкодера
- Эту функцию нужно однократно вызывать в основном цикле программы (для виртуальных - с передачей значения)
- Функция возвращает
trueпри наступлении события (для энкодера -1или-1при повороте,0при его отсутствии. Таким образом поворот в любую сторону расценивается какtrue) - Есть отдельные функции для вызова в прерывании, они имеют суффикс
ISR, см. документацию ниже
Библиотека обрабатывает сигнал внутри этой функции, результат можно получить из функций опроса событий. Они бывают двух типов:
[событие]- функция вернётtrueоднократно при наступлении события. Сбросится после следующего вызова функции обработки (например клик, поворот энкодера). За исключением событияtimeout[состояние]- функция возвращаетtrue, пока активно это состояние (например кнопка удерживается)
Для простоты восприятия функцию обработки нужно размещать в начале цикла, а опросы делать ниже:
voidloop() {
btn.tick(); // опросif (btn.click()) Serial.println("click"); // однократно выведет при кликеif (btn.click()) Serial.println("click"); // тот же клик!
}В отличие от предыдущих версий библиотеки, функции обработки сбрасываются не внутри себя, а внутри тикера. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение
click(). Это позволяет использовать функции опроса по несколько раз за текущую итерацию цикла для создания сложной логики работы программы.
По очевидным причинам нельзя вызывать функцию обработки больше одного раза за цикл - каждый следующий вызов сбросит события от предыдущего и код будет работать некорректно. Вот так - нельзя:
// так нельзяvoidloop() {
btn.tick();
if (btn.click()) ...
// ....
btn.tick();
if (btn.hold()) ...
}Если очень нужно попасть в глухой цикл и опрашивать там кнопку, то вот так - можно:
// так можноvoidloop() {
btn.tick();
if (btn.click()) ...
while (true) {
btn.tick();
if (btn.hold()) ...
if (btn.click()) break;
}
}Если библиотека используется с подключенным обработчиком событий attach() (см. ниже), то можно вызывать tick() где угодно и сколько угодно раз, события будут обработаны в обработчике:
// так можноvoidcb() {
switch (btn.action()) {
// ...
}
}
voidsetup() {
btn.attach(cb);
}
voidloop() {
btn.tick();
// ...
btn.tick();
// ...
btn.tick();
}Библиотека EncButton - асинхронная: она не ждёт, пока закончится обработка кнопки, а позволяет программе выполняться дальше. Это означает, что для корректной работы библиотеки основной цикл программы должен выполняться как можно быстрее и не содержать задержек и других "глухих" циклов внутри себя. Для обеспечения правильной обработки кнопки не рекомендуется иметь в основном цикле задержки длительностью более 50-100 мс. Несколько советов:
- Новичкам: изучить цикл уроков как написать скетч
- Писать асинхронный код в
loop() - Любую синхронную конструкцию на
delay()можно сделать асинхронной при помощиmillis() - Если в программе каждая итерация главного цикла выполняется дольше 50-100мс - в большинстве случаев программа написана неправильно, за исключением каких-то особых случаев
- Писать асинхронный код в
- Подключить кнопку на аппаратное прерывание (см. ниже)
- Избегать выполнения "тяжёлых" участков кода, пока идёт обработка кнопки, например поместив их в условие
if (!button.busy()) { тяжёлый код } - Если оптимизировать основной цикл невозможно - вызывать тикер в другом "потоке" и использовать функцию-обработчик:
- В прерывании таймера с периодом ~50мс или чаще
- На другом ядре (например ESP32)
- В другом таске FreeRTOS
- Внутри
yield()(внутриdelay())
Имеет смысл только при ручном опросе событий! При подключенной функции-обработчике достаточно вызывать обычный
tick()между тяжёлыми участками программы
Также в загруженной программе можно разделить обработку и сброс событий: вместо tick() использовать tickRaw() между тяжёлыми участками кода и ручной сброс clear(). Порядок следующий:
- Опросить действия (click, press, turn...)
- Вызвать
clear() - Вызывать
tickRaw()между тяжёлыми участками кода
voidloop() {
if (btn.click()) ...
if (btn.press()) ...
if (btn.step()) ...
btn.clear();
// ...
btn.tickRaw();
// ...
btn.tickRaw();
// ...
btn.tickRaw();
// ...
}Это позволит опрашивать кнопку/энкодер в не очень хорошо написанной программе, где основной цикл завален тяжёлым кодом. Внутри tickRaw() накапливаются события, которые раз в цикл разбираются, а затем вручную сбрасываются.
В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события
releaseXxx
Если сложно избавиться от delay() внутри главного цикла программы, то на некоторых платформах можно поместить свой код внутри него. Таким образом можно получить даже обработку энкодера в цикле с дилеями без использования прерываний:
// вставка кода в delayvoidyield() {
eb.tickRaw();
}
voidloop() {
if (eb.click()) ...
if (btn.turn()) ...
eb.clear();
// ...delay(10);
// ...delay(50);
// ...
}В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события
releaseXxx
Библиотека обрабатывает кнопку следующим образом:
- Нажатие с программным подавлением дребезга (удержание дольше таймаута deb), результат - событие
press, состоянияpressingиbusy - Удержание дольше таймаута удержания hold - событие
hold, состояниеholding - Удержание дольше таймаута удержания hold + таймаута степ - импульсное событие
step, срабатывает с периодом step пока кнопка удерживается - Отпускание кнопки, результат - событие
release, снятие состоянийpressingиholding- Отпускание до таймаута удержания - событие
click - Отпускание после удержания - событие
releaseHold - Отпускание после импульсного удержания - событие
releaseStep - События
releaseHoldиreleaseStepвзаимоисключающие, если кнопка была удержана доstep-releaseHoldуже не сработает
- Отпускание до таймаута удержания - событие
- Ожидание нового клика в течение таймаута click, состояние
waiting - Если нового клика нет - снятие состоятия
busy, обработка закончена- Если кнопка снова нажата - обработка нового клика
- Счётчик кликов
getClicks()сбрасывается после событийreleaseHold/releaseStep, которые проверяют предварительные клики. В общем обработчикеaction()это событияEB_REL_HOLD_CилиEB_REL_STEP_C - Количество сделанных кликов нужно проверять по событию
hasClicks, а также можно опросить внутри почти всех событий кнопки, которые идут доreleaseXxx
- Если ожидается
timeout- событие timeout периодом изsetTimeout - Обработка кнопки в прерывании сообщает библиотеке о факте нажатия, вся остальная обработка выполняется штатно в
tick()
Отличие
click(n)отhasClicks(n):click(n)вернётtrueв любом случае при совпадении количества кликов, даже если будет сделано больше кликов.hasClicks(n)вернётtrueтолько в том случае, если было сделано ровно указанное количество кликов и больше кликов не было!
Лучше один раз увидеть, чем сто раз прочитать. Запусти пример demo и понажимай на кнопку, или попробуй онлайн-симуляцию в Wokwi
Онлайн-симуляция доступна здесь
- "Быстрым" поворотом считается поворот, совершённый менее чем за настроенный таймаут от предыдущего поворота
- Обработанные в прерывании повороты становятся активными (вызывают события) после вызова
tick() - Доступ к счётчику энкодера
counter- это публичная переменная класса, можно делать с ней всё что угодно:
Serial.println(eb.counter); // читать
eb.counter += 1234; // менять
eb.counter = 0; // обнулятьПри опросе энкодера в прерывании для атомарного доступа к 32-битному счётчику
на AVR используй getCounter() и setCounter(value).
- Поворот энкодера при зажатой кнопке снимает и блокирует все последующие события и клики, за исключением события
release. Состояния нажатой кнопки не изменяются - Поворот энкодера также влияет на системный таймаут (функция
timeout()) - сработает через указанное время после поворота энкодера - Счётчик кликов доступен при нажатом повороте: несколько кликов, зажатие кнопки, поворот
Библиотека считает количество кликов по кнопке и некоторые функции опроса могут отдельно обрабатываться с предварительными кликами. Например 3 клика, затем удержание. Это очень сильно расширяет возможности одной кнопки. Есть два варианта работы с такими событиями:
// 1if (btn.hold()) {
if (btn.getClicks() == 2) Serial.println("hold 2 clicks");
}
// 2if (btn.hold(2)) Serial.println("hold 2 clicks");В первом варианте можно получить количество кликов для дальнейшей обработки вручную, а во втором - библиотека сделает это сама, если количество кликов для действия заранее известно.
В некоторых сценариях бывает нужно получить состояние кнопки "здесь и сейчас", например определить удерживается ли кнопка сразу после запуска микроконтроллера (старта программы). Функцию tick() нужно вызывать постоянно в цикле, чтобы шла обработка кнопки с гашением дребезга контактов и прочими расчётами, поэтому конструкция следующего вида работать не будет:
voidsetup() {
btn.tick();
if (btn.press()) Serial.println("Кнопка нажата при старте");
}Для таких сценариев помогут следующие функции, возвращают true если кнопка нажата:
read()для библиотек Button и ButtonTreadBtn()для библиотек EncButton и EncButtonT
Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно:
voidsetup() {
// btn.setBtnLevel(LOW); // можно настроить уровеньif (btn.read()) Serial.println("Кнопка нажата при старте");
}Допустим нужно обработать кнопку синхронно и с гашением дребезга. Например если кнопка зажата при старте микроконтроллера - получить её удержание или даже импульсное удержание внутри блока setup, то есть до начала выполнения основной программы. Можно воспользоваться состоянием busy и опрашивать кнопку из цикла:
voidsetup() {
Serial.begin(115200);
btn.tick();
while (btn.busy()) {
btn.tick();
if (btn.hold()) Serial.println("hold");
if (btn.step()) Serial.println("step");
}
Serial.println("program start");
}Как это работает: первый тик опрашивает кнопку, если кнопка нажата - сразу же активируется состояние busy и система попадает в цикл while. Внутри него продолжаем тикать и получать события с кнопки. Когда кнопка будет отпущена и сработают все события - флаг busy опустится и программа автоматически покинет цикл. Можно переписать эту конструкцию на цикл с постусловием, более красиво:
do {
btn.tick();
if (btn.hold()) Serial.println("hold");
if (btn.step()) Serial.println("step");
} while (btn.busy());В связанных с кнопкой классах (Button, EncButton) есть функция timeout() - она однократно вернёт true, если после окончания действий с кнопкой/энкодером прошло указанное в setTimeout время. Это можно использовать для сохранения параметров после ввода, например:
voidsetup() {
//eb.setTimeout(1500); // умолч. 1000
}
voidloop() {
eb.tick();
// ...if (eb.timeout()) {
// после взаимодействия с энкодером прошло 1000 мс// EEPROM.put(0, settings);
}
}В текущей версии обработка таймаута реализована не очень красиво ради экономии места: системный флаг таймаута активен всё время (action будет возвращать событие таймаута), сбрасывается в следующих случаях:
- Вызов
timeout()- данный метод однократно вернётtrue, последующие вызовы будут возвращатьfalseдо нового действия - Автоматически сбросится после вызова обработчика, если он подключен
- При вызове
clear(true)- с флагом очистки таймаута - При вызове
reset()
Если нужно пробросить опрос таймаута через несколько вызовов - можно использовать timeoutState(), но последний вызов должен быть timeout().
Функция busy() возвращает true, пока идёт обработка кнопки, т.е. пока система ожидает действий и выхода таймаутов. Это можно использовать для оптимизации кода, например избегать каких то долгих и тяжёлых частей программы на время обработки кнопки:
voidloop() {
eb.tick();
// ...if (!eb.busy()) {
// потенциально долгий и тяжёлый код
}
}Доступно во всех классах с кнопкой:
VirtButtonButtonVirtEncButtonEncButton
Функция action() при наступлении события возвращает код события (отличный от нуля, что само по себе является индикацией наличия события):
EB_PRESS- нажатие на кнопкуEB_HOLD- кнопка удержанаEB_STEP- импульсное удержаниеEB_RELEASE- кнопка отпущенаEB_CLICK- одиночный кликEB_CLICKS- сигнал о нескольких кликахEB_TURN- поворот энкодераEB_REL_HOLD- кнопка отпущена после удержанияEB_REL_HOLD_C- кнопка отпущена после удержания с предв. кликамиEB_REL_STEP- кнопка отпущена после степаEB_REL_STEP_C- кнопка отпущена после степа с предв. кликамиEB_TIMEOUT- прошёл таймаут после нажатия кнопки или поворота энкодера
Полученный код события можно обработать через switch:
switch (eb.action()) {
caseEB_PRESS:
// ...break;
caseEB_HOLD:
// ...break;
// ...
}Есть аналогичная функция getAction(), вернёт то же самое, но с более читаемыми константами (удобно с автодополнением):
EBAction::PressEBAction::HoldEBAction::StepEBAction::ReleaseEBAction::ClickEBAction::ClicksEBAction::TurnEBAction::ReleaseHoldEBAction::ReleaseHoldClicksEBAction::ReleaseStepEBAction::ReleaseStepClicksEBAction::Timeout
Полученный код события можно обработать через switch:
switch (eb.getAction()) {
case EBAction::Press:
// ...break;
case EBAction::Hold:
// ...break;
// ...
}Результат функций
action()/getAction()сбрасывается после следующего вызоваtick(), то есть доступен на всей текущей итерации основного цикла
Для максимального уменьшения веса библиотеки (в частности в оперативной памяти) нужно задавать тайматуы константами через define (экономия 1 байт за таймаут), отключить обработчик событий, счётчики-буферы и использовать T-класс (экономия 1 байт за пин):
#defineEB_NO_FOR
#defineEB_NO_CALLBACK
#defineEB_NO_COUNTER
#defineEB_NO_BUFFER
#defineEB_DEB_TIME50// таймаут гашения дребезга кнопки (кнопка)
#defineEB_CLICK_TIME500// таймаут ожидания кликов (кнопка)
#defineEB_HOLD_TIME600// таймаут удержания (кнопка)
#defineEB_STEP_TIME200// таймаут импульсного удержания (кнопка)
#defineEB_FAST_TIME30// таймаут быстрого поворота (энкодер)
#defineEB_TOUT_TIME1000// таймаут действия (кнопка и энкодер)
#include<EncButton.h>
EncButtonT<2, 3, 4> eb;В таком случае энкодер с кнопкой займёт в SRAM всего 8 байт, а просто кнопка - 5.
Чтобы сократить время на проверку системных флагов событий (незначительно, но приятно) можно поместить все опросы в условие по tick(), так как tick() возвращает true только при наступлении события:
voidloop() {
if (eb.tick()) {
if (eb.turn()) ...;
if (eb.click()) ...;
}
}Также опрос событий при помощи функции action() выполняется быстрее, чем ручной опрос отдельных функций событий, поэтому максимально эффективно библиотека будет работать вот в таком формате:
voidloop() {
if (eb.tick()) {
switch (eb.action()) {
caseEB_PRESS:
// ...break;
caseEB_HOLD:
// ...break;
// ...
}
}
}Для опроса состояний кнопки pressing(), holding(), waiting() можно поместить их вовнутрь условия по busy(), чтобы не опрашивать состояния пока их гарантированно нет:
if (btn.busy()) {
if (btn.pressing())...
if (btn.holding())...
if (btn.waiting())...
}Можно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах с кнопкой:
VirtButtonButtonVirtEncButtonEncButton
Внутри коллбэка можно получить указатель на текущий объект (который вызвал коллбэк) из переменной
void* EB_self
EncButton eb(2, 3, 4);
voidcallback() {
switch (eb.action()) {
caseEB_PRESS:
// ...break;
caseEB_HOLD:
// ...break;
// ...
}
// здесь EB_self указатель на eb
}
voidsetup() {
eb.attach(callback);
}
voidloop() {
eb.tick();
}Библиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно:
- Cоздать специальную кнопку
MultiButton - Передать виртуальной кнопке в обработку свои кнопки (это могут быть объекты классов
ButtonиEncButton+ ихT-версии). Мульти-кнопка сама опросит обе кнопки! - Опрашивать события или слушать обработчик
Button b0(4);
Button b1(5);
MultiButton b2; // 1voidloop() {
b2.tick(b0, b1); // 2// 3if (b0.click()) Serial.println("b0 click");
if (b1.click()) Serial.println("b1 click");
if (b2.click()) Serial.println("b0+b1 click");
}Библиотека сама "сбросит" лишние события с реальных кнопок, если они были нажаты вместе, за исключением события press. Таким образом получается полноценная третья кнопка из двух других с удобным опросом.
- Создать дополнительную виртуальную кнопку
- Вызвать тикеры у отдельных кнопок
- Вызвать тикер у виртуальной кнопки, передав в него отдельные кнопки
Данный способ не работает с обработчиком - он будет вызван у отдельных кнопок в любом случае
Button b0(4);
Button b1(5);
VirtButton b2; // виртуальная - 1voidloop() {
// тикеры - 2
b0.tick();
b1.tick();
// обработка одновременного нажатия двух кнопок - 3
b2.tick(b0, b1);
if (b0.click()) Serial.println("b0 click");
if (b1.click()) Serial.println("b1 click");
if (b2.click()) Serial.println("b0+b1 click");
if (b2.step()) Serial.println("b0+b1 step");
}Для обработки энкодера в загруженной программе нужно:
- Подключить оба его пина на аппаратные прерывания по
CHANGE - Установить
setEncISR(true) - Вызывать в обработчике специальный тикер для прерывания
- Основной тикер также нужно вызывать в
loopдля корреткной работы - события генерируются в основном тикере:
// пример для ATmega328 и EncButton
EncButton eb(2, 3, 4);
/*// esp8266/esp32IRAM_ATTR void isr() { eb.tickISR();}*/voidisr() {
eb.tickISR();
}
voidsetup() {
attachInterrupt(0, isr, CHANGE);
attachInterrupt(1, isr, CHANGE);
eb.setEncISR(true);
}
voidloop() {
eb.tick();
}Примечание: использование работы в прерывании позволяет корректно обрабатывать позицию энкодера и не пропустить новый поворот. Событие с поворотом, полученное из прерывания, станет доступно после вызова tick в основном цикле программы, что позволяет не нарушать последовательность работы основного цикла:
- Буферизация отключена: событие
turnактивируется только один раз, независимо от количества щелчков энкодера, совершённых между двумя вызовамиtick(щелчки обработаны в прерывании) - Буферизация включена: событие
turnбудет вызвано столько раз, сколько реально было щелчков энкодера, это позволяет вообще не пропускать повороты и не нагружать систему в прерывании. Размер буфера - 5 необработанных щелчков энкодера
Примечания:
- Функция
setEncISRработает только в не виртуальных классах. Если он включен - основной тикерtickпросто не опрашивает пины энкодера, что экономит процессорное время. Обработка происходит только в прерывании - Счётчик энкодера всегда имеет актуальное значение и может опережать буферизированные повороты в программе с большими задержками в основном цикле!
- На разных платформах прерывания могут работать по разному (например на ESPxx - нужно добавить функции аттрибут
IRAM_ATTR, см. документацию на свою платформу!) - Обработчик, подключенный в
attach(), будет вызван изtick(), то есть не из прерывания!
В виртуальных есть тикер, в который не нужно передавать состояние энкодера, если он обрабатывается в прерывании, это позволяет не опрашивать пины в холостую. Например:
VirtEncoder e;
voidisr() {
e.tickISR(digitalRead(2), digitalRead(3));
}
voidsetup() {
attachInterrupt(0, isr, CHANGE);
attachInterrupt(1, isr, CHANGE);
e.setEncISR(1);
}
voidloop() {
e.tick(); // не передаём состояния пинов
}Для обработки кнопки в прерывании нужно:
- Подключить прерывание на нажатие кнопки с учётом её физического подключения и уровня:
- Если кнопка замыкает
LOW- прерываниеFALLING - Если кнопка замыкает
HIGH- прерываниеRISING
- Если кнопка замыкает
- Вызывать
pressISR()в обработчике прерывания
Button b(2);
/*// esp8266/esp32IRAM_ATTR void isr() { b.pressISR();}*/voidisr() {
b.pressISR();
}
voidsetup() {
attachInterrupt(0, isr, FALLING);
}
voidloop() {
b.tick();
}Примечание: кнопка обрабатывается в основном tick(), а функция pressISR() всего лишь сообщает библиотеке, что кнопка была нажата вне tick(). Это позволяет не пропустить нажатие кнопки, пока программа была занята чем-то другим.
Нужно подключить все три пина на прерывания и действовать как выше - и для кнопки, и для энкодера:
voideisr() {
eb.tickISR();
}
voidbisr() {
eb.pressISR();
}
voidsetup() {
// номера прерываний "для примера"attachInterrupt(0, eisr, CHANGE);
attachInterrupt(1, eisr, CHANGE);
eb.setEncISR(true);
attachInterrupt(2, bisr, FALLING);
}
voidloop() {
eb.tick();
}Создать массив можно только из нешаблонных классов (без буквы T), потому что номера пинов придётся указать уже в рантайме далее в программе. Например:
Button btns[5];
EncButton ebs[3];
voidsetup() {
btns[0].init(2); // указать пин
btns[1].init(5);
btns[2].init(10);
// ...
ebs[0].init(11, 12, 13, INPUT);
ebs[1].init(14, 15, 16);
// ...
}
voidloop() {
for (int i = 0; i < 5; i++) btns[i].tick();
for (int i = 0; i < 3; i++) ebs[i].tick();
if (btns[2].click()) Serial.println("btn2 click");
// ...
}Библиотека поддерживает задание своих функций для чтения пина и получения времени без редактирования файлов библиотеки. Для этого нужно реализовать соответствующую функцию в своём .cpp или .ino файле:
bool EB_read(uint8_t pin)- для своей функции чтения пинаvoid EB_mode(uint8_t pin, uint8_t mode)- для своего аналога pinModeuint32_t EB_uptime()- для своего аналога millis()
Пример:
#include<EncButton.h>boolEB_read(uint8_t pin) {
returndigitalRead(pin);
}
voidEB_mode(uint8_t pin, uint8_t mode) {
pinMode(pin, mode);
}
uint32_tEB_uptime() {
returnmillis();
}Иногда может понадобиться вызывать tick() не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера!
voidloop() {
// таймер на 50 мсstaticuint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
btn.tick(readSomePin());
}
// будет активно в течение 50 мс!!!if (btn.click()) foo();
}В данной ситуации нужно поступить так: тикать по таймеру, там же обрабатывать события и сбрасывать флаги в конце:
voidloop() {
// таймер на 50 мсstaticuint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
// тик
btn.tick(readSomePin());
// разбор событийif (btn.click()) foo();
// сброс флагов
btn.clear();
}
}Либо можно подключить обработчик и вызывать clear() в конце функции:
voidcallback() {
switch (btn.action()) {
// ...
}
// сброс флагов
btn.clear();
}
voidloop() {
// таймер на 50 мсstaticuint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
btn.tick(readSomePin());
}
}В случае с вызовом по таймеру антидребезг будет частично обеспечиваться самим таймером и в библиотеке его можно отключить (поставить период 0).
Для корректной работы таймаутов, состояний и счётчика кликов нужен другой подход: буферизировать прочитанные по таймеру состояния и передавать их в тик в основном цикле. Например так:
bool readbuf = 0; // буфер пинаvoidloop() {
// таймер на 50 мсstaticuint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
readbuf = readSomePin(); // чтение в буфер
}
// тик из буфера
btn.tick(readbuf);
if (btn.click()) foo();
}EncButton позволяет кнопке работать в паре с энкодером для корректного отслеживания нажатых поворотов - при нажатом повороте события с кнопки будут пропущены, т.е. не обработается удержание и клик. Допустим кнопок несколько: они могут выполнять действия как сами по себе, так и в паре с энкодером (кнопка зажата и крутится энкодер, в программе меняется выбранное кнопкой значение). Чтобы при удержании кнопка не генерировала события (удержание, степ, клики...) можно включить пропуск событий. Он будет действовать до отпускания кнопки:
if (btn.pressing() && enc.turn()) {
btn.skipEvents(); // зафиксирован поворот. Пропускаем события// нажатый поворот
}
if (btn.click()) {
// просто клик
}// меняем значения переменных// поворот энкодераif (enc.turn()) {
// меняем с шагом 5
var += 5 * enc.dir();
// меняем с шагом 1 при обычном повороте, 10 при быстром
var += enc.fast() ? 10 : 1;
// меняем с шагом 1 при обычном повороте, 10 при нажатом
var += enc.pressing() ? 10 : 1;
// меняем одну переменную при повороте, другую - при нажатом поворотеif (enc.pressing()) var0++;
else var1++;
// если кнопка нажата - доступны предварительные клики// Выбираем переменную для изменения по предв. кликамif (enc.pressing()) {
switch (enc.getClicks()) {
case1: var0 += enc.dir();
break;
case2: var1 += enc.dir();
break;
case3: var2 += enc.dir();
break;
}
}
}
// импульсное удержание на каждом шаге инкрементирует переменнуюif (btn.step()) var++;
// смена направления изменения переменной после отпускания из stepif (btn.step()) var += dir;
if (btn.releaseStep()) dir = -dir;
// изменение выбранной переменной при помощи stepif (btn.step(1)) var1++; // клик-удержаниеif (btn.step(2)) var2++; // клик-клик-удержаниеif (btn.step(3)) var3++; // клик-клик-клик-удержание// если держать step больше 2 секунд - инкремент +5, пока меньше - +1if (btn.step()) {
if (btn.stepFor(2000)) var += 5;
else var += 1;
}
// включение режима по количеству кликовif (btn.hasClicks()) mode = btn.getClicks();
// включение режима по нескольким кликам и удержаниюif (btn.hold(1)) mode = 1; // клик-удержаниеif (btn.hold(2)) mode = 2; // клик-клик-удержаниеif (btn.hold(3)) mode = 3; // клик-клик-клик-удержание// или такif (btn.hold()) mode = btn.getClicks();
// кнопка отпущена, смотрим сколько её удерживалиif (btn.release()) {
// от 1 до 2 секундif (btn.pressFor() > 1000 && btn.pressFor() <= 2000) mode = 1;
// от 2 до 3 секундelseif (btn.pressFor() > 2000 && btn.pressFor() <= 3000) mode = 2;
}// ВИРТУАЛЬНЫЕ
VirtEncButton eb; // энкодер с кнопкой
VirtButton b; // кнопка
VirtEncoder e; // энкодер// РЕАЛЬНЫЕ// энкодер с кнопкой
EncButton eb(enc0, enc1, btn); // пины энкодера и кнопки
EncButton eb(enc0, enc1, btn, modeEnc); // + режим пинов энкодера (умолч. INPUT)
EncButton eb(enc0, enc1, btn, modeEnc, modeBtn); // + режим пина кнопки (умолч. INPUT_PULLUP)
EncButton eb(enc0, enc1, btn, modeEnc, modeBtn, btnLevel); // + уровень кнопки (умолч. LOW)// шаблонный
EncButton<enc0, enc1, btn> eb; // пины энкодера и кнопки
EncButton<enc0, enc1, btn> eb(modeEnc); // + режим пинов энкодера (умолч. INPUT)
EncButton<enc0, enc1, btn> eb(modeEnc, modeBtn); // + режим пина кнопки (умолч. INPUT_PULLUP)
EncButton<enc0, enc1, btn> eb(modeEnc, modeBtn, btnLevel); // + уровень кнопки (умолч. LOW)// кнопка
Button b(pin); // пин
Button b(pin, mode); // + режим пина кнопки (умолч. INPUT_PULLUP)
Button b(pin, mode, btnLevel); // + уровень кнопки (умолч. LOW)// шаблонный
ButtonT<pin> b; // пин
ButtonT<pin> b(mode); // + режим пина кнопки (умолч. INPUT_PULLUP)
ButtonT<pin> b(mode, btnLevel); // + уровень кнопки (умолч. LOW)// энкодер
Encoder e(enc0, enc1); // пины энкодера
Encoder e(enc0, enc1, mode); // + режим пинов энкодера (умолч. INPUT)// шаблонный
EncoderT<enc0, enc1> e; // пины энкодера
EncoderT<enc0, enc1> e(mode); // + режим пинов энкодера (умолч. INPUT)| v2 | v3 |
|---|---|
held() | hold() |
hold() | holding() |
state() | pressing() |
setPins() | init() |
- Изменился порядок указания пинов (см. доку выше)
clearFlags()заменена наclear()(сбросить флаги событий) иreset()(сбросить системные флаги обработки, закончить обработку)
В v3 функции опроса событий (click, turn...) не сбрасываются сразу после своего вызова - они сбрасываются при следующем вызове tick(), таким образом сохраняют своё значение во всех последующих вызовах на текущей итерации главного цикла программы. Поэтому tick() нужно вызывать только 1 раз за цикл, иначе будут пропуски действий! Читай об этом выше.
Остальные примеры смотри в examples!
Полное демо EncButton
// #define EB_NO_FOR // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)// #define EB_NO_CALLBACK // отключить обработчик событий attach (экономит 2 байта оперативки)// #define EB_NO_COUNTER // отключить счётчик энкодера (экономит 4 байта оперативки)// #define EB_NO_BUFFER // отключить буферизацию энкодера (экономит 1 байт оперативки)// #define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка)// #define EB_CLICK_TIME 500 // таймаут ожидания кликов (кнопка)// #define EB_HOLD_TIME 600 // таймаут удержания (кнопка)// #define EB_STEP_TIME 200 // таймаут импульсного удержания (кнопка)// #define EB_FAST_TIME 30 // таймаут быстрого поворота (энкодер)// #define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер)
#include<EncButton.h>
EncButton eb(2, 3, 4);
//EncButton eb(2, 3, 4, INPUT); // + режим пинов энкодера//EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP); // + режим пинов кнопки//EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP, LOW); // + уровень кнопкиvoidsetup() {
Serial.begin(115200);
// показаны значения по умолчанию
eb.setBtnLevel(LOW);
eb.setClickTimeout(500);
eb.setDebTimeout(50);
eb.setHoldTimeout(600);
eb.setStepTimeout(200);
eb.setEncReverse(0);
eb.setEncType(EB_STEP4_LOW);
eb.setFastTimeout(30);
// сбросить счётчик энкодера
eb.counter = 0;
}
voidloop() {
eb.tick();
// обработка поворота общаяif (eb.turn()) {
Serial.print("turn: dir ");
Serial.print(eb.dir());
Serial.print(", fast ");
Serial.print(eb.fast());
Serial.print(", hold ");
Serial.print(eb.pressing());
Serial.print(", counter ");
Serial.print(eb.counter);
Serial.print(", clicks ");
Serial.println(eb.getClicks());
}
// обработка поворота раздельнаяif (eb.left()) Serial.println("left");
if (eb.right()) Serial.println("right");
if (eb.leftH()) Serial.println("leftH");
if (eb.rightH()) Serial.println("rightH");
// кнопкаif (eb.press()) Serial.println("press");
if (eb.click()) Serial.println("click");
if (eb.release()) {
Serial.println("release");
Serial.print("clicks: ");
Serial.print(eb.getClicks());
Serial.print(", steps: ");
Serial.print(eb.getSteps());
Serial.print(", press for: ");
Serial.print(eb.pressFor());
Serial.print(", hold for: ");
Serial.print(eb.holdFor());
Serial.print(", step for: ");
Serial.println(eb.stepFor());
}
// состояния// Serial.println(eb.pressing());// Serial.println(eb.holding());// Serial.println(eb.busy());// Serial.println(eb.waiting());// таймаутif (eb.timeout()) Serial.println("timeout!");
// удержаниеif (eb.hold()) Serial.println("hold");
if (eb.hold(3)) Serial.println("hold 3");
// импульсное удержаниеif (eb.step()) Serial.println("step");
if (eb.step(3)) Serial.println("step 3");
// отпущена после импульсного удержанияif (eb.releaseStep()) Serial.println("release step");
if (eb.releaseStep(3)) Serial.println("release step 3");
// отпущена после удержанияif (eb.releaseHold()) Serial.println("release hold");
if (eb.releaseHold(2)) Serial.println("release hold 2");
// проверка на количество кликовif (eb.hasClicks(3)) Serial.println("has 3 clicks");
// вывести количество кликовif (eb.hasClicks()) {
Serial.print("has clicks: ");
Serial.println(eb.getClicks());
}
}Подключение обработчика
#include<EncButton.h>
EncButton eb(2, 3, 4);
voidcallback() {
Serial.print("callback: ");
switch (eb.action()) {
caseEB_PRESS:
Serial.println("press");
break;
caseEB_HOLD:
Serial.println("hold");
break;
caseEB_STEP:
Serial.println("step");
break;
caseEB_RELEASE:
Serial.println("release");
break;
caseEB_CLICK:
Serial.println("click");
break;
caseEB_CLICKS:
Serial.print("clicks ");
Serial.println(eb.getClicks());
break;
caseEB_TURN:
Serial.print("turn ");
Serial.print(eb.dir());
Serial.print("");
Serial.print(eb.fast());
Serial.print("");
Serial.println(eb.pressing());
break;
caseEB_REL_HOLD:
Serial.println("release hold");
break;
caseEB_REL_HOLD_C:
Serial.print("release hold clicks ");
Serial.println(eb.getClicks());
break;
caseEB_REL_STEP:
Serial.println("release step");
break;
caseEB_REL_STEP_C:
Serial.print("release step clicks ");
Serial.println(eb.getClicks());
break;
}
}
voidsetup() {
Serial.begin(115200);
eb.attach(callback);
}
voidloop() {
eb.tick();
}Все типы кнопок
#include<EncButton.h>
Button btn(4);
ButtonT<5> btnt;
VirtButton btnv;
voidsetup() {
Serial.begin(115200);
}
voidloop() {
// Button
btn.tick();
if (btn.click()) Serial.println("btn click");
// ButtonT
btnt.tick();
if (btnt.click()) Serial.println("btnt click");
// VirtButton
btnv.tick(!digitalRead(4)); // передать логическое значениеif (btnv.click()) Serial.println("btnv click");
}Все типы энкодеров
#include<EncButton.h>
Encoder enc(2, 3);
EncoderT<5, 6> enct;
VirtEncoder encv;
voidsetup() {
Serial.begin(115200);
}
voidloop() {
// опрос одинаковый для всех, 3 способа:// 1// tick вернёт 1 или -1, значит это шагif (enc.tick()) Serial.println(enc.counter);
// 2// можно опросить через turn()
enct.tick();
if (enct.turn()) Serial.println(enct.dir());
// 3// можно не использовать опросные функции, а получить направление напрямуюint8_t v = encv.tick(digitalRead(2), digitalRead(3));
if (v) Serial.println(v); // выведет 1 или -1
}Старые
- 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. hold() и held() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс
- 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
- v1.21 - EB_HALFSTEP_ENC теперь работает для обычного режима
- v1.22 - улучшен EB_HALFSTEP_ENC для обычного режима
- v1.23 - getDir() заменил на dir()
- v2.0
- Алгоритм EB_BETTER_ENC оптимизирован и установлен по умолчанию, дефайн EB_BETTER_ENC упразднён
- Добавлен setEncType() для настройки типа энкодера из программы, дефайн EB_HALFSTEP_ENC упразднён
- Добавлен setEncReverse() для смены направления энкодера из программы
- Добавлен setStepTimeout() для установки периода импульсного удержания, дефайн EB_STEP упразднён
- Мелкие улучшения и оптимизация
- v3.0
- Библиотека переписана с нуля, с предыдущими версиями несовместима!
- Полностью другая инициализация объекта
- Переименованы: hold()->holding(), held()->hold()
- Оптимизация Flash памяти: библиотека весит меньше, в некоторых сценариях - на несколько килобайт
- Оптимизация скорости выполнения кода, в том числе в прерывании
- На несколько байт меньше оперативной памяти, несколько уровней оптимизации на выбор
- Более простое, понятное и удобное использование
- Более читаемый исходный код
- Разбитие на классы для использования в разных сценариях
- Новые функции, возможности и обработчики для кнопки и энкодера
- Буферизация энкодера в прерывании
- Нативная обработка двух одновременно нажимаемых кнопок как третьей кнопки
- Поддержка 4-х типов энкодеров
- Переписана документация
- EncButton теперь заменяет GyverLibs/VirtualButton (архивирована)
- Библиотека переписана с нуля, с предыдущими версиями несовместима!
- v3.1
- Расширена инициализация кнопки
- Убраны holdEncButton() и toggleEncButton()
- Добавлен turnH()
- Оптимизированы прерывания энкодера, добавлена setEncISR()
- Буферизация направления и быстрого поворота
- Сильно оптимизирована скорость работы action() (общий обработчик)
- Добавлено подключение внешней функции-обработчика событий
- Добавлена обработка кнопки в прерывании - pressISR()
- v3.2
- Добавлены функции tickRaw() и clear() для всех классов. Позволяет проводить раздельную обработку (см. доку)
- Улучшена обработка кнопки с использованием прерываний
- v3.3
- Добавлены функции получения времени удержания pressFor(), holdFor(), stepFor() (отключаемые)
- Добавлен счётчик степов getSteps() (отключаемый)
- v3.4
- Доступ к счётчику кликов во время нажатого поворота
- Добавлена функция detach()
- v3.5
- Добавлена зависимость GyverIO (ускорен опрос пинов)
- Добавлена возможность задать свои функции аптайма и чтения пина
- v3.5.2
- Оптимизация
- Упрощена замена кастомных функций
- Исправлена ошибка компиляции при использовании библиотеки в нескольких .cpp файлах
- v3.5.3
- Добавлено количество кликов в опрос press/release/click/pressing
- v3.5.5 - коллбэк на базе std::function для ESP
- v3.5.8 - добавлен метод releaseHoldStep()
- v3.5.11 - добавлен метод skipEvents() для игнорирования событий кнопки в сложных сценариях использования
- v3.6.0
- Добавлен класс MultiButton для корректного опроса нескольких кнопок с вызовом обработчика
- Добавлено подключение обработчика с передачей указателя на объект
При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru
Библиотека открыта для доработки и ваших Pull Request'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код




