383 lines
16 KiB
C++
383 lines
16 KiB
C++
/*
|
||
Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки
|
||
Документация:
|
||
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
|
||
|
||
Версии:
|
||
v1.1 - пуллап отдельныи методом
|
||
v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч)
|
||
v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения
|
||
v1.4 - обработка нажатия и отпускания кнопки
|
||
v1.5 - добавлен виртуальный режим
|
||
v1.6 - оптимизация работы в прерывании
|
||
v1.6.1 - PULLUP по умолчанию
|
||
v1.7 - большая оптимизация памяти, переделан FastIO
|
||
v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех)
|
||
*/
|
||
|
||
#ifndef EncButton_h
|
||
#define EncButton_h
|
||
|
||
// =========== НАСТРОЙКИ (можно передефайнить из скетча) ============
|
||
#define _EB_FAST 30 // таймаут быстрого поворота
|
||
#define _EB_DEB 80 // дебаунс кнопки
|
||
#define _EB_HOLD 1000 // таймаут удержания кнопки
|
||
#define _EB_STEP 500 // период срабатывания степ
|
||
#define _EB_CLICK 400 // таймаут накликивания
|
||
|
||
// =========== НЕ ТРОГАЙ ============
|
||
#include <Arduino.h>
|
||
#include "FastIO_v2.h"
|
||
// флаг макро
|
||
#define _setFlag(x) (flags |= 1 << x)
|
||
#define _clrFlag(x) (flags &= ~(1 << x))
|
||
#define _readFlag(x) ((flags >> x) & 1)
|
||
|
||
#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,
|
||
RIGHT_HANDLER,
|
||
LEFT_HANDLER,
|
||
RIGHT_H_HANDLER,
|
||
LEFT_H_HANDLER,
|
||
CLICK_HANDLER,
|
||
HOLDED_HANDLER,
|
||
STEP_HANDLER,
|
||
HOLD_HANDLER,
|
||
CLICKS_HANDLER,
|
||
PRESS_HANDLER,
|
||
RELEASE_HANDLER,
|
||
};
|
||
|
||
// константы
|
||
#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
|
||
|
||
// класс
|
||
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();
|
||
}
|
||
|
||
// подтянуть пины внутренней подтяжкой
|
||
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) _setFlag(7);
|
||
else _clrFlag(7);
|
||
}
|
||
|
||
// тикер, вызывать как можно чаще или в прерывании
|
||
// вернёт отличное от нуля значение, если произошло какое то событие
|
||
uint8_t tick(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 = F_fastRead(_S1) | (F_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 = !F_fastRead(_S1); // обычная кнопка
|
||
if (_KEY != EB_NO_PIN) _btnState = !F_fastRead(_KEY); // энк с кнопкой
|
||
if (_S1 == VIRT_BTN) _btnState = s1; // вирт кнопка
|
||
if (_S1 == VIRT_ENCBTN) _btnState = key; // вирт энк с кнопкой
|
||
poolBtn();
|
||
}
|
||
|
||
if (_EB_MODE) {
|
||
if (*_callback[0] && isTurn()) _callback[0]();
|
||
switch (EBState) {
|
||
case 1: if (*_callback[1]) _callback[1](); break; // isRight
|
||
case 2: if (*_callback[2]) _callback[2](); break; // isLeft
|
||
case 3: if (*_callback[3]) _callback[3](); break; // isRightH
|
||
case 4: if (*_callback[4]) _callback[4](); break; // isLeftH
|
||
case 5: if (*_callback[5]) _callback[5](); break; // isClick
|
||
case 6: if (*_callback[6]) _callback[6](); break; // isHolded
|
||
case 7: if (*_callback[7]) _callback[7](); break; // isStep
|
||
case 8: if (*_callback[11]) _callback[11](); break; // isPress
|
||
case 9: if (*_callback[12]) _callback[12](); break; // isRelease
|
||
}
|
||
EBState = 0;
|
||
if (*_callback[8] && _readFlag(4)) _callback[8](); // isHold
|
||
if (_readFlag(6)) {
|
||
if (*_callback[9]) _callback[9](); // clicks
|
||
if (*_callback[10] && clicks == _amount) _callback[10]();
|
||
_clrFlag(6);
|
||
}
|
||
}
|
||
}
|
||
_isrFlag = 0;
|
||
return EBState;
|
||
}
|
||
|
||
// подключить обработчик
|
||
void attach(eb_callback type, void (*handler)()) {
|
||
_callback[type] = *handler;
|
||
}
|
||
|
||
// отключить обработчик
|
||
void detach(eb_callback type) {
|
||
_callback[type] = NULL;
|
||
}
|
||
|
||
// подключить обработчик на количество кликов (может быть только один!)
|
||
void attachClicks(uint8_t amount, void (*handler)()) {
|
||
_amount = amount;
|
||
_callback[10] = *handler;
|
||
}
|
||
|
||
// отключить обработчик на количество кликов
|
||
void detachClicks() {
|
||
_callback[10] = NULL;
|
||
}
|
||
|
||
// получить статус
|
||
uint8_t getState() { return EBState; }
|
||
|
||
// сбросить статус
|
||
void resetState() { EBState = 0; }
|
||
|
||
// поворот вправо
|
||
bool isRight() { return _EB_MODE ? (_dir == 1 ? 1 : 0) : checkState(1); }
|
||
|
||
// поворот влево
|
||
bool isLeft() { return _EB_MODE ? (_dir == 2 ? 1 : 0) : checkState(2); }
|
||
|
||
// поворот вправо нажатый
|
||
bool isRightH() { return _EB_MODE ? (_dir == 3 ? 1 : 0) : checkState(3); }
|
||
|
||
// поворот влево нажатый
|
||
bool isLeftH() { return _EB_MODE ? (_dir == 4 ? 1 : 0) : checkState(4); }
|
||
|
||
// быстрый поворот
|
||
bool isFast() { return _readFlag(1); }
|
||
|
||
// энкодер повёрнут
|
||
bool isTurn() {
|
||
if (_readFlag(0)) {
|
||
_clrFlag(0);
|
||
return true;
|
||
} return false;
|
||
}
|
||
|
||
// кнопка нажата
|
||
bool isPress() { return checkState(8); }
|
||
|
||
// кнопка отпущена
|
||
bool isRelease() { return checkState(9); }
|
||
|
||
// клик по кнопке
|
||
bool isClick() { return checkState(5); }
|
||
|
||
// кнопка удержана
|
||
bool isHolded() { return checkState(6); }
|
||
|
||
// кнопка удержана (грамотный аналог holded =)
|
||
bool isHeld() { return checkState(6); }
|
||
|
||
// кнопка удерживается
|
||
bool isHold() { return _readFlag(4); }
|
||
|
||
// режим импульсного удержания
|
||
bool isStep() { return checkState(7); }
|
||
|
||
// статус кнопки
|
||
bool state() { return !F_fastRead(_S1); }
|
||
|
||
// имеются клики
|
||
bool hasClicks(uint8_t numClicks) {
|
||
if (clicks == numClicks && _readFlag(6)) {
|
||
_clrFlag(6);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// имеются клики
|
||
uint8_t hasClicks() {
|
||
if (_readFlag(6)) {
|
||
_clrFlag(6);
|
||
return clicks;
|
||
} return 0;
|
||
}
|
||
|
||
// счётчик энкодера
|
||
int counter = 0;
|
||
|
||
// счётчик кликов
|
||
uint8_t clicks = 0;
|
||
|
||
private:
|
||
void poolEnc(uint8_t state) {
|
||
if (_encRST && state == 0b11) { // ресет и энк защёлкнул позицию
|
||
if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой
|
||
if (!_readFlag(4)) { // если кнопка не "удерживается"
|
||
if (_lastState == 0b10) EBState = (_btnState || _readFlag(7)) ? 3 : 1, counter++;
|
||
else if (_lastState == 0b01) EBState = (_btnState || _readFlag(7)) ? 4 : 2, counter--;
|
||
}
|
||
} else { // просто энкодер
|
||
if (_lastState == 0b10) EBState = 1, counter++;
|
||
else if (_lastState == 0b01) EBState = 2, counter--;
|
||
}
|
||
if (EBState > 0) {
|
||
if (_EB_MODE) _dir = EBState;
|
||
if (millis() - _debTimer < EB_FAST) _setFlag(1); // быстрый поворот
|
||
else _clrFlag(1); // обычный поворот
|
||
if (EBState < 5) _setFlag(0); // флаг поворота для юзера
|
||
}
|
||
|
||
_encRST = 0;
|
||
_debTimer = millis();
|
||
}
|
||
if (state == 0b00) _encRST = 1;
|
||
_lastState = state;
|
||
}
|
||
|
||
void poolBtn() {
|
||
uint32_t thisMls = millis();
|
||
uint32_t debounce = thisMls - _debTimer;
|
||
if (_btnState) { // кнопка нажата
|
||
if (!_readFlag(3)) { // и не была нажата ранее
|
||
if (debounce > EB_DEB) { // и прошел дебаунс
|
||
_setFlag(3); // флаг кнопка была нажата
|
||
_debTimer = thisMls; // сброс таймаутов
|
||
EBState = 8; // кнопка нажата
|
||
}
|
||
if (debounce > EB_CLICK) { // кнопка нажата после EB_CLICK
|
||
clicks = 0; // сбросить счётчик и флаг кликов
|
||
flags &= ~0b01100000;
|
||
}
|
||
} else { // кнопка уже была нажата
|
||
if (!_readFlag(4)) { // и удержание ещё не зафиксировано
|
||
if (debounce < (_holdT << 7)) { // прошло меньше удержания
|
||
if (EBState != 0 && EBState != 8) _setFlag(2); // но энкодер повёрнут! Запомнили
|
||
} else { // прошло больше времени удержания
|
||
if (!_readFlag(2)) { // и энкодер не повёрнут
|
||
EBState = 6; // значит это удержание (сигнал)
|
||
_setFlag(4); // запомнили что удерживается
|
||
_debTimer = thisMls; // сброс таймаута
|
||
}
|
||
}
|
||
} else { // удержание зафиксировано
|
||
if (debounce > EB_STEP) { // таймер степа
|
||
EBState = 7; // сигналим
|
||
_debTimer = thisMls; // сброс таймаута
|
||
}
|
||
}
|
||
}
|
||
} else { // кнопка не нажата
|
||
if (_readFlag(3)) { // но была нажата
|
||
if (debounce > EB_DEB && !_readFlag(4) && !_readFlag(2)) { // энкодер не трогали и не удерживали - это клик
|
||
EBState = 5;
|
||
clicks++;
|
||
} else EBState = 9; // кнопка отпущена
|
||
flags &= ~0b00011100; // clear 2 3 4
|
||
_debTimer = thisMls; // сброс таймаута
|
||
} else if (clicks > 0 && debounce > EB_CLICK && !_readFlag(5)) flags |= 0b01100000; // флаг на клики
|
||
}
|
||
}
|
||
|
||
bool checkState(uint8_t val) {
|
||
if (EBState == val) {
|
||
EBState = 0;
|
||
return 1;
|
||
} return 0;
|
||
}
|
||
uint32_t _debTimer = 0;
|
||
uint8_t _lastState = 0, EBState = 0;
|
||
bool _btnState = 0, _encRST = 0, _isrFlag = 0;
|
||
uint8_t flags = 0;
|
||
uint8_t _holdT = EB_HOLD >> 7;
|
||
|
||
uint8_t _dir = 0;
|
||
void (*_callback[_EB_MODE ? 13 : 0])() = {};
|
||
uint8_t _amount = 0;
|
||
|
||
|
||
// flags
|
||
// 0 - enc turn
|
||
// 1 - enc fast
|
||
// 2 - enc был поворот
|
||
// 3 - флаг кнопки
|
||
// 4 - hold
|
||
// 5 - clicks flag
|
||
// 6 - clicks get
|
||
// 7 - enc button hold
|
||
|
||
// EBState
|
||
// 0 - idle
|
||
// 1 - right
|
||
// 2 - left
|
||
// 3 - rightH
|
||
// 4 - leftH
|
||
// 5 - click
|
||
// 6 - holded
|
||
// 7 - step
|
||
// 8 - press
|
||
// 9 - release
|
||
};
|
||
|
||
#endif |