feeder/EncButton.h

463 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки
Документация:
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 для полушаговых энкодеров
*/
#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
// флаг макро
#define _EB_setFlag(x) (flags |= 1 << x)
#define _EB_clrFlag(x) (flags &= ~(1 << x))
#define _EB_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, // 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(LOW);
}
// подтянуть пины внутренней подтяжкой
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) _EB_setFlag(8);
else _EB_clrFlag(8);
}
// уровень кнопки: LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC
void setButtonLevel(bool level) {
if (level) _EB_clrFlag(11);
else _EB_setFlag(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 ^= _EB_readFlag(11); // инверсия кнопки
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 _EB_readFlag(1); } // быстрый поворот
bool turn() { return checkFlag(0); } // энкодер повёрнут
bool turnH() { return checkFlag(9); } // энкодер повёрнут нажато
int8_t getDir() { return _dir; } // направление последнего поворота, 1 или -1
int16_t counter = 0; // счётчик энкодера
// ======================================= BTN =======================================
bool press() { return checkState(8); } // кнопка нажата
bool release() { return checkFlag(10); } // кнопка отпущена
bool click() { return checkState(5); } // клик по кнопке
bool held() { return checkState(6); } // кнопка удержана
bool hold() { return _EB_readFlag(4); } // кнопка удерживается
bool step() { return checkState(7); } // режим импульсного удержания
bool state() { return _btnState; } // статус кнопки
bool releaseStep() {return checkFlag(12);} // кнопка отпущена после импульсного удержания
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 != 0) {
#else // полношаговый
if (state == 0x3 && _ecount != 0) { // защёлкнули позицию
#endif
EBState = (_ecount < 0) ? 1 : 2;
_ecount = 0;
if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой
if (!_EB_readFlag(4) && (_btnState || _EB_readFlag(8))) EBState += 2; // если кнопка не "удерживается"
}
_dir = (EBState & 1) ? -1 : 1; // направление
counter += _dir; // счётчик
if (EBState <= 2) _EB_setFlag(0); // флаг поворота для юзера
else if (EBState <= 4) _EB_setFlag(9); // флаг нажатого поворота для юзера
if (millis() - _debTimer < EB_FAST) _EB_setFlag(1); // быстрый поворот
else _EB_clrFlag(1); // обычный поворот
_debTimer = millis();
}
}
#else
if (_encRST && state == 0b11) { // ресет и энк защёлкнул позицию
if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой
if ((_prev == 1 || _prev == 2) && !_EB_readFlag(4)) { // если кнопка не "удерживается" и энкодер в позиции 1 или 2
EBState = _prev;
if (_btnState || _EB_readFlag(8)) EBState += 2;
}
} else { // просто энкодер
if (_prev == 1 || _prev == 2) EBState = _prev;
}
if (EBState > 0) { // был поворот
_dir = (EBState & 1) ? -1 : 1; // направление
counter += _dir; // счётчик
if (EBState <= 2) _EB_setFlag(0); // флаг поворота для юзера
else if (EBState <= 4) _EB_setFlag(9); // флаг нажатого поворота для юзера
if (millis() - _debTimer < EB_FAST) _EB_setFlag(1); // быстрый поворот
else _EB_clrFlag(1); // обычный поворот
}
_encRST = 0;
_debTimer = millis();
}
if (state == 0b00) _encRST = 1;
_prev = state;
#endif
}
// ===================================== POOL BTN =====================================
void poolBtn() {
uint32_t thisMls = millis();
uint32_t debounce = thisMls - _debTimer;
if (_btnState) { // кнопка нажата
if (!_EB_readFlag(3)) { // и не была нажата ранее
if (debounce > EB_DEB) { // и прошел дебаунс
_EB_setFlag(3); // флаг кнопка была нажата
_debTimer = thisMls; // сброс таймаутов
EBState = 8; // кнопка нажата
}
if (debounce > EB_CLICK) { // кнопка нажата после EB_CLICK
clicks = 0; // сбросить счётчик и флаг кликов
flags &= ~0b0011000011100000; // clear 5 6 7 12 13 (клики)
}
} else { // кнопка уже была нажата
if (!_EB_readFlag(4)) { // и удержание ещё не зафиксировано
if (debounce < (_holdT << 7)) { // прошло меньше удержания
if (EBState != 0 && EBState != 8) _EB_setFlag(2); // но энкодер повёрнут! Запомнили
} else { // прошло больше времени удержания
if (!_EB_readFlag(2)) { // и энкодер не повёрнут
EBState = 6; // значит это удержание (сигнал)
_EB_setFlag(4); // запомнили что удерживается
_debTimer = thisMls; // сброс таймаута
}
}
} else { // удержание зафиксировано
if (debounce > EB_STEP) { // таймер степа
EBState = 7; // сигналим
_EB_setFlag(13); // зафиксирован режим step
_debTimer = thisMls; // сброс таймаута
}
}
}
} else { // кнопка не нажата
if (_EB_readFlag(3)) { // но была нажата
if (debounce > EB_DEB) {
if (!_EB_readFlag(4) && !_EB_readFlag(2)) { // энкодер не трогали и не удерживали - это клик
EBState = 5;
clicks++;
}
flags &= ~0b00011100; // clear 2 3 4
_debTimer = thisMls; // сброс таймаута
_EB_setFlag(10); // кнопка отпущена
if (checkFlag(13)) _EB_setFlag(12); // кнопка отпущена после step
}
} else if (clicks > 0 && debounce > EB_CLICK && !_EB_readFlag(5)) flags |= 0b11100000; // set 5 6 7 (клики)
}
}
// ===================================== MISC =====================================
bool checkState(uint8_t val) {
if (EBState == val) {
EBState = 0;
return 1;
} return 0;
}
bool checkFlag(uint8_t val) {
if (_EB_readFlag(val)) {
_EB_clrFlag(val);
return 1;
} return 0;
}
void exec(uint8_t num) {
if (*_callback[num]) _callback[num]();
}
uint8_t _prev : 2;
uint8_t EBState : 4;
bool _btnState : 1;
bool _encRST : 1;
bool _isrFlag : 1;
uint16_t flags = 0;
#ifdef EB_BETTER_ENC
int8_t _ecount = 0;
#endif
uint32_t _debTimer = 0;
uint8_t _holdT = (EB_HOLD >> 7);
int8_t _dir = 0;
void (*_callback[_EB_MODE ? 14 : 0])() = {};
uint8_t _amount = 0;
// 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
// EBState
// 0 - idle
// 1 - left
// 2 - right
// 3 - leftH
// 4 - rightH
// 5 - click
// 6 - held
// 7 - step
// 8 - press
};
#endif