├── .gitattributes ├── .github └── workflows │ └── tg-send.yml ├── LICENSE ├── README.md ├── README_EN.md ├── doc ├── btn_scheme.png ├── click.gif ├── encAli.png ├── enc_scheme.png ├── enc_type.png ├── hold.gif └── step.gif ├── examples ├── callback │ └── callback.ino ├── callback2 │ └── callback2.ino ├── demo │ └── demo.ino ├── double │ └── double.ino ├── doubleCallback │ └── doubleCallback.ino ├── isr │ └── isr.ino ├── one_button_3_var │ └── one_button_3_var.ino ├── one_enc_3_var │ └── one_enc_3_var.ino └── virtual_buttons │ ├── virtual_AnalogKey │ └── virtual_AnalogKey.ino │ ├── virtual_SimpleKeypad │ └── virtual_SimpleKeypad.ino │ └── virtual_SimpleKeypad_array │ └── virtual_SimpleKeypad_array.ino ├── keywords.txt ├── library.properties └── src ├── EncButton.h └── core ├── Button.h ├── EncButton.h ├── Encoder.h ├── MultiButton.h ├── VirtButton.h ├── VirtEncButton.h ├── VirtEncoder.h ├── flags.h ├── io.cpp ├── io.h ├── self.cpp └── self.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/tg-send.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Telegram Message 3 | on: 4 | release: 5 | types: [published] 6 | jobs: 7 | build: 8 | name: Send Message 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: send telegram message on push 12 | uses: appleboy/telegram-action@master 13 | with: 14 | to: ${{ secrets.TELEGRAM_TO }} 15 | token: ${{ secrets.TELEGRAM_TOKEN }} 16 | disable_web_page_preview: true 17 | message: | 18 | ${{ github.event.repository.name }} v${{ github.event.release.tag_name }} 19 | ${{ github.event.release.body }} 20 | https://github.com/${{ github.repository }} 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 AlexGyver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![latest](https://img.shields.io/github/v/release/GyverLibs/EncButton.svg?color=brightgreen)](https://github.com/GyverLibs/EncButton/releases/latest/download/EncButton.zip) 2 | [![PIO](https://badges.registry.platformio.org/packages/gyverlibs/library/EncButton.svg)](https://registry.platformio.org/libraries/gyverlibs/EncButton) 3 | [![Foo](https://img.shields.io/badge/Website-AlexGyver.ru-blue.svg?style=flat-square)](https://alexgyver.ru/) 4 | [![Foo](https://img.shields.io/badge/%E2%82%BD%24%E2%82%AC%20%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%82%D1%8C-%D0%B0%D0%B2%D1%82%D0%BE%D1%80%D0%B0-orange.svg?style=flat-square)](https://alexgyver.ru/support_alex/) 5 | [![Foo](https://img.shields.io/badge/README-ENGLISH-blueviolet.svg?style=flat-square)](https://github-com.translate.goog/GyverLibs/EncButton?_x_tr_sl=ru&_x_tr_tl=en) 6 | 7 | [![Foo](https://img.shields.io/badge/ПОДПИСАТЬСЯ-НА%20ОБНОВЛЕНИЯ-brightgreen.svg?style=social&logo=telegram&color=blue)](https://t.me/GyverLibs) 8 | 9 | # EncButton 10 | 11 | | ⚠️⚠️⚠️
**Новая версия v3 несовместима с предыдущими, смотри [документацию](#docs), [примеры](#example) и краткий [гайд по миграции](#migrate) с v2 на v3!**
⚠️⚠️⚠️ | 12 | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 13 | 14 | Лёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino 15 | - Кнопка 16 | - Обработка событий: нажатие, отпускание, клик, счётчик кликов, удержание, импульсное удержание, время удержания + предварительные клики для всех режимов 17 | - Программное подавление дребезга 18 | - Поддержка обработки двух одновременно нажимаемых кнопок как третьей кнопки 19 | - Энкодер 20 | - Обработка событий: обычный поворот, нажатый поворот, быстрый поворот 21 | - Поддержка четырёх типов инкрементальных энкодеров 22 | - Высокоточный алгоритм определения позиции 23 | - Буферизация в прерывании 24 | - Простое и понятное использование 25 | - Огромное количество возможностей и их комбинаций для разных сценариев использования даже одной кнопки 26 | - Виртуальный режим (например для работы с расширителем пинов) 27 | - Оптимизирована для работы в прерывании 28 | - Максимально быстрое чтение пинов для AVR, esp8266, esp32 (используется GyverIO) 29 | - Быстрые асинхронные алгоритмы опроса действий с кнопки и энкодера 30 | - Жёсткая оптимизация и небольшой вес во Flash и SRAM памяти: 5 байт SRAM (на экземпляр) и ~350 байт Flash на обработку кнопки 31 | 32 | Примеры сценариев использования: 33 | - Несколько кликов - включение режима (по кол-ву кликов) 34 | - Несколько кликов + короткое удержание - ещё вариант включения режима (по кол-ву кликов) 35 | - Несколько кликов + удержание - постепенное изменение значения выбранной переменной (по кол-ву кликов) 36 | - Несколько кликов выбирают переменную, энкодер её изменяет 37 | - Изменение шага изменения переменной при вращении энкодера - например уменьшение при зажатой кнопке и увеличение при быстром вращении 38 | - Навигация по меню при вращении энкодера, изменение переменной при вращении зажатого энкодера 39 | - Полноценная навигация по меню при использовании двух кнопок (одновременное удержание для перехода на следующий уровень, одновременное нажатие для возврата на предыдущий) 40 | - И так далее 41 | 42 | ### Совместимость 43 | Совместима со всеми Arduino платформами (используются Arduino-функции) 44 | 45 | ## Содержание 46 | - [Установка](#install) 47 | - [Информация](#info) 48 | - [Документация](#docs) 49 | - [Настройки компиляции](#config) 50 | - [Полное описание классов](#class) 51 | - [Обработка и опрос](#tick) 52 | - [Предварительные клики](#preclicks) 53 | - [Прямое чтение кнопки](#btnread) 54 | - [Погружение в цикл](#loop) 55 | - [Timeout](#timeout) 56 | - [Busy](#busy) 57 | - [Получение события](#actions) 58 | - [Оптимизация](#optimise) 59 | - [Коллбэки](#callback) 60 | - [Одновременное нажатие](#double) 61 | - [Прерывания](#isr) 62 | - [Массив кнопок/энкодеров](#array) 63 | - [Кастомные функции](#custom) 64 | - [Опрос по таймеру](#timer) 65 | - [Мини примеры, сценарии](#examples-mini) 66 | - [Миграция с v2](#migrate) 67 | - [Примеры](#example) 68 | - [Версии](#versions) 69 | - [Баги и обратная связь](#feedback) 70 | 71 | 72 | ## Установка 73 | - Для работы требуется библиотека [GyverIO](https://github.com/GyverLibs/GyverIO) 74 | - Библиотеку можно найти по названию **EncButton** и установить через менеджер библиотек в: 75 | - Arduino IDE 76 | - Arduino IDE v2 77 | - PlatformIO 78 | - [Скачать библиотеку](https://github.com/GyverLibs/EncButton/archive/refs/heads/main.zip) .zip архивом для ручной установки: 79 | - Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64) 80 | - Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32) 81 | - Распаковать и положить в *Документы/Arduino/libraries/* 82 | - (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив 83 | - Читай более подробную инструкцию по установке библиотек [здесь](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) 84 | ### Обновление 85 | - Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи 86 | - Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить" 87 | - Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам! 88 | 89 | 90 | 91 | ## Информация 92 | ### Энкодер 93 | #### Тип энкодера 94 | Библиотека поддерживает все 4 типа *инкрементальных* энкодеров, тип можно настроить при помощи `setEncType(тип)`: 95 | - `EB_STEP4_LOW` - активный низкий сигнал (подтяжка к VCC). Полный период (4 фазы) за один щелчок. *Установлен по умолчанию* 96 | - `EB_STEP4_HIGH` - активный высокий сигнал (подтяжка к GND). Полный период (4 фазы) за один щелчок 97 | - `EB_STEP2` - половина периода (2 фазы) за один щелчок 98 | - `EB_STEP1` - четверть периода (1 фаза) за один щелчок, а также энкодеры без фиксации 99 | 100 | ![diagram](/doc/enc_type.png) 101 | 102 | #### Рекомендации 103 | Для работы по сценарию "энкодер с кнопкой" рекомендую вот такие ([ссылка](https://ali.ski/cmPI2), [ссылка](https://ali.ski/sZbTK)) круглые китайские модули с распаянными цепями антидребезга (имеют тип `EB_STEP4_LOW` по классификации выше): 104 | ![scheme](/doc/encAli.png) 105 | 106 | Самостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC): 107 | ![scheme](/doc/enc_scheme.png) 108 | 109 | > Примечание: по умолчанию в библиотеке пины энкодера настроены на `INPUT` с расчётом на внешнюю подтяжку. Если у вас энкодер без подтяжки - можно использовать внутреннюю `INPUT_PULLUP`, указав это при инициализации энкодера (см. документацию ниже). 110 | 111 | ### Кнопка 112 | #### Уровень кнопки 113 | Кнопка может быть подключена к микроконтроллеру двумя способами и давать при нажатии высокий или низкий сигнал. В библиотеке предусмотрена настройка `setBtnLevel(уровень)`, где уровень - активный сигнал кнопки: 114 | - `HIGH` - кнопка подключает VCC. Установлен по умолчанию в `Virt`-библиотеках 115 | - `LOW` - кнопка подключает GND. Установлен по умолчанию в основных библиотеках 116 | 117 | ![scheme](/doc/btn_scheme.png) 118 | 119 | #### Подтяжка пина 120 | В схемах с микроконтроллерами чаще всего используется подключение кнопки к GND с подтяжкой пина к VCC. Подтяжка может быть внешней (режим пина нужно поставить `INPUT`) или внутренней (режим пина `INPUT_PULLUP`). В "реальных" проектах рекомендуется внешняя подтяжка, т.к. она менее подвержена помехам - у внутренней слишком высокое сопротивление. 121 | 122 | 123 | 124 | ## Документация 125 | 126 | 127 | 128 | ### Дефайны настроек 129 | Объявлять до подключения библиотеки 130 | 131 | ```cpp 132 | 133 | // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки) 134 | #define EB_NO_FOR 135 | 136 | // отключить обработчик событий attach (экономит 2 байта оперативки) 137 | #define EB_NO_CALLBACK 138 | 139 | // отключить счётчик энкодера [VirtEncoder, Encoder, EncButton] (экономит 4 байта оперативки) 140 | #define EB_NO_COUNTER 141 | 142 | // отключить буферизацию энкодера (экономит 2 байта оперативки) 143 | #define EB_NO_BUFFER 144 | 145 | /* 146 | Настройка таймаутов для всех классов 147 | - Заменяет таймауты константами, изменить их из программы (SetXxxTimeout()) будет нельзя 148 | - Настройка влияет на все объявленные в программе кнопки/энкодеры 149 | - Экономит 1 байт оперативки на объект за каждый таймаут 150 | - Показаны значения по умолчанию в мс 151 | - Значения не ограничены 4000мс, как при установке из программы (SetXxxTimeout()) 152 | */ 153 | #define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка) 154 | #define EB_CLICK_TIME 500 // таймаут ожидания кликов (кнопка) 155 | #define EB_HOLD_TIME 600 // таймаут удержания (кнопка) 156 | #define EB_STEP_TIME 200 // таймаут импульсного удержания (кнопка) 157 | #define EB_FAST_TIME 30 // таймаут быстрого поворота (энкодер) 158 | #define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер) 159 | ``` 160 | 161 | 162 | 163 | ### Классы 164 | Как работать с документацией: EncButton начиная с версии 3.0 представляет собой несколько библиотек (классов) для различных сценариев использования, они друг друга наследуют для расширения функциональности. Таким образом библиотека представляет собой "луковицу", каждый слой которой имеет доступ к функциям нижних слоёв: 165 | - Базовые классы: 166 | - `VirtButton` - базовый класс виртуальной кнопки, обеспечивает все возможности кнопки 167 | - `VirtEncoder` - базовый класс виртуального энкодера, определяет факт и направление вращения энкодера 168 | - `VirtEncButton` - базовый класс виртуального энкодера с кнопкой, обеспечивает опрос энкодера с учётом кнопки, *наследует VirtButton и VirtEncoder* 169 | - Основные классы: 170 | - `Button`, `ButtonT` - класс кнопки, *наследует VirtButton* 171 | - `Encoder`, `EncoderT` - класс энкодера, *наследует VirtEncoder* 172 | - `EncButton`, `EncButtonT` - класс энкодера с кнопкой, *наследует VirtEncButton, VirtButton, VirtEncoder* 173 | 174 | Таким образом для изучения всех доступных функций конкретной библиотеки нужно смотреть не только её, но и то что она наследует. Например для обработки кнопки при помощи `Button` нужно открыть ниже описание `Button` и `VirtButton`. 175 | 176 | > *Виртуальный* - без указания пина микроконтроллера, работает напрямую с переданным значением, например для опроса кнопок-энкодеров через расширители пинов и сдвиговые регистры. 177 | 178 | > `T`-версии библиотек требуют указания пинов константами (цифрами). Номера пинов будут храниться в памяти программы, это ускоряет работу и делает код легче на 1 байт за каждый пин. 179 | 180 | > Примечание: `#include ` подключает все инструменты библиотеки! 181 | 182 |
183 | Таблица функций кнопки 184 | 185 | | | VirtButton | VirtEncButton | Button | EncButton | 186 | | ----------------- | :--------: | :-----------: | :----: | :-------: | 187 | | read | | | ✔ | | 188 | | readBtn | | | | ✔ | 189 | | tickRaw | ✔ | ✔ | ✔ | ✔ | 190 | | setHoldTimeout | ✔ | ✔ | ✔ | ✔ | 191 | | setStepTimeout | ✔ | ✔ | ✔ | ✔ | 192 | | setClickTimeout | ✔ | ✔ | ✔ | ✔ | 193 | | setDebTimeout | ✔ | ✔ | ✔ | ✔ | 194 | | setTimeout | ✔ | ✔ | ✔ | ✔ | 195 | | setBtnLevel | ✔ | ✔ | ✔ | ✔ | 196 | | pressISR | ✔ | ✔ | ✔ | ✔ | 197 | | reset | ✔ | ✔ | ✔ | ✔ | 198 | | clear | ✔ | ✔ | ✔ | ✔ | 199 | | skipEvents | ✔ | ✔ | ✔ | ✔ | 200 | | attach | ✔ | ✔ | ✔ | ✔ | 201 | | detach | ✔ | ✔ | ✔ | ✔ | 202 | | press | ✔ | ✔ | ✔ | ✔ | 203 | | release | ✔ | ✔ | ✔ | ✔ | 204 | | click | ✔ | ✔ | ✔ | ✔ | 205 | | pressing | ✔ | ✔ | ✔ | ✔ | 206 | | hold | ✔ | ✔ | ✔ | ✔ | 207 | | holding | ✔ | ✔ | ✔ | ✔ | 208 | | step | ✔ | ✔ | ✔ | ✔ | 209 | | hasClicks | ✔ | ✔ | ✔ | ✔ | 210 | | getClicks | ✔ | ✔ | ✔ | ✔ | 211 | | getSteps | ✔ | ✔ | ✔ | ✔ | 212 | | releaseHold | ✔ | ✔ | ✔ | ✔ | 213 | | releaseStep | ✔ | ✔ | ✔ | ✔ | 214 | | releaseHoldStep | ✔ | ✔ | ✔ | ✔ | 215 | | waiting | ✔ | ✔ | ✔ | ✔ | 216 | | busy | ✔ | ✔ | ✔ | ✔ | 217 | | action | ✔ | ✔ | ✔ | ✔ | 218 | | getAction | ✔ | ✔ | ✔ | ✔ | 219 | | timeout | ✔ | ✔ | ✔ | ✔ | 220 | | pressFor | ✔ | ✔ | ✔ | ✔ | 221 | | holdFor | ✔ | ✔ | ✔ | ✔ | 222 | | stepFor | ✔ | ✔ | ✔ | ✔ | 223 |
224 | 225 |
226 | Таблица функций энкодера 227 | 228 | | | VirtEncoder | Encoder | VirtEncButton | EncButton | 229 | | -------------- | :---------: | :-----: | :-----------: | :-------: | 230 | | readEnc | | | | ✔ | 231 | | initEnc | ✔ | ✔ | ✔ | ✔ | 232 | | setEncReverse | ✔ | ✔ | ✔ | ✔ | 233 | | setEncType | ✔ | ✔ | ✔ | ✔ | 234 | | setEncISR | ✔ | ✔ | ✔ | ✔ | 235 | | clear | ✔ | ✔ | ✔ | ✔ | 236 | | turn | ✔ | ✔ | ✔ | ✔ | 237 | | dir | ✔ | ✔ | ✔ | ✔ | 238 | | tickRaw | ✔ | ✔ | ✔ | ✔ | 239 | | pollEnc | ✔ | ✔ | ✔ | ✔ | 240 | | counter | ✔ | ✔ | ✔ | ✔ | 241 | | setFastTimeout | | | ✔ | ✔ | 242 | | turnH | | | ✔ | ✔ | 243 | | fast | | | ✔ | ✔ | 244 | | right | | | ✔ | ✔ | 245 | | left | | | ✔ | ✔ | 246 | | rightH | | | ✔ | ✔ | 247 | | leftH | | | ✔ | ✔ | 248 | | action | | | ✔ | ✔ | 249 | | getAction | | | ✔ | ✔ | 250 | | timeout | | | ✔ | ✔ | 251 | | attach | | | ✔ | ✔ | 252 | | detach | | | ✔ | ✔ | 253 |
254 | 255 |
256 | VirtButton 257 | 258 | ```cpp 259 | // ================ НАСТРОЙКИ ================ 260 | // установить таймаут удержания, умолч. 600 (макс. 4000 мс) 261 | void setHoldTimeout(uint16_t tout); 262 | 263 | // установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс) 264 | void setStepTimeout(uint16_t tout); 265 | 266 | // установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс) 267 | void setClickTimeout(uint16_t tout); 268 | 269 | // установить таймаут антидребезга, умолч. 50 (макс. 255 мс) 270 | void setDebTimeout(uint8_t tout); 271 | 272 | // установить время таймаута, умолч. 1000 (макс. 4000 мс) 273 | void setTimeout(const uint16_t tout); 274 | 275 | // установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND) 276 | // умолч. HIGH, то есть true - кнопка нажата 277 | void setBtnLevel(bool level); 278 | 279 | // подключить функцию-обработчик событий 280 | void attach(void (*handler)()); 281 | 282 | // отключить функцию-обработчик событий 283 | void detach(); 284 | 285 | // ================== СБРОС ================== 286 | // сбросить системные флаги (принудительно закончить обработку) 287 | void reset(); 288 | 289 | // принудительно сбросить флаги событий 290 | void clear(bool resetTout = false); 291 | 292 | // игнорировать все события до отпускания кнопки 293 | void skipEvents(); 294 | 295 | // ================ ОБРАБОТКА ================ 296 | // обработка кнопки значением 297 | bool tick(bool s); 298 | 299 | // обработка виртуальной кнопки как одновременное нажатие двух других кнопок 300 | bool tick(VirtButton& b0, VirtButton& b1); 301 | 302 | // кнопка нажата в прерывании кнопки 303 | void pressISR(); 304 | 305 | // обработка кнопки без сброса событий и вызова коллбэка 306 | bool tickRaw(bool s); 307 | 308 | // ================== ОПРОС ================== 309 | // кнопка нажата [событие] 310 | bool press(); 311 | bool press(uint8_t clicks); 312 | 313 | // кнопка отпущена (в любом случае) [событие] 314 | bool release(); 315 | bool release(uint8_t clicks); 316 | 317 | // клик по кнопке (отпущена без удержания) [событие] 318 | bool click(); 319 | bool click(uint8_t clicks); 320 | 321 | // кнопка зажата (между press() и release()) [состояние] 322 | bool pressing(); 323 | bool pressing(uint8_t clicks); 324 | 325 | // кнопка была удержана (больше таймаута) [событие] 326 | bool hold(); 327 | bool hold(uint8_t clicks); 328 | 329 | // кнопка удерживается (больше таймаута) [состояние] 330 | bool holding(); 331 | bool holding(uint8_t clicks); 332 | 333 | // импульсное удержание [событие] 334 | bool step(); 335 | bool step(uint8_t clicks); 336 | 337 | // зафиксировано несколько кликов [событие] 338 | bool hasClicks(); 339 | bool hasClicks(uint8_t clicks); 340 | 341 | // кнопка отпущена после удержания [событие] 342 | bool releaseHold(); 343 | bool releaseHold(uint8_t clicks); 344 | 345 | // кнопка отпущена после импульсного удержания [событие] 346 | bool releaseStep(); 347 | bool releaseStep(uint8_t clicks); 348 | 349 | // кнопка отпущена после удержания или импульсного удержания [событие] 350 | bool releaseHoldStep(); 351 | bool releaseHoldStep(uint8_t clicks); 352 | 353 | // получить количество кликов 354 | uint8_t getClicks(); 355 | 356 | // получить количество степов 357 | uint16_t getSteps(); 358 | 359 | // кнопка ожидает повторных кликов (между click() и hasClicks()) [состояние] 360 | bool waiting(); 361 | 362 | // идёт обработка (между первым нажатием и после ожидания кликов) [состояние] 363 | bool busy(); 364 | 365 | // было действие с кнопки, вернёт код события [событие] 366 | uint16_t action(); 367 | EBAction getAction(); 368 | 369 | // ================== ВРЕМЯ ================== 370 | // после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [событие] 371 | bool timeout(); 372 | 373 | // после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [состояние] 374 | bool timeoutState(); 375 | 376 | // время, которое кнопка удерживается (с начала нажатия), мс 377 | uint16_t pressFor(); 378 | 379 | // кнопка удерживается дольше чем (с начала нажатия), мс [состояние] 380 | bool pressFor(uint16_t ms); 381 | 382 | // время, которое кнопка удерживается (с начала удержания), мс 383 | uint16_t holdFor(); 384 | 385 | // кнопка удерживается дольше чем (с начала удержания), мс [состояние] 386 | bool holdFor(uint16_t ms); 387 | 388 | // время, которое кнопка удерживается (с начала степа), мс 389 | uint16_t stepFor(); 390 | 391 | // кнопка удерживается дольше чем (с начала степа), мс [состояние] 392 | bool stepFor(uint16_t ms); 393 | ``` 394 |
395 |
396 | VirtEncoder 397 | 398 | ```cpp 399 | // ==================== НАСТРОЙКИ ==================== 400 | // инвертировать направление энкодера (умолч. 0) 401 | void setEncReverse(bool rev); 402 | 403 | // установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1) 404 | void setEncType(uint8_t type); 405 | 406 | // использовать обработку энкодера в прерывании 407 | void setEncISR(bool use); 408 | 409 | // инициализация энкодера 410 | void initEnc(bool e0, bool e1); 411 | 412 | // инициализация энкодера совмещённым значением 413 | void initEnc(int8_t v); 414 | 415 | // сбросить флаги событий 416 | void clear(); 417 | 418 | // ====================== ОПРОС ====================== 419 | // был поворот [событие] 420 | bool turn(); 421 | 422 | // направление энкодера (1 или -1) [состояние] 423 | int8_t dir(); 424 | 425 | // счётчик 426 | int32_t counter; 427 | 428 | // ==================== ОБРАБОТКА ==================== 429 | // опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановке 430 | int8_t tickISR(bool e0, bool e1); 431 | 432 | // опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке 433 | int8_t tick(bool e0, bool e1); 434 | int8_t tick(); // сама обработка в прерывании 435 | 436 | // опросить энкодер без сброса события поворота. Вернёт 1 или -1 при вращении, 0 при остановке 437 | int8_t tickRaw(bool e0, bool e1); 438 | int8_t tickRaw(); // сама обработка в прерывании 439 | 440 | // опросить энкодер без установки флагов на поворот (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке 441 | int8_t pollEnc(bool e0, bool e1); 442 | ``` 443 |
444 |
445 | VirtEncButton 446 | 447 | - Доступны функции из `VirtButton` 448 | - Доступны функции из `VirtEncoder` 449 | 450 | ```cpp 451 | // ================== НАСТРОЙКИ ================== 452 | // установить таймаут быстрого поворота, мс 453 | void setFastTimeout(uint8_t tout); 454 | 455 | // сбросить флаги энкодера и кнопки 456 | void clear(bool resetTout = false); 457 | 458 | // ==================== ОПРОС ==================== 459 | // ЛЮБОЙ поворот энкодера [событие] 460 | bool turn(); 461 | 462 | // нажатый поворот энкодера [событие] 463 | bool turnH(); 464 | 465 | // быстрый поворот энкодера [состояние] 466 | bool fast(); 467 | 468 | // ненажатый поворот направо [событие] 469 | bool right(); 470 | 471 | // ненажатый поворот налево [событие] 472 | bool left(); 473 | 474 | // нажатый поворот направо [событие] 475 | bool rightH(); 476 | 477 | // нажатый поворот налево [событие] 478 | bool leftH(); 479 | 480 | // было действие с кнопки или энкодера, вернёт код события [событие] 481 | uint16_t action(); 482 | EBAction getAction(); 483 | 484 | // ==================== ОБРАБОТКА ==================== 485 | // обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при повороте 486 | int8_t tickISR(bool e0, bool e1); 487 | 488 | // обработка энкодера и кнопки 489 | bool tick(bool e0, bool e1, bool btn); 490 | bool tick(bool btn); // энкодер в прерывании 491 | 492 | // обработка энкодера и кнопки без сброса флагов и вызова коллбэка 493 | bool tickRaw(bool e0, bool e1, bool btn); 494 | bool tickRaw(bool btn); // энкодер в прерывании 495 | ``` 496 |
497 |
498 | Button 499 | 500 | - Доступны функции из `VirtButton` 501 | - Режим кнопки по умолчанию - `LOW` 502 | 503 | ```cpp 504 | Button; 505 | Button(uint8_t pin); // с указанием пина 506 | Button(uint8_t npin, uint8_t mode); // + режим работы (умолч. INPUT_PULLUP) 507 | Button(uint8_t npin, uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW) 508 | ``` 509 | ```cpp 510 | // указать пин и его режим работы 511 | void init(uint8_t npin, uint8_t mode); 512 | 513 | // прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel 514 | bool read(); 515 | 516 | // функция обработки, вызывать в loop 517 | bool tick(); 518 | 519 | // обработка кнопки без сброса событий и вызова коллбэка 520 | bool tickRaw(); 521 | ``` 522 |
523 |
524 | ButtonT 525 | 526 | - Доступны функции из `VirtButton` 527 | - Режим кнопки по умолчанию - `LOW` 528 | 529 | ```cpp 530 | ButtonT; // с указанием пина 531 | ButtonT (uint8_t mode); // + режим работы (умолч. INPUT_PULLUP) 532 | ButtonT (uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW) 533 | ``` 534 | ```cpp 535 | // указать режим работы 536 | void init(uint8_t mode); 537 | 538 | // прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel 539 | bool read(); 540 | 541 | // функция обработки, вызывать в loop 542 | bool tick(); 543 | ``` 544 |
545 |
546 | Encoder 547 | 548 | - Доступны функции из `VirtEncoder` 549 | 550 | ```cpp 551 | Encoder; 552 | Encoder(uint8_t encA, uint8_t encB); // с указанием пинов 553 | Encoder(uint8_t encA, uint8_t encB, uint8_t mode); // + режим работы (умолч. INPUT) 554 | ``` 555 | ```cpp 556 | // указать пины и их режим работы 557 | void init(uint8_t encA, uint8_t encB, uint8_t mode); 558 | 559 | // функция обработки для вызова в прерывании энкодера 560 | int8_t tickISR(); 561 | 562 | // функция обработки для вызова в loop 563 | int8_t tick(); 564 | ``` 565 |
566 |
567 | EncoderT 568 | 569 | - Доступны функции из `VirtEncoder` 570 | 571 | ```cpp 572 | EncoderT; // с указанием пинов 573 | EncoderT (uint8_t mode); // + режим работы (умолч. INPUT) 574 | ``` 575 | ```cpp 576 | // указать режим работы пинов 577 | void init(uint8_t mode); 578 | 579 | // функция обработки для вызова в прерывании энкодера 580 | int8_t tickISR(); 581 | 582 | // функция обработки для вызова в loop 583 | int8_t tick(); 584 | ``` 585 |
586 |
587 | EncButton 588 | 589 | - Доступны функции из `VirtButton` 590 | - Доступны функции из `VirtEncoder` 591 | - Доступны функции из `VirtEncButton` 592 | 593 | ```cpp 594 | EncButton; 595 | 596 | // настроить пины (энк, энк, кнопка) 597 | EncButton(uint8_t encA, uint8_t encB, uint8_t btn); 598 | 599 | // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки) 600 | EncButton(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW); 601 | ``` 602 | ```cpp 603 | // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки) 604 | void init(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW); 605 | 606 | // функция обработки для вызова в прерывании энкодера 607 | int8_t tickISR(); 608 | 609 | // функция обработки, вызывать в loop 610 | bool tick(); 611 | 612 | // прочитать значение кнопки с учётом setBtnLevel 613 | bool readBtn(); 614 | 615 | // прочитать значение энкодера 616 | int8_t readEnc(); 617 | ``` 618 |
619 |
620 | EncButtonT 621 | 622 | - Доступны функции из `VirtButton` 623 | - Доступны функции из `VirtEncoder` 624 | - Доступны функции из `VirtEncButton` 625 | 626 | ```cpp 627 | // с указанием пинов 628 | EncButtonT; 629 | 630 | // + режим работы пинов, уровень кнопки 631 | EncButtonT (uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW); 632 | ``` 633 | ```cpp 634 | // настроить режим работы пинов, уровень кнопки 635 | void init(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW); 636 | 637 | // функция обработки для вызова в прерывании энкодера 638 | int8_t tickISR(); 639 | 640 | // функция обработки, вызывать в loop 641 | bool tick(); 642 | 643 | // прочитать значение кнопки 644 | bool readBtn(); 645 | 646 | // прочитать значение энкодера 647 | int8_t readEnc(); 648 | ``` 649 |
650 | 651 | 652 | 653 | ### Обработка и опрос 654 | Во всех библиотеках есть общая **функция обработки** (тикер `tick`), которая получает текущий сигнал с кнопки и энкодера 655 | - Эту функцию нужно однократно вызывать в основном цикле программы (для виртуальных - с передачей значения) 656 | - Функция возвращает `true` при наступлении события (для энкодера - `1` или `-1` при повороте, `0` при его отсутствии. Таким образом поворот в любую сторону расценивается как `true`) 657 | - Есть отдельные функции для вызова в прерывании, они имеют суффикс `ISR`, см. документацию ниже 658 | 659 | Библиотека обрабатывает сигнал внутри этой функции, результат можно получить из **функций опроса** событий. Они бывают двух типов: 660 | - `[событие]` - функция вернёт `true` однократно при наступлении события. Сбросится после следующего вызова функции обработки (например клик, поворот энкодера). За исключением события `timeout` 661 | - `[состояние]` - функция возвращает `true`, пока активно это состояние (например кнопка удерживается) 662 | 663 | Для простоты восприятия функцию обработки нужно размещать в начале цикла, а опросы делать ниже: 664 | ```cpp 665 | void loop() { 666 | btn.tick(); // опрос 667 | 668 | if (btn.click()) Serial.println("click"); // однократно выведет при клике 669 | if (btn.click()) Serial.println("click"); // тот же клик! 670 | } 671 | ``` 672 | > В отличие от предыдущих версий библиотеки, функции опроса сбрасываются не внутри себя, а *внутри функции обработки*. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение `click()`. Это позволяет использовать функции опроса по несколько раз за текущую итерацию цикла для создания сложной логики работы программы. 673 | 674 | #### Несколько функций обработки 675 | По очевидным причинам нельзя вызывать функцию обработки больше одного раза за цикл - каждый следующий вызов сбросит события от предыдущего и код будет работать некорректно. Вот так - нельзя: 676 | ```cpp 677 | // так нельзя 678 | void loop() { 679 | btn.tick(); 680 | if (btn.click()) ... 681 | 682 | // .... 683 | 684 | btn.tick(); 685 | if (btn.hold()) ... 686 | } 687 | ``` 688 | 689 | Если очень нужно попасть в глухой цикл и опрашивать там кнопку, то вот так - можно: 690 | ```cpp 691 | // так можно 692 | void loop() { 693 | btn.tick(); 694 | if (btn.click()) ... 695 | 696 | while (true) { 697 | btn.tick(); 698 | if (btn.hold()) ... 699 | if (btn.click()) break; 700 | } 701 | } 702 | ``` 703 | 704 | Если библиотека используется с подключенным обработчиком событий `attach()` (см. ниже), то можно вызывать `tick()` где угодно и сколько угодно раз, события будут обработаны в обработчике: 705 | ```cpp 706 | // так можно 707 | void cb() { 708 | switch (btn.action()) { 709 | // ... 710 | } 711 | } 712 | 713 | void setup() { 714 | btn.attach(cb); 715 | } 716 | 717 | void loop() { 718 | btn.tick(); 719 | // ... 720 | btn.tick(); 721 | // ... 722 | btn.tick(); 723 | } 724 | ``` 725 | 726 | #### "Загруженная" программа 727 | Библиотека EncButton - **асинхронная**: она не ждёт, пока закончится обработка кнопки, а позволяет программе выполняться дальше. Это означает, что для корректной работы библиотеки основной цикл программы должен выполняться как можно быстрее и не содержать задержек и других "глухих" циклов внутри себя. Для обеспечения правильной обработки кнопки не рекомендуется иметь в основном цикле задержки длительностью более 50-100 мс. Несколько советов: 728 | - Новичкам: изучить цикл уроков [как написать скетч](https://alexgyver.ru/lessons/how-to-sketch/) 729 | - Писать асинхронный код в `loop()` 730 | - Любую синхронную конструкцию на `delay()` можно сделать асинхронной при помощи `millis()` 731 | - Если в программе *каждая* итерация главного цикла выполняется дольше 50-100мс - в большинстве случаев программа написана неправильно, за исключением каких-то особых случаев 732 | - Подключить кнопку на аппаратное прерывание (см. ниже) 733 | - Избегать выполнения "тяжёлых" участков кода, пока идёт обработка кнопки, например поместив их в условие `if (!button.busy()) { тяжёлый код }` 734 | - Если оптимизировать основной цикл невозможно - вызывать тикер в другом "потоке" и использовать функцию-обработчик: 735 | - В прерывании таймера с периодом ~50мс или чаще 736 | - На другом ядре (например ESP32) 737 | - В другом таске FreeRTOS 738 | - Внутри `yield()` (внутри `delay()`) 739 | 740 | #### Раздельная обработка 741 | > Имеет смысл только при ручном опросе событий! При подключенной функции-обработчике достаточно вызывать обычный `tick()` между тяжёлыми участками программы 742 | 743 | Также в загруженной программе можно разделить обработку и сброс событий: вместо `tick()` использовать `tickRaw()` между тяжёлыми участками кода и ручной сброс `clear()`. Порядок следующий: 744 | - Опросить действия (click, press, turn...) 745 | - Вызвать `clear()` 746 | - Вызывать `tickRaw()` между тяжёлыми участками кода 747 | 748 | ```cpp 749 | void loop() { 750 | if (btn.click()) ... 751 | if (btn.press()) ... 752 | if (btn.step()) ... 753 | 754 | btn.clear(); 755 | 756 | // ... 757 | btn.tickRaw(); 758 | // ... 759 | btn.tickRaw(); 760 | // ... 761 | btn.tickRaw(); 762 | // ... 763 | } 764 | ``` 765 | Это позволит опрашивать кнопку/энкодер в не очень хорошо написанной программе, где основной цикл завален тяжёлым кодом. Внутри `tickRaw()` накапливаются события, которые раз в цикл разбираются, а затем вручную сбрасываются. 766 | 767 | > В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `releaseXxx` 768 | 769 | #### Обработка внутри delay 770 | Если сложно избавиться от `delay()` внутри главного цикла программы, то на некоторых платформах можно поместить свой код внутри него. Таким образом можно получить даже обработку энкодера в цикле с дилеями без использования прерываний: 771 | ```cpp 772 | // вставка кода в delay 773 | void yield() { 774 | eb.tickRaw(); 775 | } 776 | 777 | void loop() { 778 | if (eb.click()) ... 779 | if (btn.turn()) ... 780 | 781 | eb.clear(); 782 | 783 | // ... 784 | delay(10); 785 | // ... 786 | delay(50); 787 | // ... 788 | } 789 | ``` 790 | 791 | > В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события `releaseXxx` 792 | 793 | #### Обработка кнопки 794 | Библиотека обрабатывает кнопку следующим образом: 795 | - Нажатие с программным подавлением дребезга (удержание дольше таймаута deb), результат - событие `press`, состояния `pressing` и `busy` 796 | - Удержание дольше таймаута удержания hold - событие `hold`, состояние `holding` 797 | - Удержание дольше таймаута удержания hold + таймаута степ - импульсное событие `step`, срабатывает с периодом step пока кнопка удерживается 798 | - Отпускание кнопки, результат - событие `release`, снятие состояний `pressing` и `holding` 799 | - Отпускание до таймаута удержания - событие `click` 800 | - Отпускание после удержания - событие `releaseHold` 801 | - Отпускание после импульсного удержания - событие `releaseStep` 802 | - События `releaseHold` и `releaseStep` взаимоисключающие, если кнопка была удержана до `step` - `releaseHold` уже не сработает 803 | - Ожидание нового клика в течение таймаута click, состояние `waiting` 804 | - Если нового клика нет - снятие состоятия `busy`, обработка закончена 805 | - Если кнопка снова нажата - обработка нового клика 806 | - Счётчик кликов `getClicks()` сбрасывается после событий `releaseHold`/`releaseStep`, которые проверяют предварительные клики. В общем обработчике `action()` это события `EB_REL_HOLD_C` или `EB_REL_STEP_C` 807 | - Количество сделанных кликов нужно проверять по событию `hasClicks`, а также можно опросить внутри почти всех событий кнопки, которые идут до `releaseXxx` 808 | - Если ожидается `timeout` - событие timeout периодом из `setTimeout` 809 | - Обработка кнопки в прерывании сообщает библиотеке о факте нажатия, вся остальная обработка выполняется штатно в `tick()` 810 | 811 | > Отличие `click(n)` от `hasClicks(n)`: `click(n)` вернёт `true` в любом случае при совпадении количества кликов, даже если будет сделано больше кликов. `hasClicks(n)` вернёт `true` только в том случае, если было сделано ровно указанное количество кликов и больше кликов не было! 812 | 813 | > Лучше один раз увидеть, чем сто раз прочитать. Запусти пример demo и понажимай на кнопку, или попробуй [онлайн-симуляцию в Wokwi](https://wokwi.com/projects/373591584298469377) 814 | 815 | ##### Click 816 | ![click](/doc/click.gif) 817 | 818 | ##### Hold 819 | ![hold](/doc/hold.gif) 820 | 821 | ##### Step 822 | ![step](/doc/step.gif) 823 | 824 | Онлайн-симуляция доступна [здесь](https://wokwi.com/projects/373591584298469377) 825 | 826 | #### Обработка энкодера 827 | - "Быстрым" поворотом считается поворот, совершённый менее чем за настроенный таймаут от предыдущего поворота 828 | - Обработанные в прерывании повороты становятся активными (вызывают события) после вызова `tick()` 829 | - Доступ к счётчику энкодера `counter` - это публичная переменная класса, можно делать с ней всё что угодно: 830 | ```cpp 831 | Serial.println(eb.counter); // читать 832 | eb.counter += 1234; // менять 833 | eb.counter = 0; // обнулять 834 | ``` 835 | 836 | #### Обработка энкодера с кнопкой 837 | - Поворот энкодера при зажатой кнопке снимает и блокирует все последующие события и клики, за исключением события `release`. Состояния нажатой кнопки не изменяются 838 | - Поворот энкодера также влияет на системный таймаут (функция `timeout()`) - сработает через указанное время после поворота энкодера 839 | - Счётчик кликов доступен при нажатом повороте: несколько кликов, зажатие кнопки, поворот 840 | 841 | 842 | 843 | ### Предварительные клики 844 | Библиотека считает количество кликов по кнопке и некоторые функции опроса могут отдельно обрабатываться с *предварительными кликами*. Например 3 клика, затем удержание. Это очень сильно расширяет возможности одной кнопки. Есть два варианта работы с такими событиями: 845 | ```cpp 846 | // 1 847 | if (btn.hold()) { 848 | if (btn.getClicks() == 2) Serial.println("hold 2 clicks"); 849 | } 850 | 851 | // 2 852 | if (btn.hold(2)) Serial.println("hold 2 clicks"); 853 | ``` 854 | 855 | В первом варианте можно получить количество кликов для дальнейшей обработки вручную, а во втором - библиотека сделает это сама, если количество кликов для действия заранее известно. 856 | 857 | 858 | 859 | ### Прямое чтение кнопки 860 | В некоторых сценариях бывает нужно получить состояние кнопки "здесь и сейчас", например определить удерживается ли кнопка сразу после запуска микроконтроллера (старта программы). Функцию `tick()` нужно вызывать постоянно в цикле, чтобы шла обработка кнопки с гашением дребезга контактов и прочими расчётами, поэтому конструкция следующего вида **работать не будет**: 861 | ```cpp 862 | void setup() { 863 | btn.tick(); 864 | if (btn.press()) Serial.println("Кнопка нажата при старте"); 865 | } 866 | ``` 867 | 868 | Для таких сценариев помогут следующие функции, возвращают `true` если кнопка нажата: 869 | - `read()` для библиотек Button и ButtonT 870 | - `readBtn()` для библиотек EncButton и EncButtonT 871 | 872 | > Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно: 873 | 874 | ```cpp 875 | void setup() { 876 | // btn.setBtnLevel(LOW); // можно настроить уровень 877 | 878 | if (btn.read()) Serial.println("Кнопка нажата при старте"); 879 | } 880 | ``` 881 | 882 | 883 | 884 | ### Погружение в цикл 885 | Допустим нужно обработать кнопку синхронно и с гашением дребезга. Например если кнопка зажата при старте микроконтроллера - получить её удержание или даже импульсное удержание внутри блока `setup`, то есть до начала выполнения основной программы. Можно воспользоваться состоянием `busy` и опрашивать кнопку из цикла: 886 | ```cpp 887 | void setup() { 888 | Serial.begin(115200); 889 | 890 | btn.tick(); 891 | while (btn.busy()) { 892 | btn.tick(); 893 | if (btn.hold()) Serial.println("hold"); 894 | if (btn.step()) Serial.println("step"); 895 | } 896 | 897 | Serial.println("program start"); 898 | } 899 | ``` 900 | Как это работает: первый тик опрашивает кнопку, если кнопка нажата - сразу же активируется состояние busy и система попадает в цикл `while`. Внутри него продолжаем тикать и получать события с кнопки. Когда кнопка будет отпущена и сработают все события - флаг busy опустится и программа автоматически покинет цикл. Можно переписать эту конструкцию на цикл с постусловием, более красиво: 901 | ```cpp 902 | do { 903 | btn.tick(); 904 | if (btn.hold()) Serial.println("hold"); 905 | if (btn.step()) Serial.println("step"); 906 | } while (btn.busy()); 907 | ``` 908 | 909 | 910 | 911 | ### Timeout 912 | В связанных с кнопкой классах (Button, EncButton) есть функция `timeout()` - она однократно вернёт `true`, если после окончания действий с кнопкой/энкодером прошло указанное в `setTimeout` время. Это можно использовать для сохранения параметров после ввода, например: 913 | ```cpp 914 | void setup() { 915 | //eb.setTimeout(1500); // умолч. 1000 916 | } 917 | void loop() { 918 | eb.tick(); 919 | 920 | // ... 921 | 922 | if (eb.timeout()) { 923 | // после взаимодействия с энкодером прошло 1000 мс 924 | // EEPROM.put(0, settings); 925 | } 926 | } 927 | ``` 928 | 929 | В текущей версии обработка таймаута реализована не очень красиво ради экономии места: системный флаг таймаута активен всё время (`action` будет возвращать событие таймаута), сбрасывается в следующих случаях: 930 | 931 | - Вызов `timeout()` - данный метод однократно вернёт `true`, последующие вызовы будут возвращать `false` до нового действия 932 | - Автоматически сбросится после вызова обработчика, если он подключен 933 | - При вызове `clear(true)` - с флагом очистки таймаута 934 | - При вызове `reset()` 935 | 936 | Если нужно пробросить опрос таймаута через несколько вызовов - можно использовать `timeoutState()`, но последний вызов должен быть `timeout()`. 937 | 938 | 939 | 940 | ### Busy 941 | Функция `busy()` возвращает `true`, пока идёт обработка кнопки, т.е. пока система ожидает действий и выхода таймаутов. Это можно использовать для оптимизации кода, например избегать каких то долгих и тяжёлых частей программы на время обработки кнопки: 942 | ```cpp 943 | void loop() { 944 | eb.tick(); 945 | 946 | // ... 947 | 948 | if (!eb.busy()) { 949 | // потенциально долгий и тяжёлый код 950 | } 951 | } 952 | ``` 953 | 954 | 955 | 956 | ### Получение события 957 | Доступно во всех классах **с кнопкой**: 958 | - `VirtButton` 959 | - `Button` 960 | - `VirtEncButton` 961 | - `EncButton` 962 | 963 | Функция `action()` при наступлении события возвращает код события (отличный от нуля, что само по себе является индикацией наличия события): 964 | - `EB_PRESS` - нажатие на кнопку 965 | - `EB_HOLD` - кнопка удержана 966 | - `EB_STEP` - импульсное удержание 967 | - `EB_RELEASE` - кнопка отпущена 968 | - `EB_CLICK` - одиночный клик 969 | - `EB_CLICKS` - сигнал о нескольких кликах 970 | - `EB_TURN` - поворот энкодера 971 | - `EB_REL_HOLD` - кнопка отпущена после удержания 972 | - `EB_REL_HOLD_C` - кнопка отпущена после удержания с предв. кликами 973 | - `EB_REL_STEP` - кнопка отпущена после степа 974 | - `EB_REL_STEP_C` - кнопка отпущена после степа с предв. кликами 975 | - `EB_TIMEOUT` - прошёл таймаут после нажатия кнопки или поворота энкодера 976 | 977 | Полученный код события можно обработать через `switch`: 978 | ```cpp 979 | switch (eb.action()) { 980 | case EB_PRESS: 981 | // ... 982 | break; 983 | case EB_HOLD: 984 | // ... 985 | break; 986 | // ... 987 | } 988 | ``` 989 | 990 | Есть аналогичная функция `getAction()`, вернёт то же самое, но с более читаемыми константами (удобно с автодополнением): 991 | 992 | - `EBAction::Press` 993 | - `EBAction::Hold` 994 | - `EBAction::Step` 995 | - `EBAction::Release` 996 | - `EBAction::Click` 997 | - `EBAction::Clicks` 998 | - `EBAction::Turn` 999 | - `EBAction::ReleaseHold` 1000 | - `EBAction::ReleaseHoldClicks` 1001 | - `EBAction::ReleaseStep` 1002 | - `EBAction::ReleaseStepClicks` 1003 | - `EBAction::Timeout` 1004 | 1005 | Полученный код события можно обработать через `switch`: 1006 | ```cpp 1007 | switch (eb.getAction()) { 1008 | case EBAction::Press: 1009 | // ... 1010 | break; 1011 | case EBAction::Hold: 1012 | // ... 1013 | break; 1014 | // ... 1015 | } 1016 | ``` 1017 | 1018 | > Результат функций `action()`/`getAction()` сбрасывается после следующего вызова `tick()`, то есть доступен на всей текущей итерации основного цикла 1019 | 1020 | 1021 | 1022 | ### Оптимизация 1023 | #### Вес библиотеки 1024 | Для максимального уменьшения веса библиотеки (в частности в оперативной памяти) нужно задавать тайматуы константами через define (экономия 1 байт за таймаут), отключить обработчик событий, счётчики-буферы и использовать T-класс (экономия 1 байт за пин): 1025 | ```cpp 1026 | #define EB_NO_FOR 1027 | #define EB_NO_CALLBACK 1028 | #define EB_NO_COUNTER 1029 | #define EB_NO_BUFFER 1030 | #define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка) 1031 | #define EB_CLICK_TIME 500 // таймаут ожидания кликов (кнопка) 1032 | #define EB_HOLD_TIME 600 // таймаут удержания (кнопка) 1033 | #define EB_STEP_TIME 200 // таймаут импульсного удержания (кнопка) 1034 | #define EB_FAST_TIME 30 // таймаут быстрого поворота (энкодер) 1035 | #define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер) 1036 | #include 1037 | EncButtonT<2, 3, 4> eb; 1038 | ``` 1039 | В таком случае энкодер с кнопкой займёт в SRAM всего 8 байт, а просто кнопка - 5. 1040 | 1041 | #### Скорость выполнения 1042 | Чтобы сократить время на проверку системных флагов событий (незначительно, но приятно) можно поместить все опросы в условие по `tick()`, так как `tick()` возвращает `true` только при наступлении **события**: 1043 | ```cpp 1044 | void loop() { 1045 | if (eb.tick()) { 1046 | if (eb.turn()) ...; 1047 | if (eb.click()) ...; 1048 | } 1049 | } 1050 | ``` 1051 | 1052 | Также опрос событий при помощи функции `action()` выполняется быстрее, чем ручной опрос отдельных функций событий, поэтому максимально эффективно библиотека будет работать вот в таком формате: 1053 | ```cpp 1054 | void loop() { 1055 | if (eb.tick()) { 1056 | switch (eb.action()) { 1057 | case EB_PRESS: 1058 | // ... 1059 | break; 1060 | case EB_HOLD: 1061 | // ... 1062 | break; 1063 | // ... 1064 | } 1065 | } 1066 | } 1067 | ``` 1068 | 1069 | Для опроса **состояний** кнопки `pressing()`, `holding()`, `waiting()` можно поместить их вовнутрь условия по `busy()`, чтобы не опрашивать состояния пока их гарантированно нет: 1070 | ```cpp 1071 | if (btn.busy()) { 1072 | if (btn.pressing())... 1073 | if (btn.holding())... 1074 | if (btn.waiting())... 1075 | } 1076 | ``` 1077 | 1078 | 1079 | 1080 | ### Коллбэки 1081 | Можно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах **с кнопкой**: 1082 | - `VirtButton` 1083 | - `Button` 1084 | - `VirtEncButton` 1085 | - `EncButton` 1086 | 1087 | > Внутри коллбэка можно получить указатель на текущий объект (который вызвал коллбэк) из переменной `void* EB_self` 1088 | 1089 | ```cpp 1090 | EncButton eb(2, 3, 4); 1091 | 1092 | void callback() { 1093 | switch (eb.action()) { 1094 | case EB_PRESS: 1095 | // ... 1096 | break; 1097 | case EB_HOLD: 1098 | // ... 1099 | break; 1100 | // ... 1101 | } 1102 | 1103 | // здесь EB_self указатель на eb 1104 | } 1105 | 1106 | void setup() { 1107 | eb.attach(callback); 1108 | } 1109 | 1110 | void loop() { 1111 | eb.tick(); 1112 | } 1113 | ``` 1114 | 1115 | 1116 | 1117 | ### Одновременное нажатие 1118 | Библиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно: 1119 | 1. Cоздать специальную кнопку `MultiButton` 1120 | 2. Передать виртуальной кнопке в обработку свои кнопки (это могут быть объекты классов `VirtButton`, `Button`, `EncButton` + их `T`-версии). **Мульти-кнопка сама опросит обе кнопки!** 1121 | 3. Опрашивать события или слушать обработчик 1122 | 1123 | ```cpp 1124 | Button b0(4); 1125 | Button b1(5); 1126 | MultiButton b2; // 1 1127 | 1128 | void loop() { 1129 | b2.tick(b0, b1); // 2 1130 | 1131 | // 3 1132 | if (b0.click()) Serial.println("b0 click"); 1133 | if (b1.click()) Serial.println("b1 click"); 1134 | if (b2.click()) Serial.println("b0+b1 click"); 1135 | } 1136 | ``` 1137 | 1138 | Библиотека сама "сбросит" лишние события с реальных кнопок, если они были нажаты вместе, за исключением события `press`. Таким образом получается полноценная третья кнопка из двух других с удобным опросом. 1139 | 1140 | 1141 | 1142 | ### Прерывания 1143 | #### Энкодер 1144 | Для обработки энкодера в загруженной программе нужно: 1145 | - Подключить оба его пина на аппаратные прерывания по `CHANGE` 1146 | - Установить `setEncISR(true)` 1147 | - Вызывать в обработчике специальный тикер для прерывания 1148 | - Основной тикер также нужно вызывать в `loop` для корреткной работы - события генерируются в основном тикере: 1149 | ```cpp 1150 | // пример для ATmega328 и EncButton 1151 | EncButton eb(2, 3, 4); 1152 | 1153 | /* 1154 | // esp8266/esp32 1155 | IRAM_ATTR void isr() { 1156 | eb.tickISR(); 1157 | } 1158 | */ 1159 | 1160 | void isr() { 1161 | eb.tickISR(); 1162 | } 1163 | void setup() { 1164 | attachInterrupt(0, isr, CHANGE); 1165 | attachInterrupt(1, isr, CHANGE); 1166 | eb.setEncISR(true); 1167 | } 1168 | void loop() { 1169 | eb.tick(); 1170 | } 1171 | ``` 1172 | 1173 | Примечание: использование работы в прерывании позволяет корректно обрабатывать позицию энкодера и не пропустить новый поворот. Событие с поворотом, полученное из прерывания, станет доступно *после* вызова `tick` в основном цикле программы, что позволяет не нарушать последовательность работы основного цикла: 1174 | - Буферизация отключена: событие `turn` активируется только один раз, независимо от количества щелчков энкодера, совершённых между двумя вызовами `tick` (щелчки обработаны в прерывании) 1175 | - Буферизация включена: событие `turn` будет вызвано столько раз, сколько реально было щелчков энкодера, это позволяет вообще не пропускать повороты и не нагружать систему в прерывании. **Размер буфера - 5 необработанных щелчков энкодера** 1176 | 1177 | Примечания: 1178 | - Функция `setEncISR` работает только в не виртуальных классах. Если он включен - основной тикер `tick` просто не опрашивает пины энкодера, что экономит процессорное время. Обработка происходит только в прерывании 1179 | - Счётчик энкодера всегда имеет актуальное значение и может опережать буферизированные повороты в программе с большими задержками в основном цикле! 1180 | - На разных платформах прерывания могут работать по разному (например на ESPxx - нужно добавить функции аттрибут `IRAM_ATTR`, см. документацию на свою платформу!) 1181 | - Обработчик, подключенный в `attach()`, будет вызван из `tick()`, то есть *не из прерывания*! 1182 | 1183 | #### Виртуальные классы 1184 | В виртуальных есть тикер, в который не нужно передавать состояние энкодера, если он обрабатывается в прерывании, это позволяет не опрашивать пины в холостую. Например: 1185 | 1186 | ```cpp 1187 | VirtEncoder e; 1188 | 1189 | void isr() { 1190 | e.tickISR(digitalRead(2), digitalRead(3)); 1191 | } 1192 | void setup() { 1193 | attachInterrupt(0, isr, CHANGE); 1194 | attachInterrupt(1, isr, CHANGE); 1195 | 1196 | e.setEncISR(1); 1197 | } 1198 | void loop() { 1199 | e.tick(); // не передаём состояния пинов 1200 | } 1201 | ``` 1202 | 1203 | #### Кнопка 1204 | Для обработки кнопки в прерывании нужно: 1205 | - Подключить прерывание на **нажатие** кнопки с учётом её физического подключения и уровня: 1206 | - Если кнопка замыкает `LOW` - прерывание `FALLING` 1207 | - Если кнопка замыкает `HIGH` - прерывание `RISING` 1208 | - Вызывать `pressISR()` в обработчике прерывания 1209 | 1210 | ```cpp 1211 | Button b(2); 1212 | 1213 | /* 1214 | // esp8266/esp32 1215 | IRAM_ATTR void isr() { 1216 | b.pressISR(); 1217 | } 1218 | */ 1219 | 1220 | void isr() { 1221 | b.pressISR(); 1222 | } 1223 | void setup() { 1224 | attachInterrupt(0, isr, FALLING); 1225 | } 1226 | void loop() { 1227 | b.tick(); 1228 | } 1229 | ``` 1230 | 1231 | Примечание: кнопка обрабатывается в основном `tick()`, а функция `pressISR()` всего лишь сообщает библиотеке, что кнопка была нажата вне `tick()`. Это позволяет не пропустить нажатие кнопки, пока программа была занята чем-то другим. 1232 | 1233 | #### Энкодер с кнопкой 1234 | Нужно подключить все три пина на прерывания и действовать как выше - и для кнопки, и для энкодера: 1235 | 1236 | ```cpp 1237 | void eisr() { 1238 | eb.tickISR(); 1239 | } 1240 | void bisr() { 1241 | eb.pressISR(); 1242 | } 1243 | 1244 | void setup() { 1245 | // номера прерываний "для примера" 1246 | attachInterrupt(0, eisr, CHANGE); 1247 | attachInterrupt(1, eisr, CHANGE); 1248 | eb.setEncISR(true); 1249 | 1250 | attachInterrupt(2, bisr, FALLING); 1251 | } 1252 | 1253 | void loop() { 1254 | eb.tick(); 1255 | } 1256 | ``` 1257 | 1258 | 1259 | 1260 | ### Массив кнопок/энкодеров 1261 | Создать массив можно только из нешаблонных классов (без буквы `T`), потому что номера пинов придётся указать уже в рантайме далее в программе. Например: 1262 | ```cpp 1263 | Button btns[5]; 1264 | EncButton ebs[3]; 1265 | 1266 | void setup() { 1267 | btns[0].init(2); // указать пин 1268 | btns[1].init(5); 1269 | btns[2].init(10); 1270 | // ... 1271 | 1272 | ebs[0].init(11, 12, 13, INPUT); 1273 | ebs[1].init(14, 15, 16); 1274 | // ... 1275 | } 1276 | void loop() { 1277 | for (int i = 0; i < 5; i++) btns[i].tick(); 1278 | for (int i = 0; i < 3; i++) ebs[i].tick(); 1279 | 1280 | if (btns[2].click()) Serial.println("btn2 click"); 1281 | // ... 1282 | } 1283 | ``` 1284 | 1285 | 1286 | 1287 | ### Кастомные функции 1288 | Библиотека поддерживает задание своих функций для чтения пина и получения времени без редактирования файлов библиотеки. Для этого нужно реализовать соответствующую функцию в своём .cpp или .ino файле: 1289 | - `bool EB_read(uint8_t pin)` - для своей функции чтения пина 1290 | - `void EB_mode(uint8_t pin, uint8_t mode)` - для своего аналога pinMode 1291 | - `uint32_t EB_uptime()` - для своего аналога millis() 1292 | 1293 | Пример: 1294 | 1295 | ```cpp 1296 | #include 1297 | 1298 | bool EB_read(uint8_t pin) { 1299 | return digitalRead(pin); 1300 | } 1301 | 1302 | void EB_mode(uint8_t pin, uint8_t mode) { 1303 | pinMode(pin, mode); 1304 | } 1305 | 1306 | uint32_t EB_uptime() { 1307 | return millis(); 1308 | } 1309 | ``` 1310 | 1311 | 1312 | 1313 | ### Опрос по таймеру 1314 | Иногда может понадобиться вызывать `tick()` не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера! 1315 | ```cpp 1316 | void loop() { 1317 | // таймер на 50 мс 1318 | static uint32_t tmr; 1319 | if (millis() - tmr >= 50) { 1320 | tmr = millis(); 1321 | btn.tick(readSomePin()); 1322 | } 1323 | 1324 | // будет активно в течение 50 мс!!! 1325 | if (btn.click()) foo(); 1326 | } 1327 | ``` 1328 | 1329 | В данной ситуации нужно поступить так: тикать по таймеру, там же обрабатывать события и сбрасывать флаги в конце: 1330 | ```cpp 1331 | void loop() { 1332 | // таймер на 50 мс 1333 | static uint32_t tmr; 1334 | if (millis() - tmr >= 50) { 1335 | tmr = millis(); 1336 | // тик 1337 | btn.tick(readSomePin()); 1338 | 1339 | // разбор событий 1340 | if (btn.click()) foo(); 1341 | 1342 | // сброс флагов 1343 | btn.clear(); 1344 | } 1345 | } 1346 | ``` 1347 | 1348 | Либо можно подключить обработчик и вызывать `clear()` в конце функции: 1349 | ```cpp 1350 | void callback() { 1351 | switch (btn.action()) { 1352 | // ... 1353 | } 1354 | 1355 | // сброс флагов 1356 | btn.clear(); 1357 | } 1358 | 1359 | void loop() { 1360 | // таймер на 50 мс 1361 | static uint32_t tmr; 1362 | if (millis() - tmr >= 50) { 1363 | tmr = millis(); 1364 | btn.tick(readSomePin()); 1365 | } 1366 | } 1367 | ``` 1368 | 1369 | В случае с вызовом по таймеру антидребезг будет частично обеспечиваться самим таймером и в библиотеке его можно отключить (поставить период 0). 1370 | 1371 | Для корректной работы таймаутов, состояний и счётчика кликов нужен другой подход: буферизировать прочитанные по таймеру состояния и передавать их в тик в основном цикле. Например так: 1372 | ```cpp 1373 | bool readbuf = 0; // буфер пина 1374 | 1375 | void loop() { 1376 | // таймер на 50 мс 1377 | static uint32_t tmr; 1378 | if (millis() - tmr >= 50) { 1379 | tmr = millis(); 1380 | readbuf = readSomePin(); // чтение в буфер 1381 | } 1382 | 1383 | // тик из буфера 1384 | btn.tick(readbuf); 1385 | 1386 | if (btn.click()) foo(); 1387 | } 1388 | ``` 1389 | 1390 | ### Пропуск событий 1391 | EncButton позволяет кнопке работать в паре с энкодером для корректного отслеживания *нажатых поворотов* - при нажатом повороте события с кнопки будут пропущены, т.е. не обработается удержание и клик. Допустим кнопок несколько: они могут выполнять действия как сами по себе, так и в паре с энкодером (кнопка зажата и крутится энкодер, в программе меняется выбранное кнопкой значение). Чтобы при удержании кнопка не генерировала события (удержание, степ, клики...) можно включить пропуск событий. Он будет действовать **до отпускания кнопки**: 1392 | 1393 | ```cpp 1394 | if (btn.pressing() && enc.turn()) { 1395 | btn.skipEvents(); // зафиксирован поворот. Пропускаем события 1396 | // нажатый поворот 1397 | } 1398 | 1399 | if (btn.click()) { 1400 | // просто клик 1401 | } 1402 | ``` 1403 | 1404 | 1405 | 1406 | ### Мини примеры, сценарии 1407 | ```cpp 1408 | // меняем значения переменных 1409 | 1410 | // поворот энкодера 1411 | if (enc.turn()) { 1412 | // меняем с шагом 5 1413 | var += 5 * enc.dir(); 1414 | 1415 | // меняем с шагом 1 при обычном повороте, 10 при быстром 1416 | var += enc.fast() ? 10 : 1; 1417 | 1418 | // меняем с шагом 1 при обычном повороте, 10 при нажатом 1419 | var += enc.pressing() ? 10 : 1; 1420 | 1421 | // меняем одну переменную при повороте, другую - при нажатом повороте 1422 | if (enc.pressing()) var0++; 1423 | else var1++; 1424 | 1425 | // если кнопка нажата - доступны предварительные клики 1426 | // Выбираем переменную для изменения по предв. кликам 1427 | if (enc.pressing()) { 1428 | switch (enc.getClicks()) { 1429 | case 1: var0 += enc.dir(); 1430 | break; 1431 | case 2: var1 += enc.dir(); 1432 | break; 1433 | case 3: var2 += enc.dir(); 1434 | break; 1435 | } 1436 | } 1437 | } 1438 | 1439 | // импульсное удержание на каждом шаге инкрементирует переменную 1440 | if (btn.step()) var++; 1441 | 1442 | // смена направления изменения переменной после отпускания из step 1443 | if (btn.step()) var += dir; 1444 | if (btn.releaseStep()) dir = -dir; 1445 | 1446 | // изменение выбранной переменной при помощи step 1447 | if (btn.step(1)) var1++; // клик-удержание 1448 | if (btn.step(2)) var2++; // клик-клик-удержание 1449 | if (btn.step(3)) var3++; // клик-клик-клик-удержание 1450 | 1451 | // если держать step больше 2 секунд - инкремент +5, пока меньше - +1 1452 | if (btn.step()) { 1453 | if (btn.stepFor(2000)) var += 5; 1454 | else var += 1; 1455 | } 1456 | 1457 | // включение режима по количеству кликов 1458 | if (btn.hasClicks()) mode = btn.getClicks(); 1459 | 1460 | // включение режима по нескольким кликам и удержанию 1461 | if (btn.hold(1)) mode = 1; // клик-удержание 1462 | if (btn.hold(2)) mode = 2; // клик-клик-удержание 1463 | if (btn.hold(3)) mode = 3; // клик-клик-клик-удержание 1464 | 1465 | // или так 1466 | if (btn.hold()) mode = btn.getClicks(); 1467 | 1468 | // кнопка отпущена, смотрим сколько её удерживали 1469 | if (btn.release()) { 1470 | // от 1 до 2 секунд 1471 | if (btn.pressFor() > 1000 && btn.pressFor() <= 2000) mode = 1; 1472 | // от 2 до 3 секунд 1473 | else if (btn.pressFor() > 2000 && btn.pressFor() <= 3000) mode = 2; 1474 | } 1475 | ``` 1476 | 1477 | 1478 | 1479 | ## Гайд по миграции с v2 на v3 1480 | ### Инициализация 1481 | ```cpp 1482 | // ВИРТУАЛЬНЫЕ 1483 | VirtEncButton eb; // энкодер с кнопкой 1484 | VirtButton b; // кнопка 1485 | VirtEncoder e; // энкодер 1486 | 1487 | // РЕАЛЬНЫЕ 1488 | // энкодер с кнопкой 1489 | EncButton eb(enc0, enc1, btn); // пины энкодера и кнопки 1490 | EncButton eb(enc0, enc1, btn, modeEnc); // + режим пинов энкодера (умолч. INPUT) 1491 | EncButton eb(enc0, enc1, btn, modeEnc, modeBtn); // + режим пина кнопки (умолч. INPUT_PULLUP) 1492 | EncButton eb(enc0, enc1, btn, modeEnc, modeBtn, btnLevel); // + уровень кнопки (умолч. LOW) 1493 | // шаблонный 1494 | EncButton eb; // пины энкодера и кнопки 1495 | EncButton eb(modeEnc); // + режим пинов энкодера (умолч. INPUT) 1496 | EncButton eb(modeEnc, modeBtn); // + режим пина кнопки (умолч. INPUT_PULLUP) 1497 | EncButton eb(modeEnc, modeBtn, btnLevel); // + уровень кнопки (умолч. LOW) 1498 | 1499 | // кнопка 1500 | Button b(pin); // пин 1501 | Button b(pin, mode); // + режим пина кнопки (умолч. INPUT_PULLUP) 1502 | Button b(pin, mode, btnLevel); // + уровень кнопки (умолч. LOW) 1503 | // шаблонный 1504 | ButtonT b; // пин 1505 | ButtonT b(mode); // + режим пина кнопки (умолч. INPUT_PULLUP) 1506 | ButtonT b(mode, btnLevel); // + уровень кнопки (умолч. LOW) 1507 | 1508 | // энкодер 1509 | Encoder e(enc0, enc1); // пины энкодера 1510 | Encoder e(enc0, enc1, mode); // + режим пинов энкодера (умолч. INPUT) 1511 | // шаблонный 1512 | EncoderT e; // пины энкодера 1513 | EncoderT e(mode); // + режим пинов энкодера (умолч. INPUT) 1514 | ``` 1515 | 1516 | ### Функции 1517 | | v2 | v3 | 1518 | | ----------- | ------------ | 1519 | | `held()` | `hold()` | 1520 | | `hold()` | `holding()` | 1521 | | `state()` | `pressing()` | 1522 | | `setPins()` | `init()` | 1523 | 1524 | - Изменился порядок указания пинов (см. доку выше) 1525 | - `clearFlags()` заменена на `clear()` (сбросить флаги событий) и `reset()` (сбросить системные флаги обработки, закончить обработку) 1526 | 1527 | ### Логика работы 1528 | В v3 функции опроса событий (click, turn...) не сбрасываются сразу после своего вызова - они сбрасываются при следующем вызове `tick()`, таким образом сохраняют своё значение во всех последующих вызовах на текущей итерации главного цикла программы. **Поэтому `tick()` нужно вызывать только 1 раз за цикл, иначе будут пропуски действий!** Читай об этом выше. 1529 | 1530 | 1531 | ## Примеры 1532 | Остальные примеры смотри в **examples**! 1533 |
1534 | Полное демо EncButton 1535 | 1536 | ```cpp 1537 | // #define EB_NO_FOR // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки) 1538 | // #define EB_NO_CALLBACK // отключить обработчик событий attach (экономит 2 байта оперативки) 1539 | // #define EB_NO_COUNTER // отключить счётчик энкодера (экономит 4 байта оперативки) 1540 | // #define EB_NO_BUFFER // отключить буферизацию энкодера (экономит 1 байт оперативки) 1541 | 1542 | // #define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка) 1543 | // #define EB_CLICK_TIME 500 // таймаут ожидания кликов (кнопка) 1544 | // #define EB_HOLD_TIME 600 // таймаут удержания (кнопка) 1545 | // #define EB_STEP_TIME 200 // таймаут импульсного удержания (кнопка) 1546 | // #define EB_FAST_TIME 30 // таймаут быстрого поворота (энкодер) 1547 | // #define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер) 1548 | 1549 | #include 1550 | EncButton eb(2, 3, 4); 1551 | //EncButton eb(2, 3, 4, INPUT); // + режим пинов энкодера 1552 | //EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP); // + режим пинов кнопки 1553 | //EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP, LOW); // + уровень кнопки 1554 | 1555 | void setup() { 1556 | Serial.begin(115200); 1557 | 1558 | // показаны значения по умолчанию 1559 | eb.setBtnLevel(LOW); 1560 | eb.setClickTimeout(500); 1561 | eb.setDebTimeout(50); 1562 | eb.setHoldTimeout(600); 1563 | eb.setStepTimeout(200); 1564 | 1565 | eb.setEncReverse(0); 1566 | eb.setEncType(EB_STEP4_LOW); 1567 | eb.setFastTimeout(30); 1568 | 1569 | // сбросить счётчик энкодера 1570 | eb.counter = 0; 1571 | } 1572 | 1573 | void loop() { 1574 | eb.tick(); 1575 | 1576 | // обработка поворота общая 1577 | if (eb.turn()) { 1578 | Serial.print("turn: dir "); 1579 | Serial.print(eb.dir()); 1580 | Serial.print(", fast "); 1581 | Serial.print(eb.fast()); 1582 | Serial.print(", hold "); 1583 | Serial.print(eb.pressing()); 1584 | Serial.print(", counter "); 1585 | Serial.print(eb.counter); 1586 | Serial.print(", clicks "); 1587 | Serial.println(eb.getClicks()); 1588 | } 1589 | 1590 | // обработка поворота раздельная 1591 | if (eb.left()) Serial.println("left"); 1592 | if (eb.right()) Serial.println("right"); 1593 | if (eb.leftH()) Serial.println("leftH"); 1594 | if (eb.rightH()) Serial.println("rightH"); 1595 | 1596 | // кнопка 1597 | if (eb.press()) Serial.println("press"); 1598 | if (eb.click()) Serial.println("click"); 1599 | 1600 | if (eb.release()) { 1601 | Serial.println("release"); 1602 | 1603 | Serial.print("clicks: "); 1604 | Serial.print(eb.getClicks()); 1605 | Serial.print(", steps: "); 1606 | Serial.print(eb.getSteps()); 1607 | Serial.print(", press for: "); 1608 | Serial.print(eb.pressFor()); 1609 | Serial.print(", hold for: "); 1610 | Serial.print(eb.holdFor()); 1611 | Serial.print(", step for: "); 1612 | Serial.println(eb.stepFor()); 1613 | } 1614 | 1615 | // состояния 1616 | // Serial.println(eb.pressing()); 1617 | // Serial.println(eb.holding()); 1618 | // Serial.println(eb.busy()); 1619 | // Serial.println(eb.waiting()); 1620 | 1621 | // таймаут 1622 | if (eb.timeout()) Serial.println("timeout!"); 1623 | 1624 | // удержание 1625 | if (eb.hold()) Serial.println("hold"); 1626 | if (eb.hold(3)) Serial.println("hold 3"); 1627 | 1628 | // импульсное удержание 1629 | if (eb.step()) Serial.println("step"); 1630 | if (eb.step(3)) Serial.println("step 3"); 1631 | 1632 | // отпущена после импульсного удержания 1633 | if (eb.releaseStep()) Serial.println("release step"); 1634 | if (eb.releaseStep(3)) Serial.println("release step 3"); 1635 | 1636 | // отпущена после удержания 1637 | if (eb.releaseHold()) Serial.println("release hold"); 1638 | if (eb.releaseHold(2)) Serial.println("release hold 2"); 1639 | 1640 | // проверка на количество кликов 1641 | if (eb.hasClicks(3)) Serial.println("has 3 clicks"); 1642 | 1643 | // вывести количество кликов 1644 | if (eb.hasClicks()) { 1645 | Serial.print("has clicks: "); 1646 | Serial.println(eb.getClicks()); 1647 | } 1648 | } 1649 | ``` 1650 |
1651 |
1652 | Подключение обработчика 1653 | 1654 | ```cpp 1655 | #include 1656 | EncButton eb(2, 3, 4); 1657 | 1658 | void callback() { 1659 | Serial.print("callback: "); 1660 | switch (eb.action()) { 1661 | case EB_PRESS: 1662 | Serial.println("press"); 1663 | break; 1664 | case EB_HOLD: 1665 | Serial.println("hold"); 1666 | break; 1667 | case EB_STEP: 1668 | Serial.println("step"); 1669 | break; 1670 | case EB_RELEASE: 1671 | Serial.println("release"); 1672 | break; 1673 | case EB_CLICK: 1674 | Serial.println("click"); 1675 | break; 1676 | case EB_CLICKS: 1677 | Serial.print("clicks "); 1678 | Serial.println(eb.getClicks()); 1679 | break; 1680 | case EB_TURN: 1681 | Serial.print("turn "); 1682 | Serial.print(eb.dir()); 1683 | Serial.print(" "); 1684 | Serial.print(eb.fast()); 1685 | Serial.print(" "); 1686 | Serial.println(eb.pressing()); 1687 | break; 1688 | case EB_REL_HOLD: 1689 | Serial.println("release hold"); 1690 | break; 1691 | case EB_REL_HOLD_C: 1692 | Serial.print("release hold clicks "); 1693 | Serial.println(eb.getClicks()); 1694 | break; 1695 | case EB_REL_STEP: 1696 | Serial.println("release step"); 1697 | break; 1698 | case EB_REL_STEP_C: 1699 | Serial.print("release step clicks "); 1700 | Serial.println(eb.getClicks()); 1701 | break; 1702 | } 1703 | } 1704 | 1705 | void setup() { 1706 | Serial.begin(115200); 1707 | eb.attach(callback); 1708 | } 1709 | 1710 | void loop() { 1711 | eb.tick(); 1712 | } 1713 | ``` 1714 |
1715 |
1716 | Все типы кнопок 1717 | 1718 | ```cpp 1719 | #include 1720 | 1721 | Button btn(4); 1722 | ButtonT<5> btnt; 1723 | VirtButton btnv; 1724 | 1725 | void setup() { 1726 | Serial.begin(115200); 1727 | } 1728 | 1729 | void loop() { 1730 | // Button 1731 | btn.tick(); 1732 | if (btn.click()) Serial.println("btn click"); 1733 | 1734 | // ButtonT 1735 | btnt.tick(); 1736 | if (btnt.click()) Serial.println("btnt click"); 1737 | 1738 | // VirtButton 1739 | btnv.tick(!digitalRead(4)); // передать логическое значение 1740 | if (btnv.click()) Serial.println("btnv click"); 1741 | } 1742 | ``` 1743 |
1744 |
1745 | Все типы энкодеров 1746 | 1747 | ```cpp 1748 | #include 1749 | 1750 | Encoder enc(2, 3); 1751 | EncoderT<5, 6> enct; 1752 | VirtEncoder encv; 1753 | 1754 | void setup() { 1755 | Serial.begin(115200); 1756 | } 1757 | 1758 | void loop() { 1759 | // опрос одинаковый для всех, 3 способа: 1760 | 1761 | // 1 1762 | // tick вернёт 1 или -1, значит это шаг 1763 | if (enc.tick()) Serial.println(enc.counter); 1764 | 1765 | // 2 1766 | // можно опросить через turn() 1767 | enct.tick(); 1768 | if (enct.turn()) Serial.println(enct.dir()); 1769 | 1770 | // 3 1771 | // можно не использовать опросные функции, а получить направление напрямую 1772 | int8_t v = encv.tick(digitalRead(2), digitalRead(3)); 1773 | if (v) Serial.println(v); // выведет 1 или -1 1774 | } 1775 | ``` 1776 |
1777 | 1778 | 1779 | ## Версии 1780 |
1781 | Старые 1782 | 1783 | - v1.1 - пуллап отдельныи методом 1784 | - v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч) 1785 | - v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения 1786 | - v1.4 - обработка нажатия и отпускания кнопки 1787 | - v1.5 - добавлен виртуальный режим 1788 | - v1.6 - оптимизация работы в прерывании 1789 | - v1.6.1 - подтяжка по умолчанию INPUT_PULLUP 1790 | - v1.7 - большая оптимизация памяти, переделан FastIO 1791 | - v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех) 1792 | - v1.8.1 - убран FastIO 1793 | - v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления 1794 | - v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги 1795 | - v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки 1796 | - v1.11.1 - совместимость Digispark 1797 | - v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC 1798 | - v1.13 - добавлен экспериментальный EncButton2 1799 | - v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс 1800 | - v1.15 - добавлен setPins() для EncButton2 1801 | - v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров 1802 | - v1.17 - добавлен step с предварительными кликами 1803 | - v1.18 - не считаем клики после активации step. hold() и held() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс 1804 | - v1.18.1 - исправлена ошибка в releaseStep() (не возвращала результат) 1805 | - v1.18.2 - fix compiler warnings 1806 | - v1.19 - оптимизация скорости, уменьшен вес в sram 1807 | - v1.19.1 - ещё чутка увеличена производительность 1808 | - v1.19.2 - ещё немного увеличена производительность, спасибо XRay3D 1809 | - v1.19.3 - сделал высокий уровень кнопки по умолчанию в виртуальном режиме 1810 | - v1.19.4 - фикс EncButton2 1811 | - v1.20 - исправлена критическая ошибка в EncButton2 1812 | - v1.21 - EB_HALFSTEP_ENC теперь работает для обычного режима 1813 | - v1.22 - улучшен EB_HALFSTEP_ENC для обычного режима 1814 | - v1.23 - getDir() заменил на dir() 1815 | - v2.0 1816 | - Алгоритм EB_BETTER_ENC оптимизирован и установлен по умолчанию, дефайн EB_BETTER_ENC упразднён 1817 | - Добавлен setEncType() для настройки типа энкодера из программы, дефайн EB_HALFSTEP_ENC упразднён 1818 | - Добавлен setEncReverse() для смены направления энкодера из программы 1819 | - Добавлен setStepTimeout() для установки периода импульсного удержания, дефайн EB_STEP упразднён 1820 | - Мелкие улучшения и оптимизация 1821 |
1822 | 1823 | - v3.0 1824 | - Библиотека переписана с нуля, с предыдущими версиями несовместима! 1825 | - Полностью другая инициализация объекта 1826 | - Переименованы: hold()->holding(), held()->hold() 1827 | - Оптимизация Flash памяти: библиотека весит меньше, в некоторых сценариях - на несколько килобайт 1828 | - Оптимизация скорости выполнения кода, в том числе в прерывании 1829 | - На несколько байт меньше оперативной памяти, несколько уровней оптимизации на выбор 1830 | - Более простое, понятное и удобное использование 1831 | - Более читаемый исходный код 1832 | - Разбитие на классы для использования в разных сценариях 1833 | - Новые функции, возможности и обработчики для кнопки и энкодера 1834 | - Буферизация энкодера в прерывании 1835 | - Нативная обработка двух одновременно нажимаемых кнопок как третьей кнопки 1836 | - Поддержка 4-х типов энкодеров 1837 | - Переписана документация 1838 | - EncButton теперь заменяет GyverLibs/VirtualButton (архивирована) 1839 | - v3.1 1840 | - Расширена инициализация кнопки 1841 | - Убраны holdEncButton() и toggleEncButton() 1842 | - Добавлен turnH() 1843 | - Оптимизированы прерывания энкодера, добавлена setEncISR() 1844 | - Буферизация направления и быстрого поворота 1845 | - Сильно оптимизирована скорость работы action() (общий обработчик) 1846 | - Добавлено подключение внешней функции-обработчика событий 1847 | - Добавлена обработка кнопки в прерывании - pressISR() 1848 | - v3.2 1849 | - Добавлены функции tickRaw() и clear() для всех классов. Позволяет проводить раздельную обработку (см. доку) 1850 | - Улучшена обработка кнопки с использованием прерываний 1851 | - v3.3 1852 | - Добавлены функции получения времени удержания pressFor(), holdFor(), stepFor() (отключаемые) 1853 | - Добавлен счётчик степов getSteps() (отключаемый) 1854 | - v3.4 1855 | - Доступ к счётчику кликов во время нажатого поворота 1856 | - Добавлена функция detach() 1857 | - v3.5 1858 | - Добавлена зависимость GyverIO (ускорен опрос пинов) 1859 | - Добавлена возможность задать свои функции аптайма и чтения пина 1860 | - v3.5.2 1861 | - Оптимизация 1862 | - Упрощена замена кастомных функций 1863 | - Исправлена ошибка компиляции при использовании библиотеки в нескольких .cpp файлах 1864 | - v3.5.3 1865 | - Добавлено количество кликов в опрос press/release/click/pressing 1866 | - v3.5.5 - коллбэк на базе std::function для ESP 1867 | - v3.5.8 - добавлен метод releaseHoldStep() 1868 | - v3.5.11 - добавлен метод skipEvents() для игнорирования событий кнопки в сложных сценариях использования 1869 | - v3.6.0 1870 | - Добавлен класс MultiButton для корректного опроса нескольких кнопок с вызовом обработчика 1871 | - Добавлено подключение обработчика с передачей указателя на объект 1872 | 1873 | 1874 | ## Баги и обратная связь 1875 | При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru) 1876 | Библиотека открыта для доработки и ваших **Pull Request**'ов! 1877 | 1878 | При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать: 1879 | - Версия библиотеки 1880 | - Какой используется МК 1881 | - Версия SDK (для ESP) 1882 | - Версия Arduino IDE 1883 | - Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде 1884 | - Какой код загружался, какая работа от него ожидалась и как он работает в реальности 1885 | - В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код 1886 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | This is an automatic translation, may be incorrect in some places. See sources and examples! 2 | 3 | # ENCBUTTON 4 | 5 | |⚠️⚠️⚠️
** The new version of V3 is incompatible with the previous ones, see [documentation] (#docs), [examples] (# Example) and brief [migration guide] (#migrate) from v2 to v3! ** <***
⚠️⚠️⚠️ | 6 | | ----------------------------------------------------------------------------------------------------------------------------------------------------- | 7 | 8 | A light and very functional library for an encoder with a button, encoder or buttons with Arduino 9 | - Button 10 | - processing of events: pressing, releasing, click, click counter, retention, impulse retention, deduction time + preliminary clicks for all modes 11 | - Program suppression of rubbish 12 | - support for processing two simultaneously pressed buttons as a third button 13 | - Encoder 14 | - Processing of events: a normal turn, pressed turn, fast turn 15 | - Support of four types of incidental encoders 16 | - high -precision algorithm for determining the position 17 | - buffer in interruption 18 | - simple and understandable use 19 | - a huge number of opportunities and their combinations for different scenarios for using even one button 20 | - virtual regime (for example, for working with a pain expander) 21 | - optimized to work in interruption 22 | - The fastest reading of pins for AVR, ESP8266, ESP32 (used by gyverio) 23 | - Fast asynchronous algorithms of survey of actions from the button and encoder 24 | - rigid optimization and low weight in Flash and SRAM memory: 5 bytes SRAM (on an instance) and ~ 350 bytes Flash to process the button 25 | 26 | Examples of use scenarios: 27 | - Several clicks - inclusion of the regime (according to the number of clicks) 28 | - Several clicks + short retention - another option for turning on the mode (according to the number of clicks) 29 | - several clicks + holding - a gradual change in the value of the selected variable (on the number of clicks) 30 | - Several clicks choose a variable, encoder changes it 31 | - changing the step of changes in the variable during the rotation of the encoder - for example, a decrease with a closed button and an increase with rapid rotation 32 | - navigation by menu during the rotation of the encoder, a change in the variable during the rotation of a clamped encoder 33 | - full -fledged navigation by the menu when using two buttons (simultaneous retention to go to the next level, simultaneous pressing for return to the previous one) 34 | - And so on 35 | 36 | ## compatibility 37 | Compatible with all arduino platforms (used arduino functions) 38 | 39 | ## Content 40 | - [installation] (# Install) 41 | - [Information] (# Info) 42 | - [documentation] (#docs) 43 | - [compilation settings] (#config) 44 | - [full description of classes] (#class) 45 | - [processing and survey] (#tick) 46 | - [preliminary clicks] (# PRECLICS) 47 | - [direct reading of the button] (#btnread) 48 | - [immersion in the cycle] (#loop) 49 | - [Timeout] (# Timeout) 50 | - [Busy] (# Busy) 51 | - [receipt of an event] 52 | - [Optimization] (# Optimise) 53 | - [Collback] (#callback) 54 | - [Simultaneous pressing] (# Double) 55 | - [interruption] (# ISR) 56 | - [array of buttons/encoders] (# Array) 57 | - [custom functions] (# Custom) 58 | - [Timer survey] (# Timer) 59 | - [Mini examples, scenarios] (# Examples-Mini) 60 | - [migration with v2] (#migrate) 61 | - [Examples] (# Example) 62 | - [versions] (#varsions) 63 | - [bugs and feedback] (#fedback) 64 | 65 | 66 | ## Installation 67 | - For work, a library is required [gyverio] (https://github.com/gyverlibs/gyverio) 68 | -Library can be found by the name ** encbutton ** and installed through the library manager in: 69 | - Arduino ide 70 | - Arduino ide v2 71 | - Platformio 72 | - [download the library] (https://github.com/gyverlibs/encbuton/archive/refs/heads/main.zip) .Zip archive for manual installation: 73 | - unpack and put in * C: \ Program Files (X86) \ Arduino \ Libraries * (Windows X64) 74 | - unpack and put in * C: \ Program Files \ Arduino \ Libraries * (Windows X32) 75 | - unpack and put in *documents/arduino/libraries/ * 76 | - (Arduino id) Automatic installation from. Zip: * sketch/connect the library/add .Zip library ... * and specify downloaded archive 77 | - Read more detailed instructions for installing libraries [here] (https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%BD%D0%BE%BE%BE%BED0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA) 78 | ### Update 79 | - I recommend always updating the library: errors and bugs are corrected in the new versions, as well as optimization and new features are added 80 | - through the IDE library manager: find the library how to install and click "update" 81 | - Manually: ** remove the folder with the old version **, and then put a new one in its place.“Replacement” cannot be done: sometimes in new versions, files that remain when replacing are deleted and can lead to errors! 82 | 83 | 84 | 85 | ## Information 86 | ## encoder 87 | #### type of encoder 88 | The library supports all 4 types of * incidental * encoders, the type can be configured using `setenctype (type)`: 89 | - `eb_step4_Low` - active low signal (tightening to VCC).Full period (4 phases) per click.*Set by default* 90 | - `eb_step4_high` - an active high signal (tightening to GND).Full period (4 phases) for one click 91 | - `eb_step2` - half of the period (2 phases) per click 92 | - `eb_step1` - a quarter of the period (1 phase) per click, as well as encoders without fixation 93 | 94 | ! [Diagram] (/doc/enc_type.png) 95 | 96 | #### Recommendations 97 | To work according to the Encoder with the button, I recommend these ([link] (https://ali.ski/cmpi2), [link] (https://ali.ski/szbtk)) Round Chinese modules with broken anti -ship chains(have the type `eb_step4_low` according to the classification above): 98 | ! [Scheme] (/doc/encali.png) 99 | 100 | You can tie an encoder yourself according to the following scheme (RC filters to the encoder channels + tightening of all pens to VCC): 101 | ! [Scheme] (/doc/enc_scheme.png) 102 | 103 | > Note: by default in the library of Pino Encoder, you are configured by `Input` with the calculation of an external luster.If you have an encoder without lifting, you can use the internal `Input_pullup`, indicating this in the initialization of the encoder (see the documentation below). 104 | 105 | ### Button 106 | ### The level of the button 107 | The button can be connected to the microcontroller in two ways and give a high or low signal when pressed.The library provides for setting up `setbtnlevel (level)`, where the level is an active button signal: 108 | - `High` - the button connects the VCC.Installed by default in `virt`-bibliotexes 109 | - `Low` - the button connects GND.Set by default in the main libraries 110 | 111 | ! [Scheme] (/doc/btn_scheme.png) 112 | 113 | #### Pin 114 | In diagrams with microcontrollers, the connection of the GND button with a PIN suspension to VCC is most often used.The tightening can be external (Pin mode must be put `Input`) or internal (PIN mode` Input_pullup`).In "real" projects, an external lifelong is recommended, becauseIt is less susceptible to interference - the internal has too high resistance. 115 | 116 | 117 | 118 | ## Documentation 119 | 120 | 121 | 122 | ### Defaine settings 123 | Be up to the library 124 | 125 | `` `CPP 126 | 127 | // Disable PressFor/Holdfor/StepFor support and Stepov counter (saves 2 bytes of RAM) 128 | #define eb_no_for 129 | 130 | // Disable the event processor attach (saves 2 bytes of RAM) 131 | #define eb_no_callback 132 | 133 | // Disable the encoder counter [Virtencoder, Encoder, Encbutton] (saves 4 bytes of RAM) 134 | #define eb_no_counter 135 | 136 | // Disconnect the buffer of the encoder (saves 2 bytes of RAM) 137 | #define eb_no_buffer 138 | 139 | /* 140 | Setting up timeouts for all classes 141 | - replaces the timauts constants, changeCranberries from the program (setxxxtimeout ()) will not be 142 | - Setting affects all buttons announced in the program/Encoders 143 | - saves 1 bytes of RAM for an object for each timeout 144 | - shows the default values in MS 145 | - values are not limited to 4000MS, as when installing from the program (SetXXXTimeout ()) 146 | */ 147 | #define eb_deb_time 50 // Timesout to extinguish the trim button (button) 148 | #define eb_click_time 500 // Click Stayout (button) 149 | #define eb_hold_time 600 // Maintenance Times (button) 150 | #define eb_step_time 200 // Impulse retention Timesout (button) 151 | #define EB_FAST_TIME 30 // TIMAUT RAM (ENCODER) 152 | `` ` 153 | 154 | 155 | 156 | ### classes 157 | How to work with the documentation: Encbutton starting with version 3.0 is several libraries (classes) for various use scenarios, they inherit each other to expand functionality.Thus, the library is a “onion”, each layer of which has access to the functions of the lower layers: 158 | - Basic classes: 159 | - `virtbutton` - the base class of the virtual button, provides all the possibilities of the buttons 160 | - `virtencoder` - the base class of the virtual encoder, determines the fact and direction of the rotation of the enkoder 161 | - `virtencbutton` - the base class of a virtual encoder with a button, provides an encoder poll taking into account the button, *inherits Virtbututton and Virtencoder * 162 | - Main classes: 163 | - `Button`,` buttont` - button class, *inherits virtbutton * 164 | - `Encoder`,` Encodert` - Encoder class, *inherits virtencoder * 165 | - `ENCBUTTON`,` ENCBUTTONT` - ENCODER class with a button, *inherits VirtenCbutton, Virtbutton, Virtencoder * 166 | 167 | Thus, to study all the available functions of a particular library, you need to watch not only it, but also what it inherits.For example, to process the button using `Button`, you need to open below the description of` button` and `virtbutton`. 168 | 169 | > * Virtual * - without specifying a PIN of the microcontroller, works directly with the transmitted value, for example, for a survey of the enemies buttons through pain extensors and shift registers. 170 | 171 | > `T'- version of libraries require indicating Pino constants (numbers).Pino numbers will be stored in the memory of the program, this accelerates the work and makes the code easier by 1 byte for each pin. 172 | 173 | > Note: `# Include ` connects all the library tools! 174 | 175 |
176 | Table of Functions of the button 177 | 178 | ||Virtbutton |Virtencbutton |Button |Encbutton | 179 | | ---------------- |: -------------------------------- |: ----------------: |:--------: |: ---------: | 180 | |Read |||✔ || 181 | |Readbtn ||||✔ | 182 | |TickRaW |✔ |✔ |✔ |✔ | 183 | |SetHoldtimeout |✔ |✔ |✔ |✔ | 184 | |Setsteptimeout |✔ |✔ |✔ |✔ | 185 | |SetClicktimeout |✔ |✔ |✔ |✔ | 186 | |Setdebtimeout |✔ |✔ |✔ |✔ | 187 | |Setbtnlevel |✔ |✔ |✔ |✔ | 188 | |Pressisr |✔ |✔ |✔ |✔ | 189 | |Reset |✔ |✔ |✔ |✔ | 190 | |Clear |✔ |✔ |✔ |✔ | 191 | |Attach |✔ |✔ |✔ |✔ | 192 | |Detach |✔ |✔ |✔ |✔ | 193 | |Press |✔ |✔ |✔ |✔ | 194 | |Release |✔ |✔ |✔ |✔ | 195 | |Click |✔ |✔ |✔ |✔ | 196 | |Pressing |✔ |✔ |✔ |✔ | 197 | |Hold |✔ |✔ |✔ |✔ | 198 | |Holding |✔ |✔ |✔ |✔ | 199 | |STEP |✔ |✔ |✔ |✔ | 200 | |Hasclicks |✔ |✔ |✔ |✔ | 201 | |GetClicks |✔ |✔ |✔ |✔ | 202 | |Getsteps |✔ |✔ |✔ |✔ | 203 | |Releasehold |✔ |✔ |✔ |✔ | 204 | |ReleaseStep |✔ |✔ |✔ |✔ | 205 | |Waiting |✔ |✔ |✔ |✔ | 206 | |Busy |✔ |✔ |✔ |✔ | 207 | |Action |✔ |✔ |✔ |✔ | 208 | |Timeout |✔ |✔ |✔ |✔ | 209 | |Pressfor |✔ |✔ |✔ |✔ | 210 | |Holdfor |✔ |✔ |✔ |✔ | 211 | |STEPFOR |✔ |✔ |✔ |✔ | 212 |
213 | 214 |
215 | Encoder functions table 216 | 217 | ||Virtencoder |Encoder |Virtencbutton |Encbutton | 218 | | -------------- |: ----------------------------- |: -------: |: -----------------------------: |: ---------: | 219 | |Readenc ||||✔ | 220 | |Initenc |✔ |✔ |✔ |✔ | 221 | |Setencreverse |✔ |✔ |✔ |✔ | 222 | |Setenctype |✔ |✔ |✔ |✔ | 223 | |Setencisr |✔ |✔ |✔ |✔ | 224 | |Clear |✔ |✔ |✔ |✔ | 225 | |Turn |✔ |✔ |✔ |✔ | 226 | |Dir |✔ |✔ |✔ |✔ | 227 | |TickRaW |✔ |✔ |✔ |✔ | 228 | |Pollenc |✔ |✔ |✔ |✔ | 229 | |Counter |✔ |✔ |✔ |✔ | 230 | |SetFasttimeout |||✔ |✔ | 231 | |Turnh |||✔ |✔ | 232 | |Fast |||✔ |✔ | 233 | |Right |||✔ |✔ | 234 | |Left |||✔ |✔ | 235 | |Righth |||✔ |✔ | 236 | |Lefth |||✔ |✔ | 237 | |Action |||✔ |✔ | 238 | |Timeout |||✔ |✔ | 239 | |Attach |||✔ |✔ | 240 | |Detach |||✔ |✔ | 241 |
242 | 243 |
244 | virtbutton 245 | 246 | `` `CPP 247 | // ==ward 248 | // Set the deduction timeout, silence.600 (max. 4000 ms) 249 | VOID SetHoldtimeout (Uint16_T Tout); 250 | 251 | // Install the timout of impulse retention, silence.200 (max. 4000 ms) 252 | VOID Setsteptimeout (Uint16_T Tout); 253 | 254 | // Install the expectations of clicks, silence.500 (max. 4000 ms) 255 | VOID setClicktimeout (Uint16_T Tout); 256 | 257 | // Install the Timout of the Anti -Direct, silence.50 (Max. 255 ms) 258 | VOID Setdebtimeout (Uint8_t Tout); 259 | 260 | // set the level of the button (HIGH - button closes VCC, Low - closes GND) 261 | // silent.High, that is, True - the button is pressed 262 | VOID setbtnlevel (Bool LEVEL); 263 | 264 | // Connect the function-processor of events (type VOID F ()) 265 | VOID attach (VOID (*handler) ()); 266 | 267 | // Disconnect the event-handle function 268 | VOID Detach (); 269 | 270 | // ==============ward 271 | // throw off system flags (forcibly finish processing) 272 | VOID Reset (); 273 | 274 | // forcibly drop the flags of events 275 | Void Clear (); 276 | 277 | // ====ward 278 | // button processing with value 279 | Bool Tick (Bool S); 280 | 281 | // Processing of the virtual button as simultaneous pressing two other buttons 282 | Bool Tick (Virtbutton & B0, Virtbutton & B1); 283 | 284 | // button pressed in the interruption of the button 285 | VOID Pressisr (); 286 | 287 | // Processing the button without reseting events and calling collobes 288 | Bool Tickrad (Bool S); 289 | 290 | // ================== Poll ================================ward 291 | // Pressure button [event] 292 | Bool Press (); 293 | Bool Press (Uint8_T Clicks); 294 | 295 | // button released (in any case) [evente] 296 | Bool Release (); 297 | Bool Release (Uint8_T Clicks); 298 | 299 | // click on the button (released without holding) [event] 300 | Bool click (); 301 | Bool Click (Uint8_T Clicks); 302 | 303 | // Squeezed button (between Press () and Release ()) [condition] 304 | Bool Pressing (); 305 | Bool Pressing (Uint8_T Clicks); 306 | 307 | // The button was withheld (more timeout) [event] 308 | Bool Hold (); 309 | Bool Hold (Uint8_T Clicks); 310 | 311 | // The button is held (more timeout) [condition] 312 | Bool Holding (); 313 | Bool Holding (Uint8_T Clicks); 314 | 315 | // Impulse retention [event] 316 | Bool Step (); 317 | Bool Step (Uint8_T Clicks); 318 | 319 | // Several clicks were recorded [event] 320 | Bool HasClicks (); 321 | Bool HasClicks (Uint8_T Clicks); 322 | 323 | // button released after holding [Event] 324 | Bool ReleaseHold (); 325 | Bool ReleaseHold (Uint8_T Clicks); 326 | 327 | // Button is released after impulse retention [Event] 328 | Bool ReleaseStep (); 329 | Bool ReleaseStep (Uint8_T Clicks); 330 | 331 | // get the number of clicks 332 | uint8_t getclicks (); 333 | 334 | // Get the number of steps 335 | uint16_t getsteps (); 336 | 337 | // button awaits repeated clicks (between Click () and HasClicks ()) [condition] 338 | Bool Waiting (); 339 | 340 | // processing (between the first press and after waiting for clicks) [condition] 341 | Bool Busy (); 342 | 343 | // there was an action from the button, the event code [event] will return 344 | Uint16_T Action (); 345 | 346 | // ========================================================================== 347 | // After interacting with the button (or enkoder Encbutton), the specified time has passed, ms [event] 348 | Bool Timeout (Uint16_T MS); 349 | 350 | // The time that the button is held (from the beginning of the press), ms 351 | uint16_t Pressfor (); 352 | 353 | // The button is held longer than (from the beginning of pressing), ms [condition] 354 | Bool Pressfor (Uint16_T MS); 355 | 356 | // The time that the button is held (from the beginning of retention), ms 357 | uint16_t holdfor (); 358 | 359 | // The button is held longer than (from the beginning of retention), ms [condition] 360 | Bool Holdfor (Uint16_T MS); 361 | 362 | // The time that the button is held (from the beginning of the step), ms 363 | uint16_t stepfor (); 364 | 365 | // The button is held longer than (from the beginning of the step), ms [condition] 366 | Bool StepFor (Uint16_T MS); 367 | `` ` 368 |
369 |
370 | virtencoder 371 | 372 | `` `CPP 373 | // =============================================== 374 | // Invert the direction of the encoder (silence 0) 375 | VOID Setencreverse (Bool Rev); 376 | 377 | // Install the type of encoder (eb_step4_low, eb_step4_high, eb_step2, eb_step1) 378 | VOID Setenctype (Uint8_T Type); 379 | 380 | // use encoder processing in interruption 381 | VOID Setencisr (Bool Use); 382 | 383 | // Initialization of the encoder 384 | VOID Initenc (Bool E0, Bool E1); 385 | 386 | // Encoder initialization with a combined value 387 | VOID Initenc (int8_t v); 388 | 389 | // throw off the flags of events 390 | Void Clear (); 391 | 392 | // ===================== Support ========================== 393 | // there was a turn [event] 394 | Bool Turn (); 395 | 396 | // Direction of the encoder (1 or -1) [condition] 397 | int8_t die (); 398 | 399 | // counter 400 | Int32_T Counter; 401 | 402 | // ====ward 403 | // Interrogate the encoder in interruption.Will return 1 or -1 during rotation, 0 when stopping 404 | Int8_t Tickisr (Bool E0, Bool E1); 405 | Int8_T Tickisr (int8_t state); 406 | 407 | // Introduce the Encoder.Will return 1 or -1 during rotation, 0 when stopping 408 | Int8_T Tick (Bool E0, Bool E1); 409 | int8_t tick (int8_t state); 410 | int8_t tick ();// The processing itself in interruption 411 | 412 | // Introduce the Encoder without resetting the turning event.Will return 1 or -1 during rotation, 0 when stopping 413 | Int8_T TickRaW (Bool E0, Bool E1); 414 | Int8_T TickRaW (int8_t state); 415 | Int8_T TickRaW ();// The processing itself in interruption 416 | 417 | // Introduce the encoder without installing flags on a turn (faster).Will return 1 or -1 during rotation, 0 when stopping 418 | Int8_t Pollenc (Bool E0, Bool E1); 419 | Int8_t Pollenc (Int8_T State); 420 | `` ` 421 |
422 |
423 | virtencbutton 424 | 425 | - Available functions from `virtbutton` 426 | - Available functions from `virtencoder` 427 | 428 | `` `CPP 429 | // ============ward 430 | // Install a timeout of fast turning, ms 431 | VOID setfasttimeout (Uint8_t Tout); 432 | 433 | // throw the flags of encoder and buttons 434 | Void Clear (); 435 | 436 | // =================== Poll ================================= 437 | // Any turn of the encoder [event] 438 | Bool Turn (); 439 | 440 | // pressed turn of enkoder [event] 441 | Bool Turnh (); 442 | 443 | // Fast turning of the encoder [condition] 444 | Bool Fast (); 445 | 446 | // Open Turn to the right [event] 447 | Bool Right (); 448 | 449 | // Non -pressed turnfrom left [event] 450 | Bool Left (); 451 | 452 | // pressed turn to the right [event] 453 | Bool Righth (); 454 | 455 | // pressed turn to the left [event] 456 | Bool Lefth (); 457 | 458 | // there was an action from a button or encoder, will return the event code [event] 459 | Uint16_T Action (); 460 | 461 | // ====ward 462 | // processing in interruption (only encoder).Will return 0 at rest, 1 or -1 when turning 463 | Int8_t Tickisr (Bool E0, Bool E1); 464 | Int8_T Tickisr (int8_t e01); 465 | 466 | // processing of encoder and buttons 467 | Bool Tick (Bool E0, Bool E1, Bool BTN); 468 | Bool Tick (Int8_t E01, Bool BTN); 469 | Bool Tick (Bool BTN);// Encoder in interruption 470 | 471 | // Processing the encoder and buttons without discharge flags and calling collobes 472 | Bool Tickrad (Bool E0, Bool E1, Bool BTN); 473 | Bool Tickrade (Int8_t E01, Bool BTN); 474 | Bool Tickrade (Bool BTN);// Encoder in interruption 475 | `` ` 476 |
477 |
478 | Button 479 | 480 | - Available functions from `virtbutton` 481 | - default buttons mode - `Low` 482 | 483 | `` `CPP 484 | Button; 485 | Button (uint8_t pin);// indicating Pin 486 | Button (uint8_t npin, uint8_t mode);// + mode of operation (silence input_pullup) 487 | Button (uint8_t npin, uint8_t mode, uint8_t btnlevel);// + button level (silence) 488 | `` ` 489 | `` `CPP 490 | // indicate the pin and its operating mode 491 | VOID Init (uint8_t npin, uint8_t mode); 492 | 493 | // Read the current value of the button (without debate) taking into account Setbtnlevel 494 | Bool Read (); 495 | 496 | // processing function, call in loop 497 | Bool Tick (); 498 | 499 | // Processing the button without reseting events and calling collobes 500 | Bool Tickrade (); 501 | `` ` 502 |
503 |
504 | buttont 505 | 506 | - Available functions from `virtbutton` 507 | - default buttons mode - `Low` 508 | 509 | `` `CPP 510 | Buttont ;// indicating Pin 511 | Buttont (uint8_t mode);// + mode of operation (silence input_pullup) 512 | Buttont (uint8_t mode, uint8_t btnlevel);// + button level (silence) 513 | `` ` 514 | `` `CPP 515 | // specify the operating mode 516 | VOID Init (Uint8_t Mode); 517 | 518 | // Read the current value of the button (without debate) taking into account Setbtnlevel 519 | Bool Read (); 520 | 521 | // processing function, call in loop 522 | Bool Tick (); 523 | `` ` 524 |
525 |
526 | encoder 527 | 528 | - Available functions from `virtencoder` 529 | 530 | `` `CPP 531 | Encoder; 532 | Encoder (Uint8_t Enca, Uint8_T ENCB);// indicating Pinov 533 | Encoder (Uint8_t Enca, Uint8_t Encb, Uint8_t Mode);// + mode of operation (silence. Input) 534 | `` ` 535 | `` `CPP 536 | // Indicate pins and their operating mode 537 | VOID Init (Uint8_t Enca, Uint8_t Encb, Uint8_t Mode); 538 | 539 | // Function of processing for calling in an interruption of encoder 540 | int8_t tickisr (); 541 | 542 | // Function of processing for calling in LOOP 543 | int8_t tick (); 544 | `` ` 545 |
546 |
547 | encodert 548 | 549 | - Available functions from `virtencoder` 550 | 551 | `` `CPP 552 | Encodert ;// indicating Pinov 553 | Encodert (Uint8_t Mode);// + mode of operation (silence. Input) 554 | `` ` 555 | `` `CPP 556 | // specify the mode of operation of Pinov 557 | VOID Init (Uint8_t Mode); 558 | 559 | // Function of processing for calling in an interruption of encoder 560 | int8_t tickisr (); 561 | 562 | // Function of processing for calling in LOOP 563 | int8_t tick (); 564 | `` ` 565 |
566 |
567 | encbutton 568 | 569 | - Available functions from `virtbutton` 570 | - Available functions from `virtencoder` 571 | - Available functions from `virtencbutton` 572 | 573 | `` `CPP 574 | ENCBUTTON; 575 | 576 | // Set the Pins (ENK, ENK, button) 577 | ENCBUTTON (UINT8_T ENCA, UINT8_T ENCB, UINT8_T BTN); 578 | 579 | // Reference Pins (ENK, ENK, button, Pinmode ENK, Pinmode button, button level) 580 | ENCBUTTON (UINT8_T ENCA, UINT8_T ENCB, UINT8_T BTN, UINT8_T MODEENC = Input, Uint8_t Modebtn = Input_Pullup, Uint8_T BTNLEVEL = LOW); 581 | `` ` 582 | `` `CPP 583 | // Reference Pins (ENK, ENK, button, Pinmode ENK, Pinmode button, button level) 584 | VOID Init (Uint8_t Enca, Uint8_t Encb, Uint8_t BTN, UINT8_T MODEENC = Input, Uint8_t Modebtn = Input_Pullup, Uint8_T BTNLEVEL = LOW); 585 | 586 | // Function of processing for calling in an interruption of encoder 587 | int8_t tickisr (); 588 | 589 | // processing function, call in loop 590 | Bool Tick (); 591 | 592 | // Read the value of the button taking into account Setbtnlevel 593 | Bool Readbtn (); 594 | 595 | // Read the value of the encoder 596 | Int8_T Readenc (); 597 | `` ` 598 |
599 |
600 | encbuttont 601 | 602 | - Available functions from `virtbutton` 603 | - Available functions from `viRencoder` 604 | - Available functions from `virtencbutton` 605 | 606 | `` `CPP 607 | // indicating Pinov 608 | ENCBUTTONT ; 609 | 610 | // + Pino operation mode, button level 611 | ENCBUTTONT (Uint8_t Modeenc = Input, Uint8_t Modebtn = Input_Pullup, Uint8_T BTNlevel = Low); 612 | `` ` 613 | `` `CPP 614 | // Configure Pino operation mode, button level 615 | VOID Init (Uint8_t Modeenc = Input, Uint8_t Modebtn = Input_pullup, Uint8_t Btnlevel = Low); 616 | 617 | // Function of processing for calling in an interruption of encoder 618 | int8_t tickisr (); 619 | 620 | // processing function, call in loop 621 | Bool Tick (); 622 | 623 | // Read the button value 624 | Bool Readbtn (); 625 | 626 | // Read the value of the encoder 627 | Int8_T Readenc (); 628 | `` ` 629 |
630 | 631 | 632 | 633 | ### Processing and Poll 634 | All libraries have a general ** function of processing ** (ticker `tick`), which receives the current signal from the button and encoder 635 | - this function must be caused once in the main cycle of the program (for virtual - with the transmission of the meaning) 636 | - the function returns `true` when the event occurs (for encoder -` 1` or `-1` when turning,` 0` in its absence. Thus, the turn in any direction is regarded as `true`) 637 | - There are separate functions for calling in interruption, they have a suffix `isr`, see documentation below 638 | 639 | The library processes the signal inside this function, the result can be obtained from ** functions of the survey ** events.They are of two types: 640 | - `[event]` - the function will return `true` once upon the event of an event.It will be reset after the next call call (for example, click, turning enncoder) 641 | - `[condition]` - the function returns `true`, while this condition is actively (for example, the button is held) 642 | 643 | For simplicity of perception, the processing function must be placed at the beginning of the cycle, and polls do below: 644 | `` `CPP 645 | VOID loop () { 646 | btn.tick ();// survey 647 | 648 | if (btn.click ()) serial.println ("click");// will display once when clicking 649 | if (btn.click ()) serial.println ("click");// The same click! 650 | } 651 | `` ` 652 | > Unlike previous versions of the library, the survey functions are not reset inside themselves, but *inside the processing function *.Thus, in the example above, when clicking on the button in the port, the message `click ()` is displayed twice.This allows you to use the survey functions several times for the current iteration of the cycle to create a complex logic of the program. 653 | 654 | #### several functions of processing 655 | For obvious reasons, it is impossible to cause the processing function more than once per cycle - each next call will drop events from the previous one and the code will work incorrectly.So - you can’t: 656 | `` `CPP 657 | // you can not do it this way 658 | VOID loop () { 659 | btn.tick (); 660 | if (btn.click ()) ... 661 | 662 | // .... 663 | 664 | btn.tick (); 665 | if (btn.hold ()) ... 666 | } 667 | `` ` 668 | 669 | If you really need to get into a deaf cycle and interrogate the button there, then it can: you can: 670 | `` `CPP 671 | // so it is possible 672 | VOID loop () { 673 | btn.tick (); 674 | if (btn.click ()) ... 675 | 676 | While (True) { 677 | btn.tick (); 678 | if (btn.hold ()) ... 679 | if (btn.click ()) Break; 680 | } 681 | } 682 | `` ` 683 | 684 | If the library is used with an connected event handler `Attach ()` (see below), then you can call `tick ()` anywhere and as many times as you like, the events will be processed in the handler: 685 | `` `CPP 686 | // so it is possible 687 | VOID CB () { 688 | switch (btn.action ()) { 689 | // ... 690 | } 691 | } 692 | 693 | VOID setup () { 694 | btn.attach (CB); 695 | } 696 | 697 | VOID loop () { 698 | btn.tick (); 699 | // ... 700 | btn.tick (); 701 | // ... 702 | btn.tick (); 703 | } 704 | `` ` 705 | 706 | ### "loaded" program 707 | The Encbutton - ** asynchronous ** library: it does not wait until the button is completed, and allows the program to be executed further.This means that for the correct operation of the library, the main cycle of the program should be performed as quickly as possible and not contain delays and other "deaf" cycles within itself.To ensure proper processing of the button, it is not recommended to have a main delay cycle lasting more than 50-100 ms.A few tips: 708 | -beginners: to study the lesson cycle [how to write a sketch] (https://alexgyver.ru/lessns/how-to-sketch/) 709 | - write asynchronous code in `loop ()` 710 | - Any synchronous structure on `delay ()` can be made asynchronous using `millis ()` ` 711 | - if in the program * each * Iteration gThe cranberries of the bay cycle are performed longer than 50-100ms-in most cases the program is written incorrectly, with the exception of some special cases 712 | - connect the button to the hardware interruption (see below) 713 | - avoid the execution of "heavy" sections of the code while the button is processing, for example, by placing them in the condition `If (! Button.busy ()) {heavy code}`} ` 714 | - If it is impossible to optimize the main cycle - call the ticker in another "stream" and use the processor: 715 | - in interruption of a timer with a period of ~ 50ms or more often 716 | - on another core (for example, ESP32) 717 | - In another Task Freertos 718 | - inside `yield ()` (inside `delay ()`) 719 | 720 | #### Separate processing 721 | > It makes sense only with a manual survey of events!With a connected processing function, it is enough to call the usual `tick ()` between the heavy sections of the program 722 | 723 | Also, in a loaded program, you can divide the processing and reset of events: instead of `tick ()` use `tickRAW ()` between heavy sections of the code and manual reset `Clear ()`.The order is as follows: 724 | - Surrender actions (Click, Press, Turn ...) 725 | - Call `Clear ()` 726 | - call `tickRaW ()` between heavy sections of code 727 | 728 | `` `CPP 729 | VOID loop () { 730 | if (btn.click ()) ... 731 | if (btn.press ()) ... 732 | if (btn.step ()) ... 733 | 734 | btn.clear (); 735 | 736 | // ... 737 | BTN.TickRAW (); 738 | // ... 739 | BTN.TickRAW (); 740 | // ... 741 | BTN.TickRAW (); 742 | // ... 743 | } 744 | `` ` 745 | This will allow to interview the button/encoder in a not very well written program, where the main cycle is littered with heavy code.Inside the `Tickrade ()` Events accumulate that are dismantled once in the cycle, and then manually reset. 746 | 747 | > In this scenario, the Encoder's buffering in the interruption does not work and all events are not processed `Releasexxx` 748 | 749 | #### Processing inside Delay 750 | If it is difficult to get rid of the `delay ()` inside the main cycle of the program, then on some platforms you can place your code inside it.Thus, you can even get encoder processing in a cycle with deals without using interruptions: 751 | `` `CPP 752 | // Code insertion in Delay 753 | VOID yield () { 754 | EB.TickRAW (); 755 | } 756 | 757 | VOID loop () { 758 | if.click ()) ... 759 | if (btn.turn ()) ... 760 | 761 | eb.clear (); 762 | 763 | // ... 764 | Delay (10); 765 | // ... 766 | DELAY (50); 767 | // ... 768 | } 769 | `` ` 770 | 771 | > In this scenario, the Encoder's buffering in the interruption does not work and all events are not processed `Releasexxx` 772 | 773 | #### button processing 774 | The library processes the button as follows: 775 | - Pressing with software suppression of rubbish (holding longer than the Deb time), the result is the event `Press`, the state of` Pressing` and `Busy` 776 | - retention longer than the Hold Hold time - the event `hold`, the state` holding` 777 | - holding longer than the Hold + Timeshot Timeshu Taimout - a pulse event `step`, triggers with the STEP period while the button holds 778 | - release of the button, the result - the event `Release`, the removal of the states` Pressing` and `Holding` 779 | - release to the deduction time - event `click` 780 | - release after holding - event `Releasehold` 781 | - release after impulse deduction - event `ReleaseStep` 782 | - Events `Releasehold` and` ReleaseStep` mutually exclusive, if the button was withheld `step` -` Releasehold` will no longer work 783 | - Waiting for a new click during the Click timeout, the state `Waiting` 784 | - If there is no new click - the removal of the state of `Busy`, the processing is completed 785 | - If the button is pressed again - processing a new click 786 | - The Clicks Clicks `getClicks ()` is discarded after the events `Releasehold`/` Releastep`, which check the preliminary clicks.In the general processor `Action ()` Events `EB_REL_Hold_C` or` EB_REL_STEP_C` 787 | - The number of clicks made must be checked by the `Hasclicks` event, and you can also interview almost all the events of the buttons that go to` Releasexxx` 788 | - If `Timeout` is expected - Timeout event with the specified period from the current moment 789 | - processing the button in the interruption informs the library about the fact of pressing, the rest of the processing is performed regularly in `Tick ()` ` 790 | 791 | > The difference is `Click (n)` `Hasclicks (n)`: `Click (n)` will return `true` in any case when the number of clicks coincides, even if more clicks are made.`HasClicks (n)` will return `true` only inCranberry, if the exactly indicated number of clicks was made and there were no more clicks! 792 | 793 | > It is better to see once than read a hundred times.Launch an example of Demo and go on the button, or try [online symulation in Wokwi] (https://wokwi.com/projects/373591584298469377) 794 | 795 | ##### Click 796 | ! [click] (/doc/click.gif) 797 | 798 | ##### Hold 799 | ! [Hold] (/doc/hold.gif) 800 | 801 | ##### STEP 802 | ! [STEP] (/DOC/STEP.GIF) 803 | 804 | Online symulation is available [here] (https://wokwi.com/projects/373591584298469377) 805 | 806 | #### Encoder Processing 807 | - "Fast" turn is considered a turn committed less than tuned timaut from the previous turn 808 | - the turns processed in interruption become active (cause events) after calling `tick ()` 809 | - Access to the encoder’s counter `Counter` is a public variable of the class, you can do anything with it: 810 | `` `CPP 811 | Serial.println (eb.counter);// read 812 | Eb.counter += 1234;// change 813 | eb.counter = 0;// Knock 814 | `` ` 815 | 816 | #### encoder processing with a button 817 | - The turning of the encoder with a clamped button removes and blocks all subsequent events and clicks, with the exception of the event `redase`.The states of the pressed button do not change 818 | - The turning of the encoder also affects the system timout (the `timeout ()` function) will work after the indicated time after turning the enkoder 819 | - the Klikov counter is available when pressed: several clicks, click of a button, turn 820 | 821 | 822 | 823 | ### Preliminary clicks 824 | The library considers the number of clicks by the button and some survey functions can separately be processed with *preliminary clicks *.For example, 3 clicks, then retention.This greatly expands the capabilities of one button.There are two options for working with such events: 825 | `` `CPP 826 | // 1 827 | if (btn.hold ()) { 828 | if (btn.getclics () == 2) serial.println ("Hold 2 Clicks"); 829 | } 830 | 831 | // 2 832 | if (btn.hold (2)) serial.println ("Hold 2 Clicks"); 833 | `` ` 834 | 835 | In the first version, you can get the number of clicks for further processing manually, and in the second - the library will do this itself if the number of clicks for action is known in advance. 836 | 837 | 838 | 839 | ## direct reading button 840 | In some scenarios, you need to get the state of the "here and now" button, for example, determine whether the button is held immediately after starting the microcontroller (program start).The function `tick ()` must be called constantly in the cycle so that the button is processing with the extinguishing of the ratio of contacts and other calculations, so the design of the next type ** will not work **: 841 | `` `CPP 842 | VOID setup () { 843 | btn.tick (); 844 | if (btn.press ()) serial.println ("button pressed at start"); 845 | } 846 | `` ` 847 | 848 | The following functions will help for such scenarios, return `true` if the button is pressed: 849 | - `read ()` for libraries button and buttont 850 | - `Readbtn ()` for library libraries and encbuttont 851 | 852 | > The button survey is performed taking into account the previously configured button level (Setbtnlevel)!It is not necessary to manually invert the logic: 853 | 854 | `` `CPP 855 | VOID setup () { 856 | // btn.setbtnlevel (LOW);// you can configure the level 857 | 858 | if (btn.read ()) serial.println ("button pressed at start"); 859 | } 860 | `` ` 861 | 862 | 863 | 864 | ### immersion in the cycle 865 | Suppose you need to process the button synchronously and with the extinguishing of the rattles.For example, if the button is clamped at the start of the microcontroller, get its retention or even the pulse retention inside the `setup` unit, that is, before the start of the main program.You can use the state of `Busy` and interview the button from the cycle: 866 | `` `CPP 867 | VOID setup () { 868 | Serial.Begin (115200); 869 | 870 | btn.tick (); 871 | While (btn.busy ()) { 872 | btn.tick (); 873 | if (btn.hold ()) serial.println ("Hold"); 874 | if (btn.step ()) serial.println ("step"); 875 | } 876 | 877 | Serial.println ("Program Start"); 878 | } 879 | `` ` 880 | How it works: the first tick interrogates the button, if the button is pressed - the state of the Busy is immediately activated and the system enters the `While` cycle.Inside it, we continue to tick and get events from the button.When the button is released and all events will work - the Busy flag will drop and the program will automatically leave the cycle.You can rewrite this design to the cycle with a postcryption, more beautiful: 881 | `` `CPP 882 | do { 883 | B.tn.tick (); 884 | if (btn.hold ()) serial.println ("Hold"); 885 | if (btn.step ()) serial.println ("step"); 886 | } While (btn.busy ()); 887 | `` ` 888 | 889 | 890 | 891 | ## Timeout 892 | In the classes associated with the button (Button, Encbutton) there is a function `Timeout (Time)` - it will once return `true` if the indicated time has passed after the action with the button/encoder.This can be used to preserve parameters after entering, for example: 893 | `` `CPP 894 | VOID loop () { 895 | eb.tick (); 896 | 897 | // ... 898 | 899 | if.timeout ()) { 900 | // after interaction with encoder 1 second passed 901 | // eeprom.put (0, settings); 902 | } 903 | } 904 | `` ` 905 | 906 | 907 | 908 | ### Busy 909 | The `Busy () function` Returns `True` while the button processing is underway, i.e.So far, the system awaits actions and the release of timeouts.This can be used to optimize the code, for example, avoid some long and heavy parts of the program during the button processing: 910 | `` `CPP 911 | VOID loop () { 912 | eb.tick (); 913 | 914 | // ... 915 | 916 | if (! eb.busy ()) { 917 | // Potentially long and difficult code 918 | } 919 | } 920 | `` ` 921 | 922 | 923 | 924 | ### Obtaining an event 925 | Available in all classes ** with the ** button: 926 | - `virtbutton` 927 | - `Button` 928 | - `virtencbutton` 929 | - `encbutton` 930 | 931 | The function `Action ()` when the event occurs, the event is returned (different from scratch, which in itself is an indication of the existence of an event): 932 | - `eb_press` - click on the button 933 | - `eb_hold` - the button is kept 934 | - `eb_step` - impulse retention 935 | - `eb_release` - the button is released 936 | - `eb_click` - Single click 937 | - `eb_clicks` - A few click signal 938 | - `eb_turn` - turn of the encoder 939 | - `eb_rel_hold` - the button is released after holding 940 | - `EB_REL_HOLD_C` - the button is released after holding off the premises.clicks 941 | - `EB_REL_STEP` - the button is released after the step 942 | - `EB_REL_STEP_C` - the button is released after the step with a border.clicks 943 | 944 | > The result of the function `Action ()` is reset after the next call `tick ()`, that is, is available on the entire current iteration of the main cycle 945 | 946 | The obtained event code can be processed through `switch`: 947 | `` `CPP 948 | switch (eb.action ()) { 949 | Case eb_press: 950 | // ... 951 | Break; 952 | Case eb_hold: 953 | // ... 954 | Break; 955 | // ... 956 | } 957 | `` ` 958 | 959 | 960 | 961 | ## Optimization 962 | #### Library weight 963 | To maximally reduce the weight of the library (in particular in RAM), you need to set the Timatui constants through Define (saving 1 bytes per timaut), turn off the events processor, counters-buffers and use the T-class (saving 1 byte per pin): 964 | `` `CPP 965 | #define eb_no_for 966 | #define eb_no_callback 967 | #define eb_no_counter 968 | #define eb_no_buffer 969 | #define eb_deb_time 50 // Timesout to extinguish the trim button (button) 970 | #define eb_click_time 500 // Click Stayout (button) 971 | #define eb_hold_time 600 // Maintenance Times (button) 972 | #define eb_step_time 200 // Impulse retention Timesout (button) 973 | #define EB_FAST_TIME 30 // TIMAUT RAM (ENCODER) 974 | #include 975 | ENCBUTTONT <2, 3, 4> EB; 976 | `` ` 977 | In this case, an encoder with a button will take only 8 bytes in SRAM, and just a button - 5. 978 | 979 | #### Fulfillment speed 980 | To reduce the time for checking the system flags of events (insignificantly, but pleasant), you can place all the polls in the condition by `tick ()`, since `tick ()` returns `true` only when ** events **: the events **: 981 | `` `CPP 982 | VOID loop () { 983 | if (eb.tick ()) { 984 | if.turn ()) ...; 985 | if.click ()) ...; 986 | } 987 | } 988 | `` ` 989 | 990 | Also, a survey of events using the `Action () function is performed faster than a manual survey of individual functions of events, so the library will work in this format as efficiently as possible: 991 | `` `CPP 992 | VOID loop () { 993 | if (eb.tick ()) { 994 | switch (eb.action ()) { 995 | Case eb_press: 996 | // ... 997 | Break; 998 | Case eb_hold: 999 | // ... 1000 | Break; 1001 | // ... 1002 | } 1003 | } 1004 | } 1005 | `` ` 1006 | 1007 | For polling ** states ** buttons `Pressing ()`, `Holding ()`, `WATING ()` You can place them inside the conditions of `BUSY ()` so as not to interview them until they are guaranteed: 1008 | `` `CPP 1009 | if (btn.busy ()) { 1010 | if (btn.pressing ()) ... 1011 | if (btn.holding ()) ... 1012 | if (btn.waiting ()) ... 1013 | } 1014 | `` ` 1015 | 1016 | 1017 | 1018 | ### Collback 1019 | You can connect the external function-shacklerCranberry, it will be caused when any event occurs.This opportunity works in all classes ** with the ** button: 1020 | - `virtbutton` 1021 | - `Button` 1022 | - `virtencbutton` 1023 | - `encbutton` 1024 | 1025 | `` `CPP 1026 | ENCBUTTON EB (2, 3, 4); 1027 | 1028 | Void callback () { 1029 | switch (eb.action ()) { 1030 | Case eb_press: 1031 | // ... 1032 | Break; 1033 | Case eb_hold: 1034 | // ... 1035 | Break; 1036 | // ... 1037 | } 1038 | } 1039 | 1040 | VOID setup () { 1041 | eb.attach (callback); 1042 | } 1043 | 1044 | VOID loop () { 1045 | eb.tick (); 1046 | } 1047 | `` ` 1048 | 1049 | 1050 | 1051 | ### Simultaneous pressing 1052 | The library supports work with two simultaneously pressed buttons as with the third button.For this you need: 1053 | 1. To make a virtual button `virtbutton` 1054 | 2. Call the processing of real buttons 1055 | 3. Pass these buttons to the virtual button to process (these can be objects of classes `virtbutton`,` button`, `encbutton` + their` t`- version) 1056 | 4. Next to interrogate events 1057 | 1058 | `` `CPP 1059 | Button b0 (4); 1060 | Button b1 (5); 1061 | Virtbutton B2;// 1 1062 | 1063 | VOID loop () { 1064 | b0.tick ();// 2 1065 | b1.tick ();// 2 1066 | B2.Tick (B0, B1);// 3 1067 | 1068 | // 4 1069 | if (b0.click ()) serial.println ("b0 click"); 1070 | if (b1.click ()) serial.println ("b1 click"); 1071 | if (b2.click ()) serial.println ("b0+b1 click"); 1072 | } 1073 | `` ` 1074 | 1075 | The library itself will “drop” unnecessary events from real buttons if they were pressed together, with the exception of the event `Press`.Thus, a full -fledged third button of two others with a convenient survey is obtained. 1076 | 1077 | 1078 | 1079 | ## interrupt 1080 | ### encoder 1081 | For processing an encoder in a loaded program you need: 1082 | - Connect both of his pins to hardware interruptions by `Change` 1083 | - install `setencisr (true)` 1084 | - call a special ticker for interruption in the handler 1085 | - The main ticker also needs to be called in `loop` for corrething work - events are generated in the main ticker: 1086 | `` `CPP 1087 | // Example for Atmega328 and Encbutton 1088 | ENCBUTTON EB (2, 3, 4); 1089 | 1090 | /* 1091 | // ESP8266/ESP32 1092 | IRAM_ATTR VOID ISR () { 1093 | eb.tickisr (); 1094 | } 1095 | */ 1096 | 1097 | VOID isr () { 1098 | eb.tickisr (); 1099 | } 1100 | VOID setup () { 1101 | Attachinterrupt (0, Isr, Change); 1102 | Attachinterrupt (1, ISR, Change); 1103 | eb.setencisr (true); 1104 | } 1105 | VOID loop () { 1106 | eb.tick (); 1107 | } 1108 | `` ` 1109 | 1110 | Note: The use of work in the interruption allows you to correctly process the encoder position and not miss a new turn.An event with a turn obtained from an interruption will become available * after * call `Tick` in the main cycle of the program, which allows not to violate the sequence of the main cycle: 1111 | - Buferization is disabled: the `turn` event is activated only once, regardless of the number of clicks of the encoder made between the two challenges of` tick` (clicks are processed in interruption) 1112 | - The buffering is included: the `turn` event will be caused as many times as there were really clicks of the encoder, this allows you to not miss the turns and not load the system in the interruption.** Boofer size - 5 unprocessed clicks of encoder ** 1113 | 1114 | Notes: 1115 | - The `setencisr` function works only in non - virtual classes.If it is turned on, the main ticker `tick` simply does not interview Encoder's pins, which saves processor time.Processing occurs only in interruption 1116 | - The encoder counter is always relevant and can be ahead of buffering turns in the program with large delays in the main cycle! 1117 | - on different interrupt platforms, they can work differently (for example, on ESPXX - you need to add the functions of the atrica `` IRAM_ATTR`, see documentation on your platform!) 1118 | - a processor connected to `Attach ()` will be called from `Tick ()`, that is, *not from interruption *! 1119 | 1120 | ### virtual classes 1121 | In the virtual ones there is a ticker in which it is not necessary to transmit the state of the encoder, if it is processed in an interruption, this allows you to not interview pins in idle.For example: 1122 | 1123 | `` `CPP 1124 | Virtencoder e; 1125 | 1126 | VOID isr () { 1127 | E.tickisr (DigitalRead (2), DigitalRead (3)); 1128 | } 1129 | VOID setup () { 1130 | Attachinterrupt (0, Isr, Change); 1131 | Attachinterrupt (1, ISR, Change); 1132 | 1133 | E.Setencisr (1); 1134 | } 1135 | VOID loop () { 1136 | E.tick ();// Do not transmit the states of Pinov 1137 | } 1138 | `` ` 1139 | 1140 | #### Button 1141 | To process the button in the interruption, you need: 1142 | - Connect an interruption on ** press ** buttons taking into account its physical connection and level: 1143 | - If the button is deputy`Low` - Interruption` Falling` 1144 | - if the button closes `high` - interruption` rising` 1145 | - call `Pressisr ()` in the interruption processor 1146 | 1147 | `` `CPP 1148 | Button b (2); 1149 | 1150 | /* 1151 | // ESP8266/ESP32 1152 | IRAM_ATTR VOID ISR () { 1153 | B.Pressisr (); 1154 | } 1155 | */ 1156 | 1157 | VOID isr () { 1158 | B.Pressisr (); 1159 | } 1160 | VOID setup () { 1161 | Attachinterrupt (0, ISR, Falling); 1162 | } 1163 | VOID loop () { 1164 | B.tick (); 1165 | } 1166 | `` ` 1167 | 1168 | Note: the button is processed mainly `tick ()`, and the function `Pressisr ()` just informs the library that the button was pressed outside `Tick ()`.This allows you not to miss the pressing of the button until the program was busy with something else. 1169 | 1170 | 1171 | 1172 | ### Array of buttons/Encoder 1173 | You can create an array only from non -step classes (without the letter `t`), because Pinov numbers will have to be indicated already in the radio further in the program.For example: 1174 | `` `CPP 1175 | Button btns [5]; 1176 | ENCBUTTON EBS [3]; 1177 | 1178 | VOID setup () { 1179 | btns [0] .init (2);// Indicate the pin 1180 | btns [1] .init (5); 1181 | btns [2] .init (10); 1182 | // ... 1183 | 1184 | EBS [0] .init (11, 12, 13, Input); 1185 | EBS [1] .init (14, 15, 16); 1186 | // ... 1187 | } 1188 | VOID loop () { 1189 | for (int i = 0; i <5; i ++) btns [i] .Tick (); 1190 | for (int i = 0; i <3; i ++) EBS [i] .Tick (); 1191 | 1192 | if (btns [2] .Click ()) serial.println ("BTN2 click"); 1193 | // ... 1194 | } 1195 | `` ` 1196 | 1197 | 1198 | 1199 | ### Caste functions 1200 | The library supports the task of its functions for reading PIN and getting time without editing library files.To do this, you need to implement the corresponding function in your .cpp or. 1201 | - `bool eb_read (uint8_t pin)` - for its pine reading function 1202 | - `void eb_mode (uint8_t pin, uint8_t mode)` - for your analogue Pinmode 1203 | - `uint32_t eb_uptime ()` - for your analogue millis () 1204 | 1205 | Example: 1206 | 1207 | `` `CPP 1208 | #include 1209 | 1210 | Bool eb_read (uint8_t pin) { 1211 | Return DigitalRead (PIN); 1212 | } 1213 | 1214 | VOID eb_mode (uint8_t pin, uint8_t mode) { 1215 | Pinmode (PIN, Mode); 1216 | } 1217 | 1218 | uint32_t eb_uptime () { 1219 | Return Millis (); 1220 | } 1221 | `` ` 1222 | 1223 | 1224 | 1225 | ### Survey by timer 1226 | Sometimes it may be necessary to call `tick ()` not on every iteration, but by the timer.For example, for a virtual button from the Pino Expand, when reading the Pino Expand is a long operation, and it often does not make sense to call it.You can’t do this, events will be active during the timer! 1227 | `` `CPP 1228 | VOID loop () { 1229 | // Timer for 50 ms 1230 | Static uint32_t tmr; 1231 | if (millis () - tmr> = 50) { 1232 | TMR = Millis (); 1233 | btn.tick (Readsomepin ()); 1234 | } 1235 | 1236 | // will be actively within 50 ms !!! 1237 | if (btn.click ()) foo (); 1238 | } 1239 | `` ` 1240 | 1241 | In this situation, you need to do this: tick along the timer, process events there and drop flags at the end: 1242 | `` `CPP 1243 | VOID loop () { 1244 | // Timer for 50 ms 1245 | Static uint32_t tmr; 1246 | if (millis () - tmr> = 50) { 1247 | TMR = Millis (); 1248 | // TIK 1249 | btn.tick (Readsomepin ()); 1250 | 1251 | // analysis of events 1252 | if (btn.click ()) foo (); 1253 | 1254 | // Reset of the flags 1255 | btn.clear (); 1256 | } 1257 | } 1258 | `` ` 1259 | 1260 | Or you can connect the handler and call `clear ()` at the end of the function: 1261 | `` `CPP 1262 | Void callback () { 1263 | switch (btn.action ()) { 1264 | // ... 1265 | } 1266 | 1267 | // Reset of the flags 1268 | btn.clear (); 1269 | } 1270 | 1271 | VOID loop () { 1272 | // Timer for 50 ms 1273 | Static uint32_t tmr; 1274 | if (millis () - tmr> = 50) { 1275 | TMR = Millis (); 1276 | btn.tick (Readsomepin ()); 1277 | } 1278 | } 1279 | `` ` 1280 | 1281 | In the case of calling the timer, the anti -departments will be partially provided by the timer itself and in the library it can be turned off (set the period 0). 1282 | 1283 | For the correct operation of timeouts, conditions and a click counter, you need another approach: buffering the states read according to the timer and transfer them to the TIC in the main cycle.For example: 1284 | `` `CPP 1285 | Bool Readbuf = 0;// buffer Pina 1286 | 1287 | VOID loop () { 1288 | // Timer for 50 ms 1289 | Static uint32_t tmr; 1290 | if (millis () - tmr> = 50) { 1291 | TMR = Millis (); 1292 | Readbuf = Readsomepin ();// Reading in the buffer 1293 | } 1294 | 1295 | // tick from the buffer 1296 | BTN.Tick (Readbuf); 1297 | 1298 | if (btn.click ()) foo (); 1299 | } 1300 | `` ` 1301 | 1302 | 1303 | 1304 | ### Mini examples, scripts 1305 | `` `CPP 1306 | // Change the values of the variables 1307 | 1308 | // Turn of the encoder 1309 | if (enc.turn ()) { 1310 | // Change in step 5 1311 | var += 5 * enc.dir (); 1312 | 1313 | // Change in step 1 with a normal turn, 10 with fast 1314 | Var += ENC.FAST ()?10: 1; 1315 | 1316 | // Change in step 1 with a normal turn, 10 with pressed 1317 | vAR += ENC.Pressing ()?10: 1; 1318 | 1319 | // Change one variable when turning, the other - with a pressed turn 1320 | if (enc.pressing ()) Var0 ++; 1321 | Else Var1 ++; 1322 | 1323 | // If the button is pressed - preliminary clicks are available 1324 | // Choose a variable for changes in the premises.Clicks 1325 | if (enc.pressing ()) { 1326 | Switch (enc.getClicks ()) { 1327 | CASE 1: VAR0 += ENC.DIR (); 1328 | Break; 1329 | CASE 2: VAR1 += ENC.DIR (); 1330 | Break; 1331 | CASE 3: VAR2 += ENC.DIR (); 1332 | Break; 1333 | } 1334 | } 1335 | } 1336 | 1337 | // Impulse retention at every step is increasing the variable 1338 | if (btn.step ()) var ++; 1339 | 1340 | // Change the direction of change in the variable after letting go from STEP 1341 | if (btn.step ()) var += dir; 1342 | if (btn.releastep ()) die = -dir; 1343 | 1344 | // Change the selected variable using STEP 1345 | if (btn.step (1)) Var1 ++;// Click-holding 1346 | if (btn.step (2)) Var2 ++;// Click-Click-holding 1347 | if (btn.step (3)) var3 ++;// Click-Click-Click-hold 1348 | 1349 | // if you keep the STEP for more than 2 seconds - an incremental +5, so far less - +1 1350 | if (btn.step ()) { 1351 | if (btn.stepfor (2000)) var += 5; 1352 | Else Var += 1; 1353 | } 1354 | 1355 | // inclusion of the mode by the number of clicks 1356 | if (btn.hasclicks ()) mode = btn.getclicks (); 1357 | 1358 | // inclusion of the mode by several clicks and retention 1359 | if (btn.hold (1)) mode = 1;// Click-holding 1360 | if (btn.hold (2)) mode = 2;// Click-Click-holding 1361 | if (btn.hold (3)) mode = 3;// Click-Click-Click-hold 1362 | 1363 | // or so 1364 | if (btn.hold ()) mode = btn.getclicks (); 1365 | 1366 | // Button is released, look how much it was held 1367 | if (btn.release ()) { 1368 | // from 1 to 2 seconds 1369 | if (btn.pressfor ()> 1000 && btn.pressfor () <= 2000) mod = 1; 1370 | // from 2 to 3 seconds 1371 | Else if (BTN.PressFor ()> 2000 && BTN.PressFor () <= 3000) Mode = 2; 1372 | } 1373 | `` ` 1374 | 1375 | 1376 | 1377 | ## guide for migration from v2 to v3 1378 | ## H initialization 1379 | `` `CPP 1380 | // virtual 1381 | Virtencbutton eb;// Encoder with button 1382 | Virtbutton b;// button 1383 | Virtencoder e;// Encoder 1384 | 1385 | // Real 1386 | // Encoder with button 1387 | ENCBUTTON EB (ENC0, ENC1, BTN);// Encoder Pins and buttons 1388 | ENCBUTTON EB (ENC0, ENC1, BTN, MODEENC);// + Pino Pino Encoder Pin (silence. Input) 1389 | ENCBUTTON EB (ENC0, ENC1, BTN, MODEENC, MODEBTN);// + Pin mode buttons (silent. Input_pullup) 1390 | ENCBUTTON EB (ENC0, ENC1, BTN, MODEENC, MODEBTN, BTNlevel);// + button level (silence) 1391 | // template 1392 | ENCBUTTON EB;// Encoder Pins and buttons 1393 | ENCBUTTON EB (Modeenc);// + Pino Pino Encoder Pin (silence. Input) 1394 | ENCBUTTON EB (Modeenc, Modebtn);// + Pin mode buttons (silent. Input_pullup) 1395 | ENCBUTTON EB (Modeenc, Modebtn, Btnlevel);// + button level (silence) 1396 | 1397 | // button 1398 | Button b (pin);// PIN 1399 | Button b (PIN, Mode);// + Pin mode buttons (silent. Input_pullup) 1400 | Button B (PIN, Mode, Btnlevel);// + button level (silence) 1401 | // template 1402 | Buttont b;// PIN 1403 | Buttont b (mode);// + Pin mode buttons (silent. Input_pullup) 1404 | Buttont b (mode, btnlevel);// + button level (silence) 1405 | 1406 | // Encoder 1407 | ENCODER E (ENC0, ENC1);// Pines of Encoder 1408 | ENCODER E (ENC0, ENC1, Mode);// + Pino Pino Encoder Pin (silence. Input) 1409 | // template 1410 | Encodert e;// Pines of Encoder 1411 | Encodert E (Mode);// + Pino Pino Encoder Pin (silence. Input) 1412 | `` ` 1413 | 1414 | ### functions 1415 | |v2 |v3 | 1416 | | ------------- | ------------------------------------- 1417 | |`HELD ()` |`Hold ()` | 1418 | |`Hold ()` |`Holding ()` | 1419 | |`state ()` |`Pressing ()` | 1420 | |`setpins ()` |`Init ()` | 1421 | 1422 | - The procedure for indicating Pinov has changed (see DEMPLE above) 1423 | - `Clearflags ()` replaced by `Clear ()` (drop the flags of events) and `reset ()` (drop systemic flags of processing, finish processing) 1424 | 1425 | ### Logic of Work 1426 | In the V3, the functions of an event survey (Click, Turn ...) are not discarded immediately after their call - they are discarded at the next call `Tick ()`, thus retain their meaning in all subsequent challenges on the current iteration of the main cycle of the program.** Therefore, `tick ()` needs to be called only 1 time per cycle, otherwise there will be missions of actions! ** Read about thisabove. 1427 | 1428 | 1429 | ## Examples 1430 | The rest of the examples look at ** Examples **! 1431 |
1432 | Full demo encbutton 1433 | 1434 | `` `CPP 1435 | // #define eb_no_for // Disable Pressfor/Holdfor/StepFor support and Stepov counter (saves 2 bytes of RAM) 1436 | // #define eb_no_callback // Disable the event processor Attach (saves 2 bytes of RAM) 1437 | // #define eb_no_counter // Disable the enkoder counter (saves 4 bytes of RAM) 1438 | // #define EB_NO_BUFFER // Disable the buffer of the encoder (saves 1 byte of RAM) 1439 | 1440 | // #define eb_deb_time 50 // Timesout to darebells button (button) 1441 | // #define eb_click_time 500 // Clicks standstatics (button) 1442 | // #define eb_hold_time 600 // Maintenance Times (button) 1443 | // #define eb_step_time 200 // pulse retention rate (button) 1444 | // #define EB_FAST_TIME 30 // Quick turn Timesout (Encoder) 1445 | 1446 | #include 1447 | ENCBUTTON EB (2, 3, 4); 1448 | // ENCBUTTON EB (2, 3, 4, Input);// + Pino Pino mode 1449 | // ENCBUTTON EB (2, 3, 4, Input, Input_pullup);// + button pins mode 1450 | // ENCBUTTON EB (2, 3, 4, Input, Input_pullup, Low);// + button level 1451 | 1452 | VOID setup () { 1453 | Serial.Begin (115200); 1454 | 1455 | // shows the default values 1456 | eb.setbtnlevel (Low); 1457 | EB.SetClicktimeout (500); 1458 | eb.Setdebtimeout (50); 1459 | Eb.SetHoldtimeout (600); 1460 | eb.setsteptimeout (200); 1461 | 1462 | eb.setencreverse (0); 1463 | eb.setenctype (eb_step4_low); 1464 | eb.setfasttimeout (30); 1465 | 1466 | // throw the Encoder counter 1467 | eb.counter = 0; 1468 | } 1469 | 1470 | VOID loop () { 1471 | eb.tick (); 1472 | 1473 | // General rotation processing 1474 | if.turn ()) { 1475 | Serial.print ("Turn: Dir"); 1476 | Serial.print (eb.dir ()); 1477 | Serial.print (", fast"); 1478 | Serial.print (eb.fast ()); 1479 | Serial.print (", Hold"); 1480 | Serial.print (eb.pressing ()); 1481 | Serial.print (", Counter"); 1482 | Serial.print (eb.counter); 1483 | Serial.print (", clicks"); 1484 | Serial.println (eb.getClicks ()); 1485 | } 1486 | 1487 | // Turning rotation processing 1488 | if.left ()) serial.println ("Left"); 1489 | if.right ()) serial.println ("right"); 1490 | if.left ()) serial.println ("Lefth"); 1491 | if.righth ()) serial.println ("righth"); 1492 | 1493 | // button 1494 | if.press ()) serial.println ("Press"); 1495 | if.click ()) serial.println ("click"); 1496 | 1497 | if.release ()) { 1498 | Serial.println ("Release"); 1499 | 1500 | Serial.print ("Clicks:"); 1501 | Serial.print (eb.getClicks ()); 1502 | Serial.print (", stps:"); 1503 | Serial.print (eb.getsteps ()); 1504 | Serial.print (", Press for:"); 1505 | Serial.print (eb.pressfor ()); 1506 | Serial.print (", Hold for:"); 1507 | Serial.print (eb.holdfor ()); 1508 | Serial.print (", step for:"); 1509 | Serial.println (eb.stepfor ()); 1510 | } 1511 | 1512 | // States 1513 | // serial.println (eb.pressing ()); 1514 | // serial.println (eb.holding ()); 1515 | // serial.println (eb.busy ()); 1516 | // serial.println (eb.waiting ()); 1517 | 1518 | // Timesout 1519 | if (eb.timeout ()) serial.println ("Timeout!"); 1520 | 1521 | // Holding 1522 | if.hold ()) serial.println ("Hold"); 1523 | if.hold (3)) serial.println ("Hold 3"); 1524 | 1525 | // Impulse retention 1526 | if.step ()) serial.println ("step"); 1527 | if.step (3)) serial.println ("STEP 3"); 1528 | 1529 | // released after impulse deduction 1530 | if (eb.releastep ()) serial.println ("Release Step"); 1531 | if (eb.releastep (3)) serial.println ("Release Step 3"); 1532 | 1533 | // released after holding 1534 | if.releasehold ()) serial.println ("Release Hold"); 1535 | if (eb.releasehold (2)) serial.println ("Release Hold 2"); 1536 | 1537 | // Check for the number of clicks 1538 | if.hasclicks (3)) Serial.println ("Has 3 Clicks"); 1539 | 1540 | // Bring the number of clicks 1541 | if.hasclicks ()) { 1542 | Serial.print ("Has Clicks:"); 1543 | Serial.println (eb.getClicks ()); 1544 | } 1545 | } 1546 | `` ` 1547 |
1548 |
1549 | connection of the handler 1550 | 1551 | `` `CPP 1552 | #include 1553 | ENCBUTTON EB (2, 3, 4); 1554 | 1555 | Void callback () { 1556 | Serial.print ("callback:"); 1557 | switch (eb.action ()) { 1558 | Case eb_press: 1559 | Serial.println ("Press"); 1560 | Break; 1561 | Case eb_hold:serial.println ("Hold"); 1562 | Break; 1563 | Case eb_step: 1564 | Serial.println ("STEP"); 1565 | Break; 1566 | Case eb_release: 1567 | Serial.println ("Release"); 1568 | Break; 1569 | Case eb_click: 1570 | Serial.println ("click"); 1571 | Break; 1572 | Case eb_clicks: 1573 | Serial.print ("Clicks"); 1574 | Serial.println (eb.getClicks ()); 1575 | Break; 1576 | Case eb_turn: 1577 | Serial.print ("turn"); 1578 | Serial.print (eb.dir ()); 1579 | Serial.print (""); 1580 | Serial.print (eb.fast ()); 1581 | Serial.print (""); 1582 | Serial.println (eb.pressing ()); 1583 | Break; 1584 | Case eb_rel_hold: 1585 | Serial.println ("Release Hold"); 1586 | Break; 1587 | CASE EB_REL_HOLD_C: 1588 | Serial.print ("Release Hold Clicks"); 1589 | Serial.println (eb.getClicks ()); 1590 | Break; 1591 | CASE EB_REL_STEP: 1592 | Serial.println ("Release Step"); 1593 | Break; 1594 | CASE EB_REL_STEP_C: 1595 | Serial.print ("Release Step Clicks"); 1596 | Serial.println (eb.getClicks ()); 1597 | Break; 1598 | } 1599 | } 1600 | 1601 | VOID setup () { 1602 | Serial.Begin (115200); 1603 | eb.attach (callback); 1604 | } 1605 | 1606 | VOID loop () { 1607 | eb.tick (); 1608 | } 1609 | `` ` 1610 |
1611 |
1612 | All types of buttons 1613 | 1614 | `` `CPP 1615 | #include 1616 | 1617 | Button BTN (4); 1618 | Buttont <5> btnt; 1619 | Virtbutton BTNV; 1620 | 1621 | VOID setup () { 1622 | Serial.Begin (115200); 1623 | } 1624 | 1625 | VOID loop () { 1626 | // Button 1627 | btn.tick (); 1628 | if (btn.click ()) serial.println ("btn click"); 1629 | 1630 | // Buttont 1631 | btnt.tick (); 1632 | if (btnt.click ()) serial.println ("BTNT CLICK"); 1633 | 1634 | // virtbutton 1635 | btnv.tick (! DigitalRead (4));// transmit logical value 1636 | if (btnv.click ()) serial.println ("btnv click"); 1637 | } 1638 | `` ` 1639 |
1640 |
1641 | All types of encoder 1642 | 1643 | `` `CPP 1644 | #include 1645 | 1646 | ENCODER ENC (2, 3); 1647 | ENCODERT <5, 6> ENCT; 1648 | Virtencoder encv; 1649 | 1650 | VOID setup () { 1651 | Serial.Begin (115200); 1652 | } 1653 | 1654 | VOID loop () { 1655 | // The survey is the same for everyone, 3 ways: 1656 | 1657 | // 1 1658 | // Tick will return 1 or -1, then this is a step 1659 | if (enc.tick ()) serial.println (enc.counter); 1660 | 1661 | // 2 1662 | // can be interviewed through turn () 1663 | enct.tick (); 1664 | if (enct.turn ()) serial.println (enct.dir ()); 1665 | 1666 | // 3 1667 | // you can not use survey functions, but get the direction directly 1668 | int8_t v = encv.tick (DigitalRead (2), DigitalRead (3)); 1669 | if (v) serial.println (v);// Derive 1 or -1 1670 | } 1671 | `` ` 1672 |
1673 | 1674 | 1675 | ## versions 1676 |
1677 | Old 1678 | 1679 | - V1.1 - Pullap separately by the method 1680 | - V1.2 - You can transfer the parameter input_pullup / input (silent) to the designer 1681 | - V1.3 - Virtual clamping of the encoder button is made into a separate function + minor improvements 1682 | - V1.4 - Processing of pressing and releasing the button 1683 | - v1.5 - added virtual mode 1684 | - V1.6 - Optimization of work in interruption 1685 | - V1.6.1 - Saching by default Input_pullup 1686 | - V1.7 - a large memory optimization, remade Fastio 1687 | - V1.8 - Individual tuning of the TIMUUT Maintenance of the button (was common at all) 1688 | - v1.8.1 - removed Fastio 1689 | - v1.9 - added a separate development of a pressed turn and a request for direction 1690 | - V1.10 - improved ReleASDE processing, eased the weight in callback and corrected the bugs 1691 | - V1.11 - even more than any optimization + setting button level 1692 | - V1.11.1 - Digispark compatibility 1693 | - V1.12 - added a more accurate algorithm of enkoder Eb_better_enc 1694 | - V1.13 - Added experimental ENCBUTTON2 1695 | - V1.14 - added ReleaseStep ().The release of the button is included in the debate 1696 | - v1.15 - added Setpins () for Encbutton2 1697 | - V1.16 - added EB_HALFSTEP_Enc mode for hemisphere encoders 1698 | - v1.17 - added STEP with preliminary clicks 1699 | - V1.18 - We do not consider clicks after the activation of STEP.Hold () and Held () can also take preliminary clicks.Redistributed and improved debate 1700 | - V1.18.1 - Fixed error in ReleaseStep () (did not return the result) 1701 | - V1.18.2 - Fix Compiler Warnings 1702 | - V1.19 - speed optimization, reduced weight in SRAM 1703 | - v1.19.1 - still a bit increased performance 1704 | - v1.19.2 - not yetCranberries increased a lot of performance, thanks xray3d 1705 | - v1.19.3 - made a high level of the default button in virtual mode 1706 | - V1.19.4 - Fix Encbutton2 1707 | - V1.20 - Critical error is fixed in Encbutton2 1708 | - V1.21 - EB_HALFSTEP_ENC now works for a normal mode 1709 | - V1.22 - Improved EB_HALFSTEP_Enc for a normal mode 1710 | - V1.23 - Getdir () replaced with DIR () 1711 | - V2.0 1712 | - The eb_better_enc algorithm is optimized and set by default, the define eb_better_enc is abolished 1713 | - added setenctype () to configure the type of encoder from the program, the define EB_HALFSTEP_ENC is abolished 1714 | - added Setencreverse () to change the direction of the encoder from the program 1715 | - added setteptimeout () to set the period of impulse deduction, the define EB_STEP is abolished 1716 | - Small improvements and optimization 1717 |
1718 | 1719 | - V3.0 1720 | - The library is rewritten from scratch, with previous versions is incompatible! 1721 | - completely different initialization of the object 1722 | -renamed: Hold ()-> Holding (), HELD ()-> HOLD () 1723 | - Optimization of Flash memory: the library weighs less, in some scenarios - by several kilobytes 1724 | - optimization of the speed of code execution, including in interruption 1725 | - several bytes less than RAM, several optimization levels to choose from 1726 | - a simpler, understandable and convenient use 1727 | - more readable source code 1728 | - Breaking into classes for use in different scenarios 1729 | - new functions, capabilities and handlers for the button and encoder 1730 | - Encoder's buffer in interruption 1731 | - native processing of two simultaneously pressed buttons as a third button 1732 | - support of 4 types of encoder 1733 | - The documentation is rewritten 1734 | - Encbutton is now replacing Gyverlibs/Virtualbutton (archived) 1735 | - V3.1 1736 | - The initialization of the button is expanded 1737 | - removed Holdencbutton () and Toggleencbutton () 1738 | - added Turnh () 1739 | - Optimized the interruptions of encoder, added Setencisr () 1740 | - Buerization of the direction and quick turn 1741 | - strongly optimized the speed of Action () (general processor) 1742 | - Added connection of the external function-processor of events 1743 | - Added button processing in interruption - Pressisr () 1744 | - V3.2 1745 | - Added the functions of TickRaW () and Clear () for all classes.Allows for separate processing (see document) 1746 | - improved processing button using interruptions 1747 | - V3.3 1748 | - Added functions of receiving PressFor (), HoldFor (), StepFor () (disconnected) 1749 | - Added meter of the steps Getsteps () (disconnected) 1750 | - V3.4 1751 | - access to the click counter during a pressed turn 1752 | - Added function Detach () 1753 | - V3.5 1754 | - added dependence of Gyverio (accelerated Pino survey) 1755 | - added the opportunity to set your pharmacy and pine reading functions 1756 | - V3.5.2 1757 | - Optimization 1758 | - Simplified replacement of custom functions 1759 | - Fixed a compilation error when using a library in several .cpp files 1760 | - V3.5.3 1761 | - Added the number of clicks to the Press/Release/Click/Pressing poll 1762 | - V3.5.5 - Collback based on the STD :: Function for ESP 1763 | 1764 | 1765 | ## bugs and feedback 1766 | Create ** Issue ** when you find the bugs, and better immediately write to the mail [alex@alexgyver.ru] (mailto: alex@alexgyver.ru) 1767 | The library is open for refinement and your ** pull Request ** 'ow! 1768 | 1769 | When reporting about bugs or incorrect work of the library, it is necessary to indicate: 1770 | - The version of the library 1771 | - What is MK used 1772 | - SDK version (for ESP) 1773 | - version of Arduino ide 1774 | - whether the built -in examples work correctly, in which the functions and designs are used, leading to a bug in your code 1775 | - what code has been loaded, what work was expected from it and how it works in reality 1776 | - Ideally, attach the minimum code in which the bug is observed.Not a canvas of a thousand lines, but a minimum code -------------------------------------------------------------------------------- /doc/btn_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/btn_scheme.png -------------------------------------------------------------------------------- /doc/click.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/click.gif -------------------------------------------------------------------------------- /doc/encAli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/encAli.png -------------------------------------------------------------------------------- /doc/enc_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/enc_scheme.png -------------------------------------------------------------------------------- /doc/enc_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/enc_type.png -------------------------------------------------------------------------------- /doc/hold.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/hold.gif -------------------------------------------------------------------------------- /doc/step.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyverLibs/EncButton/f15946cfa649fa66a39d1ee196a782dbee958e73/doc/step.gif -------------------------------------------------------------------------------- /examples/callback/callback.ino: -------------------------------------------------------------------------------- 1 | // опрос событий через функцию-обработчик 2 | 3 | #include 4 | #include 5 | 6 | EncButton eb(2, 3, 4); 7 | 8 | void cb() { 9 | // здесь EB_self - указатель на сам объект 10 | 11 | Serial.print("callback: "); 12 | switch (eb.action()) { 13 | case EB_PRESS: 14 | Serial.println("press"); 15 | break; 16 | case EB_HOLD: 17 | Serial.println("hold"); 18 | break; 19 | case EB_STEP: 20 | Serial.println("step"); 21 | break; 22 | case EB_RELEASE: 23 | Serial.print("release. steps: "); 24 | Serial.print(eb.getSteps()); 25 | Serial.print(", press for: "); 26 | Serial.print(eb.pressFor()); 27 | Serial.print(", hold for: "); 28 | Serial.print(eb.holdFor()); 29 | Serial.print(", step for: "); 30 | Serial.println(eb.stepFor()); 31 | break; 32 | case EB_CLICK: 33 | Serial.println("click"); 34 | break; 35 | case EB_CLICKS: 36 | Serial.print("clicks "); 37 | Serial.println(eb.getClicks()); 38 | break; 39 | case EB_TURN: 40 | Serial.print("turn "); 41 | Serial.print(eb.dir()); 42 | Serial.print(" "); 43 | Serial.print(eb.fast()); 44 | Serial.print(" "); 45 | Serial.println(eb.pressing()); 46 | break; 47 | case EB_REL_HOLD: 48 | Serial.println("release hold"); 49 | break; 50 | case EB_REL_HOLD_C: 51 | Serial.print("release hold clicks "); 52 | Serial.println(eb.getClicks()); 53 | break; 54 | case EB_REL_STEP: 55 | Serial.println("release step"); 56 | break; 57 | case EB_REL_STEP_C: 58 | Serial.print("release step clicks "); 59 | Serial.println(eb.getClicks()); 60 | break; 61 | default: 62 | Serial.println(); 63 | } 64 | } 65 | 66 | void setup() { 67 | Serial.begin(115200); 68 | eb.attach(cb); 69 | } 70 | 71 | void loop() { 72 | eb.tick(); 73 | } -------------------------------------------------------------------------------- /examples/callback2/callback2.ino: -------------------------------------------------------------------------------- 1 | // опрос событий через функцию-обработчик 2 | 3 | #include 4 | #include 5 | 6 | EncButton eb(2, 3, 4); 7 | 8 | void cb() { 9 | // здесь EB_self - указатель на сам объект 10 | 11 | Serial.print("callback: "); 12 | switch (eb.getAction()) { 13 | case EBAction::Press: 14 | Serial.println("press"); 15 | break; 16 | case EBAction::Hold: 17 | Serial.println("hold"); 18 | break; 19 | case EBAction::Step: 20 | Serial.println("step"); 21 | break; 22 | case EBAction::Release: 23 | Serial.print("release. steps: "); 24 | Serial.print(eb.getSteps()); 25 | Serial.print(", press for: "); 26 | Serial.print(eb.pressFor()); 27 | Serial.print(", hold for: "); 28 | Serial.print(eb.holdFor()); 29 | Serial.print(", step for: "); 30 | Serial.println(eb.stepFor()); 31 | break; 32 | case EBAction::Click: 33 | Serial.println("click"); 34 | break; 35 | case EBAction::Clicks: 36 | Serial.print("clicks "); 37 | Serial.println(eb.getClicks()); 38 | break; 39 | case EBAction::Turn: 40 | Serial.print("turn "); 41 | Serial.print(eb.dir()); 42 | Serial.print(" "); 43 | Serial.print(eb.fast()); 44 | Serial.print(" "); 45 | Serial.println(eb.pressing()); 46 | break; 47 | case EBAction::ReleaseHold: 48 | Serial.println("release hold"); 49 | break; 50 | case EBAction::ReleaseHoldClicks: 51 | Serial.print("release hold clicks "); 52 | Serial.println(eb.getClicks()); 53 | break; 54 | case EBAction::ReleaseStep: 55 | Serial.println("release step"); 56 | break; 57 | case EBAction::ReleaseStepClicks: 58 | Serial.print("release step clicks "); 59 | Serial.println(eb.getClicks()); 60 | break; 61 | case EBAction::Timeout: 62 | Serial.println("timeout"); 63 | break; 64 | default: break; 65 | } 66 | } 67 | 68 | void setup() { 69 | Serial.begin(115200); 70 | eb.attach(cb); 71 | } 72 | 73 | void loop() { 74 | eb.tick(); 75 | } -------------------------------------------------------------------------------- /examples/demo/demo.ino: -------------------------------------------------------------------------------- 1 | // полное демо 2 | #include 3 | // #define EB_NO_FOR // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки) 4 | // #define EB_NO_CALLBACK // отключить обработчик событий attach (экономит 2 байта оперативки) 5 | // #define EB_NO_COUNTER // отключить счётчик энкодера (экономит 4 байта оперативки) 6 | // #define EB_NO_BUFFER // отключить буферизацию энкодера (экономит 1 байт оперативки) 7 | 8 | // #define EB_DEB_TIME 50 // таймаут гашения дребезга кнопки (кнопка) 9 | // #define EB_CLICK_TIME 500 // таймаут ожидания кликов (кнопка) 10 | // #define EB_HOLD_TIME 600 // таймаут удержания (кнопка) 11 | // #define EB_STEP_TIME 200 // таймаут импульсного удержания (кнопка) 12 | // #define EB_FAST_TIME 30 // таймаут быстрого поворота (энкодер) 13 | // #define EB_TOUT_TIME 1000 // таймаут действия (кнопка и энкодер) 14 | 15 | #include 16 | EncButton eb(2, 3, 4); 17 | // EncButton eb(2, 3, 4, INPUT); // + режим пинов энкодера 18 | // EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP); // + режим пинов кнопки 19 | 20 | void setup() { 21 | Serial.begin(115200); 22 | 23 | // показаны значения по умолчанию 24 | eb.setBtnLevel(LOW); 25 | eb.setClickTimeout(500); 26 | eb.setDebTimeout(50); 27 | eb.setHoldTimeout(600); 28 | eb.setStepTimeout(200); 29 | eb.setTimeout(1000); 30 | 31 | eb.setEncReverse(0); 32 | eb.setEncType(EB_STEP4_LOW); 33 | eb.setFastTimeout(30); 34 | 35 | // сбросить счётчик энкодера 36 | eb.counter = 0; 37 | } 38 | 39 | void loop() { 40 | eb.tick(); 41 | 42 | // обработка поворота общая 43 | if (eb.turn()) { 44 | Serial.print("turn: dir "); 45 | Serial.print(eb.dir()); 46 | Serial.print(", fast "); 47 | Serial.print(eb.fast()); 48 | Serial.print(", hold "); 49 | Serial.print(eb.pressing()); 50 | Serial.print(", counter "); 51 | Serial.print(eb.counter); 52 | Serial.print(", clicks "); 53 | Serial.println(eb.getClicks()); 54 | } 55 | 56 | // обработка поворота раздельная 57 | if (eb.left()) Serial.println("left"); 58 | if (eb.right()) Serial.println("right"); 59 | if (eb.leftH()) Serial.println("leftH"); 60 | if (eb.rightH()) Serial.println("rightH"); 61 | 62 | // кнопка 63 | if (eb.press()) Serial.println("press"); 64 | if (eb.click()) Serial.println("click"); 65 | 66 | if (eb.release()) { 67 | Serial.println("release"); 68 | 69 | Serial.print("clicks: "); 70 | Serial.print(eb.getClicks()); 71 | Serial.print(", steps: "); 72 | Serial.print(eb.getSteps()); 73 | Serial.print(", press for: "); 74 | Serial.print(eb.pressFor()); 75 | Serial.print(", hold for: "); 76 | Serial.print(eb.holdFor()); 77 | Serial.print(", step for: "); 78 | Serial.println(eb.stepFor()); 79 | } 80 | 81 | // состояния 82 | // Serial.println(eb.pressing()); 83 | // Serial.println(eb.holding()); 84 | // Serial.println(eb.busy()); 85 | // Serial.println(eb.waiting()); 86 | 87 | // таймаут 88 | if (eb.timeout()) Serial.println("timeout!"); 89 | 90 | // удержание 91 | if (eb.hold()) Serial.println("hold"); 92 | if (eb.hold(3)) Serial.println("hold 3"); 93 | 94 | // импульсное удержание 95 | if (eb.step()) Serial.println("step"); 96 | if (eb.step(3)) Serial.println("step 3"); 97 | 98 | // отпущена после импульсного удержания 99 | if (eb.releaseStep()) Serial.println("release step"); 100 | if (eb.releaseStep(3)) Serial.println("release step 3"); 101 | 102 | // отпущена после удержания 103 | if (eb.releaseHold()) Serial.println("release hold"); 104 | if (eb.releaseHold(2)) Serial.println("release hold 2"); 105 | 106 | // проверка на количество кликов 107 | if (eb.hasClicks(3)) Serial.println("has 3 clicks"); 108 | 109 | // вывести количество кликов 110 | if (eb.hasClicks()) { 111 | Serial.print("has clicks: "); 112 | Serial.println(eb.getClicks()); 113 | } 114 | } -------------------------------------------------------------------------------- /examples/double/double.ino: -------------------------------------------------------------------------------- 1 | // опрос одновременного нажатия двух кнопок как нажатия третьей кнопки (виртуальной) 2 | // библиотека сама сбросит события с первых двух кнопок, если они нажаты вместе 3 | 4 | #include 5 | #include 6 | 7 | Button b0(4); 8 | Button b1(5); 9 | VirtButton b2; // виртуальная 10 | 11 | void setup() { 12 | Serial.begin(115200); 13 | } 14 | 15 | void loop() { 16 | b0.tick(); 17 | b1.tick(); 18 | 19 | // обработка одновременного нажатия двух кнопок 20 | b2.tick(b0, b1); 21 | 22 | if (b0.click()) Serial.println("b0 click"); 23 | if (b1.click()) Serial.println("b1 click"); 24 | 25 | if (b2.click()) Serial.println("b0+b1 click"); 26 | if (b2.step()) Serial.println("b0+b1 step"); 27 | } -------------------------------------------------------------------------------- /examples/doubleCallback/doubleCallback.ino: -------------------------------------------------------------------------------- 1 | // опрос одновременного нажатия двух кнопок как нажатия третьей кнопки 2 | // с корректным вызовом обработчиков 3 | 4 | #include 5 | #include 6 | 7 | Button b0(4); 8 | Button b1(5); 9 | MultiButton b12; // виртуальная 10 | 11 | void decode(uint16_t action) { 12 | switch (action) { 13 | case EB_PRESS: 14 | Serial.println("press"); 15 | break; 16 | case EB_STEP: 17 | Serial.println("step"); 18 | break; 19 | case EB_RELEASE: 20 | Serial.println("release"); 21 | break; 22 | case EB_CLICK: 23 | Serial.println("click"); 24 | break; 25 | case EB_CLICKS: 26 | Serial.println("clicks"); 27 | break; 28 | case EB_REL_HOLD: 29 | Serial.println("release hold"); 30 | break; 31 | case EB_REL_HOLD_C: 32 | Serial.println("release hold clicks "); 33 | break; 34 | case EB_REL_STEP: 35 | Serial.println("release step"); 36 | break; 37 | case EB_REL_STEP_C: 38 | Serial.println("release step clicks "); 39 | break; 40 | case EB_TIMEOUT: 41 | Serial.println("timeout"); 42 | break; 43 | } 44 | } 45 | 46 | void setup() { 47 | Serial.begin(115200); 48 | 49 | // обработчики 50 | b0.attach([]() { 51 | uint16_t action = static_cast(EB_self)->action(); 52 | if (action != EB_HOLD) Serial.print("b0: "); 53 | decode(action); 54 | }); 55 | 56 | b1.attach([]() { 57 | uint16_t action = static_cast(EB_self)->action(); 58 | if (action != EB_HOLD) Serial.print("b1: "); 59 | decode(action); 60 | }); 61 | 62 | b12.attach([]() { 63 | uint16_t action = static_cast(EB_self)->action(); 64 | if (action != EB_HOLD) Serial.print("b0+b1: "); 65 | decode(action); 66 | }); 67 | } 68 | 69 | void loop() { 70 | // обработка одновременного нажатия двух кнопок 71 | // обрабатываются все три кнопки 72 | b12.tick(b0, b1); 73 | 74 | // или вручную 75 | if (b0.click()) Serial.println("b0 click"); 76 | if (b1.click()) Serial.println("b1 click"); 77 | if (b12.click()) Serial.println("b0+b1 click"); 78 | } -------------------------------------------------------------------------------- /examples/isr/isr.ino: -------------------------------------------------------------------------------- 1 | // энкодер и прерывания 2 | #include 3 | #include 4 | EncButton eb(2, 3, 4); 5 | 6 | /* 7 | // esp8266/esp32 8 | IRAM_ATTR void isr() { 9 | eb.tickISR(); 10 | } 11 | */ 12 | 13 | void isr() { 14 | eb.tickISR(); 15 | } 16 | 17 | void setup() { 18 | Serial.begin(115200); 19 | attachInterrupt(0, isr, CHANGE); 20 | attachInterrupt(1, isr, CHANGE); 21 | eb.setEncISR(true); 22 | } 23 | 24 | void loop() { 25 | eb.tick(); 26 | 27 | if (eb.turn()) { 28 | Serial.print("turn: dir "); 29 | Serial.print(eb.dir()); 30 | Serial.print(", fast "); 31 | Serial.print(eb.fast()); 32 | Serial.print(", hold "); 33 | Serial.print(eb.pressing()); 34 | Serial.print(", counter "); 35 | Serial.println(eb.counter); 36 | } 37 | 38 | delay(100); // имитация загруженной программы 39 | } -------------------------------------------------------------------------------- /examples/one_button_3_var/one_button_3_var.ino: -------------------------------------------------------------------------------- 1 | #include 2 | // используем одну КНОПКУ для удобного изменения трёх переменных 3 | // первая - один клик, затем удержание (нажал-отпустил-нажал-держим) 4 | // вторая - два клика, затем удержание 5 | // третья - три клика, затем удержание 6 | // смотри монитор порта 7 | 8 | #include 9 | Button btn(4); 10 | 11 | // переменные для изменения 12 | int val_a, val_b, val_c; 13 | 14 | // шаги изменения (signed) 15 | int8_t step_a = 1; 16 | int8_t step_b = 5; 17 | int8_t step_c = 10; 18 | 19 | void setup() { 20 | Serial.begin(115200); 21 | } 22 | 23 | void loop() { 24 | btn.tick(); 25 | 26 | // передаём количество предварительных кликов 27 | if (btn.step(1)) { 28 | val_a += step_a; 29 | Serial.print("val_a: "); 30 | Serial.println(val_a); 31 | } 32 | if (btn.step(2)) { 33 | val_b += step_b; 34 | Serial.print("val_b: "); 35 | Serial.println(val_b); 36 | } 37 | if (btn.step(3)) { 38 | val_c += step_c; 39 | Serial.print("val_c: "); 40 | Serial.println(val_c); 41 | } 42 | 43 | // разворачиваем шаг для изменения в обратную сторону 44 | // передаём количество предварительных кликов 45 | if (btn.releaseStep(1)) step_a = -step_a; 46 | if (btn.releaseStep(2)) step_b = -step_b; 47 | if (btn.releaseStep(3)) step_c = -step_c; 48 | } -------------------------------------------------------------------------------- /examples/one_enc_3_var/one_enc_3_var.ino: -------------------------------------------------------------------------------- 1 | // управление тремя переменными при помощи энкодера: 2 | // - нащёлкай кнопкой нужную переменную (1, 2 или 3 клика) 3 | // - 1 переменная просто изменяется с постоянным шагом 4 | // - 2 переменная: шаг 1, при зажатой кнопке - шаг 5 5 | // - 3 переменная: шаг 1, при быстром вращении - шаг 5 6 | 7 | #include 8 | #include 9 | EncButton eb(2, 3, 4); 10 | 11 | int var1 = 0; 12 | int var2 = 0; 13 | int var3 = 0; 14 | uint8_t select = 1; // выбранная переменная 15 | 16 | void setup() { 17 | Serial.begin(115200); 18 | } 19 | 20 | void loop() { 21 | eb.tick(); 22 | 23 | // выбор переменной для изменения 24 | if (eb.hasClicks()) { 25 | select = eb.getClicks(); 26 | Serial.println(String("Select: ") + select); 27 | } 28 | 29 | if (eb.turn()) { 30 | // меняем переменную 31 | switch (select) { 32 | case 1: 33 | // изменение с шагом 5 34 | var1 += 5 * eb.dir(); 35 | break; 36 | case 2: 37 | // изменение с шагом 1, при зажатой кнопке шаг 5 38 | var2 += (eb.pressing() ? 5 : 1) * eb.dir(); 39 | break; 40 | case 3: 41 | // изменение с шагом 1, при быстром вращении шаг 5 42 | var3 += (eb.fast() ? 5 : 1) * eb.dir(); 43 | break; 44 | } 45 | Serial.println(String("vars ") + var1 + ',' + var2 + ',' + var3); 46 | } 47 | } -------------------------------------------------------------------------------- /examples/virtual_buttons/virtual_AnalogKey/virtual_AnalogKey.ino: -------------------------------------------------------------------------------- 1 | // пример работы в виртуальном режиме совместно с библиотекой AnalogKey 2 | // https://github.com/GyverLibs/AnalogKey 3 | 4 | #include 5 | VirtButton btn0; 6 | VirtButton btn1; 7 | 8 | #include 9 | // создаём массив значений сигналов с кнопок 10 | int16_t sigs[16] = { 11 | 1023, 927, 856, 783, 12 | 671, 632, 590, 560, 13 | 504, 480, 455, 440, 14 | 399, 319, 255, 230 15 | }; 16 | 17 | // указываем пин, количество кнопок и массив значений 18 | AnalogKey keys; 19 | 20 | void setup() { 21 | Serial.begin(9600); 22 | } 23 | 24 | void loop() { 25 | btn0.tick(keys.status(0)); 26 | btn1.tick(keys.status(1)); 27 | 28 | // забираем действия с кнопок 29 | if (btn0.click()) Serial.println("click 0"); 30 | if (btn0.hold()) Serial.println("hold 0"); 31 | 32 | if (btn1.press()) Serial.println("press 1"); 33 | if (btn1.step()) Serial.println("step 1"); 34 | } 35 | -------------------------------------------------------------------------------- /examples/virtual_buttons/virtual_SimpleKeypad/virtual_SimpleKeypad.ino: -------------------------------------------------------------------------------- 1 | // пример работы в виртуальном режиме совместно с библиотекой SimpleKeypad 2 | // https://github.com/maximebohrer/SimpleKeypad 3 | 4 | #include 5 | VirtButton btn0; 6 | VirtButton btn1; 7 | 8 | // пины подключения (по порядку штекера) 9 | byte colPins[] = {7, 6, 5, 4}; 10 | byte rowPins[] = {11, 10, 9, 8}; 11 | 12 | // массив имён кнопок 13 | char keys[4][4] = { 14 | {'1', '2', '3', 'A'}, 15 | {'4', '5', '6', 'B'}, 16 | {'7', '8', '9', 'C'}, 17 | {'*', '0', '#', 'D'} 18 | }; 19 | 20 | #include 21 | SimpleKeypad pad((char*)keys, rowPins, colPins, 4, 4); 22 | 23 | void setup() { 24 | Serial.begin(9600); 25 | btn0.setDebTimeout(0); 26 | btn1.setDebTimeout(0); 27 | } 28 | 29 | void loop() { 30 | btn0.tick(0); 31 | btn1.tick(0); 32 | 33 | // тикаем все кнопки, передавая сравнение с кодом кнопки в цикле 34 | // делаем это по таймеру, чтобы не опрашивать клавиатуру постоянно 35 | static uint32_t tmr; 36 | if (millis() - tmr >= 10) { 37 | tmr = millis(); 38 | char key = pad.scan(); 39 | btn0.tick(key == '1'); 40 | btn1.tick(key == '2'); 41 | } 42 | 43 | // забираем действия с кнопок 44 | if (btn0.click()) Serial.println("click 0"); 45 | if (btn0.hold()) Serial.println("hold 0"); 46 | 47 | if (btn1.press()) Serial.println("press 1"); 48 | if (btn1.step()) Serial.println("step 1"); 49 | } 50 | -------------------------------------------------------------------------------- /examples/virtual_buttons/virtual_SimpleKeypad_array/virtual_SimpleKeypad_array.ino: -------------------------------------------------------------------------------- 1 | // пример работы в виртуальном режиме совместно с библиотекой SimpleKeypad 2 | // https://github.com/maximebohrer/SimpleKeypad 3 | // передаём EncButton сразу всю клавиатуру через массивы и циклы 4 | 5 | #include 6 | VirtButton btn[16]; 7 | 8 | // пины подключения (по порядку штекера) 9 | byte colPins[] = {7, 6, 5, 4}; 10 | byte rowPins[] = {11, 10, 9, 8}; 11 | 12 | // массив имён кнопок 13 | char keys[4][4] = { 14 | {'1', '2', '3', 'A'}, 15 | {'4', '5', '6', 'B'}, 16 | {'7', '8', '9', 'C'}, 17 | {'*', '0', '#', 'D'} 18 | }; 19 | 20 | #include 21 | SimpleKeypad pad((char*)keys, rowPins, colPins, 4, 4); 22 | 23 | void setup() { 24 | Serial.begin(9600); 25 | for (int i = 0; i < 16; i++) btn[i].setDebTimeout(0); 26 | } 27 | 28 | void loop() { 29 | for (int i = 0; i < 16; i++) btn[i].tick(0); 30 | 31 | // массово тикаем все кнопки, передавая сравнение с кодом кнопки в цикле 32 | // делаем это по таймеру, чтобы не опрашивать клавиатуру постоянно 33 | static uint32_t tmr; 34 | if (millis() - tmr >= 10) { 35 | tmr = millis(); 36 | char key = pad.scan(); 37 | char* keysPtr = (char*)keys; // указатель для удобства опроса 38 | for (int i = 0; i < 16; i++) btn[i].tick(key == keysPtr[i]); 39 | } 40 | 41 | // забираем действия с кнопок 42 | if (btn[0].click()) Serial.println("click 0"); 43 | if (btn[0].hold()) Serial.println("hold 0"); 44 | 45 | if (btn[1].press()) Serial.println("press 1"); 46 | if (btn[1].step()) Serial.println("step 1"); 47 | } 48 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For EncButton 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | EncButton KEYWORD1 9 | EncButtonT KEYWORD1 10 | VirtEncButton KEYWORD1 11 | Button KEYWORD1 12 | ButtonT KEYWORD1 13 | VirtEncoder KEYWORD1 14 | VirtButton KEYWORD1 15 | MultiButton KEYWORD1 16 | 17 | EB_NO_COUNTER KEYWORD1 18 | EB_NO_BUFFER KEYWORD1 19 | EB_NO_CALLBACK KEYWORD1 20 | EB_NO_FOR KEYWORD1 21 | 22 | EB_DEB_TIME KEYWORD1 23 | EB_CLICK_TIME KEYWORD1 24 | EB_HOLD_TIME KEYWORD1 25 | EB_STEP_TIME KEYWORD1 26 | EB_FAST_TIME KEYWORD1 27 | 28 | ####################################### 29 | # Methods and Functions (KEYWORD2) 30 | ####################################### 31 | 32 | EB_read KEYWORD2 33 | EB_uptime KEYWORD2 34 | EB_mode KEYWORD2 35 | 36 | setHoldTimeout KEYWORD2 37 | setStepTimeout KEYWORD2 38 | setClickTimeout KEYWORD2 39 | setDebTimeout KEYWORD2 40 | setTimeout KEYWORD2 41 | setBtnLevel KEYWORD2 42 | reset KEYWORD2 43 | clear KEYWORD2 44 | press KEYWORD2 45 | release KEYWORD2 46 | click KEYWORD2 47 | pressing KEYWORD2 48 | hold KEYWORD2 49 | holding KEYWORD2 50 | step KEYWORD2 51 | hasClicks KEYWORD2 52 | getClicks KEYWORD2 53 | getSteps KEYWORD2 54 | releaseHold KEYWORD2 55 | releaseStep KEYWORD2 56 | releaseHoldStep KEYWORD2 57 | timeout KEYWORD2 58 | timeoutState KEYWORD2 59 | waiting KEYWORD2 60 | busy KEYWORD2 61 | action KEYWORD2 62 | getAction KEYWORD2 63 | attach KEYWORD2 64 | detach KEYWORD2 65 | pressISR KEYWORD2 66 | pressFor KEYWORD2 67 | holdFor KEYWORD2 68 | stepFor KEYWORD2 69 | 70 | setEncReverse KEYWORD2 71 | setEncType KEYWORD2 72 | initEnc KEYWORD2 73 | 74 | setFastTimeout KEYWORD2 75 | setEncISR KEYWORD2 76 | turn KEYWORD2 77 | turnH KEYWORD2 78 | right KEYWORD2 79 | left KEYWORD2 80 | rightH KEYWORD2 81 | leftH KEYWORD2 82 | encHolding KEYWORD2 83 | dir KEYWORD2 84 | fast KEYWORD2 85 | 86 | pollEnc KEYWORD2 87 | init KEYWORD2 88 | tickRaw KEYWORD2 89 | tickISR KEYWORD2 90 | tick KEYWORD2 91 | readBtn KEYWORD2 92 | readEnc KEYWORD2 93 | 94 | ####################################### 95 | # Constants (LITERAL1) 96 | ####################################### 97 | EB_PRESS LITERAL1 98 | EB_HOLD LITERAL1 99 | EB_STEP LITERAL1 100 | EB_RELEASE LITERAL1 101 | EB_CLICK LITERAL1 102 | EB_CLICKS LITERAL1 103 | EB_TURN LITERAL1 104 | EB_REL_HOLD LITERAL1 105 | EB_REL_HOLD_C LITERAL1 106 | EB_REL_STEP LITERAL1 107 | EB_REL_STEP_C LITERAL1 108 | EB_TIMEOUT LITERAL1 109 | 110 | EB_STEP4_LOW LITERAL1 111 | EB_STEP4_HIGH LITERAL1 112 | EB_STEP2 LITERAL1 113 | EB_STEP1 LITERAL1 114 | 115 | None LITERAL1 116 | Press LITERAL1 117 | Hold LITERAL1 118 | Step LITERAL1 119 | Release LITERAL1 120 | Click LITERAL1 121 | Clicks LITERAL1 122 | Turn LITERAL1 123 | ReleaseHold LITERAL1 124 | ReleaseHoldClicks LITERAL1 125 | ReleaseStep LITERAL1 126 | ReleaseStepClicks LITERAL1 127 | Timeout LITERAL1 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=EncButton 2 | version=3.7.3 3 | author=AlexGyver 4 | maintainer=AlexGyver 5 | sentence=Light and powerful library for button and encoder operation for Arduino 6 | paragraph=Debounce, click count, hold, step hold mode and many more. Maximum possibilities for button and encoder 7 | category=Sensors 8 | url=https://github.com/GyverLibs/EncButton 9 | architectures=* 10 | depends=GyverIO -------------------------------------------------------------------------------- /src/EncButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "core/Button.h" 5 | #include "core/EncButton.h" 6 | #include "core/Encoder.h" 7 | #include "core/MultiButton.h" 8 | #include "core/VirtButton.h" 9 | #include "core/VirtEncButton.h" 10 | #include "core/VirtEncoder.h" -------------------------------------------------------------------------------- /src/core/Button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "VirtButton.h" 5 | #include "io.h" 6 | 7 | // ============= VAR PIN ============= 8 | class Button : public VirtButton { 9 | public: 10 | Button(uint8_t npin = 0, uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) { 11 | init(npin, mode, btnLevel); 12 | } 13 | 14 | // указать пин и его режим работы 15 | void init(uint8_t npin = 0, uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) { 16 | pin = npin; 17 | EB_mode(pin, mode); 18 | setBtnLevel(btnLevel); 19 | } 20 | 21 | // прочитать текущее значение кнопки (без дебаунса) 22 | bool read() { 23 | return EB_read(pin) ^ bf.read(EB_INV); 24 | } 25 | 26 | // функция обработки, вызывать в loop 27 | bool tick() { 28 | return VirtButton::tick(EB_read(pin)); 29 | } 30 | 31 | // обработка кнопки без сброса событий и вызова коллбэка 32 | bool tickRaw() { 33 | return VirtButton::tickRaw(EB_read(pin)); 34 | } 35 | 36 | private: 37 | uint8_t pin; 38 | }; 39 | 40 | // ============= TEMPLATE PIN ============= 41 | template 42 | class ButtonT : public VirtButton { 43 | public: 44 | ButtonT(uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) { 45 | init(mode, btnLevel); 46 | } 47 | 48 | // указать режим работы пина 49 | void init(uint8_t mode = INPUT_PULLUP, uint8_t btnLevel = LOW) { 50 | EB_mode(PIN, mode); 51 | setBtnLevel(btnLevel); 52 | } 53 | 54 | // прочитать текущее значение кнопки (без дебаунса) 55 | bool read() { 56 | return EB_read(PIN) ^ bf.read(EB_INV); 57 | } 58 | 59 | // функция обработки, вызывать в loop 60 | bool tick() { 61 | return VirtButton::tick(EB_read(PIN)); 62 | } 63 | 64 | // обработка кнопки без сброса событий и вызова коллбэка 65 | bool tickRaw() { 66 | return VirtButton::tickRaw(EB_read(PIN)); 67 | } 68 | }; -------------------------------------------------------------------------------- /src/core/EncButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "VirtEncButton.h" 5 | #include "io.h" 6 | 7 | // ===================== CLASS ===================== 8 | class EncButton : public VirtEncButton { 9 | public: 10 | // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка) 11 | EncButton(uint8_t encA = 0, uint8_t encB = 0, uint8_t btn = 0, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) { 12 | init(encA, encB, btn, modeEnc, modeBtn, btnLevel); 13 | } 14 | 15 | // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка) 16 | void init(uint8_t encA = 0, uint8_t encB = 0, uint8_t btn = 0, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) { 17 | e0 = encA; 18 | e1 = encB; 19 | b = btn; 20 | EB_mode(e0, modeEnc); 21 | EB_mode(e1, modeEnc); 22 | EB_mode(b, modeBtn); 23 | setBtnLevel(btnLevel); 24 | initEnc(EB_read(e0), EB_read(e1)); 25 | } 26 | 27 | // ====================== TICK ====================== 28 | // функция обработки для вызова в прерывании энкодера 29 | int8_t tickISR() { 30 | return VirtEncButton::tickISR(EB_read(e0), EB_read(e1)); 31 | } 32 | 33 | // функция обработки, вызывать в loop 34 | bool tick() { 35 | return ef.read(EB_EISR) ? VirtEncButton::tick(EB_read(b)) : VirtEncButton::tick(EB_read(e0), EB_read(e1), EB_read(b)); 36 | } 37 | 38 | // функция обработки без сброса событий 39 | bool tickRaw() { 40 | return ef.read(EB_EISR) ? VirtEncButton::tickRaw(EB_read(b)) : VirtEncButton::tickRaw(EB_read(e0), EB_read(e1), EB_read(b)); 41 | } 42 | 43 | // ====================== READ ====================== 44 | // прочитать значение кнопки 45 | bool readBtn() { 46 | return EB_read(b) ^ bf.read(EB_INV); 47 | } 48 | 49 | // ===================== PRIVATE ===================== 50 | private: 51 | uint8_t e0, e1, b; 52 | }; 53 | 54 | // ===================== T CLASS ===================== 55 | template 56 | class EncButtonT : public VirtEncButton { 57 | public: 58 | // настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка) 59 | EncButtonT(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) { 60 | init(modeEnc, modeBtn, btnLevel); 61 | } 62 | 63 | // настроить пины (pinmode энк, pinmode кнопка) 64 | void init(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW) { 65 | EB_mode(ENCA, modeEnc); 66 | EB_mode(ENCB, modeEnc); 67 | EB_mode(BTN, modeBtn); 68 | setBtnLevel(btnLevel); 69 | initEnc(EB_read(ENCA), EB_read(ENCB)); 70 | } 71 | 72 | // ====================== TICK ====================== 73 | // функция обработки для вызова в прерывании энкодера 74 | int8_t tickISR() { 75 | return VirtEncButton::tickISR(EB_read(ENCA), EB_read(ENCB)); 76 | } 77 | 78 | // функция обработки, вызывать в loop 79 | bool tick() { 80 | return ef.read(EB_EISR) ? VirtEncButton::tick(EB_read(BTN)) : VirtEncButton::tick(EB_read(ENCA), EB_read(ENCB), EB_read(BTN)); 81 | } 82 | 83 | // функция обработки без сброса событий 84 | bool tickRaw() { 85 | return ef.read(EB_EISR) ? VirtEncButton::tickRaw(EB_read(BTN)) : VirtEncButton::tickRaw(EB_read(ENCA), EB_read(ENCB), EB_read(BTN)); 86 | } 87 | 88 | // ====================== READ ====================== 89 | // прочитать значение кнопки 90 | bool readBtn() { 91 | return EB_read(BTN) ^ bf.read(EB_INV); 92 | } 93 | }; -------------------------------------------------------------------------------- /src/core/Encoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "VirtEncoder.h" 5 | #include "io.h" 6 | 7 | // ============= VAR PIN ============= 8 | class Encoder : public VirtEncoder { 9 | public: 10 | // указать пины и их режим работы 11 | Encoder(uint8_t encA = 0, uint8_t encB = 0, uint8_t mode = INPUT) { 12 | init(encA, encB, mode); 13 | } 14 | 15 | // указать пины и их режим работы 16 | void init(uint8_t encA = 0, uint8_t encB = 0, uint8_t mode = INPUT) { 17 | e0 = encA; 18 | e1 = encB; 19 | EB_mode(e0, mode); 20 | EB_mode(e1, mode); 21 | initEnc(EB_read(e0), EB_read(e1)); 22 | } 23 | 24 | // функция обработки для вызова в прерывании энкодера 25 | int8_t tickISR() { 26 | return VirtEncoder::tickISR(EB_read(e0), EB_read(e1)); 27 | } 28 | 29 | // функция обработки, вызывать в loop 30 | int8_t tick() { 31 | return ef.read(EB_EISR) ? VirtEncoder::tick() : VirtEncoder::tick(EB_read(e0), EB_read(e1)); 32 | } 33 | 34 | // обработка без сброса события поворота 35 | int8_t tickRaw() { 36 | return ef.read(EB_EISR) ? VirtEncoder::tickRaw() : VirtEncoder::tickRaw(EB_read(e0), EB_read(e1)); 37 | } 38 | 39 | private: 40 | uint8_t e0, e1; 41 | }; 42 | 43 | // ============= TEMPLATE PIN ============= 44 | template 45 | class EncoderT : public VirtEncoder { 46 | public: 47 | // указать режим работы пинов 48 | EncoderT(uint8_t mode = INPUT) { 49 | init(mode); 50 | } 51 | 52 | // указать режим работы пинов 53 | void init(uint8_t mode = INPUT) { 54 | EB_mode(ENCA, mode); 55 | EB_mode(ENCB, mode); 56 | initEnc(EB_read(ENCA), EB_read(ENCB)); 57 | } 58 | 59 | // функция обработки для вызова в прерывании энкодера 60 | int8_t tickISR() { 61 | return VirtEncoder::tickISR(EB_read(ENCA), EB_read(ENCB)); 62 | } 63 | 64 | // функция обработки, вызывать в loop 65 | int8_t tick() { 66 | return ef.read(EB_EISR) ? VirtEncoder::tick() : VirtEncoder::tick(EB_read(ENCA), EB_read(ENCB)); 67 | } 68 | 69 | // обработка без сброса события поворота 70 | int8_t tickRaw() { 71 | return ef.read(EB_EISR) ? VirtEncoder::tickRaw() : VirtEncoder::tickRaw(EB_read(ENCA), EB_read(ENCB)); 72 | } 73 | }; -------------------------------------------------------------------------------- /src/core/MultiButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "VirtButton.h" 5 | 6 | class MultiButton : public VirtButton { 7 | public: 8 | template 9 | bool tick(T0& b0, T1& b1) { 10 | b0.clear(); 11 | b1.clear(); 12 | b0.tickRaw(); 13 | b1.tickRaw(); 14 | 15 | if (bf.read(EB_BOTH)) { 16 | if (!b0.pressing() && !b1.pressing()) bf.clear(EB_BOTH); 17 | if (!b0.pressing()) b0.reset(); 18 | if (!b1.pressing()) b1.reset(); 19 | b0.clear(); 20 | b1.clear(); 21 | return VirtButton::tick(true); 22 | } else { 23 | if (b0.pressing() && b1.pressing()) bf.set(EB_BOTH); 24 | b0.call(); 25 | b1.call(); 26 | return VirtButton::tick(false); 27 | } 28 | } 29 | }; -------------------------------------------------------------------------------- /src/core/VirtButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "flags.h" 5 | #include "io.h" 6 | #include "self.h" 7 | 8 | #ifndef __AVR__ 9 | #include 10 | #endif 11 | 12 | // ===================== FLAGS ====================== 13 | #define EB_PRESS (1 << 0) // нажатие на кнопку 14 | #define EB_HOLD (1 << 1) // кнопка удержана 15 | #define EB_STEP (1 << 2) // импульсное удержание 16 | #define EB_RELEASE (1 << 3) // кнопка отпущена 17 | #define EB_CLICK (1 << 4) // одиночный клик 18 | #define EB_CLICKS (1 << 5) // сигнал о нескольких кликах 19 | #define EB_TURN (1 << 6) // поворот энкодера 20 | #define EB_REL_HOLD (1 << 7) // кнопка отпущена после удержания 21 | #define EB_REL_HOLD_C (1 << 8) // кнопка отпущена после удержания с предв. кликами 22 | #define EB_REL_STEP (1 << 9) // кнопка отпущена после степа 23 | #define EB_REL_STEP_C (1 << 10) // кнопка отпущена после степа с предв. кликами 24 | #define EB_TIMEOUT (1 << 11) // прошёл таймаут после нажатия кнопки или поворота энкодера 25 | 26 | enum class EBAction { 27 | None = 0, 28 | Press = EB_PRESS, 29 | Hold = EB_HOLD, 30 | Step = EB_STEP, 31 | Release = EB_RELEASE, 32 | Click = EB_CLICK, 33 | Clicks = EB_CLICKS, 34 | Turn = EB_TURN, 35 | ReleaseHold = EB_REL_HOLD, 36 | ReleaseHoldClicks = EB_REL_HOLD_C, 37 | ReleaseStep = EB_REL_STEP, 38 | ReleaseStepClicks = EB_REL_STEP_C, 39 | Timeout = EB_TIMEOUT, 40 | }; 41 | 42 | // =================== TOUT BUILD =================== 43 | #define EB_SHIFT 4 44 | 45 | // таймаут антидребезга, мс 46 | #ifdef EB_DEB_TIME 47 | #define EB_DEB_T (EB_DEB_TIME) 48 | #else 49 | #endif 50 | 51 | // таймаут между клтками, мс 52 | #ifdef EB_CLICK_TIME 53 | #define EB_CLICK_T (EB_CLICK_TIME) 54 | #define EB_GET_CLICK_TIME() ((uint16_t)EB_CLICK_T) 55 | #else 56 | #define EB_GET_CLICK_TIME() (uint16_t)(EB_CLICK_T << EB_SHIFT) 57 | #endif 58 | 59 | // таймаут удержания, мс 60 | #ifdef EB_HOLD_TIME 61 | #define EB_HOLD_T (EB_HOLD_TIME) 62 | #define EB_GET_HOLD_TIME() ((uint16_t)EB_HOLD_T) 63 | #else 64 | #define EB_GET_HOLD_TIME() (uint16_t)(EB_HOLD_T << EB_SHIFT) 65 | #endif 66 | 67 | // период степа, мс 68 | #ifdef EB_STEP_TIME 69 | #define EB_STEP_T (EB_STEP_TIME) 70 | #define EB_GET_STEP_TIME() ((uint16_t)EB_STEP_T) 71 | #else 72 | #define EB_GET_STEP_TIME() (uint16_t)(EB_STEP_T << EB_SHIFT) 73 | #endif 74 | 75 | // время таймаута, мс 76 | #ifdef EB_TOUT_TIME 77 | #define EB_TOUT_T (EB_TOUT_TIME) 78 | #define EB_GET_TOUT_TIME() ((uint16_t)EB_TOUT_T) 79 | #else 80 | #define EB_GET_TOUT_TIME() (uint16_t)(EB_TOUT_T << EB_SHIFT) 81 | #endif 82 | 83 | // =================== PACK FLAGS =================== 84 | #define EB_CLKS_R (1 << 0) 85 | #define EB_PRS_R (1 << 1) 86 | #define EB_HLD_R (1 << 2) 87 | #define EB_STP_R (1 << 3) 88 | #define EB_REL_R (1 << 4) 89 | 90 | #define EB_PRS (1 << 5) 91 | #define EB_HLD (1 << 6) 92 | #define EB_STP (1 << 7) 93 | #define EB_REL (1 << 8) 94 | 95 | #define EB_BUSY (1 << 9) 96 | #define EB_DEB (1 << 10) 97 | #define EB_TOUT (1 << 11) 98 | #define EB_INV (1 << 12) 99 | #define EB_BOTH (1 << 13) 100 | #define EB_BISR (1 << 14) 101 | 102 | #define EB_EHLD (1 << 15) 103 | 104 | // базовый класс кнопки 105 | class VirtButton { 106 | #ifdef __AVR__ 107 | typedef void (*ActionHandler)(); 108 | #else 109 | typedef std::function ActionHandler; 110 | #endif 111 | 112 | public: 113 | // ====================== SET ====================== 114 | // установить таймаут удержания, умолч. 600 (макс. 4000 мс) 115 | void setHoldTimeout(const uint16_t tout) { 116 | #ifndef EB_HOLD_TIME 117 | EB_HOLD_T = tout >> EB_SHIFT; 118 | #endif 119 | } 120 | 121 | // установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс) 122 | void setStepTimeout(const uint16_t tout) { 123 | #ifndef EB_STEP_TIME 124 | EB_STEP_T = tout >> EB_SHIFT; 125 | #endif 126 | } 127 | 128 | // установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс) 129 | void setClickTimeout(const uint16_t tout) { 130 | #ifndef EB_CLICK_TIME 131 | EB_CLICK_T = tout >> EB_SHIFT; 132 | #endif 133 | } 134 | 135 | // установить таймаут антидребезга, умолч. 50 (макс. 255 мс) 136 | void setDebTimeout(const uint8_t tout) { 137 | #ifndef EB_DEB_TIME 138 | EB_DEB_T = tout; 139 | #endif 140 | } 141 | 142 | // установить время таймаута, умолч. 1000 (макс. 4000 мс) 143 | void setTimeout(const uint16_t tout) { 144 | #ifndef EB_TOUT_TIME 145 | EB_TOUT_T = tout >> EB_SHIFT; 146 | #endif 147 | } 148 | 149 | // установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND) 150 | void setBtnLevel(const bool level) { 151 | bf.write(EB_INV, !level); 152 | } 153 | 154 | // кнопка нажата в прерывании (не учитывает btnLevel!) 155 | void pressISR() { 156 | if (!bf.read(EB_DEB)) tmr = EB_uptime(); 157 | bf.set(EB_DEB | EB_BISR); 158 | } 159 | 160 | // сбросить системные флаги (принудительно закончить обработку) 161 | void reset() { 162 | clicks = 0; 163 | bf.clear(~EB_INV); // все кроме EB_INV 164 | } 165 | 166 | // принудительно сбросить флаги событий 167 | void clear(bool resetTout = false) { 168 | if (resetTout && bf.read(EB_TOUT)) bf.clear(EB_TOUT); 169 | if (bf.read(EB_CLKS_R)) clicks = 0; 170 | if (bf.read(EB_CLKS_R | EB_STP_R | EB_PRS_R | EB_HLD_R | EB_REL_R)) { 171 | bf.clear(EB_CLKS_R | EB_STP_R | EB_PRS_R | EB_HLD_R | EB_REL_R); 172 | } 173 | } 174 | 175 | // игнорировать все события до отпускания кнопки 176 | void skipEvents() { 177 | bf.set(EB_EHLD); 178 | } 179 | 180 | // подключить функцию-обработчик событий (вида void f()) 181 | void attach(ActionHandler handler) { 182 | #ifndef EB_NO_CALLBACK 183 | cb = handler; 184 | #else 185 | (void)handler; 186 | #endif 187 | } 188 | 189 | // отключить функцию-обработчик событий 190 | void detach() { 191 | #ifndef EB_NO_CALLBACK 192 | cb = nullptr; 193 | #endif 194 | } 195 | 196 | // ====================== GET ====================== 197 | // кнопка нажата [событие] 198 | bool press() { 199 | return bf.read(EB_PRS_R); 200 | } 201 | 202 | // кнопка нажата с предварительными кликами [событие] 203 | bool press(const uint8_t num) { 204 | return (clicks == num) && press(); 205 | } 206 | 207 | // кнопка отпущена (в любом случае) [событие] 208 | bool release() { 209 | return bf.eq(EB_REL_R | EB_REL, EB_REL_R | EB_REL); 210 | } 211 | 212 | // кнопка отпущена (в любом случае) с предварительными кликами [событие] 213 | bool release(const uint8_t num) { 214 | return (clicks == num) && release(); 215 | } 216 | 217 | // клик по кнопке (отпущена без удержания) [событие] 218 | bool click() { 219 | return bf.eq(EB_REL_R | EB_REL | EB_HLD, EB_REL_R); 220 | } 221 | 222 | // клик по кнопке (отпущена без удержания) с предварительными кликами [событие] 223 | bool click(const uint8_t num) { 224 | return (clicks == num) && click(); 225 | } 226 | 227 | // кнопка зажата (между press() и release()) [состояние] 228 | bool pressing() { 229 | return bf.read(EB_PRS); 230 | } 231 | 232 | // кнопка зажата (между press() и release()) с предварительными кликами [состояние] 233 | bool pressing(const uint8_t num) { 234 | return (clicks == num) && pressing(); 235 | } 236 | 237 | // кнопка была удержана (больше таймаута) [событие] 238 | bool hold() { 239 | return bf.read(EB_HLD_R); 240 | } 241 | 242 | // кнопка была удержана (больше таймаута) с предварительными кликами [событие] 243 | bool hold(const uint8_t num) { 244 | return (clicks == num) && hold(); 245 | } 246 | 247 | // кнопка удерживается (больше таймаута) [состояние] 248 | bool holding() { 249 | return bf.eq(EB_PRS | EB_HLD, EB_PRS | EB_HLD); 250 | } 251 | 252 | // кнопка удерживается (больше таймаута) с предварительными кликами [состояние] 253 | bool holding(const uint8_t num) { 254 | return (clicks == num) && holding(); 255 | } 256 | 257 | // импульсное удержание [событие] 258 | bool step() { 259 | return bf.read(EB_STP_R); 260 | } 261 | 262 | // импульсное удержание с предварительными кликами [событие] 263 | bool step(const uint8_t num) { 264 | return (clicks == num) && step(); 265 | } 266 | 267 | // зафиксировано несколько кликов [событие] 268 | bool hasClicks() { 269 | return bf.eq(EB_CLKS_R | EB_HLD, EB_CLKS_R); 270 | } 271 | 272 | // зафиксировано указанное количество кликов [событие] 273 | bool hasClicks(const uint8_t num) { 274 | return (clicks == num) && hasClicks(); 275 | } 276 | 277 | // получить количество кликов 278 | uint8_t getClicks() { 279 | return clicks; 280 | } 281 | 282 | // получить количество степов 283 | uint16_t getSteps() { 284 | #ifndef EB_NO_FOR 285 | return ftmr ? ((stepFor() + EB_GET_STEP_TIME() - 1) / EB_GET_STEP_TIME()) : 0; // (x + y - 1) / y 286 | #endif 287 | return 0; 288 | } 289 | 290 | // кнопка отпущена после удержания [событие] 291 | bool releaseHold() { 292 | return bf.eq(EB_REL_R | EB_REL | EB_HLD | EB_STP, EB_REL_R | EB_HLD); 293 | } 294 | 295 | // кнопка отпущена после удержания с предварительными кликами [событие] 296 | bool releaseHold(const uint8_t num) { 297 | return clicks == num && bf.eq(EB_CLKS_R | EB_HLD | EB_STP, EB_CLKS_R | EB_HLD); 298 | } 299 | 300 | // кнопка отпущена после импульсного удержания [событие] 301 | bool releaseStep() { 302 | return bf.eq(EB_REL_R | EB_REL | EB_STP, EB_REL_R | EB_STP); 303 | } 304 | 305 | // кнопка отпущена после импульсного удержания с предварительными кликами [событие] 306 | bool releaseStep(const uint8_t num) { 307 | return clicks == num && bf.eq(EB_CLKS_R | EB_STP, EB_CLKS_R | EB_STP); 308 | } 309 | 310 | // кнопка отпущена после удержания или импульсного удержания [событие] 311 | bool releaseHoldStep() { 312 | return releaseHold() || releaseStep(); 313 | } 314 | 315 | // кнопка отпущена после удержания или импульсного удержания с предварительными кликами [событие] 316 | bool releaseHoldStep(const uint8_t num) { 317 | return releaseHold(num) || releaseStep(num); 318 | } 319 | 320 | // кнопка ожидает повторных кликов [состояние] 321 | bool waiting() { 322 | return clicks && bf.eq(EB_PRS | EB_REL, 0); 323 | } 324 | 325 | // идёт обработка [состояние] 326 | bool busy() { 327 | return bf.read(EB_BUSY); 328 | } 329 | 330 | // было действие с кнопки, вернёт код события [событие] 331 | uint16_t action() { 332 | switch (bf.mask(0b111111111)) { 333 | case (EB_PRS | EB_PRS_R): return EB_PRESS; 334 | case (EB_PRS | EB_HLD | EB_HLD_R): return EB_HOLD; 335 | case (EB_PRS | EB_HLD | EB_STP | EB_STP_R): return EB_STEP; 336 | case (EB_REL | EB_REL_R): 337 | case (EB_REL | EB_REL_R | EB_HLD): 338 | case (EB_REL | EB_REL_R | EB_HLD | EB_STP): 339 | return EB_RELEASE; 340 | case (EB_REL_R): return EB_CLICK; 341 | case (EB_CLKS_R): return EB_CLICKS; 342 | case (EB_REL_R | EB_HLD): return EB_REL_HOLD; 343 | case (EB_CLKS_R | EB_HLD): return EB_REL_HOLD_C; 344 | case (EB_REL_R | EB_HLD | EB_STP): return EB_REL_STEP; 345 | case (EB_CLKS_R | EB_HLD | EB_STP): return EB_REL_STEP_C; 346 | } 347 | if (timeoutState()) return EB_TIMEOUT; 348 | return 0; 349 | } 350 | 351 | // было действие с кнопки, вернёт код события [событие] 352 | EBAction getAction() { 353 | return (EBAction)action(); 354 | } 355 | 356 | // ====================== TIME ====================== 357 | // после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [событие] 358 | bool timeout() { 359 | if (timeoutState()) { 360 | bf.clear(EB_TOUT); 361 | return 1; 362 | } 363 | return 0; 364 | } 365 | 366 | // после взаимодействия с кнопкой (или энкодером EncButton) время setTimeout, мс [состояние] 367 | bool timeoutState() { 368 | return bf.read(EB_TOUT) && (uint16_t)((uint16_t)EB_uptime() - tmr) >= EB_GET_TOUT_TIME(); 369 | } 370 | 371 | // время, которое кнопка удерживается (с начала нажатия), мс 372 | uint16_t pressFor() { 373 | #ifndef EB_NO_FOR 374 | if (ftmr) return (uint16_t)EB_uptime() - ftmr; 375 | #endif 376 | return 0; 377 | } 378 | 379 | // кнопка удерживается дольше чем (с начала нажатия), мс [состояние] 380 | bool pressFor(const uint16_t ms) { 381 | return pressFor() > ms; 382 | } 383 | 384 | // время, которое кнопка удерживается (с начала удержания), мс 385 | uint16_t holdFor() { 386 | #ifndef EB_NO_FOR 387 | if (bf.read(EB_HLD)) return pressFor() - EB_GET_HOLD_TIME(); 388 | #endif 389 | return 0; 390 | } 391 | 392 | // кнопка удерживается дольше чем (с начала удержания), мс [состояние] 393 | bool holdFor(const uint16_t ms) { 394 | return holdFor() > ms; 395 | } 396 | 397 | // время, которое кнопка удерживается (с начала степа), мс 398 | uint16_t stepFor() { 399 | #ifndef EB_NO_FOR 400 | if (bf.read(EB_STP)) return pressFor() - EB_GET_HOLD_TIME() * 2; 401 | #endif 402 | return 0; 403 | } 404 | 405 | // кнопка удерживается дольше чем (с начала степа), мс [состояние] 406 | bool stepFor(uint16_t ms) { 407 | return stepFor() > ms; 408 | } 409 | 410 | // ====================== POLL ====================== 411 | // обработка виртуальной кнопки как одновременное нажатие двух других кнопок 412 | bool tick(VirtButton& b0, VirtButton& b1) { 413 | if (bf.read(EB_BOTH)) { 414 | if (!b0.pressing() && !b1.pressing()) bf.clear(EB_BOTH); 415 | if (!b0.pressing()) b0.reset(); 416 | if (!b1.pressing()) b1.reset(); 417 | b0.clear(); 418 | b1.clear(); 419 | return tick(1); 420 | } else { 421 | if (b0.pressing() && b1.pressing()) bf.set(EB_BOTH); 422 | return tick(0); 423 | } 424 | } 425 | 426 | // обработка кнопки значением 427 | bool tick(bool s) { 428 | clear(); 429 | s = pollBtn(s); 430 | #ifndef EB_NO_CALLBACK 431 | if (s || timeoutState()) call(); 432 | #endif 433 | return s; 434 | } 435 | 436 | // обработка кнопки без сброса событий и вызова коллбэка 437 | bool tickRaw(const bool s) { 438 | return pollBtn(s); 439 | } 440 | 441 | // вызвать обработчик 442 | void call(bool force = false) { // todo force заменить на флаг 443 | #ifndef EB_NO_CALLBACK 444 | if (cb && (force || action())) { 445 | if (cb) { 446 | EB_self = this; 447 | cb(); 448 | EB_self = nullptr; 449 | timeout(); // todo clear tout 450 | } 451 | } 452 | #else 453 | (void)force; 454 | #endif 455 | } 456 | 457 | uint8_t clicks; 458 | 459 | // deprecated 460 | void setButtonLevel(bool level) __attribute__((deprecated)) { 461 | bf.write(EB_INV, !level); 462 | } 463 | 464 | // после взаимодействия с кнопкой (или энкодером EncButton) прошло указанное время, мс [событие] 465 | bool timeout(const uint16_t tout) /*__attribute__((deprecated))*/ { 466 | if (timeoutState(tout)) { 467 | bf.clear(EB_TOUT); 468 | return 1; 469 | } 470 | return 0; 471 | } 472 | 473 | // после взаимодействия с кнопкой (или энкодером EncButton) прошло указанное время, мс [состояние] 474 | bool timeoutState(const uint16_t tout) /*__attribute__((deprecated))*/ { 475 | return (bf.read(EB_TOUT) && (uint16_t)((uint16_t)EB_uptime() - tmr) > tout); 476 | } 477 | 478 | // ====================== PRIVATE ====================== 479 | protected: 480 | uint16_t tmr = 0; 481 | encb::Flags bf; 482 | 483 | #ifndef EB_NO_CALLBACK 484 | ActionHandler cb = nullptr; 485 | #endif 486 | 487 | private: 488 | #ifndef EB_NO_FOR 489 | uint16_t ftmr = 0; 490 | #endif 491 | #ifndef EB_DEB_TIME 492 | uint8_t EB_DEB_T = 50; 493 | #endif 494 | #ifndef EB_CLICK_TIME 495 | uint8_t EB_CLICK_T = (500 >> EB_SHIFT); 496 | #endif 497 | #ifndef EB_HOLD_TIME 498 | uint8_t EB_HOLD_T = (600 >> EB_SHIFT); 499 | #endif 500 | #ifndef EB_STEP_TIME 501 | uint8_t EB_STEP_T = (200 >> EB_SHIFT); 502 | #endif 503 | #ifndef EB_TOUT_TIME 504 | uint8_t EB_TOUT_T = (1000 >> EB_SHIFT); 505 | #endif 506 | 507 | bool pollBtn(bool s) { 508 | if (bf.read(EB_BISR)) { 509 | bf.clear(EB_BISR); 510 | s = 1; 511 | } else s ^= bf.read(EB_INV); 512 | 513 | if (!bf.read(EB_BUSY)) { 514 | if (s) bf.set(EB_BUSY); 515 | else return 0; 516 | } 517 | 518 | uint16_t ms = EB_uptime(); 519 | uint16_t deb = ms - tmr; 520 | 521 | if (s) { // кнопка нажата 522 | if (!bf.read(EB_PRS)) { // кнопка не была нажата ранее 523 | if (!bf.read(EB_DEB) && EB_DEB_T) { // дебаунс ещё не сработал 524 | bf.set(EB_DEB); // будем ждать дебаунс 525 | tmr = ms; // сброс таймаута 526 | } else { // первое нажатие 527 | if (deb >= EB_DEB_T || !EB_DEB_T) { // ждём EB_DEB_TIME 528 | bf.set(EB_PRS | EB_PRS_R); // флаг на нажатие 529 | #ifndef EB_NO_FOR 530 | ftmr = ms; 531 | #endif 532 | tmr = ms; // сброс таймаута 533 | } 534 | } 535 | } else { // кнопка уже была нажата 536 | if (!bf.read(EB_EHLD)) { 537 | if (!bf.read(EB_HLD)) { // удержание ещё не зафиксировано 538 | if (deb >= EB_GET_HOLD_TIME()) { // ждём EB_HOLD_TIME - это удержание 539 | bf.set(EB_HLD_R | EB_HLD); // флаг что было удержание 540 | tmr = ms; // сброс таймаута 541 | } 542 | } else { // удержание зафиксировано 543 | if (deb >= (uint16_t)(bf.read(EB_STP) ? EB_GET_STEP_TIME() : EB_GET_HOLD_TIME())) { 544 | bf.set(EB_STP | EB_STP_R); // флаг степ 545 | tmr = ms; // сброс таймаута 546 | } 547 | } 548 | } 549 | } 550 | } else { // кнопка не нажата 551 | if (bf.read(EB_PRS)) { // но была нажата 552 | if (deb >= EB_DEB_T) { // ждём EB_DEB_TIME 553 | if (!bf.read(EB_HLD)) clicks++; // не удерживали - это клик 554 | if (bf.read(EB_EHLD)) clicks = 0; // 555 | bf.set(EB_REL | EB_REL_R); // флаг release 556 | bf.clear(EB_PRS); // кнопка отпущена 557 | } 558 | } else if (bf.read(EB_REL)) { 559 | if (!bf.read(EB_EHLD)) { 560 | bf.set(EB_REL_R); // флаг releaseHold / releaseStep 561 | } 562 | bf.clear(EB_REL | EB_EHLD); 563 | tmr = ms; // сброс таймаута 564 | } else if (clicks) { // есть клики, ждём EB_CLICK_TIME 565 | if (bf.read(EB_HLD | EB_STP) || deb >= EB_GET_CLICK_TIME()) bf.set(EB_CLKS_R); // флаг clicks 566 | #ifndef EB_NO_FOR 567 | else if (ftmr) ftmr = 0; 568 | #endif 569 | } else if (bf.read(EB_BUSY)) { 570 | bf.clear(EB_HLD | EB_STP | EB_BUSY); 571 | bf.set(EB_TOUT); 572 | #ifndef EB_NO_FOR 573 | ftmr = 0; 574 | #endif 575 | tmr = ms; // test!! 576 | } 577 | if (bf.read(EB_DEB)) bf.clear(EB_DEB); // сброс ожидания нажатия (дебаунс) 578 | } 579 | return bf.read(EB_CLKS_R | EB_PRS_R | EB_HLD_R | EB_STP_R | EB_REL_R); 580 | } 581 | }; 582 | -------------------------------------------------------------------------------- /src/core/VirtEncButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "VirtButton.h" 5 | #include "VirtEncoder.h" 6 | #include "io.h" 7 | 8 | #ifdef EB_FAST_TIME 9 | #define EB_FAST_T (EB_FAST_TIME) 10 | #endif 11 | 12 | // базовый клас энкодера с кнопкой 13 | class VirtEncButton : public VirtButton, public VirtEncoder { 14 | public: 15 | // ====================== SET ====================== 16 | // установить таймаут быстрого поворота, мс 17 | void setFastTimeout(const uint8_t tout) { 18 | #ifndef EB_FAST_TIME 19 | EB_FAST_T = tout; 20 | #endif 21 | } 22 | 23 | // сбросить флаги энкодера и кнопки 24 | void clear(bool resetTout = false) { 25 | VirtButton::clear(resetTout); 26 | VirtEncoder::clear(); 27 | } 28 | 29 | // ====================== GET ====================== 30 | // нажатый поворот энкодера [событие] 31 | bool turnH() { 32 | return turn() && bf.read(EB_EHLD); 33 | } 34 | 35 | // быстрый поворот энкодера [состояние] 36 | bool fast() { 37 | return ef.read(EB_FAST); 38 | } 39 | 40 | // поворот направо [событие] 41 | bool right() { 42 | return ef.read(EB_DIR) && turn() && !bf.read(EB_EHLD); 43 | } 44 | 45 | // поворот налево [событие] 46 | bool left() { 47 | return !ef.read(EB_DIR) && turn() && !bf.read(EB_EHLD); 48 | } 49 | 50 | // нажатый поворот направо [событие] 51 | bool rightH() { 52 | return ef.read(EB_DIR) && turnH(); 53 | } 54 | 55 | // нажатый поворот налево [событие] 56 | bool leftH() { 57 | return !ef.read(EB_DIR) && turnH(); 58 | } 59 | 60 | // нажата кнопка энкодера. Аналог pressing() [состояние] 61 | bool encHolding() { 62 | return bf.read(EB_EHLD); 63 | } 64 | 65 | // было действие с кнопки или энкодера, вернёт код события [событие] 66 | uint16_t action() { 67 | return turn() ? EB_TURN : VirtButton::action(); 68 | } 69 | 70 | // было действие с кнопки или энкодера, вернёт код события [событие] 71 | EBAction getAction() { 72 | return (EBAction)action(); 73 | } 74 | 75 | // ====================== POLL ====================== 76 | // ISR 77 | // обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при повороте 78 | int8_t tickISR(const bool e0, const bool e1) { 79 | int8_t state = VirtEncoder::pollEnc(e0, e1); 80 | if (state) { 81 | #ifdef EB_NO_BUFFER 82 | ef.set(EB_ISR_F); 83 | ef.write(EB_DIR, state > 0); 84 | ef.write(EB_FAST, _checkFast()); 85 | #else 86 | for (uint8_t i = 0; i < 15; i += 3) { 87 | if (!(ebuffer & (1 << i))) { 88 | ebuffer |= (1 << i); // turn 89 | if (state > 0) ebuffer |= (1 << (i + 1)); // dir 90 | if (_checkFast()) ebuffer |= (1 << (i + 2)); // fast 91 | break; 92 | } 93 | } 94 | #endif 95 | } 96 | return state; 97 | } 98 | 99 | // TICK 100 | // обработка энкодера и кнопки 101 | bool tick(const bool e0, const bool e1, const bool btn) { 102 | clear(); 103 | return _tick(_tickRaw(btn, pollEnc(e0, e1))); 104 | } 105 | 106 | // обработка энкодера (в прерывании) и кнопки 107 | bool tick(const bool btn) { 108 | clear(); 109 | return _tick(_tickRaw(btn)); 110 | } 111 | 112 | // RAW 113 | // обработка без сброса событий и вызова коллбэка 114 | bool tickRaw(const bool e0, const bool e1, bool btn) { 115 | return _tickRaw(btn, pollEnc(e0, e1)); 116 | } 117 | 118 | // обработка без сброса событий и вызова коллбэка (кнопка) 119 | bool tickRaw(const bool btn) { 120 | return _tickRaw(btn); 121 | } 122 | 123 | // ===================== PRIVATE ===================== 124 | protected: 125 | #ifndef EB_FAST_TIME 126 | uint8_t EB_FAST_T = 30; 127 | #endif 128 | 129 | #ifndef EB_NO_BUFFER 130 | uint16_t ebuffer = 0; 131 | #endif 132 | 133 | private: 134 | bool _checkFast() { 135 | uint16_t ms = EB_uptime(); 136 | bool f = ms - tmr < EB_FAST_T; 137 | tmr = ms; 138 | return f; 139 | } 140 | 141 | inline bool _tick(bool f) { 142 | #ifndef EB_NO_CALLBACK 143 | if (f || timeoutState()) call(true); 144 | #endif 145 | return f; 146 | } 147 | 148 | bool _tickRaw(bool btn, int8_t estate = 0) { 149 | bool encf = 0; 150 | #ifdef EB_NO_BUFFER 151 | if (ef.read(EB_ISR_F)) { 152 | ef.clear(EB_ISR_F); 153 | encf = 1; 154 | } 155 | #else 156 | if (ebuffer) { 157 | ef.write(EB_DIR, ebuffer & 0b10); 158 | ef.write(EB_FAST, ebuffer & 0b100); 159 | ebuffer >>= 3; 160 | encf = 1; 161 | } 162 | #endif 163 | else if (estate) { 164 | ef.write(EB_DIR, estate > 0); 165 | ef.write(EB_FAST, _checkFast()); 166 | encf = 1; 167 | } 168 | if (encf) { 169 | if (bf.read(EB_PRS)) bf.set(EB_EHLD); // зажать энкодер 170 | else clicks = 0; 171 | if (!bf.read(EB_TOUT)) bf.set(EB_TOUT); // таймаут 172 | ef.set(EB_ETRN_R); // флаг поворота 173 | } 174 | return VirtButton::tickRaw(btn) | encf; 175 | } 176 | }; -------------------------------------------------------------------------------- /src/core/VirtEncoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "flags.h" 5 | #include "io.h" 6 | 7 | // ===================== CONST ====================== 8 | #define EB_STEP4_LOW 0 9 | #define EB_STEP4_HIGH 1 10 | #define EB_STEP2 2 11 | #define EB_STEP1 3 12 | 13 | // ===================== FLAGS ====================== 14 | #define EB_TYPE (1 << 0) 15 | #define EB_REV (1 << 2) 16 | #define EB_FAST (1 << 3) 17 | #define EB_DIR (1 << 4) 18 | #define EB_ETRN_R (1 << 5) 19 | #define EB_ISR_F (1 << 6) 20 | #define EB_EISR (1 << 7) 21 | 22 | // базовый класс энкодера 23 | class VirtEncoder { 24 | public: 25 | VirtEncoder() { 26 | p0 = p1 = epos = 0; 27 | } 28 | 29 | // ====================== SET ====================== 30 | // инвертировать направление энкодера 31 | void setEncReverse(const bool rev) { 32 | rev ? ef.set(EB_REV) : ef.clear(EB_REV); 33 | } 34 | 35 | // установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1) 36 | void setEncType(const uint8_t type) { 37 | ef.flags = (ef.flags & 0b11111100) | type; 38 | } 39 | 40 | // использовать обработку энкодера в прерывании 41 | void setEncISR(const bool use) { 42 | ef.write(EB_EISR, use); 43 | } 44 | 45 | // инициализация энкодера 46 | void initEnc(const bool e0, const bool e1) { 47 | p0 = e0, p1 = e1; 48 | } 49 | 50 | // сбросить флаги событий 51 | void clear() { 52 | if (ef.read(EB_ETRN_R)) ef.clear(EB_ETRN_R); 53 | } 54 | 55 | // ====================== ОПРОС ====================== 56 | // был поворот [событие] 57 | bool turn() { 58 | return ef.read(EB_ETRN_R); 59 | } 60 | 61 | // направление энкодера (1 или -1) [состояние] 62 | int8_t dir() { 63 | return ef.read(EB_DIR) ? 1 : -1; 64 | } 65 | 66 | // ====================== POLL ====================== 67 | // ISR 68 | // опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановке 69 | int8_t tickISR(const bool e0, const bool e1) { 70 | int8_t state = pollEnc(e0, e1); 71 | if (state) { 72 | ef.set(EB_ISR_F); 73 | ef.write(EB_DIR, state > 0); 74 | } 75 | return state; 76 | } 77 | 78 | // TICK 79 | // опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке 80 | int8_t tick(const bool e0, const bool e1) { 81 | int8_t state = tickRaw(e0, e1); 82 | if (!state) clear(); 83 | return state; 84 | } 85 | 86 | // опросить энкодер (сам опрос в прерывании) 87 | int8_t tick() { 88 | int8_t state = tickRaw(); 89 | if (!state) clear(); 90 | return state; 91 | } 92 | 93 | // RAW 94 | // опросить энкодер без сброса события поворота 95 | int8_t tickRaw(const bool e0, const bool e1) { 96 | int8_t state = tickRaw(); 97 | if (state) return state; 98 | 99 | state = pollEnc(e0, e1); 100 | if (state) { 101 | ef.write(EB_DIR, state > 0); 102 | ef.set(EB_ETRN_R); 103 | } 104 | return state; 105 | } 106 | 107 | // опросить энкодер без сброса события поворота (сам опрос в прерывании) 108 | int8_t tickRaw() { 109 | if (ef.read(EB_ISR_F)) { 110 | ef.clear(EB_ISR_F); 111 | ef.set(EB_ETRN_R); 112 | return dir(); 113 | } 114 | return 0; 115 | } 116 | 117 | // POLL 118 | // опросить энкодер без установки события поворота (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке 119 | int8_t pollEnc(const bool e0, const bool e1) { 120 | if (p0 ^ p1 ^ e0 ^ e1) { 121 | (p1 ^ e0) ? ++epos : --epos; 122 | p0 = e0, p1 = e1; 123 | if (!epos) return 0; 124 | 125 | switch (ef.mask(0b11)) { 126 | case EB_STEP4_LOW: 127 | if (!(e0 & e1)) return 0; // skip 01, 10, 00 128 | break; 129 | case EB_STEP4_HIGH: 130 | if (e0 | e1) return 0; // skip 01, 10, 11 131 | break; 132 | case EB_STEP2: 133 | if (e0 ^ e1) return 0; // skip 10 01 134 | break; 135 | } 136 | int8_t state = ((epos > 0) ^ ef.read(EB_REV)) ? -1 : 1; 137 | epos = 0; 138 | #ifndef EB_NO_COUNTER 139 | counter += state; 140 | #endif 141 | return state; 142 | } 143 | return 0; 144 | } 145 | 146 | #ifndef EB_NO_COUNTER 147 | int32_t counter = 0; 148 | #endif 149 | 150 | // ===================== PRIVATE ===================== 151 | protected: 152 | encb::Flags ef; 153 | 154 | private: 155 | int8_t p0 : 2; 156 | int8_t p1 : 2; // signed 2 bit! 157 | int8_t epos : 4; 158 | }; -------------------------------------------------------------------------------- /src/core/flags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace encb { 5 | 6 | template 7 | struct Flags { 8 | T flags = 0; 9 | 10 | inline T mask(const T x) __attribute__((always_inline)) { 11 | return flags & x; 12 | } 13 | inline void set(const T x) __attribute__((always_inline)) { 14 | flags |= x; 15 | } 16 | inline void clear(const T x) __attribute__((always_inline)) { 17 | flags &= ~x; 18 | } 19 | inline bool read(const T x) __attribute__((always_inline)) { 20 | return flags & x; 21 | } 22 | inline void write(const T x, const bool v) __attribute__((always_inline)) { 23 | v ? set(x) : clear(x); 24 | } 25 | inline bool eq(const T x, const T y) __attribute__((always_inline)) { 26 | return (flags & x) == y; 27 | } 28 | }; 29 | 30 | } // namespace encb -------------------------------------------------------------------------------- /src/core/io.cpp: -------------------------------------------------------------------------------- 1 | #include "io.h" 2 | 3 | bool __attribute__((weak)) EB_read(uint8_t pin) { 4 | return gio::read(pin); 5 | } 6 | 7 | void __attribute__((weak)) EB_mode(uint8_t pin, uint8_t mode) { 8 | gio::init(pin, mode); 9 | } 10 | 11 | uint32_t __attribute__((weak)) EB_uptime() { 12 | return millis(); 13 | } -------------------------------------------------------------------------------- /src/core/io.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | bool EB_read(uint8_t pin); 6 | void EB_mode(uint8_t pin, uint8_t mode); 7 | uint32_t EB_uptime(); -------------------------------------------------------------------------------- /src/core/self.cpp: -------------------------------------------------------------------------------- 1 | #include "self.h" 2 | 3 | void* EB_self = nullptr; -------------------------------------------------------------------------------- /src/core/self.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern void* EB_self; --------------------------------------------------------------------------------