2021-08-17 16:17:49 +03:00

383 lines
16 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
Версии:
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