Новая версия v3 несовместима с предыдущими, смотри документацию, примеры и краткий гайд по миграции с v2 на v3! |
---|
Лёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino
- Кнопка
- Обработка событий: нажатие, отпускание, клик, счётчик кликов, удержание, импульсное удержание, время удержания + предварительные клики для всех режимов
- Программное подавление дребезга
- Поддержка обработки двух одновременно нажимаемых кнопок как третьей кнопки
- Энкодер
- Обработка событий: обычный поворот, нажатый поворот, быстрый поворот
- Поддержка четырёх типов инкрементальных энкодеров
- Высокоточный алгоритм определения позиции
- Буферизация в прерывании
- Простое и понятное использование
- Огромное количество возможностей и их комбинаций для разных сценариев использования даже одной кнопки
- Виртуальный режим (например для работы с расширителем пинов)
- Оптимизирована для работы в прерывании
- Максимально быстрое чтение пинов для AVR, esp8266, esp32 (используется GyverIO)
- Быстрые асинхронные алгоритмы опроса действий с кнопки и энкодера
- Жёсткая оптимизация и небольшой вес во Flash и SRAM памяти: 5 байт SRAM (на экземпляр) и ~350 байт Flash на обработку кнопки
Примеры сценариев использования:
- Несколько кликов - включение режима (по кол-ву кликов)
- Несколько кликов + короткое удержание - ещё вариант включения режима (по кол-ву кликов)
- Несколько кликов + удержание - постепенное изменение значения выбранной переменной (по кол-ву кликов)
- Несколько кликов выбирают переменную, энкодер её изменяет
- Изменение шага изменения переменной при вращении энкодера - например уменьшение при зажатой кнопке и увеличение при быстром вращении
- Навигация по меню при вращении энкодера, изменение переменной при вращении зажатого энкодера
- Полноценная навигация по меню при использовании двух кнопок (одновременное удержание для перехода на следующий уровень, одновременное нажатие для возврата на предыдущий)
- И так далее
Совместима со всеми 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 байта оперативки)
#define EB_NO_FOR
// отключить обработчик событий attach (экономит 2 байта оперативки)
#define EB_NO_CALLBACK
// отключить счётчик энкодера [VirtEncoder, Encoder, EncButton] (экономит 4 байта оперативки)
#define EB_NO_COUNTER
// отключить буферизацию энкодера (экономит 2 байта оперативки)
#define EB_NO_BUFFER
/*
Настройка таймаутов для всех классов
- Заменяет таймауты константами, изменить их из программы (SetXxxTimeout()) будет нельзя
- Настройка влияет на все объявленные в программе кнопки/энкодеры
- Экономит 1 байт оперативки на объект за каждый таймаут
- Показаны значения по умолчанию в мс
- Значения не ограничены 4000мс, как при установке из программы (SetXxxTimeout())
*/
#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 // таймаут быстрого поворота (энкодер)
Как работать с документацией: 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 | ✔ | ✔ | ✔ | ✔ |
setBtnLevel | ✔ | ✔ | ✔ | ✔ |
pressISR | ✔ | ✔ | ✔ | ✔ |
reset | ✔ | ✔ | ✔ | ✔ |
clear | ✔ | ✔ | ✔ | ✔ |
attach | ✔ | ✔ | ✔ | ✔ |
detach | ✔ | ✔ | ✔ | ✔ |
press | ✔ | ✔ | ✔ | ✔ |
release | ✔ | ✔ | ✔ | ✔ |
click | ✔ | ✔ | ✔ | ✔ |
pressing | ✔ | ✔ | ✔ | ✔ |
hold | ✔ | ✔ | ✔ | ✔ |
holding | ✔ | ✔ | ✔ | ✔ |
step | ✔ | ✔ | ✔ | ✔ |
hasClicks | ✔ | ✔ | ✔ | ✔ |
getClicks | ✔ | ✔ | ✔ | ✔ |
getSteps | ✔ | ✔ | ✔ | ✔ |
releaseHold | ✔ | ✔ | ✔ | ✔ |
releaseStep | ✔ | ✔ | ✔ | ✔ |
waiting | ✔ | ✔ | ✔ | ✔ |
busy | ✔ | ✔ | ✔ | ✔ |
action | ✔ | ✔ | ✔ | ✔ |
timeout | ✔ | ✔ | ✔ | ✔ |
pressFor | ✔ | ✔ | ✔ | ✔ |
holdFor | ✔ | ✔ | ✔ | ✔ |
stepFor | ✔ | ✔ | ✔ | ✔ |
Таблица функций энкодера
VirtEncoder | Encoder | VirtEncButton | EncButton | |
---|---|---|---|---|
readEnc | ✔ | |||
initEnc | ✔ | ✔ | ✔ | ✔ |
setEncReverse | ✔ | ✔ | ✔ | ✔ |
setEncType | ✔ | ✔ | ✔ | ✔ |
setEncISR | ✔ | ✔ | ✔ | ✔ |
clear | ✔ | ✔ | ✔ | ✔ |
turn | ✔ | ✔ | ✔ | ✔ |
dir | ✔ | ✔ | ✔ | ✔ |
tickRaw | ✔ | ✔ | ✔ | ✔ |
pollEnc | ✔ | ✔ | ✔ | ✔ |
counter | ✔ | ✔ | ✔ | ✔ |
setFastTimeout | ✔ | ✔ | ||
turnH | ✔ | ✔ | ||
fast | ✔ | ✔ | ||
right | ✔ | ✔ | ||
left | ✔ | ✔ | ||
rightH | ✔ | ✔ | ||
leftH | ✔ | ✔ | ||
action | ✔ | ✔ | ||
timeout | ✔ | ✔ | ||
attach | ✔ | ✔ | ||
detach | ✔ | ✔ |
VirtButton
// ================ НАСТРОЙКИ ================
// установить таймаут удержания, умолч. 600 (макс. 4000 мс)
void setHoldTimeout(uint16_t tout);
// установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс)
void setStepTimeout(uint16_t tout);
// установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс)
void setClickTimeout(uint16_t tout);
// установить таймаут антидребезга, умолч. 50 (макс. 255 мс)
void setDebTimeout(uint8_t tout);
// установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND)
// умолч. HIGH, то есть true - кнопка нажата
void setBtnLevel(bool level);
// подключить функцию-обработчик событий (вида void f())
void attach(void (*handler)());
// отключить функцию-обработчик событий
void detach();
// ================== СБРОС ==================
// сбросить системные флаги (принудительно закончить обработку)
void reset();
// принудительно сбросить флаги событий
void clear();
// ================ ОБРАБОТКА ================
// обработка кнопки значением
bool tick(bool s);
// обработка виртуальной кнопки как одновременное нажатие двух других кнопок
bool tick(VirtButton& b0, VirtButton& b1);
// кнопка нажата в прерывании кнопки
void pressISR();
// обработка кнопки без сброса событий и вызова коллбэка
bool tickRaw(bool s);
// ================== ОПРОС ==================
// кнопка нажата [событие]
bool press();
bool press(uint8_t clicks);
// кнопка отпущена (в любом случае) [событие]
bool release();
bool release(uint8_t clicks);
// клик по кнопке (отпущена без удержания) [событие]
bool click();
bool click(uint8_t clicks);
// кнопка зажата (между press() и release()) [состояние]
bool pressing();
bool pressing(uint8_t clicks);
// кнопка была удержана (больше таймаута) [событие]
bool hold();
bool hold(uint8_t clicks);
// кнопка удерживается (больше таймаута) [состояние]
bool holding();
bool holding(uint8_t clicks);
// импульсное удержание [событие]
bool step();
bool step(uint8_t clicks);
// зафиксировано несколько кликов [событие]
bool hasClicks();
bool hasClicks(uint8_t clicks);
// кнопка отпущена после удержания [событие]
bool releaseHold();
bool releaseHold(uint8_t clicks);
// кнопка отпущена после импульсного удержания [событие]
bool releaseStep();
bool releaseStep(uint8_t clicks);
// получить количество кликов
uint8_t getClicks();
// получить количество степов
uint16_t getSteps();
// кнопка ожидает повторных кликов (между click() и hasClicks()) [состояние]
bool waiting();
// идёт обработка (между первым нажатием и после ожидания кликов) [состояние]
bool busy();
// было действие с кнопки, вернёт код события [событие]
uint16_t action();
// ================== ВРЕМЯ ==================
// после взаимодействия с кнопкой (или энкодером EncButton) прошло указанное время, мс [событие]
bool timeout(uint16_t ms);
// время, которое кнопка удерживается (с начала нажатия), мс
uint16_t pressFor();
// кнопка удерживается дольше чем (с начала нажатия), мс [состояние]
bool pressFor(uint16_t ms);
// время, которое кнопка удерживается (с начала удержания), мс
uint16_t holdFor();
// кнопка удерживается дольше чем (с начала удержания), мс [состояние]
bool holdFor(uint16_t ms);
// время, которое кнопка удерживается (с начала степа), мс
uint16_t stepFor();
// кнопка удерживается дольше чем (с начала степа), мс [состояние]
bool stepFor(uint16_t ms);
VirtEncoder
// ==================== НАСТРОЙКИ ====================
// инвертировать направление энкодера (умолч. 0)
void setEncReverse(bool rev);
// установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1)
void setEncType(uint8_t type);
// использовать обработку энкодера в прерывании
void setEncISR(bool use);
// инициализация энкодера
void initEnc(bool e0, bool e1);
// инициализация энкодера совмещённым значением
void initEnc(int8_t v);
// сбросить флаги событий
void clear();
// ====================== ОПРОС ======================
// был поворот [событие]
bool turn();
// направление энкодера (1 или -1) [состояние]
int8_t dir();
// счётчик
int32_t counter;
// ==================== ОБРАБОТКА ====================
// опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановке
int8_t tickISR(bool e0, bool e1);
int8_t tickISR(int8_t state);
// опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке
int8_t tick(bool e0, bool e1);
int8_t tick(int8_t state);
int8_t tick(); // сама обработка в прерывании
// опросить энкодер без сброса события поворота. Вернёт 1 или -1 при вращении, 0 при остановке
int8_t tickRaw(bool e0, bool e1);
int8_t tickRaw(int8_t state);
int8_t tickRaw(); // сама обработка в прерывании
// опросить энкодер без установки флагов на поворот (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке
int8_t pollEnc(bool e0, bool e1);
int8_t pollEnc(int8_t state);
VirtEncButton
- Доступны функции из
VirtButton
- Доступны функции из
VirtEncoder
// ================== НАСТРОЙКИ ==================
// установить таймаут быстрого поворота, мс
void setFastTimeout(uint8_t tout);
// сбросить флаги энкодера и кнопки
void clear();
// ==================== ОПРОС ====================
// ЛЮБОЙ поворот энкодера [событие]
bool turn();
// нажатый поворот энкодера [событие]
bool turnH();
// быстрый поворот энкодера [состояние]
bool fast();
// ненажатый поворот направо [событие]
bool right();
// ненажатый поворот налево [событие]
bool left();
// нажатый поворот направо [событие]
bool rightH();
// нажатый поворот налево [событие]
bool leftH();
// было действие с кнопки или энкодера, вернёт код события [событие]
uint16_t action();
// ==================== ОБРАБОТКА ====================
// обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при повороте
int8_t tickISR(bool e0, bool e1);
int8_t tickISR(int8_t e01);
// обработка энкодера и кнопки
bool tick(bool e0, bool e1, bool btn);
bool tick(int8_t e01, bool btn);
bool tick(bool btn); // энкодер в прерывании
// обработка энкодера и кнопки без сброса флагов и вызова коллбэка
bool tickRaw(bool e0, bool e1, bool btn);
bool tickRaw(int8_t e01, bool btn);
bool tickRaw(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)
// указать пин и его режим работы
void init(uint8_t npin, uint8_t mode);
// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel
bool read();
// функция обработки, вызывать в loop
bool tick();
// обработка кнопки без сброса событий и вызова коллбэка
bool tickRaw();
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)
// указать режим работы
void init(uint8_t mode);
// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel
bool read();
// функция обработки, вызывать в loop
bool tick();
Encoder
- Доступны функции из
VirtEncoder
Encoder;
Encoder(uint8_t encA, uint8_t encB); // с указанием пинов
Encoder(uint8_t encA, uint8_t encB, uint8_t mode); // + режим работы (умолч. INPUT)
// указать пины и их режим работы
void init(uint8_t encA, uint8_t encB, uint8_t mode);
// функция обработки для вызова в прерывании энкодера
int8_t tickISR();
// функция обработки для вызова в loop
int8_t tick();
EncoderT
- Доступны функции из
VirtEncoder
EncoderT<uint8_t encA, uint8_t encB>; // с указанием пинов
EncoderT<uint8_t encA, uint8_t encB> (uint8_t mode); // + режим работы (умолч. INPUT)
// указать режим работы пинов
void init(uint8_t mode);
// функция обработки для вызова в прерывании энкодера
int8_t tickISR();
// функция обработки для вызова в loop
int8_t tick();
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 кнопка, уровень кнопки)
void init(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);
// функция обработки для вызова в прерывании энкодера
int8_t tickISR();
// функция обработки, вызывать в loop
bool tick();
// прочитать значение кнопки с учётом setBtnLevel
bool readBtn();
// прочитать значение энкодера
int8_t readEnc();
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);
// настроить режим работы пинов, уровень кнопки
void init(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);
// функция обработки для вызова в прерывании энкодера
int8_t tickISR();
// функция обработки, вызывать в loop
bool tick();
// прочитать значение кнопки
bool readBtn();
// прочитать значение энкодера
int8_t readEnc();
Во всех библиотеках есть общая функция обработки (тикер tick
), которая получает текущий сигнал с кнопки и энкодера
- Эту функцию нужно однократно вызывать в основном цикле программы (для виртуальных - с передачей значения)
- Функция возвращает
true
при наступлении события (для энкодера -1
или-1
при повороте,0
при его отсутствии. Таким образом поворот в любую сторону расценивается какtrue
) - Есть отдельные функции для вызова в прерывании, они имеют суффикс
ISR
, см. документацию ниже
Библиотека обрабатывает сигнал внутри этой функции, результат можно получить из функций опроса событий. Они бывают двух типов:
[событие]
- функция вернётtrue
однократно при наступлении события. Сбросится после следующего вызова функции обработки (например клик, поворот энкодера)[состояние]
- функция возвращаетtrue
, пока активно это состояние (например кнопка удерживается)
Для простоты восприятия функцию обработки нужно размещать в начале цикла, а опросы делать ниже:
void loop() {
btn.tick(); // опрос
if (btn.click()) Serial.println("click"); // однократно выведет при клике
if (btn.click()) Serial.println("click"); // тот же клик!
}
В отличие от предыдущих версий библиотеки, функции опроса сбрасываются не внутри себя, а внутри функции обработки. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение
click()
. Это позволяет использовать функции опроса по несколько раз за текущую итерацию цикла для создания сложной логики работы программы.
По очевидным причинам нельзя вызывать функцию обработки больше одного раза за цикл - каждый следующий вызов сбросит события от предыдущего и код будет работать некорректно. Вот так - нельзя:
// так нельзя
void loop() {
btn.tick();
if (btn.click()) ...
// ....
btn.tick();
if (btn.hold()) ...
}
Если очень нужно попасть в глухой цикл и опрашивать там кнопку, то вот так - можно:
// так можно
void loop() {
btn.tick();
if (btn.click()) ...
while (true) {
btn.tick();
if (btn.hold()) ...
if (btn.click()) break;
}
}
Если библиотека используется с подключенным обработчиком событий attach()
(см. ниже), то можно вызывать tick()
где угодно и сколько угодно раз, события будут обработаны в обработчике:
// так можно
void cb() {
switch (btn.action()) {
// ...
}
}
void setup() {
btn.attach(cb);
}
void loop() {
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()
между тяжёлыми участками кода
void loop() {
if (btn.click()) ...
if (btn.press()) ...
if (btn.step()) ...
btn.clear();
// ...
btn.tickRaw();
// ...
btn.tickRaw();
// ...
btn.tickRaw();
// ...
}
Это позволит опрашивать кнопку/энкодер в не очень хорошо написанной программе, где основной цикл завален тяжёлым кодом. Внутри tickRaw()
накапливаются события, которые раз в цикл разбираются, а затем вручную сбрасываются.
В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события
releaseXxx
Если сложно избавиться от delay()
внутри главного цикла программы, то на некоторых платформах можно поместить свой код внутри него. Таким образом можно получить даже обработку энкодера в цикле с дилеями без использования прерываний:
// вставка кода в delay
void yield() {
eb.tickRaw();
}
void loop() {
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 с указанным периодом от текущего момента - Обработка кнопки в прерывании сообщает библиотеке о факте нажатия, вся остальная обработка выполняется штатно в
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; // обнулять
- Поворот энкодера при зажатой кнопке снимает и блокирует все последующие события и клики, за исключением события
release
. Состояния нажатой кнопки не изменяются - Поворот энкодера также влияет на системный таймаут (функция
timeout()
) - сработает через указанное время после поворота энкодера - Счётчик кликов доступен при нажатом повороте: несколько кликов, зажатие кнопки, поворот
Библиотека считает количество кликов по кнопке и некоторые функции опроса могут отдельно обрабатываться с предварительными кликами. Например 3 клика, затем удержание. Это очень сильно расширяет возможности одной кнопки. Есть два варианта работы с такими событиями:
// 1
if (btn.hold()) {
if (btn.getClicks() == 2) Serial.println("hold 2 clicks");
}
// 2
if (btn.hold(2)) Serial.println("hold 2 clicks");
В первом варианте можно получить количество кликов для дальнейшей обработки вручную, а во втором - библиотека сделает это сама, если количество кликов для действия заранее известно.
В некоторых сценариях бывает нужно получить состояние кнопки "здесь и сейчас", например определить удерживается ли кнопка сразу после запуска микроконтроллера (старта программы). Функцию tick()
нужно вызывать постоянно в цикле, чтобы шла обработка кнопки с гашением дребезга контактов и прочими расчётами, поэтому конструкция следующего вида работать не будет:
void setup() {
btn.tick();
if (btn.press()) Serial.println("Кнопка нажата при старте");
}
Для таких сценариев помогут следующие функции, возвращают true
если кнопка нажата:
read()
для библиотек Button и ButtonTreadBtn()
для библиотек EncButton и EncButtonT
Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно:
void setup() {
// btn.setBtnLevel(LOW); // можно настроить уровень
if (btn.read()) Serial.println("Кнопка нажата при старте");
}
Допустим нужно обработать кнопку синхронно и с гашением дребезга. Например если кнопка зажата при старте микроконтроллера - получить её удержание или даже импульсное удержание внутри блока setup
, то есть до начала выполнения основной программы. Можно воспользоваться состоянием busy
и опрашивать кнопку из цикла:
void setup() {
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(time)
- она однократно вернёт true
, если после окончания действий с кнопкой/энкодером прошло указанное время. Это можно использовать для сохранения параметров после ввода, например:
void loop() {
eb.tick();
// ...
if (eb.timeout(2000)) {
// после взаимодействия с энкодером прошло 2 секунды
// EEPROM.put(0, settings);
}
}
Функция busy()
возвращает true
, пока идёт обработка кнопки, т.е. пока система ожидает действий и выхода таймаутов. Это можно использовать для оптимизации кода, например избегать каких то долгих и тяжёлых частей программы на время обработки кнопки:
void loop() {
eb.tick();
// ...
if (!eb.busy()) {
// потенциально долгий и тяжёлый код
}
}
Доступно во всех классах с кнопкой:
VirtButton
Button
VirtEncButton
EncButton
Функция 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
- кнопка отпущена после степа с предв. кликами
Результат функции
action()
сбрасывается после следующего вызоваtick()
, то есть доступен на всей текущей итерации основного цикла
Полученный код события можно обработать через switch
:
switch (eb.action()) {
case EB_PRESS:
// ...
break;
case EB_HOLD:
// ...
break;
// ...
}
Для максимального уменьшения веса библиотеки (в частности в оперативной памяти) нужно задавать тайматуы константами через define (экономия 1 байт за таймаут), отключить обработчик событий, счётчики-буферы и использовать T-класс (экономия 1 байт за пин):
#define EB_NO_FOR
#define EB_NO_CALLBACK
#define EB_NO_COUNTER
#define EB_NO_BUFFER
#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 // таймаут быстрого поворота (энкодер)
#include <EncButton.h>
EncButtonT<2, 3, 4> eb;
В таком случае энкодер с кнопкой займёт в SRAM всего 8 байт, а просто кнопка - 5.
Чтобы сократить время на проверку системных флагов событий (незначительно, но приятно) можно поместить все опросы в условие по tick()
, так как tick()
возвращает true
только при наступлении события:
void loop() {
if (eb.tick()) {
if (eb.turn()) ...;
if (eb.click()) ...;
}
}
Также опрос событий при помощи функции action()
выполняется быстрее, чем ручной опрос отдельных функций событий, поэтому максимально эффективно библиотека будет работать вот в таком формате:
void loop() {
if (eb.tick()) {
switch (eb.action()) {
case EB_PRESS:
// ...
break;
case EB_HOLD:
// ...
break;
// ...
}
}
}
Для опроса состояний кнопки pressing()
, holding()
, waiting()
можно поместить их вовнутрь условия по busy()
, чтобы не опрашивать состояния пока их гарантированно нет:
if (btn.busy()) {
if (btn.pressing())...
if (btn.holding())...
if (btn.waiting())...
}
Можно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах с кнопкой:
VirtButton
Button
VirtEncButton
EncButton
EncButton eb(2, 3, 4);
void callback() {
switch (eb.action()) {
case EB_PRESS:
// ...
break;
case EB_HOLD:
// ...
break;
// ...
}
}
void setup() {
eb.attach(callback);
}
void loop() {
eb.tick();
}
Библиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно:
- Cоздать виртуальную кнопку
VirtButton
- Вызвать обработку реальных кнопок
- Передать виртуальной кнопке в обработку эти кнопки (это могут быть объекты классов
VirtButton
,Button
,EncButton
+ ихT
-версии) - Далее опрашивать события
Button b0(4);
Button b1(5);
VirtButton b2; // 1
void loop() {
b0.tick(); // 2
b1.tick(); // 2
b2.tick(b0, b1); // 3
// 4
if (b0.click()) Serial.println("b0 click");
if (b1.click()) Serial.println("b1 click");
if (b2.click()) Serial.println("b0+b1 click");
}
Библиотека сама "сбросит" лишние события с реальных кнопок, если они были нажаты вместе, за исключением события press
. Таким образом получается полноценная третья кнопка из двух других с удобным опросом.
Для обработки энкодера в загруженной программе нужно:
- Подключить оба его пина на аппаратные прерывания по
CHANGE
- Установить
setEncISR(true)
- Вызывать в обработчике специальный тикер для прерывания
- Основной тикер также нужно вызывать в
loop
для корреткной работы - события генерируются в основном тикере:
// пример для ATmega328 и EncButton
EncButton eb(2, 3, 4);
/*
// esp8266/esp32
IRAM_ATTR void isr() {
eb.tickISR();
}
*/
void isr() {
eb.tickISR();
}
void setup() {
attachInterrupt(0, isr, CHANGE);
attachInterrupt(1, isr, CHANGE);
eb.setEncISR(true);
}
void loop() {
eb.tick();
}
Примечание: использование работы в прерывании позволяет корректно обрабатывать позицию энкодера и не пропустить новый поворот. Событие с поворотом, полученное из прерывания, станет доступно после вызова tick
в основном цикле программы, что позволяет не нарушать последовательность работы основного цикла:
- Буферизация отключена: событие
turn
активируется только один раз, независимо от количества щелчков энкодера, совершённых между двумя вызовамиtick
(щелчки обработаны в прерывании) - Буферизация включена: событие
turn
будет вызвано столько раз, сколько реально было щелчков энкодера, это позволяет вообще не пропускать повороты и не нагружать систему в прерывании. Размер буфера - 5 необработанных щелчков энкодера
Примечания:
- Функция
setEncISR
работает только в не виртуальных классах. Если он включен - основной тикерtick
просто не опрашивает пины энкодера, что экономит процессорное время. Обработка происходит только в прерывании - Счётчик энкодера всегда имеет актуальное значение и может опережать буферизированные повороты в программе с большими задержками в основном цикле!
- На разных платформах прерывания могут работать по разному (например на ESPxx - нужно добавить функции аттрибут
IRAM_ATTR
, см. документацию на свою платформу!) - Обработчик, подключенный в
attach()
, будет вызван изtick()
, то есть не из прерывания!
В виртуальных есть тикер, в который не нужно передавать состояние энкодера, если он обрабатывается в прерывании, это позволяет не опрашивать пины в холостую. Например:
VirtEncoder e;
void isr() {
e.tickISR(digitalRead(2), digitalRead(3));
}
void setup() {
attachInterrupt(0, isr, CHANGE);
attachInterrupt(1, isr, CHANGE);
e.setEncISR(1);
}
void loop() {
e.tick(); // не передаём состояния пинов
}
Для обработки кнопки в прерывании нужно:
- Подключить прерывание на нажатие кнопки с учётом её физического подключения и уровня:
- Если кнопка замыкает
LOW
- прерываниеFALLING
- Если кнопка замыкает
HIGH
- прерываниеRISING
- Если кнопка замыкает
- Вызывать
pressISR()
в обработчике прерывания
Button b(2);
/*
// esp8266/esp32
IRAM_ATTR void isr() {
b.pressISR();
}
*/
void isr() {
b.pressISR();
}
void setup() {
attachInterrupt(0, isr, FALLING);
}
void loop() {
b.tick();
}
Примечание: кнопка обрабатывается в основном tick()
, а функция pressISR()
всего лишь сообщает библиотеке, что кнопка была нажата вне tick()
. Это позволяет не пропустить нажатие кнопки, пока программа была занята чем-то другим.
Создать массив можно только из нешаблонных классов (без буквы T
), потому что номера пинов придётся указать уже в рантайме далее в программе. Например:
Button btns[5];
EncButton ebs[3];
void setup() {
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);
// ...
}
void loop() {
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>
bool EB_read(uint8_t pin) {
return digitalRead(pin);
}
void EB_mode(uint8_t pin, uint8_t mode) {
pinMode(pin, mode);
}
uint32_t EB_uptime() {
return millis();
}
Иногда может понадобиться вызывать tick()
не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера!
void loop() {
// таймер на 50 мс
static uint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
btn.tick(readSomePin());
}
// будет активно в течение 50 мс!!!
if (btn.click()) foo();
}
В данной ситуации нужно поступить так: тикать по таймеру, там же обрабатывать события и сбрасывать флаги в конце:
void loop() {
// таймер на 50 мс
static uint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
// тик
btn.tick(readSomePin());
// разбор событий
if (btn.click()) foo();
// сброс флагов
btn.clear();
}
}
Либо можно подключить обработчик и вызывать clear()
в конце функции:
void callback() {
switch (btn.action()) {
// ...
}
// сброс флагов
btn.clear();
}
void loop() {
// таймер на 50 мс
static uint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
btn.tick(readSomePin());
}
}
В случае с вызовом по таймеру антидребезг будет частично обеспечиваться самим таймером и в библиотеке его можно отключить (поставить период 0).
Для корректной работы таймаутов, состояний и счётчика кликов нужен другой подход: буферизировать прочитанные по таймеру состояния и передавать их в тик в основном цикле. Например так:
bool readbuf = 0; // буфер пина
void loop() {
// таймер на 50 мс
static uint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
readbuf = readSomePin(); // чтение в буфер
}
// тик из буфера
btn.tick(readbuf);
if (btn.click()) foo();
}
// меняем значения переменных
// поворот энкодера
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()) {
case 1: var0 += enc.dir();
break;
case 2: var1 += enc.dir();
break;
case 3: var2 += enc.dir();
break;
}
}
}
// импульсное удержание на каждом шаге инкрементирует переменную
if (btn.step()) var++;
// смена направления изменения переменной после отпускания из step
if (btn.step()) var += dir;
if (btn.releaseStep()) dir = -dir;
// изменение выбранной переменной при помощи step
if (btn.step(1)) var1++; // клик-удержание
if (btn.step(2)) var2++; // клик-клик-удержание
if (btn.step(3)) var3++; // клик-клик-клик-удержание
// если держать step больше 2 секунд - инкремент +5, пока меньше - +1
if (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 секунд
else if (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 // таймаут быстрого поворота (энкодер)
#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); // + уровень кнопки
void setup() {
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;
}
void loop() {
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(1000)) 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);
void callback() {
Serial.print("callback: ");
switch (eb.action()) {
case EB_PRESS:
Serial.println("press");
break;
case EB_HOLD:
Serial.println("hold");
break;
case EB_STEP:
Serial.println("step");
break;
case EB_RELEASE:
Serial.println("release");
break;
case EB_CLICK:
Serial.println("click");
break;
case EB_CLICKS:
Serial.print("clicks ");
Serial.println(eb.getClicks());
break;
case EB_TURN:
Serial.print("turn ");
Serial.print(eb.dir());
Serial.print(" ");
Serial.print(eb.fast());
Serial.print(" ");
Serial.println(eb.pressing());
break;
case EB_REL_HOLD:
Serial.println("release hold");
break;
case EB_REL_HOLD_C:
Serial.print("release hold clicks ");
Serial.println(eb.getClicks());
break;
case EB_REL_STEP:
Serial.println("release step");
break;
case EB_REL_STEP_C:
Serial.print("release step clicks ");
Serial.println(eb.getClicks());
break;
}
}
void setup() {
Serial.begin(115200);
eb.attach(callback);
}
void loop() {
eb.tick();
}
Все типы кнопок
#include <EncButton.h>
Button btn(4);
ButtonT<5> btnt;
VirtButton btnv;
void setup() {
Serial.begin(115200);
}
void loop() {
// 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;
void setup() {
Serial.begin(115200);
}
void loop() {
// опрос одинаковый для всех, 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
При нахождении багов создавайте Issue, а лучше сразу пишите на почту [email protected]
Библиотека открыта для доработки и ваших Pull Request'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код