Add libraries dir
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,539 @@
|
||||
[](#versions)
|
||||
[](https://alexgyver.ru/)
|
||||
[](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)) круглые китайские модули с распаянными цепями антидребезга:
|
||||

|
||||
|
||||
Самостоятельно обвязать энкодер можно по следующей схеме (RC фильтры на каналы энкодера + подтяжка всех пинов к VCC):
|
||||

|
||||
|
||||
## Производительность
|
||||
Время холостого выполнения функции 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 после нового клика.
|
||||

|
||||
|
||||
#### Энкодер
|
||||
- `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");
|
||||
}
|
||||
+44
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=*
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user