|
|
@@ -0,0 +1,1867 @@
|
|
|
+[](https://github.com/GyverLibs/EncButton/releases/latest/download/EncButton.zip)
|
|
|
+[](https://registry.platformio.org/libraries/gyverlibs/EncButton)
|
|
|
+[](https://alexgyver.ru/)
|
|
|
+[](https://alexgyver.ru/support_alex/)
|
|
|
+[](https://github-com.translate.goog/GyverLibs/EncButton?_x_tr_sl=ru&_x_tr_tl=en)
|
|
|
+
|
|
|
+[](https://t.me/GyverLibs)
|
|
|
+
|
|
|
+# EncButton
|
|
|
+
|
|
|
+| ⚠️⚠️⚠️<br>**Новая версия v3 несовместима с предыдущими, смотри [документацию](#docs), [примеры](#example) и краткий [гайд по миграции](#migrate) с v2 на v3!**<br>⚠️⚠️⚠️ |
|
|
|
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
|
+
|
|
|
+Лёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino
|
|
|
+- Кнопка
|
|
|
+ - Обработка событий: нажатие, отпускание, клик, счётчик кликов, удержание, импульсное удержание, время удержания + предварительные клики для всех режимов
|
|
|
+ - Программное подавление дребезга
|
|
|
+ - Поддержка обработки двух одновременно нажимаемых кнопок как третьей кнопки
|
|
|
+- Энкодер
|
|
|
+ - Обработка событий: обычный поворот, нажатый поворот, быстрый поворот
|
|
|
+ - Поддержка четырёх типов инкрементальных энкодеров
|
|
|
+ - Высокоточный алгоритм определения позиции
|
|
|
+ - Буферизация в прерывании
|
|
|
+- Простое и понятное использование
|
|
|
+- Огромное количество возможностей и их комбинаций для разных сценариев использования даже одной кнопки
|
|
|
+- Виртуальный режим (например для работы с расширителем пинов)
|
|
|
+- Оптимизирована для работы в прерывании
|
|
|
+- Максимально быстрое чтение пинов для AVR, esp8266, esp32 (используется GyverIO)
|
|
|
+- Быстрые асинхронные алгоритмы опроса действий с кнопки и энкодера
|
|
|
+- Жёсткая оптимизация и небольшой вес во Flash и SRAM памяти: 5 байт SRAM (на экземпляр) и ~350 байт Flash на обработку кнопки
|
|
|
+
|
|
|
+Примеры сценариев использования:
|
|
|
+- Несколько кликов - включение режима (по кол-ву кликов)
|
|
|
+- Несколько кликов + короткое удержание - ещё вариант включения режима (по кол-ву кликов)
|
|
|
+- Несколько кликов + удержание - постепенное изменение значения выбранной переменной (по кол-ву кликов)
|
|
|
+- Несколько кликов выбирают переменную, энкодер её изменяет
|
|
|
+- Изменение шага изменения переменной при вращении энкодера - например уменьшение при зажатой кнопке и увеличение при быстром вращении
|
|
|
+- Навигация по меню при вращении энкодера, изменение переменной при вращении зажатого энкодера
|
|
|
+- Полноценная навигация по меню при использовании двух кнопок (одновременное удержание для перехода на следующий уровень, одновременное нажатие для возврата на предыдущий)
|
|
|
+- И так далее
|
|
|
+
|
|
|
+### Совместимость
|
|
|
+Совместима со всеми Arduino платформами (используются Arduino-функции)
|
|
|
+
|
|
|
+## Содержание
|
|
|
+- [Установка](#install)
|
|
|
+- [Информация](#info)
|
|
|
+- [Документация](#docs)
|
|
|
+ - [Настройки компиляции](#config)
|
|
|
+ - [Полное описание классов](#class)
|
|
|
+ - [Обработка и опрос](#tick)
|
|
|
+ - [Предварительные клики](#preclicks)
|
|
|
+ - [Прямое чтение кнопки](#btnread)
|
|
|
+ - [Погружение в цикл](#loop)
|
|
|
+ - [Timeout](#timeout)
|
|
|
+ - [Busy](#busy)
|
|
|
+ - [Получение события](#actions)
|
|
|
+ - [Оптимизация](#optimise)
|
|
|
+ - [Коллбэки](#callback)
|
|
|
+ - [Одновременное нажатие](#double)
|
|
|
+ - [Прерывания](#isr)
|
|
|
+ - [Массив кнопок/энкодеров](#array)
|
|
|
+ - [Кастомные функции](#custom)
|
|
|
+ - [Опрос по таймеру](#timer)
|
|
|
+ - [Мини примеры, сценарии](#examples-mini)
|
|
|
+- [Миграция с v2](#migrate)
|
|
|
+- [Примеры](#example)
|
|
|
+- [Версии](#versions)
|
|
|
+- [Баги и обратная связь](#feedback)
|
|
|
+
|
|
|
+<a id="install"></a>
|
|
|
+## Установка
|
|
|
+- Для работы требуется библиотека [GyverIO](https://github.com/GyverLibs/GyverIO)
|
|
|
+- Библиотеку можно найти по названию **EncButton** и установить через менеджер библиотек в:
|
|
|
+ - Arduino IDE
|
|
|
+ - Arduino IDE v2
|
|
|
+ - PlatformIO
|
|
|
+- [Скачать библиотеку](https://github.com/GyverLibs/EncButton/archive/refs/heads/main.zip) .zip архивом для ручной установки:
|
|
|
+ - Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64)
|
|
|
+ - Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32)
|
|
|
+ - Распаковать и положить в *Документы/Arduino/libraries/*
|
|
|
+ - (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив
|
|
|
+- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)
|
|
|
+### Обновление
|
|
|
+- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
|
|
|
+- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
|
|
|
+- Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
|
|
|
+
|
|
|
+<a id="info"></a>
|
|
|
+
|
|
|
+## Информация
|
|
|
+### Энкодер
|
|
|
+#### Тип энкодера
|
|
|
+Библиотека поддерживает все 4 типа *инкрементальных* энкодеров, тип можно настроить при помощи `setEncType(тип)`:
|
|
|
+- `EB_STEP4_LOW` - активный низкий сигнал (подтяжка к VCC). Полный период (4 фазы) за один щелчок. *Установлен по умолчанию*
|
|
|
+- `EB_STEP4_HIGH` - активный высокий сигнал (подтяжка к GND). Полный период (4 фазы) за один щелчок
|
|
|
+- `EB_STEP2` - половина периода (2 фазы) за один щелчок
|
|
|
+- `EB_STEP1` - четверть периода (1 фаза) за один щелчок, а также энкодеры без фиксации
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#### Рекомендации
|
|
|
+Для работы по сценарию "энкодер с кнопкой" рекомендую вот такие ([ссылка](https://ali.ski/cmPI2), [ссылка](https://ali.ski/sZbTK)) круглые китайские модули с распаянными цепями антидребезга (имеют тип `EB_STEP4_LOW` по классификации выше):
|
|
|
+
|
|
|
+
|
|
|
+Самостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC):
|
|
|
+
|
|
|
+
|
|
|
+> Примечание: по умолчанию в библиотеке пины энкодера настроены на `INPUT` с расчётом на внешнюю подтяжку. Если у вас энкодер без подтяжки - можно использовать внутреннюю `INPUT_PULLUP`, указав это при инициализации энкодера (см. документацию ниже).
|
|
|
+
|
|
|
+### Кнопка
|
|
|
+#### Уровень кнопки
|
|
|
+Кнопка может быть подключена к микроконтроллеру двумя способами и давать при нажатии высокий или низкий сигнал. В библиотеке предусмотрена настройка `setBtnLevel(уровень)`, где уровень - активный сигнал кнопки:
|
|
|
+- `HIGH` - кнопка подключает VCC. Установлен по умолчанию в `Virt`-библиотеках
|
|
|
+- `LOW` - кнопка подключает GND. Установлен по умолчанию в основных библиотеках
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#### Подтяжка пина
|
|
|
+В схемах с микроконтроллерами чаще всего используется подключение кнопки к GND с подтяжкой пина к VCC. Подтяжка может быть внешней (режим пина нужно поставить `INPUT`) или внутренней (режим пина `INPUT_PULLUP`). В "реальных" проектах рекомендуется внешняя подтяжка, т.к. она менее подвержена помехам - у внутренней слишком высокое сопротивление.
|
|
|
+
|
|
|
+<a id="docs"></a>
|
|
|
+
|
|
|
+## Документация
|
|
|
+
|
|
|
+<a id="config"></a>
|
|
|
+
|
|
|
+### Дефайны настроек
|
|
|
+Объявлять до подключения библиотеки
|
|
|
+
|
|
|
+```cpp
|
|
|
+
|
|
|
+// отключить поддержку 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 // таймаут быстрого поворота (энкодер)
|
|
|
+#define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер)
|
|
|
+```
|
|
|
+
|
|
|
+<a id="class"></a>
|
|
|
+
|
|
|
+### Классы
|
|
|
+Как работать с документацией: EncButton начиная с версии 3.0 представляет собой несколько библиотек (классов) для различных сценариев использования, они друг друга наследуют для расширения функциональности. Таким образом библиотека представляет собой "луковицу", каждый слой которой имеет доступ к функциям нижних слоёв:
|
|
|
+- Базовые классы:
|
|
|
+ - `VirtButton` - базовый класс виртуальной кнопки, обеспечивает все возможности кнопки
|
|
|
+ - `VirtEncoder` - базовый класс виртуального энкодера, определяет факт и направление вращения энкодера
|
|
|
+ - `VirtEncButton` - базовый класс виртуального энкодера с кнопкой, обеспечивает опрос энкодера с учётом кнопки, *наследует VirtButton и VirtEncoder*
|
|
|
+- Основные классы:
|
|
|
+ - `Button`, `ButtonT` - класс кнопки, *наследует VirtButton*
|
|
|
+ - `Encoder`, `EncoderT` - класс энкодера, *наследует VirtEncoder*
|
|
|
+ - `EncButton`, `EncButtonT` - класс энкодера с кнопкой, *наследует VirtEncButton, VirtButton, VirtEncoder*
|
|
|
+
|
|
|
+Таким образом для изучения всех доступных функций конкретной библиотеки нужно смотреть не только её, но и то что она наследует. Например для обработки кнопки при помощи `Button` нужно открыть ниже описание `Button` и `VirtButton`.
|
|
|
+
|
|
|
+> *Виртуальный* - без указания пина микроконтроллера, работает напрямую с переданным значением, например для опроса кнопок-энкодеров через расширители пинов и сдвиговые регистры.
|
|
|
+
|
|
|
+> `T`-версии библиотек требуют указания пинов константами (цифрами). Номера пинов будут храниться в памяти программы, это ускоряет работу и делает код легче на 1 байт за каждый пин.
|
|
|
+
|
|
|
+> Примечание: `#include <EncButton.h>` подключает все инструменты библиотеки!
|
|
|
+
|
|
|
+<details>
|
|
|
+<summary>Таблица функций кнопки</summary>
|
|
|
+
|
|
|
+| | 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 | ✔ | ✔ | ✔ | ✔ |
|
|
|
+</details>
|
|
|
+
|
|
|
+<details>
|
|
|
+<summary>Таблица функций энкодера</summary>
|
|
|
+
|
|
|
+| | VirtEncoder | Encoder | VirtEncButton | EncButton |
|
|
|
+| -------------- | :---------: | :-----: | :-----------: | :-------: |
|
|
|
+| readEnc | | | | ✔ |
|
|
|
+| initEnc | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| setEncReverse | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| setEncType | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| setEncISR | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| clear | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| turn | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| dir | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| tickRaw | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| pollEnc | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| counter | ✔ | ✔ | ✔ | ✔ |
|
|
|
+| setFastTimeout | | | ✔ | ✔ |
|
|
|
+| turnH | | | ✔ | ✔ |
|
|
|
+| fast | | | ✔ | ✔ |
|
|
|
+| right | | | ✔ | ✔ |
|
|
|
+| left | | | ✔ | ✔ |
|
|
|
+| rightH | | | ✔ | ✔ |
|
|
|
+| leftH | | | ✔ | ✔ |
|
|
|
+| action | | | ✔ | ✔ |
|
|
|
+| getAction | | | ✔ | ✔ |
|
|
|
+| timeout | | | ✔ | ✔ |
|
|
|
+| attach | | | ✔ | ✔ |
|
|
|
+| detach | | | ✔ | ✔ |
|
|
|
+</details>
|
|
|
+
|
|
|
+<details>
|
|
|
+<summary>VirtButton</summary>
|
|
|
+
|
|
|
+```cpp
|
|
|
+// ================ НАСТРОЙКИ ================
|
|
|
+// установить таймаут удержания, умолч. 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);
|
|
|
+
|
|
|
+// установить время таймаута, умолч. 1000 (макс. 4000 мс)
|
|
|
+void setTimeout(const uint16_t tout);
|
|
|
+
|
|
|
+// установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND)
|
|
|
+// умолч. HIGH, то есть true - кнопка нажата
|
|
|
+void setBtnLevel(bool level);
|
|
|
+
|
|
|
+// подключить функцию-обработчик событий
|
|
|
+void attach(void (*handler)());
|
|
|
+
|
|
|
+// отключить функцию-обработчик событий
|
|
|
+void detach();
|
|
|
+
|
|
|
+// ================== СБРОС ==================
|
|
|
+// сбросить системные флаги (принудительно закончить обработку)
|
|
|
+void reset();
|
|
|
+
|
|
|
+// принудительно сбросить флаги событий
|
|
|
+void clear(bool resetTout = false);
|
|
|
+
|
|
|
+// игнорировать все события до отпускания кнопки
|
|
|
+void skipEvents();
|
|
|
+
|
|
|
+// ================ ОБРАБОТКА ================
|
|
|
+// обработка кнопки значением
|
|
|
+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);
|
|
|
+
|
|
|
+// кнопка отпущена после удержания или импульсного удержания [событие]
|
|
|
+bool releaseHoldStep();
|
|
|
+bool releaseHoldStep(uint8_t clicks);
|
|
|
+
|
|
|
+// получить количество кликов
|
|
|
+uint8_t getClicks();
|
|
|
+
|
|
|
+// получить количество степов
|
|
|
+uint16_t getSteps();
|
|
|
+
|
|
|
+// кнопка ожидает повторных кликов (между click() и hasClicks()) [состояние]
|
|
|
+bool waiting();
|
|
|
+
|
|
|
+// идёт обработка (между первым нажатием и после ожидания кликов) [состояние]
|
|
|
+bool busy();
|
|
|
+
|
|
|
+// было действие с кнопки, вернёт код события [событие]
|
|
|
+uint16_t action();
|
|
|
+EBAction getAction();
|
|
|
+
|
|
|
+// ================== ВРЕМЯ ==================
|
|
|
+// после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [событие]
|
|
|
+bool timeout();
|
|
|
+
|
|
|
+// после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [состояние]
|
|
|
+bool timeoutState();
|
|
|
+
|
|
|
+// время, которое кнопка удерживается (с начала нажатия), мс
|
|
|
+uint16_t pressFor();
|
|
|
+
|
|
|
+// кнопка удерживается дольше чем (с начала нажатия), мс [состояние]
|
|
|
+bool pressFor(uint16_t ms);
|
|
|
+
|
|
|
+// время, которое кнопка удерживается (с начала удержания), мс
|
|
|
+uint16_t holdFor();
|
|
|
+
|
|
|
+// кнопка удерживается дольше чем (с начала удержания), мс [состояние]
|
|
|
+bool holdFor(uint16_t ms);
|
|
|
+
|
|
|
+// время, которое кнопка удерживается (с начала степа), мс
|
|
|
+uint16_t stepFor();
|
|
|
+
|
|
|
+// кнопка удерживается дольше чем (с начала степа), мс [состояние]
|
|
|
+bool stepFor(uint16_t ms);
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>VirtEncoder</summary>
|
|
|
+
|
|
|
+```cpp
|
|
|
+// ==================== НАСТРОЙКИ ====================
|
|
|
+// инвертировать направление энкодера (умолч. 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);
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>VirtEncButton</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtButton`
|
|
|
+- Доступны функции из `VirtEncoder`
|
|
|
+
|
|
|
+```cpp
|
|
|
+// ================== НАСТРОЙКИ ==================
|
|
|
+// установить таймаут быстрого поворота, мс
|
|
|
+void setFastTimeout(uint8_t tout);
|
|
|
+
|
|
|
+// сбросить флаги энкодера и кнопки
|
|
|
+void clear(bool resetTout = false);
|
|
|
+
|
|
|
+// ==================== ОПРОС ====================
|
|
|
+// ЛЮБОЙ поворот энкодера [событие]
|
|
|
+bool turn();
|
|
|
+
|
|
|
+// нажатый поворот энкодера [событие]
|
|
|
+bool turnH();
|
|
|
+
|
|
|
+// быстрый поворот энкодера [состояние]
|
|
|
+bool fast();
|
|
|
+
|
|
|
+// ненажатый поворот направо [событие]
|
|
|
+bool right();
|
|
|
+
|
|
|
+// ненажатый поворот налево [событие]
|
|
|
+bool left();
|
|
|
+
|
|
|
+// нажатый поворот направо [событие]
|
|
|
+bool rightH();
|
|
|
+
|
|
|
+// нажатый поворот налево [событие]
|
|
|
+bool leftH();
|
|
|
+
|
|
|
+// было действие с кнопки или энкодера, вернёт код события [событие]
|
|
|
+uint16_t action();
|
|
|
+EBAction getAction();
|
|
|
+
|
|
|
+// ==================== ОБРАБОТКА ====================
|
|
|
+// обработка в прерывании (только энкодер). Вернёт 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); // энкодер в прерывании
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>Button</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtButton`
|
|
|
+- Режим кнопки по умолчанию - `LOW`
|
|
|
+
|
|
|
+```cpp
|
|
|
+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)
|
|
|
+```
|
|
|
+```cpp
|
|
|
+// указать пин и его режим работы
|
|
|
+void init(uint8_t npin, uint8_t mode);
|
|
|
+
|
|
|
+// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel
|
|
|
+bool read();
|
|
|
+
|
|
|
+// функция обработки, вызывать в loop
|
|
|
+bool tick();
|
|
|
+
|
|
|
+// обработка кнопки без сброса событий и вызова коллбэка
|
|
|
+bool tickRaw();
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>ButtonT</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtButton`
|
|
|
+- Режим кнопки по умолчанию - `LOW`
|
|
|
+
|
|
|
+```cpp
|
|
|
+ButtonT<uint8_t pin>; // с указанием пина
|
|
|
+ButtonT<uint8_t pin> (uint8_t mode); // + режим работы (умолч. INPUT_PULLUP)
|
|
|
+ButtonT<uint8_t pin> (uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)
|
|
|
+```
|
|
|
+```cpp
|
|
|
+// указать режим работы
|
|
|
+void init(uint8_t mode);
|
|
|
+
|
|
|
+// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel
|
|
|
+bool read();
|
|
|
+
|
|
|
+// функция обработки, вызывать в loop
|
|
|
+bool tick();
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>Encoder</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtEncoder`
|
|
|
+
|
|
|
+```cpp
|
|
|
+Encoder;
|
|
|
+Encoder(uint8_t encA, uint8_t encB); // с указанием пинов
|
|
|
+Encoder(uint8_t encA, uint8_t encB, uint8_t mode); // + режим работы (умолч. INPUT)
|
|
|
+```
|
|
|
+```cpp
|
|
|
+// указать пины и их режим работы
|
|
|
+void init(uint8_t encA, uint8_t encB, uint8_t mode);
|
|
|
+
|
|
|
+// функция обработки для вызова в прерывании энкодера
|
|
|
+int8_t tickISR();
|
|
|
+
|
|
|
+// функция обработки для вызова в loop
|
|
|
+int8_t tick();
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>EncoderT</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtEncoder`
|
|
|
+
|
|
|
+```cpp
|
|
|
+EncoderT<uint8_t encA, uint8_t encB>; // с указанием пинов
|
|
|
+EncoderT<uint8_t encA, uint8_t encB> (uint8_t mode); // + режим работы (умолч. INPUT)
|
|
|
+```
|
|
|
+```cpp
|
|
|
+// указать режим работы пинов
|
|
|
+void init(uint8_t mode);
|
|
|
+
|
|
|
+// функция обработки для вызова в прерывании энкодера
|
|
|
+int8_t tickISR();
|
|
|
+
|
|
|
+// функция обработки для вызова в loop
|
|
|
+int8_t tick();
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>EncButton</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtButton`
|
|
|
+- Доступны функции из `VirtEncoder`
|
|
|
+- Доступны функции из `VirtEncButton`
|
|
|
+
|
|
|
+```cpp
|
|
|
+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);
|
|
|
+```
|
|
|
+```cpp
|
|
|
+// настроить пины (энк, энк, кнопка, 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();
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>EncButtonT</summary>
|
|
|
+
|
|
|
+- Доступны функции из `VirtButton`
|
|
|
+- Доступны функции из `VirtEncoder`
|
|
|
+- Доступны функции из `VirtEncButton`
|
|
|
+
|
|
|
+```cpp
|
|
|
+// с указанием пинов
|
|
|
+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);
|
|
|
+```
|
|
|
+```cpp
|
|
|
+// настроить режим работы пинов, уровень кнопки
|
|
|
+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();
|
|
|
+```
|
|
|
+</details>
|
|
|
+
|
|
|
+<a id="tick"></a>
|
|
|
+
|
|
|
+### Обработка и опрос
|
|
|
+Во всех библиотеках есть общая **функция обработки** (тикер `tick`), которая получает текущий сигнал с кнопки и энкодера
|
|
|
+- Эту функцию нужно однократно вызывать в основном цикле программы (для виртуальных - с передачей значения)
|
|
|
+- Функция возвращает `true` при наступлении события (для энкодера - `1` или `-1` при повороте, `0` при его отсутствии. Таким образом поворот в любую сторону расценивается как `true`)
|
|
|
+- Есть отдельные функции для вызова в прерывании, они имеют суффикс `ISR`, см. документацию ниже
|
|
|
+
|
|
|
+Библиотека обрабатывает сигнал внутри этой функции, результат можно получить из **функций опроса** событий. Они бывают двух типов:
|
|
|
+- `[событие]` - функция вернёт `true` однократно при наступлении события. Сбросится после следующего вызова функции обработки (например клик, поворот энкодера). За исключением события `timeout`
|
|
|
+- `[состояние]` - функция возвращает `true`, пока активно это состояние (например кнопка удерживается)
|
|
|
+
|
|
|
+Для простоты восприятия функцию обработки нужно размещать в начале цикла, а опросы делать ниже:
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ btn.tick(); // опрос
|
|
|
+
|
|
|
+ if (btn.click()) Serial.println("click"); // однократно выведет при клике
|
|
|
+ if (btn.click()) Serial.println("click"); // тот же клик!
|
|
|
+}
|
|
|
+```
|
|
|
+> В отличие от предыдущих версий библиотеки, функции опроса сбрасываются не внутри себя, а *внутри функции обработки*. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение `click()`. Это позволяет использовать функции опроса по несколько раз за текущую итерацию цикла для создания сложной логики работы программы.
|
|
|
+
|
|
|
+#### Несколько функций обработки
|
|
|
+По очевидным причинам нельзя вызывать функцию обработки больше одного раза за цикл - каждый следующий вызов сбросит события от предыдущего и код будет работать некорректно. Вот так - нельзя:
|
|
|
+```cpp
|
|
|
+// так нельзя
|
|
|
+void loop() {
|
|
|
+ btn.tick();
|
|
|
+ if (btn.click()) ...
|
|
|
+
|
|
|
+ // ....
|
|
|
+
|
|
|
+ btn.tick();
|
|
|
+ if (btn.hold()) ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Если очень нужно попасть в глухой цикл и опрашивать там кнопку, то вот так - можно:
|
|
|
+```cpp
|
|
|
+// так можно
|
|
|
+void loop() {
|
|
|
+ btn.tick();
|
|
|
+ if (btn.click()) ...
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ btn.tick();
|
|
|
+ if (btn.hold()) ...
|
|
|
+ if (btn.click()) break;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Если библиотека используется с подключенным обработчиком событий `attach()` (см. ниже), то можно вызывать `tick()` где угодно и сколько угодно раз, события будут обработаны в обработчике:
|
|
|
+```cpp
|
|
|
+// так можно
|
|
|
+void cb() {
|
|
|
+ switch (btn.action()) {
|
|
|
+ // ...
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void setup() {
|
|
|
+ btn.attach(cb);
|
|
|
+}
|
|
|
+
|
|
|
+void loop() {
|
|
|
+ btn.tick();
|
|
|
+ // ...
|
|
|
+ btn.tick();
|
|
|
+ // ...
|
|
|
+ btn.tick();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### "Загруженная" программа
|
|
|
+Библиотека EncButton - **асинхронная**: она не ждёт, пока закончится обработка кнопки, а позволяет программе выполняться дальше. Это означает, что для корректной работы библиотеки основной цикл программы должен выполняться как можно быстрее и не содержать задержек и других "глухих" циклов внутри себя. Для обеспечения правильной обработки кнопки не рекомендуется иметь в основном цикле задержки длительностью более 50-100 мс. Несколько советов:
|
|
|
+- Новичкам: изучить цикл уроков [как написать скетч](https://alexgyver.ru/lessons/how-to-sketch/)
|
|
|
+ - Писать асинхронный код в `loop()`
|
|
|
+ - Любую синхронную конструкцию на `delay()` можно сделать асинхронной при помощи `millis()`
|
|
|
+ - Если в программе *каждая* итерация главного цикла выполняется дольше 50-100мс - в большинстве случаев программа написана неправильно, за исключением каких-то особых случаев
|
|
|
+- Подключить кнопку на аппаратное прерывание (см. ниже)
|
|
|
+- Избегать выполнения "тяжёлых" участков кода, пока идёт обработка кнопки, например поместив их в условие `if (!button.busy()) { тяжёлый код }`
|
|
|
+- Если оптимизировать основной цикл невозможно - вызывать тикер в другом "потоке" и использовать функцию-обработчик:
|
|
|
+ - В прерывании таймера с периодом ~50мс или чаще
|
|
|
+ - На другом ядре (например ESP32)
|
|
|
+ - В другом таске FreeRTOS
|
|
|
+ - Внутри `yield()` (внутри `delay()`)
|
|
|
+
|
|
|
+#### Раздельная обработка
|
|
|
+> Имеет смысл только при ручном опросе событий! При подключенной функции-обработчике достаточно вызывать обычный `tick()` между тяжёлыми участками программы
|
|
|
+
|
|
|
+Также в загруженной программе можно разделить обработку и сброс событий: вместо `tick()` использовать `tickRaw()` между тяжёлыми участками кода и ручной сброс `clear()`. Порядок следующий:
|
|
|
+- Опросить действия (click, press, turn...)
|
|
|
+- Вызвать `clear()`
|
|
|
+- Вызывать `tickRaw()` между тяжёлыми участками кода
|
|
|
+
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ if (btn.click()) ...
|
|
|
+ if (btn.press()) ...
|
|
|
+ if (btn.step()) ...
|
|
|
+
|
|
|
+ btn.clear();
|
|
|
+
|
|
|
+ // ...
|
|
|
+ btn.tickRaw();
|
|
|
+ // ...
|
|
|
+ btn.tickRaw();
|
|
|
+ // ...
|
|
|
+ btn.tickRaw();
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+Это позволит опрашивать кнопку/энкодер в не очень хорошо написанной программе, где основной цикл завален тяжёлым кодом. Внутри `tickRaw()` накапливаются события, которые раз в цикл разбираются, а затем вручную сбрасываются.
|
|
|
+
|
|
|
+> В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `releaseXxx`
|
|
|
+
|
|
|
+#### Обработка внутри delay
|
|
|
+Если сложно избавиться от `delay()` внутри главного цикла программы, то на некоторых платформах можно поместить свой код внутри него. Таким образом можно получить даже обработку энкодера в цикле с дилеями без использования прерываний:
|
|
|
+```cpp
|
|
|
+// вставка кода в 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 периодом из `setTimeout`
|
|
|
+- Обработка кнопки в прерывании сообщает библиотеке о факте нажатия, вся остальная обработка выполняется штатно в `tick()`
|
|
|
+
|
|
|
+> Отличие `click(n)` от `hasClicks(n)`: `click(n)` вернёт `true` в любом случае при совпадении количества кликов, даже если будет сделано больше кликов. `hasClicks(n)` вернёт `true` только в том случае, если было сделано ровно указанное количество кликов и больше кликов не было!
|
|
|
+
|
|
|
+> Лучше один раз увидеть, чем сто раз прочитать. Запусти пример demo и понажимай на кнопку, или попробуй [онлайн-симуляцию в Wokwi](https://wokwi.com/projects/373591584298469377)
|
|
|
+
|
|
|
+##### Click
|
|
|
+
|
|
|
+
|
|
|
+##### Hold
|
|
|
+
|
|
|
+
|
|
|
+##### Step
|
|
|
+
|
|
|
+
|
|
|
+Онлайн-симуляция доступна [здесь](https://wokwi.com/projects/373591584298469377)
|
|
|
+
|
|
|
+#### Обработка энкодера
|
|
|
+- "Быстрым" поворотом считается поворот, совершённый менее чем за настроенный таймаут от предыдущего поворота
|
|
|
+- Обработанные в прерывании повороты становятся активными (вызывают события) после вызова `tick()`
|
|
|
+- Доступ к счётчику энкодера `counter` - это публичная переменная класса, можно делать с ней всё что угодно:
|
|
|
+```cpp
|
|
|
+Serial.println(eb.counter); // читать
|
|
|
+eb.counter += 1234; // менять
|
|
|
+eb.counter = 0; // обнулять
|
|
|
+```
|
|
|
+
|
|
|
+#### Обработка энкодера с кнопкой
|
|
|
+- Поворот энкодера при зажатой кнопке снимает и блокирует все последующие события и клики, за исключением события `release`. Состояния нажатой кнопки не изменяются
|
|
|
+- Поворот энкодера также влияет на системный таймаут (функция `timeout()`) - сработает через указанное время после поворота энкодера
|
|
|
+- Счётчик кликов доступен при нажатом повороте: несколько кликов, зажатие кнопки, поворот
|
|
|
+
|
|
|
+<a id="preclicks"></a>
|
|
|
+
|
|
|
+### Предварительные клики
|
|
|
+Библиотека считает количество кликов по кнопке и некоторые функции опроса могут отдельно обрабатываться с *предварительными кликами*. Например 3 клика, затем удержание. Это очень сильно расширяет возможности одной кнопки. Есть два варианта работы с такими событиями:
|
|
|
+```cpp
|
|
|
+ // 1
|
|
|
+ if (btn.hold()) {
|
|
|
+ if (btn.getClicks() == 2) Serial.println("hold 2 clicks");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2
|
|
|
+ if (btn.hold(2)) Serial.println("hold 2 clicks");
|
|
|
+```
|
|
|
+
|
|
|
+В первом варианте можно получить количество кликов для дальнейшей обработки вручную, а во втором - библиотека сделает это сама, если количество кликов для действия заранее известно.
|
|
|
+
|
|
|
+<a id="btnread"></a>
|
|
|
+
|
|
|
+### Прямое чтение кнопки
|
|
|
+В некоторых сценариях бывает нужно получить состояние кнопки "здесь и сейчас", например определить удерживается ли кнопка сразу после запуска микроконтроллера (старта программы). Функцию `tick()` нужно вызывать постоянно в цикле, чтобы шла обработка кнопки с гашением дребезга контактов и прочими расчётами, поэтому конструкция следующего вида **работать не будет**:
|
|
|
+```cpp
|
|
|
+void setup() {
|
|
|
+ btn.tick();
|
|
|
+ if (btn.press()) Serial.println("Кнопка нажата при старте");
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Для таких сценариев помогут следующие функции, возвращают `true` если кнопка нажата:
|
|
|
+- `read()` для библиотек Button и ButtonT
|
|
|
+- `readBtn()` для библиотек EncButton и EncButtonT
|
|
|
+
|
|
|
+> Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно:
|
|
|
+
|
|
|
+```cpp
|
|
|
+void setup() {
|
|
|
+ // btn.setBtnLevel(LOW); // можно настроить уровень
|
|
|
+
|
|
|
+ if (btn.read()) Serial.println("Кнопка нажата при старте");
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="loop"></a>
|
|
|
+
|
|
|
+### Погружение в цикл
|
|
|
+Допустим нужно обработать кнопку синхронно и с гашением дребезга. Например если кнопка зажата при старте микроконтроллера - получить её удержание или даже импульсное удержание внутри блока `setup`, то есть до начала выполнения основной программы. Можно воспользоваться состоянием `busy` и опрашивать кнопку из цикла:
|
|
|
+```cpp
|
|
|
+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 опустится и программа автоматически покинет цикл. Можно переписать эту конструкцию на цикл с постусловием, более красиво:
|
|
|
+```cpp
|
|
|
+do {
|
|
|
+ btn.tick();
|
|
|
+ if (btn.hold()) Serial.println("hold");
|
|
|
+ if (btn.step()) Serial.println("step");
|
|
|
+} while (btn.busy());
|
|
|
+```
|
|
|
+
|
|
|
+<a id="timeout"></a>
|
|
|
+
|
|
|
+### Timeout
|
|
|
+В связанных с кнопкой классах (Button, EncButton) есть функция `timeout()` - она однократно вернёт `true`, если после окончания действий с кнопкой/энкодером прошло указанное в `setTimeout` время. Это можно использовать для сохранения параметров после ввода, например:
|
|
|
+```cpp
|
|
|
+void setup() {
|
|
|
+ //eb.setTimeout(1500); // умолч. 1000
|
|
|
+}
|
|
|
+void loop() {
|
|
|
+ eb.tick();
|
|
|
+
|
|
|
+ // ...
|
|
|
+
|
|
|
+ if (eb.timeout()) {
|
|
|
+ // после взаимодействия с энкодером прошло 1000 мс
|
|
|
+ // EEPROM.put(0, settings);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+В текущей версии обработка таймаута реализована не очень красиво ради экономии места: системный флаг таймаута активен всё время (`action` будет возвращать событие таймаута), сбрасывается в следующих случаях:
|
|
|
+
|
|
|
+- Вызов `timeout()` - данный метод однократно вернёт `true`, последующие вызовы будут возвращать `false` до нового действия
|
|
|
+- Автоматически сбросится после вызова обработчика, если он подключен
|
|
|
+- При вызове `clear(true)` - с флагом очистки таймаута
|
|
|
+- При вызове `reset()`
|
|
|
+
|
|
|
+Если нужно пробросить опрос таймаута через несколько вызовов - можно использовать `timeoutState()`, но последний вызов должен быть `timeout()`.
|
|
|
+
|
|
|
+<a id="busy"></a>
|
|
|
+
|
|
|
+### Busy
|
|
|
+Функция `busy()` возвращает `true`, пока идёт обработка кнопки, т.е. пока система ожидает действий и выхода таймаутов. Это можно использовать для оптимизации кода, например избегать каких то долгих и тяжёлых частей программы на время обработки кнопки:
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ eb.tick();
|
|
|
+
|
|
|
+ // ...
|
|
|
+
|
|
|
+ if (!eb.busy()) {
|
|
|
+ // потенциально долгий и тяжёлый код
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="actions"></a>
|
|
|
+
|
|
|
+### Получение события
|
|
|
+Доступно во всех классах **с кнопкой**:
|
|
|
+- `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` - кнопка отпущена после степа с предв. кликами
|
|
|
+- `EB_TIMEOUT` - прошёл таймаут после нажатия кнопки или поворота энкодера
|
|
|
+
|
|
|
+Полученный код события можно обработать через `switch`:
|
|
|
+```cpp
|
|
|
+switch (eb.action()) {
|
|
|
+ case EB_PRESS:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ case EB_HOLD:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Есть аналогичная функция `getAction()`, вернёт то же самое, но с более читаемыми константами (удобно с автодополнением):
|
|
|
+
|
|
|
+- `EBAction::Press`
|
|
|
+- `EBAction::Hold`
|
|
|
+- `EBAction::Step`
|
|
|
+- `EBAction::Release`
|
|
|
+- `EBAction::Click`
|
|
|
+- `EBAction::Clicks`
|
|
|
+- `EBAction::Turn`
|
|
|
+- `EBAction::ReleaseHold`
|
|
|
+- `EBAction::ReleaseHoldClicks`
|
|
|
+- `EBAction::ReleaseStep`
|
|
|
+- `EBAction::ReleaseStepClicks`
|
|
|
+- `EBAction::Timeout`
|
|
|
+
|
|
|
+Полученный код события можно обработать через `switch`:
|
|
|
+```cpp
|
|
|
+switch (eb.getAction()) {
|
|
|
+ case EBAction::Press:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ case EBAction::Hold:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+> Результат функций `action()`/`getAction()` сбрасывается после следующего вызова `tick()`, то есть доступен на всей текущей итерации основного цикла
|
|
|
+
|
|
|
+<a id="optimise"></a>
|
|
|
+
|
|
|
+### Оптимизация
|
|
|
+#### Вес библиотеки
|
|
|
+Для максимального уменьшения веса библиотеки (в частности в оперативной памяти) нужно задавать тайматуы константами через define (экономия 1 байт за таймаут), отключить обработчик событий, счётчики-буферы и использовать T-класс (экономия 1 байт за пин):
|
|
|
+```cpp
|
|
|
+#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 // таймаут быстрого поворота (энкодер)
|
|
|
+#define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер)
|
|
|
+#include <EncButton.h>
|
|
|
+EncButtonT<2, 3, 4> eb;
|
|
|
+```
|
|
|
+В таком случае энкодер с кнопкой займёт в SRAM всего 8 байт, а просто кнопка - 5.
|
|
|
+
|
|
|
+#### Скорость выполнения
|
|
|
+Чтобы сократить время на проверку системных флагов событий (незначительно, но приятно) можно поместить все опросы в условие по `tick()`, так как `tick()` возвращает `true` только при наступлении **события**:
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ if (eb.tick()) {
|
|
|
+ if (eb.turn()) ...;
|
|
|
+ if (eb.click()) ...;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Также опрос событий при помощи функции `action()` выполняется быстрее, чем ручной опрос отдельных функций событий, поэтому максимально эффективно библиотека будет работать вот в таком формате:
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ if (eb.tick()) {
|
|
|
+ switch (eb.action()) {
|
|
|
+ case EB_PRESS:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ case EB_HOLD:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ // ...
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Для опроса **состояний** кнопки `pressing()`, `holding()`, `waiting()` можно поместить их вовнутрь условия по `busy()`, чтобы не опрашивать состояния пока их гарантированно нет:
|
|
|
+```cpp
|
|
|
+if (btn.busy()) {
|
|
|
+ if (btn.pressing())...
|
|
|
+ if (btn.holding())...
|
|
|
+ if (btn.waiting())...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="callback"></a>
|
|
|
+
|
|
|
+### Коллбэки
|
|
|
+Можно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах **с кнопкой**:
|
|
|
+- `VirtButton`
|
|
|
+- `Button`
|
|
|
+- `VirtEncButton`
|
|
|
+- `EncButton`
|
|
|
+
|
|
|
+> Внутри коллбэка можно получить указатель на текущий объект (который вызвал коллбэк) из переменной `void* EB_self`
|
|
|
+
|
|
|
+```cpp
|
|
|
+EncButton eb(2, 3, 4);
|
|
|
+
|
|
|
+void callback() {
|
|
|
+ switch (eb.action()) {
|
|
|
+ case EB_PRESS:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ case EB_HOLD:
|
|
|
+ // ...
|
|
|
+ break;
|
|
|
+ // ...
|
|
|
+ }
|
|
|
+
|
|
|
+ // здесь EB_self указатель на eb
|
|
|
+}
|
|
|
+
|
|
|
+void setup() {
|
|
|
+ eb.attach(callback);
|
|
|
+}
|
|
|
+
|
|
|
+void loop() {
|
|
|
+ eb.tick();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="double"></a>
|
|
|
+
|
|
|
+### Одновременное нажатие
|
|
|
+Библиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно:
|
|
|
+1. Cоздать специальную кнопку `MultiButton`
|
|
|
+2. Передать виртуальной кнопке в обработку свои кнопки (это могут быть объекты классов `VirtButton`, `Button`, `EncButton` + их `T`-версии). **Мульти-кнопка сама опросит обе кнопки!**
|
|
|
+3. Опрашивать события или слушать обработчик
|
|
|
+
|
|
|
+```cpp
|
|
|
+Button b0(4);
|
|
|
+Button b1(5);
|
|
|
+MultiButton b2; // 1
|
|
|
+
|
|
|
+void loop() {
|
|
|
+ b2.tick(b0, b1); // 2
|
|
|
+
|
|
|
+ // 3
|
|
|
+ if (b0.click()) Serial.println("b0 click");
|
|
|
+ if (b1.click()) Serial.println("b1 click");
|
|
|
+ if (b2.click()) Serial.println("b0+b1 click");
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Библиотека сама "сбросит" лишние события с реальных кнопок, если они были нажаты вместе, за исключением события `press`. Таким образом получается полноценная третья кнопка из двух других с удобным опросом.
|
|
|
+
|
|
|
+<a id="isr"></a>
|
|
|
+
|
|
|
+### Прерывания
|
|
|
+#### Энкодер
|
|
|
+Для обработки энкодера в загруженной программе нужно:
|
|
|
+- Подключить оба его пина на аппаратные прерывания по `CHANGE`
|
|
|
+- Установить `setEncISR(true)`
|
|
|
+- Вызывать в обработчике специальный тикер для прерывания
|
|
|
+- Основной тикер также нужно вызывать в `loop` для корреткной работы - события генерируются в основном тикере:
|
|
|
+```cpp
|
|
|
+// пример для 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()`, то есть *не из прерывания*!
|
|
|
+
|
|
|
+#### Виртуальные классы
|
|
|
+В виртуальных есть тикер, в который не нужно передавать состояние энкодера, если он обрабатывается в прерывании, это позволяет не опрашивать пины в холостую. Например:
|
|
|
+
|
|
|
+```cpp
|
|
|
+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()` в обработчике прерывания
|
|
|
+
|
|
|
+```cpp
|
|
|
+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()`. Это позволяет не пропустить нажатие кнопки, пока программа была занята чем-то другим.
|
|
|
+
|
|
|
+<a id="array"></a>
|
|
|
+
|
|
|
+### Массив кнопок/энкодеров
|
|
|
+Создать массив можно только из нешаблонных классов (без буквы `T`), потому что номера пинов придётся указать уже в рантайме далее в программе. Например:
|
|
|
+```cpp
|
|
|
+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");
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="custom"></a>
|
|
|
+
|
|
|
+### Кастомные функции
|
|
|
+Библиотека поддерживает задание своих функций для чтения пина и получения времени без редактирования файлов библиотеки. Для этого нужно реализовать соответствующую функцию в своём .cpp или .ino файле:
|
|
|
+- `bool EB_read(uint8_t pin)` - для своей функции чтения пина
|
|
|
+- `void EB_mode(uint8_t pin, uint8_t mode)` - для своего аналога pinMode
|
|
|
+- `uint32_t EB_uptime()` - для своего аналога millis()
|
|
|
+
|
|
|
+Пример:
|
|
|
+
|
|
|
+```cpp
|
|
|
+#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();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="timer"></a>
|
|
|
+
|
|
|
+### Опрос по таймеру
|
|
|
+Иногда может понадобиться вызывать `tick()` не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера!
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ // таймер на 50 мс
|
|
|
+ static uint32_t tmr;
|
|
|
+ if (millis() - tmr >= 50) {
|
|
|
+ tmr = millis();
|
|
|
+ btn.tick(readSomePin());
|
|
|
+ }
|
|
|
+
|
|
|
+ // будет активно в течение 50 мс!!!
|
|
|
+ if (btn.click()) foo();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+В данной ситуации нужно поступить так: тикать по таймеру, там же обрабатывать события и сбрасывать флаги в конце:
|
|
|
+```cpp
|
|
|
+void loop() {
|
|
|
+ // таймер на 50 мс
|
|
|
+ static uint32_t tmr;
|
|
|
+ if (millis() - tmr >= 50) {
|
|
|
+ tmr = millis();
|
|
|
+ // тик
|
|
|
+ btn.tick(readSomePin());
|
|
|
+
|
|
|
+ // разбор событий
|
|
|
+ if (btn.click()) foo();
|
|
|
+
|
|
|
+ // сброс флагов
|
|
|
+ btn.clear();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Либо можно подключить обработчик и вызывать `clear()` в конце функции:
|
|
|
+```cpp
|
|
|
+void callback() {
|
|
|
+ switch (btn.action()) {
|
|
|
+ // ...
|
|
|
+ }
|
|
|
+
|
|
|
+ // сброс флагов
|
|
|
+ btn.clear();
|
|
|
+}
|
|
|
+
|
|
|
+void loop() {
|
|
|
+ // таймер на 50 мс
|
|
|
+ static uint32_t tmr;
|
|
|
+ if (millis() - tmr >= 50) {
|
|
|
+ tmr = millis();
|
|
|
+ btn.tick(readSomePin());
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+В случае с вызовом по таймеру антидребезг будет частично обеспечиваться самим таймером и в библиотеке его можно отключить (поставить период 0).
|
|
|
+
|
|
|
+Для корректной работы таймаутов, состояний и счётчика кликов нужен другой подход: буферизировать прочитанные по таймеру состояния и передавать их в тик в основном цикле. Например так:
|
|
|
+```cpp
|
|
|
+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();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Пропуск событий
|
|
|
+EncButton позволяет кнопке работать в паре с энкодером для корректного отслеживания *нажатых поворотов* - при нажатом повороте события с кнопки будут пропущены, т.е. не обработается удержание и клик. Допустим кнопок несколько: они могут выполнять действия как сами по себе, так и в паре с энкодером (кнопка зажата и крутится энкодер, в программе меняется выбранное кнопкой значение). Чтобы при удержании кнопка не генерировала события (удержание, степ, клики...) можно включить пропуск событий. Он будет действовать **до отпускания кнопки**:
|
|
|
+
|
|
|
+```cpp
|
|
|
+if (btn.pressing() && enc.turn()) {
|
|
|
+ btn.skipEvents(); // зафиксирован поворот. Пропускаем события
|
|
|
+ // нажатый поворот
|
|
|
+}
|
|
|
+
|
|
|
+if (btn.click()) {
|
|
|
+ // просто клик
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="examples-mini"></a>
|
|
|
+
|
|
|
+### Мини примеры, сценарии
|
|
|
+```cpp
|
|
|
+// меняем значения переменных
|
|
|
+
|
|
|
+// поворот энкодера
|
|
|
+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;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<a id="migrate"></a>
|
|
|
+
|
|
|
+## Гайд по миграции с v2 на v3
|
|
|
+### Инициализация
|
|
|
+```cpp
|
|
|
+// ВИРТУАЛЬНЫЕ
|
|
|
+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 раз за цикл, иначе будут пропуски действий!** Читай об этом выше.
|
|
|
+
|
|
|
+<a id="example"></a>
|
|
|
+## Примеры
|
|
|
+Остальные примеры смотри в **examples**!
|
|
|
+<details>
|
|
|
+<summary>Полное демо EncButton</summary>
|
|
|
+
|
|
|
+```cpp
|
|
|
+// #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); // + уровень кнопки
|
|
|
+
|
|
|
+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()) 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());
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>Подключение обработчика</summary>
|
|
|
+
|
|
|
+```cpp
|
|
|
+#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();
|
|
|
+}
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>Все типы кнопок</summary>
|
|
|
+
|
|
|
+```cpp
|
|
|
+#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");
|
|
|
+}
|
|
|
+```
|
|
|
+</details>
|
|
|
+<details>
|
|
|
+<summary>Все типы энкодеров</summary>
|
|
|
+
|
|
|
+```cpp
|
|
|
+#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
|
|
|
+}
|
|
|
+```
|
|
|
+</details>
|
|
|
+
|
|
|
+<a id="versions"></a>
|
|
|
+## Версии
|
|
|
+<details>
|
|
|
+<summary>Старые</summary>
|
|
|
+
|
|
|
+- 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 упразднён
|
|
|
+ - Мелкие улучшения и оптимизация
|
|
|
+</details>
|
|
|
+
|
|
|
+- 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 для корректного опроса нескольких кнопок с вызовом обработчика
|
|
|
+ - Добавлено подключение обработчика с передачей указателя на объект
|
|
|
+
|
|
|
+<a id="feedback"></a>
|
|
|
+## Баги и обратная связь
|
|
|
+При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
|
|
|
+Библиотека открыта для доработки и ваших **Pull Request**'ов!
|
|
|
+
|
|
|
+При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
|
|
|
+- Версия библиотеки
|
|
|
+- Какой используется МК
|
|
|
+- Версия SDK (для ESP)
|
|
|
+- Версия Arduino IDE
|
|
|
+- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
|
|
|
+- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
|
|
|
+- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код
|