Add libraries dir

This commit is contained in:
2022-03-12 03:50:28 +08:00
parent 816f0bb3a5
commit 7572125fcb
256 changed files with 41193 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Alex
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+539
View File
@@ -0,0 +1,539 @@
[![Foo](https://img.shields.io/badge/Version-1.20-brightgreen.svg?style=flat-square)](#versions)
[![Foo](https://img.shields.io/badge/Website-AlexGyver.ru-blue.svg?style=flat-square)](https://alexgyver.ru/)
[![Foo](https://img.shields.io/badge/%E2%82%BD$%E2%82%AC%20%D0%9D%D0%B0%20%D0%BF%D0%B8%D0%B2%D0%BE-%D1%81%20%D1%80%D1%8B%D0%B1%D0%BA%D0%BE%D0%B9-orange.svg?style=flat-square)](https://alexgyver.ru/support_alex/)
# EncButton
Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки
- Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13)
- Максимально лёгкий вес
- Быстрые и лёгкие алгоритмы опроса кнопки и энкодера
- Энкодер: обычный поворот, нажатый поворот, быстрый поворот, доступ к счётчику
- Кнопка: антидребезг, клик, несколько кликов, счётчик кликов, удержание, режим импульсного удержания
- Подключение - **только с подтяжкой к питанию** (внешней или внутренней)!
- Опциональный режим с обработчиками callback (+24 байта SRAM на каждый экземпляр)
- Виртуальный режим (кнопка, энк, энк с кнопкой)
### Совместимость
Совместима со всеми Arduino платформами (используются Arduino-функции)
## Содержание
- [Установка](#install)
- [Железо](#hardware)
- [Инициализация](#init)
- [Использование](#usage)
- [Пример](#example)
- [Версии](#versions)
- [Баги и обратная связь](#feedback)
<a id="install"></a>
## Установка
- Библиотеку можно найти по названию **EncButton** и установить через менеджер библиотек в:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- [Скачать библиотеку](https://github.com/GyverLibs/EncButton/archive/refs/heads/main.zip) .zip архивом для ручной установки:
- Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64)
- Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32)
- Распаковать и положить в *Документы/Arduino/libraries/*
- (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)
<a id="hardware"></a>
## Железо
Для работы по сценарию "энкодер с кнопкой" рекомендую вот такие ([ссылка](https://ali.ski/CYir4), [ссылка](https://ali.ski/49q5hy)) круглые китайские модули с распаянными цепями антидребезга:
![scheme](/doc/encAli.png)
Самостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC):
![scheme](/doc/enc.png)
## Производительность
Время холостого выполнения функции tick() при реальном устройстве (кнопка/энкодер подключены к пинам МК) на ATmega328, библиотека EncButton:
| Режим | Время, мкс |
| ----- | ---------- |
| Энкодер + кнопка | 3.8 |
| Энкодер | 2.4 |
| Кнопка | 1.9 |
*Для сравнения, стандартный digitalRead() на AVR выполняется 3.5 us*
## Сравнение с аналогами
- EncButton в режиме кнопки на 6 мкс быстрее, на ~450 байт Flash и 12 байт SRAM легче моей старой библиотеки [GyverButton](https://github.com/GyverLibs/GyverButton), имея при этом больше возможностей
- EncButton в режиме энкодера с кнопкой на 6 мкс быстрее, на ~400 байт Flash и 18 байт SRAM легче моей старой библиотеки [GyverEncoder](https://github.com/GyverLibs/GyverEncoder), имея при этом больше возможностей
<a id="init"></a>
## Инициализация
**Если нужен массив кнопок/энкодеров, используй EncButton2!**
<details>
<summary>Инициализация EncButton</summary>
```cpp
// ============== БАЗОВАЯ =============
EncButton<MODE, A, B, KEY> enc; // энкодер с кнопкой
EncButton<MODE, A, B> enc; // просто энкодер
EncButton<MODE, KEY> btn; // просто кнопка
// A, B, KEY: номера пинов
// MODE: EB_TICK или EB_CALLBACK - режим работы ручной или с обработчиками
// для изменения направления энкодера поменяй A и B при инициализации
// ============ ПОДКЛЮЧЕНИЕ ============
// По умолчанию пины настраиваются в INPUT_PULLUP
// Если используется внешняя подтяжка - лучше перевести в INPUT
EncButton<...> enc(INPUT);
// ========= ВИРТУАЛЬНЫЙ РЕЖИМ =========
EncButton<MODE, VIRT_BTN> enc; // виртуальная кнопка
EncButton<MODE, VIRT_ENCBTN> enc; // виртуальный энк с кнопкой
EncButton<MODE, VIRT_ENC> enc; // виртуальный энк
// в tick нужно будет передавать виртуальное значение, см. пример
```
</details>
<details>
<summary>Инициализация EncButton2</summary>
Хранит пины НЕ в шаблоне, а как член класса. Всё кроме инициализации такое же как у EncButton!
```cpp
// ================ TICK ===============
EncButton2<EB_ENCBTN> enc(INPUT, A, B, KEY); // энкодер с кнопкой
EncButton2<EB_ENC> enc(INPUT, A, B); // просто энкодер
EncButton2<EB_BTN> enc(INPUT, KEY); // просто кнопка
// режим пинов INPUT/INPUT_PULLUP
// ============== CALLBACK =============
EncButton2<EB_ENCBTN, EB_CALLBACK> enc(INPUT, A, B, KEY); // энкодер с кнопкой
EncButton2<EB_ENC, EB_CALLBACK> enc(INPUT, A, B); // просто энкодер
EncButton2<EB_BTN, EB_CALLBACK> enc(INPUT, KEY); // просто кнопка
// режим пинов INPUT/INPUT_PULLUP
// ============== VIRT TICK ============
EncButton2<VIRT_ENCBTN> enc; // энкодер с кнопкой
EncButton2<VIRT_ENC> enc; // просто энкодер
EncButton2<VIRT_BTN> enc; // просто кнопка
// ============ VIRT CALLBACK ==========
EncButton2<VIRT_ENCBTN, EB_CALLBACK> enc; // энкодер с кнопкой
EncButton2<VIRT_ENC, EB_CALLBACK> enc; // просто энкодер
EncButton2<VIRT_BTN, EB_CALLBACK> enc; // просто кнопка
```
</details>
<details>
<summary>Массив экземпляров EncButton2</summary>
```cpp
EncButton2<EB_ENCBTN> enc[количество];
EncButton2<EB_ENC> enc[количество];
EncButton2<EB_BTN> enc[количество];
EncButton2<EB_ENCBTN, EB_CALLBACK> enc[количество];
EncButton2<EB_ENC, EB_CALLBACK> enc[количество];
EncButton2<EB_BTN, EB_CALLBACK> enc[количество];
// и так далее
// Задавать пины можно через setPins()
setPins(uint8_t mode, uint8_t P1, uint8_t P2, uint8_t P3);
// mode - INPUT/INPUT_PULLUP (для всех пинов)
// указываем только нужные для выбранного режима пины:
// EB_ENCBTN - A, B, KEY
// EB_ENC - A, B
// EB_BTN - KEY
// см. пример EucButton2_array
```
</details>
## Документация
<details>
<summary>ПОЛНОЕ ОПИСАНИЕ КЛАССА</summary>
```cpp
// =============== SETTINGS ==============
void pullUp(); // подтянуть все пины внутренней подтяжкой
void holdEncButton(bool state); // виртуально зажать кнопку энкодера
void setHoldTimeout(int tout); // установить время удержания кнопки, мс (до 30 000)
void setButtonLevel(bool level); // уровень кнопки: LOW - кнопка подключает GND (по умолч.), HIGH - кнопка подключает VCC
// ================= TICK ================
// тикер, вызывать как можно чаще или в прерывании
// вернёт отличное от нуля значение, если произошло какое то событие (см. пример optimisation)
uint8_t tick();
// tick(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0)
// может принимать виртуальный сигнал при режиме VIRT_xxx:
// (сигнал кнопки)
// (сигнал энкодера А, сигнал энкодера B)
// (сигнал энкодера А, сигнал энкодера B, сигнал кнопки)
// Тикер для прерывания в режиме callback. Не вызывает подключенные функции!
// Требует наличие обычного tick() в loop() (см. примеры tickISR и callbackISR)
uint8_t tickISR();
// проверяет и вызывает подключенные функции для режима callback
// Встроено в tick(), но вынесено отдельной функцией для нестандартных сценариев работы
void checkCallback();
// =============== STATUS ================
uint8_t getState(); // получить статус кнопки/энкодера
void resetState(); // сбросить статус
// =============== ENCODER ===============
bool turn(); // поворот на один щелчок в любую сторону
bool turnH(); // поворот на один щелчок в любую сторону с зажатой кнопкой
bool fast(); // быстрый поворот на один щелчок в любую сторону
bool right(); // поворот на один щелчок направо
bool left(); // поворот на один щелчок налево
bool rightH(); // поворот на один щелчок направо с зажатой кнопкой
bool leftH(); // поворот на один щелчок налево с зажатой кнопкой
int8_t getDir(); // направление последнего поворота, 1 или -1
int counter; // доступ к счётчику энкодера
// ================ BUTTON ================
bool busy(); // вернёт true, если всё ещё нужно вызывать tick для опроса таймаутов
bool state(); // текущее состояние кнопки (true нажата, false не нажата)
bool press(); // кнопка была нажата [однократное срабатывание]
bool release(); // кнопка была отпущена [однократное срабатывание]
bool click(); // клик (нажата и отпущена) [однократное срабатывание]
bool held(); // кнопка была удержана [однократное срабатывание]
bool hold(); // кнопка удерживается [постоянное срабатывание]
bool step(); // режим импульсного удержания
bool step(uint8_t clicks); // режим импульсного удержания с предварительным накликиванием
bool releaseStep(); // отпущена после режима step
bool releaseStep(uint8_t clicks); // отпущена после режима step с предварительным накликиванием
uint8_t clicks; // доступ к счётчику кликов
uint8_t hasClicks(); // вернёт количество кликов, если они есть
bool hasClicks(uint8_t num); // проверка на наличие указанного количества кликов
// =============== CALLBACK ===============
void attach(eb_callback type, void (*handler)()); // подключить обработчик
void detach(eb_callback type); // отключить обработчик
void attachClicks(uint8_t amount, void (*handler)()); // подключить обработчик на количество кликов (может быть только один!)
void detachClicks(); // отключить обработчик на количество кликов
// eb_callback может быть:
TURN_HANDLER
TURN_H_HANDLER
RIGHT_HANDLER
LEFT_HANDLER
RIGHT_H_HANDLER
LEFT_H_HANDLER
CLICK_HANDLER
HOLDED_HANDLER
STEP_HANDLER
HOLD_HANDLER
CLICKS_HANDLER
PRESS_HANDLER
RELEASE_HANDLER
```
**Дополнительно у EncButton2**
```cpp
void pullUp(); // здесь не реализована!
void setPins(uint8_t mode, uint8_t P1, uint8_t P2, uint8_t P3); // настроить пины
// mode - INPUT/INPUT_PULLUP (для всех пинов)
// указываем только нужные для выбранного режима пины:
// EB_ENCBTN - (A, B, KEY)
// EB_ENC - (A, B)
// EB_BTN - (KEY)
// см. пример EucButton2_array
```
</details>
### Заметки
- Библиотека универсальная, но сделана с упором на максимальную оптимизацию памяти при работе во всех режимах внутри одного класса, поэтому используется шаблон и дефайны
- При создании объекта с разным количеством пинов (энкодер, кнопка, энкодер с кнопкой) библиотека будет компилироваться по разному, ненужный код будет вырезан. Это позволяет экономить Flash память.
- То же самое касается режимов работы TICK/CALLBACK, при использовании TICK весь относящийся к CALLBACK код вырезается компилятором
- Два алгоритма опроса энкодера, обычный и точный. Точный использует на 16 байт больше SRAM памяти (на всю библиотеку), но позволяет работать даже с низкокачественными и убитыми энкодерами
- Точный алгоритм активируется добавлением `#define EB_BETTER_ENC` перед подключением библиотеки
- Версия библиотеки *EncButton2.h* хранит номера пинов в классе. Используйте эту версию для создания массива объектов EncButton!
<a id="usage"></a>
## Особенности и сценарии использования
### Дефайны настроек
```cpp
// дефайнить ПЕРЕД ПОДКЛЮЧЕНИЕМ БИБЛИОТЕКИ, показаны значения по умолчанию (если они есть)
// энкодер
#define EB_FAST 30 // таймаут быстрого поворота, мс
#define EB_BETTER_ENC // улучшенный алгоритм опроса энкодера. Добавит 16 байт SRAM при подключении библиотеки
#define EB_HALFSTEP_ENC // режим опроса полушагового энкодера (включи, если твой энкодер делает два тика за один)
// кнопка
#define EB_DEB 50 // дебаунс кнопки, мс
#define EB_STEP 500 // период срабатывания степ, мс
#define EB_CLICK 400 // таймаут накликивания, мс
#define EB_HOLD 1000 // таймаут удержания кнопки (можно переназначить setHoldTimeout() из программы), мс
```
### Режим tick
- Опрос пинов энкодера/кнопки и расчёт таймаутов осуществляется внутри функции `tick()`. Эту функцию нужно однократно вызывать в основном цикле программы.
- Для повышения качества обработки энкодера/кнопки в загруженной программе (чтобы не пропустить поворот или клик) рекомендуется продублировать опрос в прерывании по *CHANGE*: внутри обработчика прерывания вызываем специальный тикер `tickISR()`, и в основном цикле программы оставляем обычный `tick()`. Он нужен для того, чтобы корректно считались все таймауты.
- `tick()` возвращает текущий статус энкодера/кнопки:
- 0 - никаких действий не было
- 1 - left + turn
- 2 - right + turn
- 3 - leftH + turnH
- 4 - rightH + turnH
- 5 - click
- 6 - held
- 7 - step
- 8 - press
Это позволяет например производить дальнейший опрос действий кнопки/энкодера только по факту их совершения: можно поместить весь опрос в блок `if (enc.tick()) {}`. В конце рекомендуется вызвать `resetState()` для сборса неопрошенных флагов, чтобы `tick()` перестал "сигналить" о действии. Подробнее смотри в примере *optimisation*.
- Основная идея работы: "тикнули", а затем вручную через условия опрашиваем нужные действия кнопки/энкодера. Почти все функции опроса имеют механизм "однократного срабатывания", то есть возвращают `true` и автоматически сбрасываются в `false` до наступления следующего события. Таким образом конструкция `if (btn.click())` позволяет выполнить какой-то блок кода однократно по клику. Подробнее разберём ниже.
#### Кнопка
- `press()` - кнопка была нажата. *[однократно вернёт true]*
- `release()` - кнопка была отпущена. *[однократно вернёт true]*
- `click()` - кнопка была кликнута, т.е. нажата и отпущена до таймаута удержания. *[однократно вернёт true]*
- `held()` - кнопка была удержана дольше таймаута удержания. *[однократно вернёт true]*
- `held(clicks)` - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: held() без аргумента перехватит вызов! См. пример *preClicks*. *[однократно вернёт true]*
- `hold()` - кнопка была удержана дольше таймаута удержания. *[возвращает true, пока удерживается]*
- `hold(clicks)` - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: hold() без аргумента перехватит вызов! См. пример *preClicks*. *[возвращает true, пока удерживается]*
- `step()` - режим "импульсного удержания": после удержания кнопки дольше таймаута данная функция *[возвращает true с периодом EB_STEP]*. Удобно использовать для пошагового изменения переменных: `if (btn.step()) val++;`.
- `step(clicks)` - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: step() без аргумента перехватит вызов! См. пример *StepMode* и *preClicks*.
- `releaseStep()` - кнопка была отпущена после импульсного удержания. Может использоваться для изменения знака инкремента переменной. См. пример *StepMode*. *[однократно вернёт true]*
- `releaseStep(clicks)` - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: releaseStep() без аргумента перехватит вызов! См. пример *StepMode* и *preClicks*. *[однократно вернёт true]*
- `hasClicks(clicks)` - было сделано указанное количество кликов с периодом менее *EB_CLICK*. *[однократно вернёт true]*
- `state()` - возвращает теукщее состояние кнопки (сигнал с пина, без антидребезга): `true` - нажата, `false` - не нажата.
- `busy()` - вернёт `true`, если всё ещё нужно вызывать tick для опроса таймаутов
- `hasClicks()` - вернёт количество кликов, сделанных с периодом менее *EB_CLICK*. В противном случае вернёт 0.
- `uint8_t clicks` - публичная переменная (член класса), хранит количество сделанных кликов с периодом менее *EB_CLICK*. Сбрасывается в 0 после нового клика.
![diagram](/doc/diagram.png)
#### Энкодер
- `turn()` - поворот на один щелчок в любую сторону. *[однократно вернёт true]*
- `turnH()` - поворот на один щелчок в любую сторону с зажатой кнопкой. *[однократно вернёт true]*
- `fast()` - был совершён быстрый поворот (с периодом менее *EB_FAST* мс) на один щелчок в любую сторону. *[возвращает true, пока энкодер крутится быстро]*
- `right()` - поворот на один щелчок направо. *[однократно вернёт true]*
- `left()` - поворот на один щелчок налево. *[однократно вернёт true]*
- `rightH()` - поворот на один щелчок направо с зажатой кнопкой. *[однократно вернёт true]*
- `leftH()` - поворот на один щелчок налево с зажатой кнопкой. *[однократно вернёт true]*
- `getDir()` - направление последнего поворота, 1 или -1.
- `int16_t counter` - публичная переменная (член класса), хранит счётчик энкодера.
### Режим callback
- В данном режиме можно подключить свою функцию-обработчик на любое действие кнопки/энкодера. Они будут автоматически вызываться при наступлении события.
- Для работы нужно вызывать `tick()` в основном цикле программы, а также можно продублировать `tickISR()` в прерывании по *CHANGE* для улучшения точности обработки энкодера.
- При работе в прерывании подключенные функции вызываются из `tick()`, т.е. из основного цикла программы. В `tickISR()` происходит только обработка алгоритмов библиотеки!
- Смотри пример *callbackMode*
```cpp
void attach(type, func); // подключить обработчик
void detach(type); // отключить обработчик
void attachClicks(uint8_t amount, func); // подключить обработчик на количество кликов (может быть только один!)
void detachClicks(); // отключить обработчик на количество кликов
```
Где `type` - тип события:
- *TURN_HANDLER* - поворот
- *TURN_H_HANDLER* - нажатый поворот
- *RIGHT_HANDLER* - поворот направо
- *LEFT_HANDLER* - поворот налево
- *RIGHT_H_HANDLER* - нажатый поворот направо
- *LEFT_H_HANDLER* - нажатый поворот налево
- *PRESS_HANDLER* - нажатие
- *RELEASE_HANDLER* - отпускание
- *CLICK_HANDLER* - клик
- *HOLDED_HANDLER* - удержание (однократное срабатывание)
- *HOLD_HANDLER* - удержание (постоянное срабатывание)
- *STEP_HANDLER* - импульсное удержание
- *CLICKS_HANDLER* - несколько кликов
### Виртуальный режим
Виртуальный режим позволяет получить все возможности библиотеки EncButton в ситуациях, когда кнопка не подключена напрямую к микроконтроллеру, либо для её опроса используется другая библиотека:
- Аналоговая клавиатура (например через библиотеку [AnalogKey](https://github.com/GyverLibs/AnalogKey)). Смотри пример *virtual_AnalogKey*
- Матричная клавиатура (например через библиотеку [SimpleKeypad](https://github.com/maximebohrer/SimpleKeypad)). Смотри пример *virtual_SimpleKeypad* и *virtual_SimpleKeypad_array*
- Кнопки или энкодеры, подключенные через расширители пинов или сдвиговые регистры
Таким образом можно получить несколько нажатий с матричной клавиатуры, удержание кнопок матричной клавиатуры, импульсное удержание и прочие фишки EncButton.
Для работы нужно передать в `tick()` текущие состояния "пинов" кнопки/энкодера: `tick(s1, s2, s3)` в следующем порядке
- Кнопка - (сигнал кнопки)
- Энкодер - (сигнал энкодера А, сигнал энкодера B)
- Энкодер с кнопкой - (сигнал энкодера А, сигнал энкодера B, сигнал кнопки)
### Настройки
```cpp
void pullUp(); // подтянуть все пины внутренней подтяжкой
void holdEncButton(bool state); // виртуально зажать кнопку энкодера
void setHoldTimeout(int tout); // установить время удержания кнопки, мс (до 30 000)
void setButtonLevel(bool level); // уровень кнопки: LOW - кнопка подключает GND (по умолч.), HIGH - кнопка подключает VCC
```
<a id="example"></a>
## Примеры
### Полное демо tick
Остальные примеры смотри в **examples**!
```cpp
// Пример с прямой работой библиотеки
#include <EncButton.h>
EncButton<EB_TICK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_TICK, 2, 3> enc; // просто энкодер <A, B>
//EncButton<EB_TICK, 4> enc; // просто кнопка <KEY>
void setup() {
Serial.begin(9600);
// ещё настройки
//enc.counter = 100; // изменение счётчика энкодера
//enc.setHoldTimeout(500); // установка таймаута удержания кнопки
//enc.setButtonLevel(HIGH); // LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
}
void loop() {
enc.tick(); // опрос происходит здесь
// =============== ЭНКОДЕР ===============
// обычный поворот
if (enc.turn()) {
Serial.println("turn");
// можно опросить ещё:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
// "нажатый поворот"
if (enc.turnH()) {
Serial.println("hold + turn");
// можно опросить ещё:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
if (enc.left()) Serial.println("left"); // поворот налево
if (enc.right()) Serial.println("right"); // поворот направо
if (enc.leftH()) Serial.println("leftH"); // нажатый поворот налево
if (enc.rightH()) Serial.println("rightH"); // нажатый поворот направо
// =============== КНОПКА ===============
if (enc.press()) Serial.println("press");
if (enc.click()) Serial.println("click");
if (enc.release()) Serial.println("release");
if (enc.held()) Serial.println("held"); // однократно вернёт true при удержании
//if (enc.hold()) Serial.println("hold"); // будет постоянно возвращать true после удержания
if (enc.step()) Serial.println("step"); // импульсное удержание
if (enc.releaseStep()) Serial.println("release step"); // отпущена после импульсного удержания
// проверка на количество кликов
if (enc.hasClicks(1)) Serial.println("action 1 clicks");
if (enc.hasClicks(2)) Serial.println("action 2 clicks");
if (enc.hasClicks(3)) Serial.println("action 3 clicks");
if (enc.hasClicks(5)) Serial.println("action 5 clicks");
// вывести количество кликов
if (enc.hasClicks()) {
Serial.print("has clicks ");
Serial.println(enc.clicks);
}
}
```
### Массив кнопок EncButton2
```cpp
// объявляем массив кнопок
#define BTN_AMOUNT 5
#include <EncButton2.h>
EncButton2<EB_BTN> btn[BTN_AMOUNT];
void setup() {
Serial.begin(9600);
btn[0].setPins(INPUT_PULLUP, D3);
btn[1].setPins(INPUT_PULLUP, D2);
}
void loop() {
for (int i = 0; i < BTN_AMOUNT; i++) btn[i].tick();
for (int i = 0; i < BTN_AMOUNT; i++) {
if (btn[i].click()) {
Serial.print("click btn: ");
Serial.println(i);
}
}
}
```
### Одна кнопка управляет несколькими переменными
```cpp
// используем одну КНОПКУ для удобного изменения трёх переменных
// первая - один клик, затем удержание (нажал-отпустил-нажал-держим)
// вторая - два клика, затем удержание
// третья - три клика, затем удержание
// смотри монитор порта
#include <EncButton.h>
EncButton<EB_TICK, 3> btn;
// переменные для изменения
int val_a, val_b, val_c;
// шаги изменения (signed)
int8_t step_a = 1;
int8_t step_b = 5;
int8_t step_c = 10;
void setup() {
Serial.begin(9600);
}
void loop() {
btn.tick();
// передаём количество предварительных кликов
if (btn.step(1)) {
val_a += step_a;
Serial.print("val_a: ");
Serial.println(val_a);
}
if (btn.step(2)) {
val_b += step_b;
Serial.print("val_b: ");
Serial.println(val_b);
}
if (btn.step(3)) {
val_c += step_c;
Serial.print("val_c: ");
Serial.println(val_c);
}
// разворачиваем шаг для изменения в обратную сторону
// передаём количество предварительных кликов
if (btn.releaseStep(1)) step_a = -step_a;
if (btn.releaseStep(2)) step_b = -step_b;
if (btn.releaseStep(3)) step_c = -step_c;
}
```
<a id="versions"></a>
## Версии
- v1.1 - пуллап отдельныи методом
- v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч)
- v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения
- v1.4 - обработка нажатия и отпускания кнопки
- v1.5 - добавлен виртуальный режим
- v1.6 - оптимизация работы в прерывании
- v1.6.1 - подтяжка по умолчанию INPUT_PULLUP
- v1.7 - большая оптимизация памяти, переделан FastIO
- v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех)
- v1.8.1 - убран FastIO
- v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления
- v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги
- v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки
- v1.11.1 - совместимость Digispark
- v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC
- v1.13 - добавлен экспериментальный EncButton2
- v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс
- v1.15 - добавлен setPins() для EncButton2
- v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров
- v1.17 - добавлен step с предварительными кликами
- v1.18 - не считаем клики после активации step. held() и hold() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс
- v1.18.1 - исправлена ошибка в releaseStep() (не возвращала результат)
- v1.18.2 - fix compiler warnings
- v1.19 - оптимизация скорости, уменьшен вес в sram
- v1.19.1 - ещё чутка увеличена производительность
- v1.19.2 - ещё немного увеличена производительность, спасибо XRay3D
- v1.19.3 - сделал высокий уровень кнопки по умолчанию в виртуальном режиме
- v1.19.4 - фикс EncButton2
- v1.20 - исправлена критическая ошибка в EncButton2
<a id="feedback"></a>
## Баги и обратная связь
При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
Библиотека открыта для доработки и ваших **Pull Request**'ов!
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@@ -0,0 +1,77 @@
// пример с библиотекой EncButton2
// Опциональные дефайн-настройки (показаны по умолчанию)
//#define EB_FAST 30 // таймаут быстрого поворота, мс
//#define EB_DEB 50 // дебаунс кнопки, мс
//#define EB_HOLD 1000 // таймаут удержания кнопки, мс
//#define EB_STEP 500 // период срабатывания степ, мс
//#define EB_CLICK 400 // таймаут накликивания, мс
#include <EncButton2.h>
EncButton2<EB_ENCBTN> enc(INPUT, 2, 3, 4); // энкодер с кнопкой
//EncButton2<EB_ENC> enc(INPUT, 2, 3); // просто энкодер
//EncButton2<EB_BTN> enc(INPUT, 4); // просто кнопка
// для изменения направления энкодера поменяй A и B при инициализации
// по умолчанию пины настроены в INPUT_PULLUP
// Если используется внешняя подтяжка - лучше перевести в INPUT
//EncButton<EB_TICK, 2, 3, 4> enc(INPUT);
void setup() {
Serial.begin(9600);
// ещё настройки
//enc.counter = 100; // изменение счётчика энкодера
//enc.setHoldTimeout(500); // установка таймаута удержания кнопки
//enc.setButtonLevel(HIGH); // LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
}
void loop() {
enc.tick(); // опрос происходит здесь
// =============== ЭНКОДЕР ===============
// обычный поворот
if (enc.turn()) {
Serial.println("turn");
// можно опросить ещё:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
// "нажатый поворот"
if (enc.turnH()) {
Serial.println("hold + turn");
// можно опросить ещё:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
if (enc.left()) Serial.println("left"); // поворот налево
if (enc.right()) Serial.println("right"); // поворот направо
if (enc.leftH()) Serial.println("leftH"); // нажатый поворот налево
if (enc.rightH()) Serial.println("rightH"); // нажатый поворот направо
// =============== КНОПКА ===============
if (enc.press()) Serial.println("press");
if (enc.click()) Serial.println("click");
if (enc.release()) Serial.println("release");
if (enc.held()) Serial.println("held"); // однократно вернёт true при удержании
//if (enc.hold()) Serial.println("hold"); // будет постоянно возвращать true после удержания
if (enc.step()) Serial.println("step"); // импульсное удержание
// проверка на количество кликов
if (enc.hasClicks(1)) Serial.println("action 1 clicks");
if (enc.hasClicks(2)) Serial.println("action 2 clicks");
if (enc.hasClicks(3)) Serial.println("action 3 clicks");
if (enc.hasClicks(5)) Serial.println("action 5 clicks");
// вывести количество кликов
if (enc.hasClicks()) {
Serial.print("has clicks ");
Serial.println(enc.clicks);
}
}
@@ -0,0 +1,20 @@
// объявляем массив кнопок
#define BTN_AMOUNT 5
#include <EncButton2.h>
EncButton2<EB_BTN> btn[BTN_AMOUNT];
void setup() {
Serial.begin(9600);
btn[0].setPins(INPUT_PULLUP, D3);
btn[1].setPins(INPUT_PULLUP, D2);
}
void loop() {
for (int i = 0; i < BTN_AMOUNT; i++) btn[i].tick();
for (int i = 0; i < BTN_AMOUNT; i++) {
if (btn[i].click()) {
Serial.print("click btn: ");
Serial.println(i);
}
}
}
@@ -0,0 +1,29 @@
// Пример с обработчиками в прерывании
#include <EncButton.h>
EncButton<EB_CALLBACK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
void setup() {
Serial.begin(9600);
enc.attach(TURN_HANDLER, myTurn); // подключим поворот
// прерывание обеих фаз энкодера на функцию isr
attachInterrupt(0, isr, CHANGE);
attachInterrupt(1, isr, CHANGE);
}
void myTurn() {
Serial.print("TURN_HANDLER: ");
Serial.println(enc.counter);
}
void isr() {
enc.tickISR(); // тикер в прерывании
// Не вызывает подключенные коллбэки внутри прерывания!!!
}
void loop() {
enc.tick(); // дополнительный опрос таймаутов и коллбэков в loop
// вызов подключенных функций будет здесь,
// чтобы не грузить прерывание
}
@@ -0,0 +1,91 @@
// Пример с обработчиками
#include <EncButton.h>
EncButton<EB_CALLBACK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_CALLBACK, 2, 3> enc; // просто энкодер <A, B>
//EncButton<EB_CALLBACK, 4> enc; // просто кнопка <KEY>
void setup() {
Serial.begin(9600);
enc.attach(TURN_HANDLER, myTurn);
enc.attach(TURN_H_HANDLER, myTurnH);
enc.attach(RIGHT_HANDLER, myRight);
enc.attach(LEFT_HANDLER, myLeft);
enc.attach(RIGHT_H_HANDLER, myRightH);
enc.attach(LEFT_H_HANDLER, myLeftH);
enc.attach(CLICK_HANDLER, myClick);
enc.attach(HOLDED_HANDLER, myHolded);
enc.attach(STEP_HANDLER, myStep);
enc.attach(PRESS_HANDLER, myPress);
enc.attach(RELEASE_HANDLER, myRelease);
//enc.attach(HOLD_HANDLER, myHold);
enc.attach(CLICKS_HANDLER, myClicks);
enc.attachClicks(5, fiveClicks);
}
void myTurn() {
Serial.println("TURN_HANDLER: ");
// тут можно:
Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
//Serial.println(enc.getDir()); // направление поворота
}
void myTurnH() {
Serial.println("TURN_H_HANDLER");
// тут можно:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
void myRight() {
Serial.println("RIGHT_HANDLER");
}
void myLeft() {
Serial.println("LEFT_HANDLER");
}
void myRightH() {
Serial.println("RIGHT_H_HANDLER");
}
void myLeftH() {
Serial.println("LEFT_H_HANDLER");
}
void myClick() {
Serial.println("CLICK_HANDLER");
}
void myHolded() {
Serial.println("HOLDED_HANDLER");
}
void myStep() {
Serial.println("STEP_HANDLER");
}
void myClicks() {
Serial.print("CLICKS_HANDLER: ");
Serial.println(enc.clicks);
}
void fiveClicks() {
Serial.println("action fiveClicks");
}
void myPress() {
Serial.println("PRESS_HANDLER");
}
void myRelease() {
Serial.println("RELEASE_HANDLER");
}
void myHold() {
Serial.println("HOLD_HANDLER");
}
// =============== LOOP =============
void loop() {
enc.tick(); // обработка всё равно здесь
}
@@ -0,0 +1,41 @@
// пример с небольшой оптимизацией опроса
#include <EncButton.h>
EncButton<EB_TICK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_TICK, 2, 3> enc; // просто энкодер <A, B>
//EncButton<EB_TICK, 4> enc; // просто кнопка <KEY>
void setup() {
Serial.begin(9600);
}
void loop() {
// тик вернёт отличное от нуля значение, если произошло событие:
// 1 - left + turn
// 2 - right + turn
// 3 - leftH + turnH
// 4 - rightH + turnH
// 5 - click
// 6 - held
// 7 - step
// 8 - press
// опрос этих событий можно проводить в условии,
// чтобы "не тратить время" на постоянный опрос в loop
if (enc.tick()) {
if (enc.turn()) Serial.println("turn");
if (enc.turnH()) Serial.println("hold + turn");
if (enc.left()) Serial.println("left");
if (enc.right()) Serial.println("right");
if (enc.leftH()) Serial.println("leftH");
if (enc.rightH()) Serial.println("rightH");
if (enc.press()) Serial.println("press");
if (enc.click()) Serial.println("click");
if (enc.held()) Serial.println("held");
if (enc.step()) Serial.println("step");
// в конце лучше вызвать resetState(), чтобы сбросить необработанные флаги!
enc.resetState();
}
}
@@ -0,0 +1,70 @@
// пример с прерываниями pinChangeInterrupt (прерывания на любом пине)
// только для ATmega328 (UNO, Nano, Pro Mini)
#define CLK 4
#define DT 5
#define SW 6
#include <EncButton.h>
EncButton<EB_TICK, CLK, DT, SW> enc;
void setup() {
Serial.begin(9600);
// настроить PCINT
attachPCINT(CLK);
attachPCINT(DT);
}
void loop() {
// оставляем тут для работы "временных" функций и антидребезга
enc.tick();
if (enc.turn()) { // любой поворот
Serial.print("turn ");
Serial.println(enc.counter); // вывод счётчика
}
if (enc.left()) {
if (enc.fast()) Serial.println("fast left");
else Serial.println("left");
}
if (enc.right()) {
if (enc.fast()) Serial.println("fast right");
else Serial.println("right");
}
}
// функция для настройки PCINT для ATmega328 (UNO, Nano, Pro Mini)
uint8_t attachPCINT(uint8_t pin) {
if (pin < 8) { // D0-D7 - PCINT2
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << pin);
return 2;
}
else if (pin > 13) { // A0-A5 - PCINT1
PCICR |= (1 << PCIE1);
PCMSK1 |= (1 << pin - 14);
return 1;
}
else { // D8-D13 - PCINT0
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << pin - 8);
return 0;
}
}
// Векторы PCINT, нужно кинуть сюда tickISR
// пины 0-7: PCINT2
// пины 8-13: PCINT0
// пины A0-A5: PCINT1
ISR(PCINT0_vect) {
}
ISR(PCINT1_vect) {
}
ISR(PCINT2_vect) {
enc.tickISR();
}
@@ -0,0 +1,33 @@
// срабатывание функций held/hold/step после предварительных кликов
#include <EncButton.h>
EncButton<EB_TICK, 3> btn;
void setup() {
Serial.begin(9600);
}
void loop() {
btn.tick();
if (btn.click()) Serial.println("click");
// вызов без количества кликов перехватит все остальные вызовы!
//if (btn.held()) Serial.println("held any clicks");
if (btn.held(0)) Serial.println("held after 0 clicks");
if (btn.held(2)) Serial.println("held after 2 clicks");
// вызов без количества кликов перехватит все остальные вызовы!
//if (btn.hold()) Serial.println("hold any clicks");
//if (btn.hold(0)) Serial.println("hold after 0 clicks");
//if (btn.hold(2)) Serial.println("hold after 2 clicks");
// вызов без количества кликов перехватит все остальные вызовы!
//if (btn.step()) Serial.println("step after any clicks");
if (btn.step(0)) Serial.println("step after 0 clicks");
if (btn.step(2)) Serial.println("step after 2 clicks");
// вызов без количества кликов перехватит все остальные вызовы!
//if (btn.releaseStep()) Serial.println("release step after any clicks");
if (btn.releaseStep(0)) Serial.println("release step after 0 clicks");
if (btn.releaseStep(2)) Serial.println("release step after 2 clicks");
}
@@ -0,0 +1,47 @@
// используем одну КНОПКУ для удобного изменения трёх переменных
// первая - один клик, затем удержание (нажал-отпустил-нажал-держим)
// вторая - два клика, затем удержание
// третья - три клика, затем удержание
// смотри монитор порта
#include <EncButton.h>
EncButton<EB_TICK, 3> btn;
// переменные для изменения
int val_a, val_b, val_c;
// шаги изменения (signed)
int8_t step_a = 1;
int8_t step_b = 5;
int8_t step_c = 10;
void setup() {
Serial.begin(9600);
}
void loop() {
btn.tick();
// передаём количество предварительных кликов
if (btn.step(1)) {
val_a += step_a;
Serial.print("val_a: ");
Serial.println(val_a);
}
if (btn.step(2)) {
val_b += step_b;
Serial.print("val_b: ");
Serial.println(val_b);
}
if (btn.step(3)) {
val_c += step_c;
Serial.print("val_c: ");
Serial.println(val_c);
}
// разворачиваем шаг для изменения в обратную сторону
// передаём количество предварительных кликов
if (btn.releaseStep(1)) step_a = -step_a;
if (btn.releaseStep(2)) step_b = -step_b;
if (btn.releaseStep(3)) step_c = -step_c;
}
@@ -0,0 +1,34 @@
// Пример с прямой работой библиотеки на прерываниях
#include <EncButton.h>
EncButton<EB_TICK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_TICK, 2, 3> enc; // просто энкодер <A, B>
//EncButton<EB_TICK, 4> enc; // просто кнопка <KEY>
void setup() {
Serial.begin(9600);
// желательно подключить оба пина энкодера на внешние прерывания по CHANGE
// можно использовать PCINT https://github.com/NicoHood/PinChangeInterrupt
attachInterrupt(0, isr, CHANGE); // D2
attachInterrupt(1, isr, CHANGE); // D3
// подключил оба прерывания на одну функцию
}
void isr() {
enc.tickISR(); // в прерывании вызываем тик ISR
}
void loop() {
// тут тоже вызываем тик, нужен для
// корректной работы дебаунсов и прочих таймеров!!!
enc.tick();
if (enc.turn()) { // любой поворот
Serial.print("turn ");
Serial.println(enc.counter); // вывод счётчика
}
// имитация загруженной программы, обработка происходит в прерывании
delay(50);
}
@@ -0,0 +1,78 @@
// Пример с прямой работой библиотеки
// просто загрузи и потыкай - всё будет понятно =)
// Опциональные дефайн-настройки (показаны по умолчанию)
//#define EB_FAST 30 // таймаут быстрого поворота, мс
//#define EB_DEB 50 // дебаунс кнопки, мс
//#define EB_HOLD 1000 // таймаут удержания кнопки, мс
//#define EB_STEP 500 // период срабатывания степ, мс
//#define EB_CLICK 400 // таймаут накликивания, мс
#include <EncButton.h>
//EncButton<EB_TICK, 2, 3, 4> enc; // энкодер с кнопкой <A, B, KEY>
//EncButton<EB_TICK, 2, 3> enc; // просто энкодер <A, B>
EncButton<EB_TICK, 4> enc; // просто кнопка <KEY>
// для изменения направления энкодера поменяй A и B при инициализации
// по умолчанию пины настроены в INPUT_PULLUP
// Если используется внешняя подтяжка - лучше перевести в INPUT
//EncButton<EB_TICK, 2, 3, 4> enc(INPUT);
void setup() {
Serial.begin(9600);
// ещё настройки
//enc.counter = 100; // изменение счётчика энкодера
//enc.setHoldTimeout(500); // установка таймаута удержания кнопки
//enc.setButtonLevel(HIGH); // LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
}
void loop() {
enc.tick(); // опрос происходит здесь
// =============== ЭНКОДЕР ===============
// обычный поворот
if (enc.turn()) {
Serial.println("turn");
// можно опросить ещё:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
// "нажатый поворот"
if (enc.turnH()) {
Serial.println("hold + turn");
// можно опросить ещё:
//Serial.println(enc.counter); // вывести счётчик
//Serial.println(enc.fast()); // проверить быстрый поворот
Serial.println(enc.getDir()); // направление поворота
}
if (enc.left()) Serial.println("left"); // поворот налево
if (enc.right()) Serial.println("right"); // поворот направо
if (enc.leftH()) Serial.println("leftH"); // нажатый поворот налево
if (enc.rightH()) Serial.println("rightH"); // нажатый поворот направо
// =============== КНОПКА ===============
if (enc.press()) Serial.println("press");
if (enc.click()) Serial.println("click");
if (enc.release()) Serial.println("release");
if (enc.held()) Serial.println("held"); // однократно вернёт true при удержании
//if (enc.hold()) Serial.println("hold"); // будет постоянно возвращать true после удержания
if (enc.step()) Serial.println("step"); // импульсное удержание
// проверка на количество кликов
if (enc.hasClicks(1)) Serial.println("action 1 clicks");
if (enc.hasClicks(2)) Serial.println("action 2 clicks");
if (enc.hasClicks(3)) Serial.println("action 3 clicks");
if (enc.hasClicks(5)) Serial.println("action 5 clicks");
// вывести количество кликов
if (enc.hasClicks()) {
Serial.print("has clicks ");
Serial.println(enc.clicks);
}
}
@@ -0,0 +1,33 @@
#include <EncButton.h>
EncButton<EB_TICK, VIRT_BTN> enc; // виртуальная кнопка
//EncButton<EB_TICK, VIRT_ENCBTN> enc; // виртуальный энк с кнопкой
//EncButton<EB_TICK, VIRT_ENC> enc; // виртуальный энк
void setup() {
Serial.begin(9600);
pinMode(4, INPUT_PULLUP); // подтянем пин
//enc.setHoldTimeout(500); // установка таймаута удержания кнопки
}
void loop() {
// tick может принимать виртуальный сигнал:
// (сигнал кнопки)
// (сигнал энкодера А, сигнал энкодера B)
// (сигнал энкодера А, сигнал энкодера B, сигнал кнопки)
enc.tick(!digitalRead(4));
if (enc.press()) Serial.println("press");
if (enc.click()) Serial.println("click");
if (enc.held()) Serial.println("held");
if (enc.step()) Serial.println("step");
if (enc.release()) Serial.println("release");
if (enc.hasClicks(1)) Serial.println("1 click");
if (enc.hasClicks(2)) Serial.println("2 click");
if (enc.hasClicks(3)) Serial.println("3 click");
if (enc.hasClicks(5)) Serial.println("5 click");
if (enc.hasClicks()) Serial.println(enc.clicks);
//if (enc.hold()) Serial.println("hold");
}
@@ -0,0 +1,35 @@
// пример работы в виртуальном режиме совместно с библиотекой AnalogKey
// https://github.com/GyverLibs/AnalogKey
#include <EncButton.h>
EncButton<EB_TICK, VIRT_BTN> btn0;
EncButton<EB_TICK, VIRT_BTN> btn1;
#include <AnalogKey.h>
// создаём массив значений сигналов с кнопок
int16_t sigs[16] = {
1023, 927, 856, 783,
671, 632, 590, 560,
504, 480, 455, 440,
399, 319, 255, 230
};
// указываем пин, количество кнопок и массив значений
AnalogKey<A0, 16, sigs> keys;
void setup() {
Serial.begin(9600);
}
void loop() {
btn0.tick(keys.status(0));
btn1.tick(keys.status(1));
// забираем действия с кнопок
if (btn0.click()) Serial.println("click 0");
if (btn0.held()) Serial.println("held 0");
if (btn1.press()) Serial.println("press 1");
if (btn1.step()) Serial.println("step 1");
}
@@ -0,0 +1,44 @@
// пример работы в виртуальном режиме совместно с библиотекой SimpleKeypad
// https://github.com/maximebohrer/SimpleKeypad
#include <EncButton.h>
EncButton<EB_TICK, VIRT_BTN> btn0;
EncButton<EB_TICK, VIRT_BTN> btn1;
// пины подключения (по порядку штекера)
byte colPins[] = {7, 6, 5, 4};
byte rowPins[] = {11, 10, 9, 8};
// массив имён кнопок
char keys[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
#include <SimpleKeypad.h>
SimpleKeypad pad((char*)keys, rowPins, colPins, 4, 4);
void setup() {
Serial.begin(9600);
}
void loop() {
// тикаем все кнопки, передавая сравнение с кодом кнопки в цикле
// делаем это по таймеру, чтобы не опрашивать клавиатуру постоянно
static uint32_t tmr;
if (millis() - tmr >= 10) {
tmr = millis();
char key = pad.scan();
btn0.tick(key == '1');
btn1.tick(key == '2');
}
// забираем действия с кнопок
if (btn0.click()) Serial.println("click 0");
if (btn0.held()) Serial.println("held 0");
if (btn1.press()) Serial.println("press 1");
if (btn1.step()) Serial.println("step 1");
}
@@ -0,0 +1,44 @@
// пример работы в виртуальном режиме совместно с библиотекой SimpleKeypad
// https://github.com/maximebohrer/SimpleKeypad
// передаём EncButton сразу всю клавиатуру через массивы и циклы
#include <EncButton.h>
EncButton<EB_TICK, VIRT_BTN> btn[16];
// пины подключения (по порядку штекера)
byte colPins[] = {7, 6, 5, 4};
byte rowPins[] = {11, 10, 9, 8};
// массив имён кнопок
char keys[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
#include <SimpleKeypad.h>
SimpleKeypad pad((char*)keys, rowPins, colPins, 4, 4);
void setup() {
Serial.begin(9600);
}
void loop() {
// массово тикаем все кнопки, передавая сравнение с кодом кнопки в цикле
// делаем это по таймеру, чтобы не опрашивать клавиатуру постоянно
static uint32_t tmr;
if (millis() - tmr >= 10) {
tmr = millis();
char key = pad.scan();
char* keysPtr = (char*)keys; // указатель для удобства опроса
for (int i = 0; i < 16; i++) btn[i].tick(key == keysPtr[i]);
}
// забираем действия с кнопок
if (btn[0].click()) Serial.println("click 0");
if (btn[0].held()) Serial.println("held 0");
if (btn[1].press()) Serial.println("press 1");
if (btn[1].step()) Serial.println("step 1");
}
+102
View File
@@ -0,0 +1,102 @@
#######################################
# Syntax Coloring Map For EncButton
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
EncButton KEYWORD1
EncButton2 KEYWORD1
EB_FAST KEYWORD1
EB_DEB KEYWORD1
EB_HOLD KEYWORD1
EB_STEP KEYWORD1
EB_CLICK KEYWORD1
EB_BETTER_ENC KEYWORD1
EB_HALFSTEP_ENC KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
tick KEYWORD2
tickISR KEYWORD2
getState KEYWORD2
resetState KEYWORD2
checkCallback KEYWORD2
setPins KEYWORD2
press KEYWORD2
release KEYWORD2
releaseStep KEYWORD2
fast KEYWORD2
turn KEYWORD2
turnH KEYWORD2
right KEYWORD2
left KEYWORD2
rightH KEYWORD2
leftH KEYWORD2
click KEYWORD2
held KEYWORD2
hold KEYWORD2
step KEYWORD2
busy KEYWORD2
getDir KEYWORD2
counter KEYWORD2
state KEYWORD2
hasClicks KEYWORD2
clicks KEYWORD2
attach KEYWORD2
detach KEYWORD2
attachClicks KEYWORD2
detachClicks KEYWORD2
pullUp KEYWORD2
holdEncButton KEYWORD2
setHoldTimeout KEYWORD2
setButtonLevel KEYWORD2
# deprecated
isPress KEYWORD2
isRelease KEYWORD2
isFast KEYWORD2
isTurn KEYWORD2
isTurnH KEYWORD2
isRight KEYWORD2
isLeft KEYWORD2
isRightH KEYWORD2
isLeftH KEYWORD2
isClick KEYWORD2
isHolded KEYWORD2
isHeld KEYWORD2
isHold KEYWORD2
isStep KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
EB_TICK LITERAL1
EB_CALLBACK LITERAL1
EB_BTN LITERAL1
EB_ENC LITERAL1
EB_ENCBTN LITERAL1
VIRT_BTN LITERAL1
VIRT_ENC LITERAL1
VIRT_ENCBTN LITERAL1
TURN_HANDLER LITERAL1
TURN_H_HANDLER LITERAL1
RIGHT_HANDLER LITERAL1
LEFT_HANDLER LITERAL1
RIGHT_H_HANDLER LITERAL1
LEFT_H_HANDLER LITERAL1
CLICK_HANDLER LITERAL1
HOLDED_HANDLER LITERAL1
STEP_HANDLER LITERAL1
HOLD_HANDLER LITERAL1
CLICKS_HANDLER LITERAL1
PRESS_HANDLER LITERAL1
RELEASE_HANDLER LITERAL1
+9
View File
@@ -0,0 +1,9 @@
name=EncButton
version=1.20
author=AlexGyver <alex@alexgyver.ru>
maintainer=AlexGyver <alex@alexgyver.ru>
sentence=Light and fast library for button and encoder operation for Arduino
paragraph=Turns, clicks, pool and callback mode, optimised for interrupt using
category=Sensors
url=https://github.com/GyverLibs/EncButton
architectures=*
+488
View File
@@ -0,0 +1,488 @@
/*
Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки
Документация:
GitHub: https://github.com/GyverLibs/EncButton
Возможности:
- Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13)
- Оптимизированный вес
- Быстрые и лёгкие алгоритмы кнопки и энкодера
- Энкодер: поворот, нажатый поворот, быстрый поворот, счётчик
- Кнопка: антидребезг, клик, несколько кликов, счётчик кликов, удержание, режим step
- Подключение - только HIGH PULL!
- Опциональный режим callback (+22б SRAM на каждый экземпляр)
AlexGyver, alex@alexgyver.ru
https://alexgyver.ru/
MIT License
Опционально используется алгоритм из библиотеки // https://github.com/mathertel/RotaryEncoder
Версии:
v1.1 - пуллап отдельныи методом
v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч)
v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения
v1.4 - обработка нажатия и отпускания кнопки
v1.5 - добавлен виртуальный режим
v1.6 - оптимизация работы в прерывании
v1.6.1 - PULLUP по умолчанию
v1.7 - большая оптимизация памяти, переделан FastIO
v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех)
v1.8.1 - убран FastIO
v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления
v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги
v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки
v1.11.1 - совместимость Digispark
v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC
v1.13 - добавлен экспериментальный EncButton2
v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс
v1.15 - добавлен setPins() для EncButton2
v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров
v1.17 - добавлен step с предварительными кликами
v1.18 - не считаем клики после активации step. held() и hold() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс
v1.18.1 - исправлена ошибка в releaseStep() (не возвращала результат)
v1.18.2 - fix compiler warnings
v1.19 - оптимизация скорости, уменьшен вес в sram
v1.19.1 - ещё чутка увеличена производительность
v1.19.2 - ещё немного увеличена производительность, спасибо XRay3D
v1.19.3 - сделал высокий уровень кнопки по умолчанию в виртуальном режиме
v1.19.4 - фикс EncButton2
v1.20 - исправлена критическая ошибка в EncButton2
*/
#ifndef _EncButton_h
#define _EncButton_h
// ========= НАСТРОЙКИ (можно передефайнить из скетча) ==========
#define _EB_FAST 30 // таймаут быстрого поворота
#define _EB_DEB 50 // дебаунс кнопки
#define _EB_HOLD 1000 // таймаут удержания кнопки
#define _EB_STEP 500 // период срабатывания степ
#define _EB_CLICK 400 // таймаут накликивания
//#define EB_BETTER_ENC // точный алгоритм отработки энкодера (можно задефайнить в скетче)
// =========== НЕ ТРОГАЙ ============
#include <Arduino.h>
#ifndef nullptr
#define nullptr NULL
#endif
#ifndef EB_FAST
#define EB_FAST _EB_FAST
#endif
#ifndef EB_DEB
#define EB_DEB _EB_DEB
#endif
#ifndef EB_HOLD
#define EB_HOLD _EB_HOLD
#endif
#ifndef EB_STEP
#define EB_STEP _EB_STEP
#endif
#ifndef EB_CLICK
#define EB_CLICK _EB_CLICK
#endif
enum eb_callback {
TURN_HANDLER, // 0
LEFT_HANDLER, // 1
RIGHT_HANDLER, // 2
LEFT_H_HANDLER, // 3
RIGHT_H_HANDLER, // 4
CLICK_HANDLER, // 5
HOLDED_HANDLER, // 6
STEP_HANDLER, // 7
PRESS_HANDLER, // 8
CLICKS_HANDLER, // 9
RELEASE_HANDLER, // 10
HOLD_HANDLER, // 11
TURN_H_HANDLER, // 12
// clicks amount 13
};
// константы
#define EB_TICK 0
#define EB_CALLBACK 1
#define EB_NO_PIN 255
#define VIRT_ENC 254
#define VIRT_ENCBTN 253
#define VIRT_BTN 252
#ifdef EB_BETTER_ENC
static const int8_t _EB_DIR[] = {
0, -1, 1, 0,
1, 0, 0, -1,
-1, 0, 0, 1,
0, 1, -1, 0
};
#endif
// ===================================== CLASS =====================================
template < uint8_t _EB_MODE, uint8_t _S1 = EB_NO_PIN, uint8_t _S2 = EB_NO_PIN, uint8_t _KEY = EB_NO_PIN >
class EncButton {
public:
// можно указать режим работы пина
EncButton(const uint8_t mode = INPUT_PULLUP) {
if (_S1 < 252 && mode == INPUT_PULLUP) pullUp();
setButtonLevel(_S1 < 252 ? LOW : HIGH); // высокий уровень в виртуальном режиме
}
// подтянуть пины внутренней подтяжкой
void pullUp() {
if (_S1 < 252) { // реальное устройство
if (_S2 == EB_NO_PIN) { // обычная кнопка
pinMode(_S1, INPUT_PULLUP);
} else if (_KEY == EB_NO_PIN) { // энк без кнопки
pinMode(_S1, INPUT_PULLUP);
pinMode(_S2, INPUT_PULLUP);
} else { // энк с кнопкой
pinMode(_S1, INPUT_PULLUP);
pinMode(_S2, INPUT_PULLUP);
pinMode(_KEY, INPUT_PULLUP);
}
}
}
// установить таймаут удержания кнопки для isHold(), мс (до 30 000)
void setHoldTimeout(int tout) {
_holdT = tout >> 7;
}
// виртуально зажать кнопку энкодера
void holdEncButton(bool state) {
if (state) setF(8);
else clrF(8);
}
// уровень кнопки: LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
void setButtonLevel(bool level) {
if (level) clrF(11);
else setF(11);
}
// ===================================== TICK =====================================
// тикер, вызывать как можно чаще
// вернёт отличное от нуля значение, если произошло какое то событие
uint8_t tick(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) {
tickISR(s1, s2, key);
checkCallback();
return EBState;
}
// тикер специально для прерывания, не проверяет коллбэки
uint8_t tickISR(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) {
if (!_isrFlag) {
_isrFlag = 1;
// обработка энка (компилятор вырежет блок если не используется)
// если объявлены два пина или выбран вирт. энкодер или энкодер с кнопкой
if ((_S1 < 252 && _S2 < 252) || _S1 == VIRT_ENC || _S1 == VIRT_ENCBTN) {
uint8_t state;
if (_S1 >= 252) state = s1 | (s2 << 1); // получаем код
else state = fastRead(_S1) | (fastRead(_S2) << 1); // получаем код
poolEnc(state);
}
// обработка кнопки (компилятор вырежет блок если не используется)
// если S2 не указан (кнопка) или указан KEY или выбран вирт. энкодер с кнопкой или кнопка
if ((_S1 < 252 && _S2 == EB_NO_PIN) || _KEY != EB_NO_PIN || _S1 == VIRT_BTN || _S1 == VIRT_ENCBTN) {
if (_S1 < 252 && _S2 == EB_NO_PIN) _btnState = fastRead(_S1); // обычная кнопка
if (_KEY != EB_NO_PIN) _btnState = fastRead(_KEY); // энк с кнопкой
if (_S1 == VIRT_BTN) _btnState = s1; // вирт кнопка
if (_S1 == VIRT_ENCBTN) _btnState = key; // вирт энк с кнопкой
_btnState ^= readF(11); // инверсия кнопки
if (_btnState || readF(15)) poolBtn(); // опрос если кнопка нажата или не вышли таймауты
}
_isrFlag = 0;
}
return EBState;
}
// ===================================== CALLBACK =====================================
// проверить callback, чтобы не дёргать в прерывании
void checkCallback() {
if (_EB_MODE) {
if (turn()) exec(0);
if (turnH()) exec(12);
if (EBState > 0 && EBState <= 8) exec(EBState);
if (release()) exec(10);
if (hold()) exec(11);
if (checkFlag(6)) {
exec(9);
if (clicks == _amount) exec(13);
}
EBState = 0;
}
}
// подключить обработчик
void attach(eb_callback type, void (*handler)()) {
_callback[type] = *handler;
}
// отключить обработчик
void detach(eb_callback type) {
_callback[type] = nullptr;
}
// подключить обработчик на количество кликов (может быть только один!)
void attachClicks(uint8_t amount, void (*handler)()) {
_amount = amount;
_callback[13] = *handler;
}
// отключить обработчик на количество кликов
void detachClicks() {
_callback[13] = nullptr;
}
// ===================================== STATUS =====================================
uint8_t getState() { return EBState; } // получить статус
void resetState() { EBState = 0; } // сбросить статус
// ======================================= ENC =======================================
bool left() { return checkState(1); } // поворот влево
bool right() { return checkState(2); } // поворот вправо
bool leftH() { return checkState(3); } // поворот влево нажатый
bool rightH() { return checkState(4); } // поворот вправо нажатый
bool fast() { return readF(1); } // быстрый поворот
bool turn() { return checkFlag(0); } // энкодер повёрнут
bool turnH() { return checkFlag(9); } // энкодер повёрнут нажато
int8_t getDir() { return _dir; } // направление последнего поворота, 1 или -1
int16_t counter = 0; // счётчик энкодера
// ======================================= BTN =======================================
bool busy() { return readF(15); } // вернёт true, если всё ещё нужно вызывать tick для опроса таймаутов
bool state() { return _btnState; } // статус кнопки
bool press() { return checkState(8); } // кнопка нажата
bool release() { return checkFlag(10); } // кнопка отпущена
bool click() { return checkState(5); } // клик по кнопке
bool held() { return checkState(6); } // кнопка удержана
bool hold() { return readF(4); } // кнопка удерживается
bool step() { return checkState(7); } // режим импульсного удержания
bool releaseStep() { return checkFlag(12); } // кнопка отпущена после импульсного удержания
bool held(uint8_t clk) { return (clicks == clk) ? checkState(6) : 0; } // кнопка удержана с предварительным накликиванием
bool hold(uint8_t clk) { return (clicks == clk) ? readF(4) : 0; } // кнопка удерживается с предварительным накликиванием
bool step(uint8_t clk) { return (clicks == clk) ? checkState(7) : 0; } // режим импульсного удержания с предварительным накликиванием
bool releaseStep(uint8_t clk) { return (clicks == clk) ? checkFlag(12) : 0; } // кнопка отпущена после импульсного удержания с предварительным накликиванием
uint8_t clicks = 0; // счётчик кликов
bool hasClicks(uint8_t num) { return (clicks == num && checkFlag(7)) ? 1 : 0; } // имеются клики
uint8_t hasClicks() { return checkFlag(6) ? clicks : 0; } // имеются клики
// ===================================================================================
// =================================== DEPRECATED ====================================
bool isStep() { return step(); }
bool isHold() { return hold(); }
bool isHolded() { return held(); }
bool isHeld() { return held(); }
bool isClick() { return click(); }
bool isRelease() { return release(); }
bool isPress() { return press(); }
bool isTurnH() { return turnH(); }
bool isTurn() { return turn(); }
bool isFast() { return fast(); }
bool isLeftH() { return leftH(); }
bool isRightH() { return rightH(); }
bool isLeft() { return left(); }
bool isRight() { return right(); }
// ===================================== PRIVATE =====================================
private:
bool fastRead(const uint8_t pin) {
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
if (pin < 8) return bitRead(PIND, pin);
else if (pin < 14) return bitRead(PINB, pin - 8);
else if (pin < 20) return bitRead(PINC, pin - 14);
#elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny13__)
return bitRead(PINB, pin);
#else
return digitalRead(pin);
#endif
return 0;
}
// ===================================== POOL ENC =====================================
void poolEnc(uint8_t state) {
#ifdef EB_BETTER_ENC
if (_prev != state) {
_ecount += _EB_DIR[state | (_prev << 2)]; // сдвиг внутреннего счётчика
_prev = state;
#ifdef EB_HALFSTEP_ENC // полушаговый энкодер
// спасибо https://github.com/GyverLibs/EncButton/issues/10#issue-1092009489
if ((state == 0x3 || state == 0x0) && _ecount) {
#else // полношаговый
if (state == 0x3 && _ecount) { // защёлкнули позицию
#endif
uint16_t ms = millis() & 0xFFFF;
EBState = (_ecount < 0) ? 1 : 2;
_ecount = 0;
if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой
if (!readF(4) && (_btnState || readF(8))) EBState += 2; // если кнопка не "удерживается"
}
_dir = (EBState & 1) ? -1 : 1; // направление
counter += _dir; // счётчик
if (EBState <= 2) setF(0); // флаг поворота для юзера
else if (EBState <= 4) setF(9); // флаг нажатого поворота для юзера
if (ms - _debTmr < EB_FAST) setF(1); // быстрый поворот
else clrF(1); // обычный поворот
_debTmr = ms;
}
}
#else
if (_encRST && state == 0b11) { // ресет и энк защёлкнул позицию
uint16_t ms = millis() & 0xFFFF;
if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой
if ((_prev == 1 || _prev == 2) && !readF(4)) { // если кнопка не "удерживается" и энкодер в позиции 1 или 2
EBState = _prev;
if (_btnState || readF(8)) EBState += 2;
}
} else { // просто энкодер
if (_prev == 1 || _prev == 2) EBState = _prev;
}
if (EBState > 0) { // был поворот
_dir = (EBState & 1) ? -1 : 1; // направление
counter += _dir; // счётчик
if (EBState <= 2) setF(0); // флаг поворота для юзера
else if (EBState <= 4) setF(9); // флаг нажатого поворота для юзера
if (ms - _debTmr < EB_FAST) setF(1); // быстрый поворот
else clrF(1); // обычный поворот
}
_encRST = 0;
_debTmr = ms;
}
if (state == 0b00) _encRST = 1;
_prev = state;
#endif
}
// ===================================== POOL BTN =====================================
void poolBtn() {
uint16_t ms = millis() & 0xFFFF;
uint16_t debounce = ms - _debTmr;
if (_btnState) { // кнопка нажата
setF(15); // busy флаг
if (!readF(3)) { // и не была нажата ранее
if (readF(14)) { // ждём дебаунс
if (debounce > EB_DEB) { // прошел дебаунс
setF(3); // флаг кнопка была нажата
EBState = 8; // кнопка нажата
_debTmr = ms; // сброс таймаутов
}
} else { // первое нажатие
EBState = 0;
setF(14); // запомнили что хотим нажать
if (debounce > EB_CLICK || readF(5)) { // кнопка нажата после EB_CLICK
clicks = 0; // сбросить счётчик и флаг кликов
flags &= ~0b0011000011100000; // clear 5 6 7 12 13 (клики)
}
_debTmr = ms;
}
} else { // кнопка уже была нажата
if (!readF(4)) { // и удержание ещё не зафиксировано
if (debounce < (uint32_t)(_holdT << 7)) { // прошло меньше удержания
if (EBState != 0 && EBState != 8) setF(2); // но энкодер повёрнут! Запомнили
} else { // прошло больше времени удержания
if (!readF(2)) { // и энкодер не повёрнут
EBState = 6; // значит это удержание (сигнал)
flags |= 0b00110000; // set 4 5 запомнили что удерживается и отключаем сигнал о кликах
_debTmr = ms; // сброс таймаута
}
}
} else { // удержание зафиксировано
if (debounce > EB_STEP) { // таймер степа
EBState = 7; // сигналим
setF(13); // зафиксирован режим step
_debTmr = ms; // сброс таймаута
}
}
}
} else { // кнопка не нажата
if (readF(3)) { // но была нажата
if (debounce > EB_DEB) {
if (!readF(4) && !readF(2)) { // энкодер не трогали и не удерживали - это клик
EBState = 5; // click
clicks++;
}
flags &= ~0b00011100; // clear 2 3 4
_debTmr = ms; // сброс таймаута
setF(10); // кнопка отпущена
if (checkFlag(13)) setF(12); // кнопка отпущена после step
}
} else if (clicks && !readF(5)) { // есть клики
if (debounce > EB_CLICK) flags |= 0b11100000; // set 5 6 7 (клики)
} else clrF(15); // снимаем busy флаг
checkFlag(14); // сброс ожидания нажатия
}
}
// ===================================== MISC =====================================
bool checkState(uint8_t val) {
return (EBState == val) ? EBState = 0, 1 : 0;
}
bool checkFlag(uint8_t val) {
return readF(val) ? clrF(val), 1 : 0;
}
void exec(uint8_t num) {
if (*_callback[num]) _callback[num]();
}
inline void setF(const uint8_t x) __attribute__((always_inline)) {flags |= 1 << x;}
inline void clrF(const uint8_t x) __attribute__((always_inline)) {flags &= ~(1 << x);}
inline bool readF(const uint8_t x) __attribute__((always_inline)) {return flags & (1 << x);}
uint8_t _amount : 6;
int8_t _dir : 2;
uint8_t EBState : 4;
uint8_t _prev : 2; // можно ускорить ещё на 0.5us, если убрать битовые поля тут и ниже
bool _btnState : 1;
bool _encRST : 1;
bool _isrFlag = 0;
uint16_t flags = 0;
uint16_t _debTmr = 0;
uint8_t _holdT = (EB_HOLD >> 7);
void (*_callback[_EB_MODE ? 14 : 0])() = {};
#ifdef EB_BETTER_ENC
int8_t _ecount = 0;
#endif
// flags
// 0 - enc turn
// 1 - enc fast
// 2 - enc был поворот
// 3 - флаг кнопки
// 4 - hold
// 5 - clicks flag
// 6 - clicks get
// 7 - clicks get num
// 8 - enc button hold
// 9 - enc turn holded
// 10 - btn released
// 11 - btn level
// 12 - btn released after step
// 13 - step flag
// 14 - deb flag
// 15 - busy flag
// EBState
// 0 - idle
// 1 - left
// 2 - right
// 3 - leftH
// 4 - rightH
// 5 - click
// 6 - held
// 7 - step
// 8 - press
};
#endif
+444
View File
@@ -0,0 +1,444 @@
#ifndef _EncButton2_h
#define _EncButton2_h
// ========= НАСТРОЙКИ (можно передефайнить из скетча) ==========
#define _EB_FAST 30 // таймаут быстрого поворота
#define _EB_DEB 50 // дебаунс кнопки
#define _EB_HOLD 1000 // таймаут удержания кнопки
#define _EB_STEP 500 // период срабатывания степ
#define _EB_CLICK 400 // таймаут накликивания
//#define EB_BETTER_ENC // точный алгоритм отработки энкодера (можно задефайнить в скетче)
// =========== НЕ ТРОГАЙ ============
#include <Arduino.h>
#ifndef nullptr
#define nullptr NULL
#endif
#ifndef EB_FAST
#define EB_FAST _EB_FAST
#endif
#ifndef EB_DEB
#define EB_DEB _EB_DEB
#endif
#ifndef EB_HOLD
#define EB_HOLD _EB_HOLD
#endif
#ifndef EB_STEP
#define EB_STEP _EB_STEP
#endif
#ifndef EB_CLICK
#define EB_CLICK _EB_CLICK
#endif
enum eb_callback {
TURN_HANDLER, // 0
LEFT_HANDLER, // 1
RIGHT_HANDLER, // 2
LEFT_H_HANDLER, // 3
RIGHT_H_HANDLER, // 4
CLICK_HANDLER, // 5
HOLDED_HANDLER, // 6
STEP_HANDLER, // 7
PRESS_HANDLER, // 8
CLICKS_HANDLER, // 9
RELEASE_HANDLER, // 10
HOLD_HANDLER, // 11
TURN_H_HANDLER, // 12
// clicks amount 13
};
#ifdef EB_BETTER_ENC
static const int8_t _EB_DIR[] = {
0, -1, 1, 0,
1, 0, 0, -1,
-1, 0, 0, 1,
0, 1, -1, 0
};
#endif
// константы
#define EB_TICK 0
#define EB_CALLBACK 1
#define EB_BTN 1
#define EB_ENCBTN 2
#define EB_ENC 3
#define VIRT_BTN 4
#define VIRT_ENCBTN 5
#define VIRT_ENC 6
#define EB_PIN_AM ((_EB_TYPE == EB_BTN) ? 1 : (_EB_TYPE == EB_ENCBTN ? 3 : 2))
// ===================================== CLASS =====================================
template < uint8_t _EB_TYPE, uint8_t _EB_MODE = EB_TICK >
class EncButton2 {
public:
// pinMode, pin1, pin2, pin3
EncButton2(uint8_t mode = INPUT_PULLUP, uint8_t P1 = 255, uint8_t P2 = 255, uint8_t P3 = 255) {
setButtonLevel(_EB_TYPE <= 3 ? LOW : HIGH); // высокий уровень в виртуальном режиме
setPins(mode, P1, P2, P3);
}
// установить пины
void setPins(uint8_t mode, uint8_t P1 = 255, uint8_t P2 = 255, uint8_t P3 = 255) {
if (_EB_TYPE == EB_BTN) {
pinMode(P1, mode);
_pins[0] = P1;
} else if (_EB_TYPE == EB_ENC) {
pinMode(P1, mode);
pinMode(P2, mode);
_pins[0] = P1;
_pins[1] = P2;
} else if (_EB_TYPE == EB_ENCBTN) {
pinMode(P1, mode);
pinMode(P2, mode);
pinMode(P3, mode);
_pins[0] = P1;
_pins[1] = P2;
_pins[2] = P3;
}
}
// подтянуть пины внутренней подтяжкой
void pullUp() {
}
// установить таймаут удержания кнопки для isHold(), мс (до 30 000)
void setHoldTimeout(int tout) {
_holdT = tout >> 7;
}
// виртуально зажать кнопку энкодера
void holdEncButton(bool state) {
if (state) setF(8);
else clrF(8);
}
// уровень кнопки: LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
void setButtonLevel(bool level) {
if (level) clrF(11);
else setF(11);
}
// ===================================== TICK =====================================
// тикер, вызывать как можно чаще
// вернёт отличное от нуля значение, если произошло какое то событие
uint8_t tick(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) {
tickISR(s1, s2, key);
checkCallback();
return EBState;
}
#define EB_BTN 1
#define EB_ENCBTN 2
#define EB_ENC 3
#define VIRT_BTN 4
#define VIRT_ENCBTN 5
#define VIRT_ENC 6
// тикер специально для прерывания, не проверяет коллбэки
uint8_t tickISR(uint8_t p0 = 0, uint8_t p1 = 0, uint8_t p2 = 0) {
if (!_isrFlag) {
_isrFlag = 1;
if (_EB_TYPE <= 3) { // РЕАЛЬНОЕ УСТРОЙСТВО
if (_EB_TYPE >= 2) poolEnc(fastRead(0) | (fastRead(1) << 1)); // энк или энк с кнопкой
if (_EB_TYPE <= 2) { // кнопка или энк с кнопкой
if (_EB_TYPE == EB_BTN) _btnState = fastRead(0); // кнопка
else _btnState = fastRead(2); // энк с кнопкой
_btnState ^= readF(11); // инверсия кнопки
poolBtn();
}
} else { // ВИРТУАЛЬНОЕ УСТРОЙСТВО
if (_EB_TYPE >= 5) poolEnc(p0 | (p1 << 1)); // энк или энк с кнопкой
if (_EB_TYPE <= 5) { // кнопка или энк с кнопкой
if (_EB_TYPE == VIRT_BTN) _btnState = p0; // кнопка
else _btnState = p2; // энк с кнопкой
_btnState ^= readF(11); // инверсия кнопки
if (_btnState || readF(15)) poolBtn(); // опрос если кнопка нажата или не вышли таймауты
}
}
_isrFlag = 0;
}
return EBState;
}
// ===================================== CALLBACK =====================================
// проверить callback, чтобы не дёргать в прерывании
void checkCallback() {
if (_EB_MODE) {
if (turn()) exec(0);
if (turnH()) exec(12);
if (EBState > 0 && EBState <= 8) exec(EBState);
if (release()) exec(10);
if (hold()) exec(11);
if (checkFlag(6)) {
exec(9);
if (clicks == _amount) exec(13);
}
EBState = 0;
}
}
// подключить обработчик
void attach(eb_callback type, void (*handler)()) {
_callback[type] = *handler;
}
// отключить обработчик
void detach(eb_callback type) {
_callback[type] = nullptr;
}
// подключить обработчик на количество кликов (может быть только один!)
void attachClicks(uint8_t amount, void (*handler)()) {
_amount = amount;
_callback[13] = *handler;
}
// отключить обработчик на количество кликов
void detachClicks() {
_callback[13] = nullptr;
}
// ===================================== STATUS =====================================
uint8_t getState() { return EBState; } // получить статус
void resetState() { EBState = 0; } // сбросить статус
// ======================================= ENC =======================================
bool left() { return checkState(1); } // поворот влево
bool right() { return checkState(2); } // поворот вправо
bool leftH() { return checkState(3); } // поворот влево нажатый
bool rightH() { return checkState(4); } // поворот вправо нажатый
bool fast() { return readF(1); } // быстрый поворот
bool turn() { return checkFlag(0); } // энкодер повёрнут
bool turnH() { return checkFlag(9); } // энкодер повёрнут нажато
int8_t getDir() { return _dir; } // направление последнего поворота, 1 или -1
int16_t counter = 0; // счётчик энкодера
// ======================================= BTN =======================================
bool busy() { return readF(15); } // вернёт true, если всё ещё нужно вызывать tick для опроса таймаутов
bool state() { return _btnState; } // статус кнопки
bool press() { return checkState(8); } // кнопка нажата
bool release() { return checkFlag(10); } // кнопка отпущена
bool click() { return checkState(5); } // клик по кнопке
bool held() { return checkState(6); } // кнопка удержана
bool hold() { return readF(4); } // кнопка удерживается
bool step() { return checkState(7); } // режим импульсного удержания
bool releaseStep() { return checkFlag(12); }// кнопка отпущена после импульсного удержания
bool held(uint8_t clk) { return (clicks == clk) ? checkState(6) : 0; } // кнопка удержана с предварительным накликиванием
bool hold(uint8_t clk) { return (clicks == clk) ? readF(4) : 0; } // кнопка удерживается с предварительным накликиванием
bool step(uint8_t clk) { return (clicks == clk) ? checkState(7) : 0; } // режим импульсного удержания с предварительным накликиванием
bool releaseStep(uint8_t clk = 0) { return (clicks == clk) ? checkFlag(12) : 0; } // кнопка отпущена после импульсного удержания с предварительным накликиванием
uint8_t clicks = 0; // счётчик кликов
bool hasClicks(uint8_t num) { return (clicks == num && checkFlag(7)) ? 1 : 0; } // имеются клики
uint8_t hasClicks() { return checkFlag(6) ? clicks : 0; } // имеются клики
// ===================================================================================
// =================================== DEPRECATED ====================================
bool isStep() { return step(); }
bool isHold() { return hold(); }
bool isHolded() { return held(); }
bool isHeld() { return held(); }
bool isClick() { return click(); }
bool isRelease() { return release(); }
bool isPress() { return press(); }
bool isTurnH() { return turnH(); }
bool isTurn() { return turn(); }
bool isFast() { return fast(); }
bool isLeftH() { return leftH(); }
bool isRightH() { return rightH(); }
bool isLeft() { return left(); }
bool isRight() { return right(); }
// ===================================== PRIVATE =====================================
private:
// ===================================== POOL ENC =====================================
void poolEnc(uint8_t state) {
#ifdef EB_BETTER_ENC
if (_prev != state) {
_ecount += _EB_DIR[state | (_prev << 2)]; // сдвиг внутреннего счётчика
_prev = state;
#ifdef EB_HALFSTEP_ENC // полушаговый энкодер
// спасибо https://github.com/GyverLibs/EncButton/issues/10#issue-1092009489
if ((state == 0x3 || state == 0x0) && _ecount) {
#else // полношаговый
if (state == 0x3 && _ecount) { // защёлкнули позицию
#endif
uint16_t ms = millis() & 0xFFFF;
EBState = (_ecount < 0) ? 1 : 2;
_ecount = 0;
if (_EB_TYPE == EB_ENCBTN || _EB_TYPE == VIRT_ENCBTN) { // энкодер с кнопкой
if (!readF(4) && (_btnState || readF(8))) EBState += 2; // если кнопка не "удерживается"
}
_dir = (EBState & 1) ? -1 : 1; // направление
counter += _dir; // счётчик
if (EBState <= 2) setF(0); // флаг поворота для юзера
else if (EBState <= 4) setF(9); // флаг нажатого поворота для юзера
if (ms - _debTmr < EB_FAST) setF(1); // быстрый поворот
else clrF(1); // обычный поворот
_debTmr = ms;
}
}
#else
if (_encRST && state == 0b11) { // ресет и энк защёлкнул позицию
uint16_t ms = millis() & 0xFFFF;
if (_EB_TYPE == EB_ENCBTN || _EB_TYPE == VIRT_ENCBTN) { // энкодер с кнопкой
if ((_prev == 1 || _prev == 2) && !readF(4)) { // если кнопка не "удерживается" и энкодер в позиции 1 или 2
EBState = _prev;
if (_btnState || readF(8)) EBState += 2;
}
} else { // просто энкодер
if (_prev == 1 || _prev == 2) EBState = _prev;
}
if (EBState > 0) { // был поворот
_dir = (EBState & 1) ? -1 : 1; // направление
counter += _dir; // счётчик
if (EBState <= 2) setF(0); // флаг поворота для юзера
else if (EBState <= 4) setF(9); // флаг нажатого поворота для юзера
if (ms - _debTmr < EB_FAST) setF(1); // быстрый поворот
else clrF(1); // обычный поворот
}
_encRST = 0;
_debTmr = ms;
}
if (state == 0b00) _encRST = 1;
_prev = state;
#endif
}
// ===================================== POOL BTN =====================================
void poolBtn() {
uint16_t ms = millis() & 0xFFFF;
uint16_t debounce = ms - _debTmr;
if (_btnState) { // кнопка нажата
setF(15); // busy флаг
if (!readF(3)) { // и не была нажата ранее
if (readF(14)) { // ждём дебаунс
if (debounce > EB_DEB) { // прошел дебаунс
setF(3); // флаг кнопка была нажата
EBState = 8; // кнопка нажата
_debTmr = ms; // сброс таймаутов
}
} else { // первое нажатие
EBState = 0;
setF(14); // запомнили что хотим нажать
if (debounce > EB_CLICK || readF(5)) { // кнопка нажата после EB_CLICK
clicks = 0; // сбросить счётчик и флаг кликов
flags &= ~0b0011000011100000; // clear 5 6 7 12 13 (клики)
}
_debTmr = ms;
}
} else { // кнопка уже была нажата
if (!readF(4)) { // и удержание ещё не зафиксировано
if (debounce < (uint32_t)(_holdT << 7)) { // прошло меньше удержания
if (EBState != 0 && EBState != 8) setF(2); // но энкодер повёрнут! Запомнили
} else { // прошло больше времени удержания
if (!readF(2)) { // и энкодер не повёрнут
EBState = 6; // значит это удержание (сигнал)
flags |= 0b00110000; // set 4 5 запомнили что удерживается и отключаем сигнал о кликах
_debTmr = ms; // сброс таймаута
}
}
} else { // удержание зафиксировано
if (debounce > EB_STEP) { // таймер степа
EBState = 7; // сигналим
setF(13); // зафиксирован режим step
_debTmr = ms; // сброс таймаута
}
}
}
} else { // кнопка не нажата
if (readF(3)) { // но была нажата
if (debounce > EB_DEB) {
if (!readF(4) && !readF(2)) { // энкодер не трогали и не удерживали - это клик
EBState = 5; // click
clicks++;
}
flags &= ~0b00011100; // clear 2 3 4
_debTmr = ms; // сброс таймаута
setF(10); // кнопка отпущена
if (checkFlag(13)) setF(12); // кнопка отпущена после step
}
} else if (clicks && !readF(5)) { // есть клики
if (debounce > EB_CLICK) flags |= 0b11100000; // set 5 6 7 (клики)
} else clrF(15); // снимаем busy флаг
checkFlag(14); // сброс ожидания нажатия
}
}
// ===================================== MISC =====================================
bool fastRead(uint8_t pin) {
return digitalRead(_pins[pin]);
}
bool checkState(uint8_t val) {
return (EBState == val) ? EBState = 0, 1 : 0;
}
bool checkFlag(uint8_t val) {
return readF(val) ? clrF(val), 1 : 0;
}
void exec(uint8_t num) {
if (*_callback[num]) _callback[num]();
}
inline void setF(const uint8_t x) __attribute__((always_inline)) {flags |= 1 << x;}
inline void clrF(const uint8_t x) __attribute__((always_inline)) {flags &= ~(1 << x);}
inline bool readF(const uint8_t x) __attribute__((always_inline)) {return flags & (1 << x);}
uint8_t _amount : 6;
int8_t _dir : 2;
uint8_t EBState : 4;
uint8_t _prev : 2;
bool _btnState : 1;
bool _encRST : 1;
bool _isrFlag = 0;
uint16_t flags = 0;
uint16_t _debTmr = 0;
uint8_t _holdT = (EB_HOLD >> 7);
void (*_callback[_EB_MODE ? 14 : 0])() = {};
#ifdef EB_BETTER_ENC
int8_t _ecount = 0;
#endif
uint8_t _pins[EB_PIN_AM];
// flags
// 0 - enc turn
// 1 - enc fast
// 2 - enc был поворот
// 3 - флаг кнопки
// 4 - hold
// 5 - clicks flag
// 6 - clicks get
// 7 - clicks get num
// 8 - enc button hold
// 9 - enc turn holded
// 10 - btn released
// 11 - btn level
// 12 - btn released after step
// 13 - step flag
// 14 - deb flag
// 15 - busy flag
// EBState
// 0 - idle
// 1 - left
// 2 - right
// 3 - leftH
// 4 - rightH
// 5 - click
// 6 - held
// 7 - step
// 8 - press
};
#endif