Giter VIP home page Giter VIP logo

encbutton's Introduction

latest PIO Foo Foo Foo

Foo

EncButton

⚠️⚠️⚠️
Новая версия v3 несовместима с предыдущими, смотри документацию, примеры и краткий гайд по миграции с v2 на v3!
⚠️⚠️⚠️

Лёгкая и очень функциональная библиотека для энкодера с кнопкой, энкодера или кнопки с Arduino

  • Кнопка
    • Обработка событий: нажатие, отпускание, клик, счётчик кликов, удержание, импульсное удержание, время удержания + предварительные клики для всех режимов
    • Программное подавление дребезга
    • Поддержка обработки двух одновременно нажимаемых кнопок как третьей кнопки
  • Энкодер
    • Обработка событий: обычный поворот, нажатый поворот, быстрый поворот
    • Поддержка четырёх типов инкрементальных энкодеров
    • Высокоточный алгоритм определения позиции
    • Буферизация в прерывании
  • Простое и понятное использование
  • Огромное количество возможностей и их комбинаций для разных сценариев использования даже одной кнопки
  • Виртуальный режим (например для работы с расширителем пинов)
  • Оптимизирована для работы в прерывании
  • Максимально быстрое чтение пинов для AVR, esp8266, esp32 (используется GyverIO)
  • Быстрые асинхронные алгоритмы опроса действий с кнопки и энкодера
  • Жёсткая оптимизация и небольшой вес во Flash и SRAM памяти: 5 байт SRAM (на экземпляр) и ~350 байт Flash на обработку кнопки

Примеры сценариев использования:

  • Несколько кликов - включение режима (по кол-ву кликов)
  • Несколько кликов + короткое удержание - ещё вариант включения режима (по кол-ву кликов)
  • Несколько кликов + удержание - постепенное изменение значения выбранной переменной (по кол-ву кликов)
  • Несколько кликов выбирают переменную, энкодер её изменяет
  • Изменение шага изменения переменной при вращении энкодера - например уменьшение при зажатой кнопке и увеличение при быстром вращении
  • Навигация по меню при вращении энкодера, изменение переменной при вращении зажатого энкодера
  • Полноценная навигация по меню при использовании двух кнопок (одновременное удержание для перехода на следующий уровень, одновременное нажатие для возврата на предыдущий)
  • И так далее

Совместимость

Совместима со всеми Arduino платформами (используются Arduino-функции)

Содержание

Установка

  • Для работы требуется библиотека GyverIO
  • Библиотеку можно найти по названию EncButton и установить через менеджер библиотек в:
    • Arduino IDE
    • Arduino IDE v2
    • PlatformIO
  • Скачать библиотеку .zip архивом для ручной установки:
    • Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
    • Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
    • Распаковать и положить в Документы/Arduino/libraries/
    • (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
  • Читай более подробную инструкцию по установке библиотек здесь

Обновление

  • Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
  • Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
  • Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!

Информация

Энкодер

Тип энкодера

Библиотека поддерживает все 4 типа инкрементальных энкодеров, тип можно настроить при помощи setEncType(тип):

  • EB_STEP4_LOW - активный низкий сигнал (подтяжка к VCC). Полный период (4 фазы) за один щелчок. Установлен по умолчанию
  • EB_STEP4_HIGH - активный высокий сигнал (подтяжка к GND). Полный период (4 фазы) за один щелчок
  • EB_STEP2 - половина периода (2 фазы) за один щелчок
  • EB_STEP1 - четверть периода (1 фаза) за один щелчок, а также энкодеры без фиксации

diagram

Рекомендации

Для работы по сценарию "энкодер с кнопкой" рекомендую вот такие (ссылка, ссылка) круглые китайские модули с распаянными цепями антидребезга (имеют тип EB_STEP4_LOW по классификации выше):
scheme

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

Примечание: по умолчанию в библиотеке пины энкодера настроены на INPUT с расчётом на внешнюю подтяжку. Если у вас энкодер без подтяжки - можно использовать внутреннюю INPUT_PULLUP, указав это при инициализации энкодера (см. документацию ниже).

Кнопка

Уровень кнопки

Кнопка может быть подключена к микроконтроллеру двумя способами и давать при нажатии высокий или низкий сигнал. В библиотеке предусмотрена настройка setBtnLevel(уровень), где уровень - активный сигнал кнопки:

  • HIGH - кнопка подключает VCC. Установлен по умолчанию в Virt-библиотеках
  • LOW - кнопка подключает GND. Установлен по умолчанию в основных библиотеках

scheme

Подтяжка пина

В схемах с микроконтроллерами чаще всего используется подключение кнопки к GND с подтяжкой пина к VCC. Подтяжка может быть внешней (режим пина нужно поставить INPUT) или внутренней (режим пина INPUT_PULLUP). В "реальных" проектах рекомендуется внешняя подтяжка, т.к. она менее подвержена помехам - у внутренней слишком высокое сопротивление.

Документация

Дефайны настроек

Объявлять до подключения библиотеки

// отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)
#define EB_NO_FOR

// отключить обработчик событий attach (экономит 2 байта оперативки)
#define EB_NO_CALLBACK

// отключить счётчик энкодера [VirtEncoder, Encoder, EncButton] (экономит 4 байта оперативки)
#define EB_NO_COUNTER

// отключить буферизацию энкодера (экономит 2 байта оперативки)
#define EB_NO_BUFFER

/*
  Настройка таймаутов для всех классов
  - Заменяет таймауты константами, изменить их из программы (SetXxxTimeout()) будет нельзя
  - Настройка влияет на все объявленные в программе кнопки/энкодеры
  - Экономит 1 байт оперативки на объект за каждый таймаут
  - Показаны значения по умолчанию в мс
  - Значения не ограничены 4000мс, как при установке из программы (SetXxxTimeout())
*/
#define EB_DEB_TIME 50      // таймаут гашения дребезга кнопки (кнопка)
#define EB_CLICK_TIME 500   // таймаут ожидания кликов (кнопка)
#define EB_HOLD_TIME 600    // таймаут удержания (кнопка)
#define EB_STEP_TIME 200    // таймаут импульсного удержания (кнопка)
#define EB_FAST_TIME 30     // таймаут быстрого поворота (энкодер)

Классы

Как работать с документацией: EncButton начиная с версии 3.0 представляет собой несколько библиотек (классов) для различных сценариев использования, они друг друга наследуют для расширения функциональности. Таким образом библиотека представляет собой "луковицу", каждый слой которой имеет доступ к функциям нижних слоёв:

  • Базовые классы:
    • VirtButton - базовый класс виртуальной кнопки, обеспечивает все возможности кнопки
    • VirtEncoder - базовый класс виртуального энкодера, определяет факт и направление вращения энкодера
    • VirtEncButton - базовый класс виртуального энкодера с кнопкой, обеспечивает опрос энкодера с учётом кнопки, наследует VirtButton и VirtEncoder
  • Основные классы:
    • Button, ButtonT - класс кнопки, наследует VirtButton
    • Encoder, EncoderT - класс энкодера, наследует VirtEncoder
    • EncButton, EncButtonT - класс энкодера с кнопкой, наследует VirtEncButton, VirtButton, VirtEncoder

Таким образом для изучения всех доступных функций конкретной библиотеки нужно смотреть не только её, но и то что она наследует. Например для обработки кнопки при помощи Button нужно открыть ниже описание Button и VirtButton.

Виртуальный - без указания пина микроконтроллера, работает напрямую с переданным значением, например для опроса кнопок-энкодеров через расширители пинов и сдвиговые регистры.

T-версии библиотек требуют указания пинов константами (цифрами). Номера пинов будут храниться в памяти программы, это ускоряет работу и делает код легче на 1 байт за каждый пин.

Примечание: #include <EncButton.h> подключает все инструменты библиотеки!

Таблица функций кнопки
VirtButton VirtEncButton Button EncButton
read
readBtn
tickRaw
setHoldTimeout
setStepTimeout
setClickTimeout
setDebTimeout
setBtnLevel
pressISR
reset
clear
skipEvents
attach
detach
press
release
click
pressing
hold
holding
step
hasClicks
getClicks
getSteps
releaseHold
releaseStep
releaseHoldStep
waiting
busy
action
timeout
pressFor
holdFor
stepFor
Таблица функций энкодера
VirtEncoder Encoder VirtEncButton EncButton
readEnc
initEnc
setEncReverse
setEncType
setEncISR
clear
turn
dir
tickRaw
pollEnc
counter
setFastTimeout
turnH
fast
right
left
rightH
leftH
action
timeout
attach
detach
VirtButton
// ================ НАСТРОЙКИ ================
// установить таймаут удержания, умолч. 600 (макс. 4000 мс)
void setHoldTimeout(uint16_t tout);

// установить таймаут импульсного удержания, умолч. 200 (макс. 4000 мс)
void setStepTimeout(uint16_t tout);

// установить таймаут ожидания кликов, умолч. 500 (макс. 4000 мс)
void setClickTimeout(uint16_t tout);

// установить таймаут антидребезга, умолч. 50 (макс. 255 мс)
void setDebTimeout(uint8_t tout);

// установить уровень кнопки (HIGH - кнопка замыкает VCC, LOW - замыкает GND)
// умолч. HIGH, то есть true - кнопка нажата
void setBtnLevel(bool level);

// подключить функцию-обработчик событий (вида void f())
void attach(void (*handler)());

// отключить функцию-обработчик событий
void detach();

// ================== СБРОС ==================
// сбросить системные флаги (принудительно закончить обработку)
void reset();

// принудительно сбросить флаги событий
void clear();

// игнорировать все события до отпускания кнопки
void skipEvents();

// ================ ОБРАБОТКА ================
// обработка кнопки значением
bool tick(bool s);

// обработка виртуальной кнопки как одновременное нажатие двух других кнопок
bool tick(VirtButton& b0, VirtButton& b1);

// кнопка нажата в прерывании кнопки
void pressISR();

// обработка кнопки без сброса событий и вызова коллбэка
bool tickRaw(bool s);

// ================== ОПРОС ==================
// кнопка нажата [событие]
bool press();
bool press(uint8_t clicks);

// кнопка отпущена (в любом случае) [событие]
bool release();
bool release(uint8_t clicks);

// клик по кнопке (отпущена без удержания) [событие]
bool click();
bool click(uint8_t clicks);

// кнопка зажата (между press() и release()) [состояние]
bool pressing();
bool pressing(uint8_t clicks);

// кнопка была удержана (больше таймаута) [событие]
bool hold();
bool hold(uint8_t clicks);

// кнопка удерживается (больше таймаута) [состояние]
bool holding();
bool holding(uint8_t clicks);

// импульсное удержание [событие]
bool step();
bool step(uint8_t clicks);

// зафиксировано несколько кликов [событие]
bool hasClicks();
bool hasClicks(uint8_t clicks);

// кнопка отпущена после удержания [событие]
bool releaseHold();
bool releaseHold(uint8_t clicks);

// кнопка отпущена после импульсного удержания [событие]
bool releaseStep();
bool releaseStep(uint8_t clicks);

// кнопка отпущена после удержания или импульсного удержания [событие]
bool releaseHoldStep();
bool releaseHoldStep(uint8_t clicks);

// получить количество кликов
uint8_t getClicks();

// получить количество степов
uint16_t getSteps();

// кнопка ожидает повторных кликов (между click() и hasClicks()) [состояние]
bool waiting();

// идёт обработка (между первым нажатием и после ожидания кликов) [состояние]
bool busy();

// было действие с кнопки, вернёт код события [событие]
uint16_t action();

// ================== ВРЕМЯ ==================
// после взаимодействия с кнопкой (или энкодером EncButton) прошло указанное время, мс [событие]
bool timeout(uint16_t ms);

// время, которое кнопка удерживается (с начала нажатия), мс
uint16_t pressFor();

// кнопка удерживается дольше чем (с начала нажатия), мс [состояние]
bool pressFor(uint16_t ms);

// время, которое кнопка удерживается (с начала удержания), мс
uint16_t holdFor();

// кнопка удерживается дольше чем (с начала удержания), мс [состояние]
bool holdFor(uint16_t ms);

// время, которое кнопка удерживается (с начала степа), мс
uint16_t stepFor();

// кнопка удерживается дольше чем (с начала степа), мс [состояние]
bool stepFor(uint16_t ms);
VirtEncoder
// ==================== НАСТРОЙКИ ====================
// инвертировать направление энкодера (умолч. 0)
void setEncReverse(bool rev);

// установить тип энкодера (EB_STEP4_LOW, EB_STEP4_HIGH, EB_STEP2, EB_STEP1)
void setEncType(uint8_t type);

// использовать обработку энкодера в прерывании
void setEncISR(bool use);

// инициализация энкодера
void initEnc(bool e0, bool e1);

// инициализация энкодера совмещённым значением
void initEnc(int8_t v);

// сбросить флаги событий
void clear();

// ====================== ОПРОС ======================
// был поворот [событие]
bool turn();

// направление энкодера (1 или -1) [состояние]
int8_t dir();

// счётчик
int32_t counter;

// ==================== ОБРАБОТКА ====================
// опросить энкодер в прерывании. Вернёт 1 или -1 при вращении, 0 при остановке
int8_t tickISR(bool e0, bool e1);
int8_t tickISR(int8_t state);

// опросить энкодер. Вернёт 1 или -1 при вращении, 0 при остановке
int8_t tick(bool e0, bool e1);
int8_t tick(int8_t state);
int8_t tick();  // сама обработка в прерывании

// опросить энкодер без сброса события поворота. Вернёт 1 или -1 при вращении, 0 при остановке
int8_t tickRaw(bool e0, bool e1);
int8_t tickRaw(int8_t state);
int8_t tickRaw();  // сама обработка в прерывании

// опросить энкодер без установки флагов на поворот (быстрее). Вернёт 1 или -1 при вращении, 0 при остановке
int8_t pollEnc(bool e0, bool e1);
int8_t pollEnc(int8_t state);
VirtEncButton
  • Доступны функции из VirtButton
  • Доступны функции из VirtEncoder
// ================== НАСТРОЙКИ ==================
// установить таймаут быстрого поворота, мс
void setFastTimeout(uint8_t tout);

// сбросить флаги энкодера и кнопки
void clear();

// ==================== ОПРОС ====================
// ЛЮБОЙ поворот энкодера [событие]
bool turn();

// нажатый поворот энкодера [событие]
bool turnH();

// быстрый поворот энкодера [состояние]
bool fast();

// ненажатый поворот направо [событие]
bool right();

// ненажатый поворот налево [событие]
bool left();

// нажатый поворот направо [событие]
bool rightH();

// нажатый поворот налево [событие]
bool leftH();

// было действие с кнопки или энкодера, вернёт код события [событие]
uint16_t action();

// ==================== ОБРАБОТКА ====================
// обработка в прерывании (только энкодер). Вернёт 0 в покое, 1 или -1 при повороте
int8_t tickISR(bool e0, bool e1);
int8_t tickISR(int8_t e01);

// обработка энкодера и кнопки
bool tick(bool e0, bool e1, bool btn);
bool tick(int8_t e01, bool btn);
bool tick(bool btn);  // энкодер в прерывании

// обработка энкодера и кнопки без сброса флагов и вызова коллбэка
bool tickRaw(bool e0, bool e1, bool btn);
bool tickRaw(int8_t e01, bool btn);
bool tickRaw(bool btn);  // энкодер в прерывании
Button
  • Доступны функции из VirtButton
  • Режим кнопки по умолчанию - LOW
Button;
Button(uint8_t pin);                // с указанием пина
Button(uint8_t npin, uint8_t mode); // + режим работы (умолч. INPUT_PULLUP)
Button(uint8_t npin, uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)
// указать пин и его режим работы
void init(uint8_t npin, uint8_t mode);

// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel
bool read();

// функция обработки, вызывать в loop
bool tick();

// обработка кнопки без сброса событий и вызова коллбэка
bool tickRaw();
ButtonT
  • Доступны функции из VirtButton
  • Режим кнопки по умолчанию - LOW
ButtonT<uint8_t pin>;                 // с указанием пина
ButtonT<uint8_t pin> (uint8_t mode);  // + режим работы (умолч. INPUT_PULLUP)
ButtonT<uint8_t pin> (uint8_t mode, uint8_t btnLevel); // + уровень кнопки (умолч. LOW)
// указать режим работы
void init(uint8_t mode);

// прочитать текущее значение кнопки (без дебаунса) с учётом setBtnLevel
bool read();

// функция обработки, вызывать в loop
bool tick();
Encoder
  • Доступны функции из VirtEncoder
Encoder;
Encoder(uint8_t encA, uint8_t encB);                // с указанием пинов
Encoder(uint8_t encA, uint8_t encB, uint8_t mode);  // + режим работы (умолч. INPUT)
// указать пины и их режим работы
void init(uint8_t encA, uint8_t encB, uint8_t mode);

// функция обработки для вызова в прерывании энкодера
int8_t tickISR();

// функция обработки для вызова в loop
int8_t tick();
EncoderT
  • Доступны функции из VirtEncoder
EncoderT<uint8_t encA, uint8_t encB>;                 // с указанием пинов
EncoderT<uint8_t encA, uint8_t encB> (uint8_t mode);  // + режим работы (умолч. INPUT)
// указать режим работы пинов
void init(uint8_t mode);

// функция обработки для вызова в прерывании энкодера
int8_t tickISR();

// функция обработки для вызова в loop
int8_t tick();
EncButton
  • Доступны функции из VirtButton
  • Доступны функции из VirtEncoder
  • Доступны функции из VirtEncButton
EncButton;

// настроить пины (энк, энк, кнопка)
EncButton(uint8_t encA, uint8_t encB, uint8_t btn);

// настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки)
EncButton(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);
// настроить пины (энк, энк, кнопка, pinmode энк, pinmode кнопка, уровень кнопки)
void init(uint8_t encA, uint8_t encB, uint8_t btn, uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);

// функция обработки для вызова в прерывании энкодера
int8_t tickISR();

// функция обработки, вызывать в loop
bool tick();

// прочитать значение кнопки с учётом setBtnLevel
bool readBtn();

// прочитать значение энкодера
int8_t readEnc();
EncButtonT
  • Доступны функции из VirtButton
  • Доступны функции из VirtEncoder
  • Доступны функции из VirtEncButton
// с указанием пинов
EncButtonT<uint8_t encA, uint8_t encB, uint8_t btn>;

// + режим работы пинов, уровень кнопки
EncButtonT<uint8_t encA, uint8_t encB, uint8_t btn> (uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);
// настроить режим работы пинов, уровень кнопки
void init(uint8_t modeEnc = INPUT, uint8_t modeBtn = INPUT_PULLUP, uint8_t btnLevel = LOW);

// функция обработки для вызова в прерывании энкодера
int8_t tickISR();

// функция обработки, вызывать в loop
bool tick();

// прочитать значение кнопки
bool readBtn();

// прочитать значение энкодера
int8_t readEnc();

Обработка и опрос

Во всех библиотеках есть общая функция обработки (тикер tick), которая получает текущий сигнал с кнопки и энкодера

  • Эту функцию нужно однократно вызывать в основном цикле программы (для виртуальных - с передачей значения)
  • Функция возвращает true при наступлении события (для энкодера - 1 или -1 при повороте, 0 при его отсутствии. Таким образом поворот в любую сторону расценивается как true)
  • Есть отдельные функции для вызова в прерывании, они имеют суффикс ISR, см. документацию ниже

Библиотека обрабатывает сигнал внутри этой функции, результат можно получить из функций опроса событий. Они бывают двух типов:

  • [событие] - функция вернёт true однократно при наступлении события. Сбросится после следующего вызова функции обработки (например клик, поворот энкодера)
  • [состояние] - функция возвращает true, пока активно это состояние (например кнопка удерживается)

Для простоты восприятия функцию обработки нужно размещать в начале цикла, а опросы делать ниже:

void loop() {
  btn.tick();   // опрос

  if (btn.click()) Serial.println("click"); // однократно выведет при клике
  if (btn.click()) Serial.println("click"); // тот же клик!
}

В отличие от предыдущих версий библиотеки, функции опроса сбрасываются не внутри себя, а внутри функции обработки. Таким образом в примере выше при клике по кнопке в порт дважды выведется сообщение click(). Это позволяет использовать функции опроса по несколько раз за текущую итерацию цикла для создания сложной логики работы программы.

Несколько функций обработки

По очевидным причинам нельзя вызывать функцию обработки больше одного раза за цикл - каждый следующий вызов сбросит события от предыдущего и код будет работать некорректно. Вот так - нельзя:

// так нельзя
void loop() {
  btn.tick();
  if (btn.click()) ...

  // ....

  btn.tick();
  if (btn.hold()) ...
}

Если очень нужно попасть в глухой цикл и опрашивать там кнопку, то вот так - можно:

// так можно
void loop() {
  btn.tick();
  if (btn.click()) ...

  while (true) {
    btn.tick();
    if (btn.hold()) ...
    if (btn.click()) break;
  }
}

Если библиотека используется с подключенным обработчиком событий attach() (см. ниже), то можно вызывать tick() где угодно и сколько угодно раз, события будут обработаны в обработчике:

// так можно
void cb() {
  switch (btn.action()) {
    // ...
  }
}

void setup() {
  btn.attach(cb);
}

void loop() {
  btn.tick();
  // ...
  btn.tick();
  // ...
  btn.tick();
}

"Загруженная" программа

Библиотека EncButton - асинхронная: она не ждёт, пока закончится обработка кнопки, а позволяет программе выполняться дальше. Это означает, что для корректной работы библиотеки основной цикл программы должен выполняться как можно быстрее и не содержать задержек и других "глухих" циклов внутри себя. Для обеспечения правильной обработки кнопки не рекомендуется иметь в основном цикле задержки длительностью более 50-100 мс. Несколько советов:

  • Новичкам: изучить цикл уроков как написать скетч
    • Писать асинхронный код в loop()
    • Любую синхронную конструкцию на delay() можно сделать асинхронной при помощи millis()
    • Если в программе каждая итерация главного цикла выполняется дольше 50-100мс - в большинстве случаев программа написана неправильно, за исключением каких-то особых случаев
  • Подключить кнопку на аппаратное прерывание (см. ниже)
  • Избегать выполнения "тяжёлых" участков кода, пока идёт обработка кнопки, например поместив их в условие if (!button.busy()) { тяжёлый код }
  • Если оптимизировать основной цикл невозможно - вызывать тикер в другом "потоке" и использовать функцию-обработчик:
    • В прерывании таймера с периодом ~50мс или чаще
    • На другом ядре (например ESP32)
    • В другом таске FreeRTOS
    • Внутри yield() (внутри delay())

Раздельная обработка

Имеет смысл только при ручном опросе событий! При подключенной функции-обработчике достаточно вызывать обычный tick() между тяжёлыми участками программы

Также в загруженной программе можно разделить обработку и сброс событий: вместо tick() использовать tickRaw() между тяжёлыми участками кода и ручной сброс clear(). Порядок следующий:

  • Опросить действия (click, press, turn...)
  • Вызвать clear()
  • Вызывать tickRaw() между тяжёлыми участками кода
void loop() {
  if (btn.click()) ...
  if (btn.press()) ...
  if (btn.step()) ...

  btn.clear();

  // ...
  btn.tickRaw();
  // ...
  btn.tickRaw();
  // ...
  btn.tickRaw();
  // ...
}

Это позволит опрашивать кнопку/энкодер в не очень хорошо написанной программе, где основной цикл завален тяжёлым кодом. Внутри tickRaw() накапливаются события, которые раз в цикл разбираются, а затем вручную сбрасываются.

В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события releaseXxx

Обработка внутри delay

Если сложно избавиться от delay() внутри главного цикла программы, то на некоторых платформах можно поместить свой код внутри него. Таким образом можно получить даже обработку энкодера в цикле с дилеями без использования прерываний:

// вставка кода в delay
void yield() {
  eb.tickRaw();
}

void loop() {
  if (eb.click()) ...
  if (btn.turn()) ...

  eb.clear();

  // ...
  delay(10);
  // ...
  delay(50);
  // ...
}

В этом сценарии буферизация энкодера в прерывании не работает и не обрабатываются все события releaseXxx

Обработка кнопки

Библиотека обрабатывает кнопку следующим образом:

  • Нажатие с программным подавлением дребезга (удержание дольше таймаута deb), результат - событие press, состояния pressing и busy
  • Удержание дольше таймаута удержания hold - событие hold, состояние holding
  • Удержание дольше таймаута удержания hold + таймаута степ - импульсное событие step, срабатывает с периодом step пока кнопка удерживается
  • Отпускание кнопки, результат - событие release, снятие состояний pressing и holding
    • Отпускание до таймаута удержания - событие click
    • Отпускание после удержания - событие releaseHold
    • Отпускание после импульсного удержания - событие releaseStep
    • События releaseHold и releaseStep взаимоисключающие, если кнопка была удержана до step - releaseHold уже не сработает
  • Ожидание нового клика в течение таймаута click, состояние waiting
  • Если нового клика нет - снятие состоятия busy, обработка закончена
    • Если кнопка снова нажата - обработка нового клика
    • Счётчик кликов getClicks() сбрасывается после событий releaseHold/releaseStep, которые проверяют предварительные клики. В общем обработчике action() это события EB_REL_HOLD_C или EB_REL_STEP_C
    • Количество сделанных кликов нужно проверять по событию hasClicks, а также можно опросить внутри почти всех событий кнопки, которые идут до releaseXxx
  • Если ожидается timeout - событие timeout с указанным периодом от текущего момента
  • Обработка кнопки в прерывании сообщает библиотеке о факте нажатия, вся остальная обработка выполняется штатно в tick()

Отличие click(n) от hasClicks(n): click(n) вернёт true в любом случае при совпадении количества кликов, даже если будет сделано больше кликов. hasClicks(n) вернёт true только в том случае, если было сделано ровно указанное количество кликов и больше кликов не было!

Лучше один раз увидеть, чем сто раз прочитать. Запусти пример demo и понажимай на кнопку, или попробуй онлайн-симуляцию в Wokwi

Click

click

Hold

hold

Step

step

Онлайн-симуляция доступна здесь

Обработка энкодера

  • "Быстрым" поворотом считается поворот, совершённый менее чем за настроенный таймаут от предыдущего поворота
  • Обработанные в прерывании повороты становятся активными (вызывают события) после вызова tick()
  • Доступ к счётчику энкодера counter - это публичная переменная класса, можно делать с ней всё что угодно:
Serial.println(eb.counter); // читать
eb.counter += 1234;         // менять
eb.counter = 0;             // обнулять

Обработка энкодера с кнопкой

  • Поворот энкодера при зажатой кнопке снимает и блокирует все последующие события и клики, за исключением события release. Состояния нажатой кнопки не изменяются
  • Поворот энкодера также влияет на системный таймаут (функция timeout()) - сработает через указанное время после поворота энкодера
  • Счётчик кликов доступен при нажатом повороте: несколько кликов, зажатие кнопки, поворот

Предварительные клики

Библиотека считает количество кликов по кнопке и некоторые функции опроса могут отдельно обрабатываться с предварительными кликами. Например 3 клика, затем удержание. Это очень сильно расширяет возможности одной кнопки. Есть два варианта работы с такими событиями:

  // 1
  if (btn.hold()) {
    if (btn.getClicks() == 2) Serial.println("hold 2 clicks");
  }

  // 2
  if (btn.hold(2)) Serial.println("hold 2 clicks");

В первом варианте можно получить количество кликов для дальнейшей обработки вручную, а во втором - библиотека сделает это сама, если количество кликов для действия заранее известно.

Прямое чтение кнопки

В некоторых сценариях бывает нужно получить состояние кнопки "здесь и сейчас", например определить удерживается ли кнопка сразу после запуска микроконтроллера (старта программы). Функцию tick() нужно вызывать постоянно в цикле, чтобы шла обработка кнопки с гашением дребезга контактов и прочими расчётами, поэтому конструкция следующего вида работать не будет:

void setup() {
  btn.tick();
  if (btn.press()) Serial.println("Кнопка нажата при старте");
}

Для таких сценариев помогут следующие функции, возвращают true если кнопка нажата:

  • read() для библиотек Button и ButtonT
  • readBtn() для библиотек EncButton и EncButtonT

Опрос кнопки выполняется с учётом настроенного ранее уровня кнопки (setBtnLevel)! Вручную дополнительно инвертировать логику не нужно:

void setup() {
  // btn.setBtnLevel(LOW); // можно настроить уровень

  if (btn.read()) Serial.println("Кнопка нажата при старте");
}

Погружение в цикл

Допустим нужно обработать кнопку синхронно и с гашением дребезга. Например если кнопка зажата при старте микроконтроллера - получить её удержание или даже импульсное удержание внутри блока setup, то есть до начала выполнения основной программы. Можно воспользоваться состоянием busy и опрашивать кнопку из цикла:

void setup() {
  Serial.begin(115200);

  btn.tick();
  while (btn.busy()) {
    btn.tick();
    if (btn.hold()) Serial.println("hold");
    if (btn.step()) Serial.println("step");
  }

  Serial.println("program start");
}

Как это работает: первый тик опрашивает кнопку, если кнопка нажата - сразу же активируется состояние busy и система попадает в цикл while. Внутри него продолжаем тикать и получать события с кнопки. Когда кнопка будет отпущена и сработают все события - флаг busy опустится и программа автоматически покинет цикл. Можно переписать эту конструкцию на цикл с постусловием, более красиво:

do {
  btn.tick();
  if (btn.hold()) Serial.println("hold");
  if (btn.step()) Serial.println("step");
} while (btn.busy());

Timeout

В связанных с кнопкой классах (Button, EncButton) есть функция timeout(time) - она однократно вернёт true, если после окончания действий с кнопкой/энкодером прошло указанное время. Это можно использовать для сохранения параметров после ввода, например:

void loop() {
  eb.tick();

  // ...

  if (eb.timeout(2000)) {
    // после взаимодействия с энкодером прошло 2 секунды
    // EEPROM.put(0, settings);
  }
}

Busy

Функция busy() возвращает true, пока идёт обработка кнопки, т.е. пока система ожидает действий и выхода таймаутов. Это можно использовать для оптимизации кода, например избегать каких то долгих и тяжёлых частей программы на время обработки кнопки:

void loop() {
  eb.tick();

  // ...

  if (!eb.busy()) {
    // потенциально долгий и тяжёлый код
  }
}

Получение события

Доступно во всех классах с кнопкой:

  • VirtButton
  • Button
  • VirtEncButton
  • EncButton

Функция action() при наступлении события возвращает код события (отличный от нуля, что само по себе является индикацией наличия события):

  • EB_PRESS - нажатие на кнопку
  • EB_HOLD - кнопка удержана
  • EB_STEP - импульсное удержание
  • EB_RELEASE - кнопка отпущена
  • EB_CLICK - одиночный клик
  • EB_CLICKS - сигнал о нескольких кликах
  • EB_TURN - поворот энкодера
  • EB_REL_HOLD - кнопка отпущена после удержания
  • EB_REL_HOLD_C - кнопка отпущена после удержания с предв. кликами
  • EB_REL_STEP - кнопка отпущена после степа
  • EB_REL_STEP_C - кнопка отпущена после степа с предв. кликами

Результат функции action() сбрасывается после следующего вызова tick(), то есть доступен на всей текущей итерации основного цикла

Полученный код события можно обработать через switch:

switch (eb.action()) {
  case EB_PRESS:
    // ...
    break;
  case EB_HOLD:
    // ...
    break;
  // ...
}

Оптимизация

Вес библиотеки

Для максимального уменьшения веса библиотеки (в частности в оперативной памяти) нужно задавать тайматуы константами через define (экономия 1 байт за таймаут), отключить обработчик событий, счётчики-буферы и использовать T-класс (экономия 1 байт за пин):

#define EB_NO_FOR
#define EB_NO_CALLBACK
#define EB_NO_COUNTER
#define EB_NO_BUFFER
#define EB_DEB_TIME 50     // таймаут гашения дребезга кнопки (кнопка)
#define EB_CLICK_TIME 500  // таймаут ожидания кликов (кнопка)
#define EB_HOLD_TIME 600   // таймаут удержания (кнопка)
#define EB_STEP_TIME 200   // таймаут импульсного удержания (кнопка)
#define EB_FAST_TIME 30    // таймаут быстрого поворота (энкодер)
#include <EncButton.h>
EncButtonT<2, 3, 4> eb;

В таком случае энкодер с кнопкой займёт в SRAM всего 8 байт, а просто кнопка - 5.

Скорость выполнения

Чтобы сократить время на проверку системных флагов событий (незначительно, но приятно) можно поместить все опросы в условие по tick(), так как tick() возвращает true только при наступлении события:

void loop() {
  if (eb.tick()) {
    if (eb.turn()) ...;
    if (eb.click()) ...;
  }
}

Также опрос событий при помощи функции action() выполняется быстрее, чем ручной опрос отдельных функций событий, поэтому максимально эффективно библиотека будет работать вот в таком формате:

void loop() {
  if (eb.tick()) {
    switch (eb.action()) {
      case EB_PRESS:
        // ...
        break;
      case EB_HOLD:
        // ...
        break;
      // ...
    }
  }
}

Для опроса состояний кнопки pressing(), holding(), waiting() можно поместить их вовнутрь условия по busy(), чтобы не опрашивать состояния пока их гарантированно нет:

if (btn.busy()) {
  if (btn.pressing())...
  if (btn.holding())...
  if (btn.waiting())...
}

Коллбэки

Можно подключить внешнюю функцию-обрбаотчик события, она будет вызвана при наступлении любого события. Данная возможность работает во всех классах с кнопкой:

  • VirtButton
  • Button
  • VirtEncButton
  • EncButton
EncButton eb(2, 3, 4);

void callback() {
  switch (eb.action()) {
    case EB_PRESS:
      // ...
      break;
    case EB_HOLD:
      // ...
      break;
    // ...
  }
}

void setup() {
  eb.attach(callback);
}

void loop() {
  eb.tick();
}

Одновременное нажатие

Библиотека нативно поддерживает работу с двумя одновременно нажатыми кнопками как с третьей кнопкой. Для этого нужно:

  1. Cоздать виртуальную кнопку VirtButton
  2. Вызвать обработку реальных кнопок
  3. Передать виртуальной кнопке в обработку эти кнопки (это могут быть объекты классов VirtButton, Button, EncButton + их T-версии)
  4. Далее опрашивать события
Button b0(4);
Button b1(5);
VirtButton b2;  // 1

void loop() {
  b0.tick();  // 2
  b1.tick();  // 2
  b2.tick(b0, b1);  // 3

  // 4
  if (b0.click()) Serial.println("b0 click");
  if (b1.click()) Serial.println("b1 click");
  if (b2.click()) Serial.println("b0+b1 click");
}

Библиотека сама "сбросит" лишние события с реальных кнопок, если они были нажаты вместе, за исключением события press. Таким образом получается полноценная третья кнопка из двух других с удобным опросом.

Прерывания

Энкодер

Для обработки энкодера в загруженной программе нужно:

  • Подключить оба его пина на аппаратные прерывания по CHANGE
  • Установить setEncISR(true)
  • Вызывать в обработчике специальный тикер для прерывания
  • Основной тикер также нужно вызывать в loop для корреткной работы - события генерируются в основном тикере:
// пример для ATmega328 и EncButton
EncButton eb(2, 3, 4);

/*
// esp8266/esp32
IRAM_ATTR void isr() {
  eb.tickISR();
}
*/

void isr() {
  eb.tickISR();
}
void setup() {
  attachInterrupt(0, isr, CHANGE);
  attachInterrupt(1, isr, CHANGE);
  eb.setEncISR(true);
}
void loop() {
  eb.tick();
}

Примечание: использование работы в прерывании позволяет корректно обрабатывать позицию энкодера и не пропустить новый поворот. Событие с поворотом, полученное из прерывания, станет доступно после вызова tick в основном цикле программы, что позволяет не нарушать последовательность работы основного цикла:

  • Буферизация отключена: событие turn активируется только один раз, независимо от количества щелчков энкодера, совершённых между двумя вызовами tick (щелчки обработаны в прерывании)
  • Буферизация включена: событие turn будет вызвано столько раз, сколько реально было щелчков энкодера, это позволяет вообще не пропускать повороты и не нагружать систему в прерывании. Размер буфера - 5 необработанных щелчков энкодера

Примечания:

  • Функция setEncISR работает только в не виртуальных классах. Если он включен - основной тикер tick просто не опрашивает пины энкодера, что экономит процессорное время. Обработка происходит только в прерывании
  • Счётчик энкодера всегда имеет актуальное значение и может опережать буферизированные повороты в программе с большими задержками в основном цикле!
  • На разных платформах прерывания могут работать по разному (например на ESPxx - нужно добавить функции аттрибут IRAM_ATTR, см. документацию на свою платформу!)
  • Обработчик, подключенный в attach(), будет вызван из tick(), то есть не из прерывания!

Виртуальные классы

В виртуальных есть тикер, в который не нужно передавать состояние энкодера, если он обрабатывается в прерывании, это позволяет не опрашивать пины в холостую. Например:

VirtEncoder e;

void isr() {
    e.tickISR(digitalRead(2), digitalRead(3));
}
void setup() {
    attachInterrupt(0, isr, CHANGE);
    attachInterrupt(1, isr, CHANGE);

    e.setEncISR(1);
}
void loop() {
    e.tick();   // не передаём состояния пинов
}

Кнопка

Для обработки кнопки в прерывании нужно:

  • Подключить прерывание на нажатие кнопки с учётом её физического подключения и уровня:
    • Если кнопка замыкает LOW - прерывание FALLING
    • Если кнопка замыкает HIGH - прерывание RISING
  • Вызывать pressISR() в обработчике прерывания
Button b(2);

/*
// esp8266/esp32
IRAM_ATTR void isr() {
  b.pressISR();
}
*/

void isr() {
  b.pressISR();
}
void setup() {
  attachInterrupt(0, isr, FALLING);
}
void loop() {
  b.tick();
}

Примечание: кнопка обрабатывается в основном tick(), а функция pressISR() всего лишь сообщает библиотеке, что кнопка была нажата вне tick(). Это позволяет не пропустить нажатие кнопки, пока программа была занята чем-то другим.

Массив кнопок/энкодеров

Создать массив можно только из нешаблонных классов (без буквы T), потому что номера пинов придётся указать уже в рантайме далее в программе. Например:

Button btns[5];
EncButton ebs[3];

void setup() {
  btns[0].init(2);  // указать пин
  btns[1].init(5);
  btns[2].init(10);
  // ...

  ebs[0].init(11, 12, 13, INPUT);
  ebs[1].init(14, 15, 16);
  // ...
}
void loop() {
  for (int i = 0; i < 5; i++) btns[i].tick();
  for (int i = 0; i < 3; i++) ebs[i].tick();

  if (btns[2].click()) Serial.println("btn2 click");
  // ...
}

Кастомные функции

Библиотека поддерживает задание своих функций для чтения пина и получения времени без редактирования файлов библиотеки. Для этого нужно реализовать соответствующую функцию в своём .cpp или .ino файле:

  • bool EB_read(uint8_t pin) - для своей функции чтения пина
  • void EB_mode(uint8_t pin, uint8_t mode) - для своего аналога pinMode
  • uint32_t EB_uptime() - для своего аналога millis()

Пример:

#include <EncButton.h>

bool EB_read(uint8_t pin) {
    return digitalRead(pin);
}

void EB_mode(uint8_t pin, uint8_t mode) {
    pinMode(pin, mode);
}

uint32_t EB_uptime() {
    return millis();
}

Опрос по таймеру

Иногда может понадобиться вызывать tick() не на каждой итерации, а по таймеру. Например для виртуальной кнопки с расширителя пинов, когда чтение расширителя пинов - долгая операция, и вызывать её часто не имеет смысла. Вот так делать нельзя, события будут активны в течение периода таймера!

void loop() {
  // таймер на 50 мс
  static uint32_t tmr;
  if (millis() - tmr >= 50) {
    tmr = millis();
    btn.tick(readSomePin());
  }

  // будет активно в течение 50 мс!!!
  if (btn.click()) foo();
}

В данной ситуации нужно поступить так: тикать по таймеру, там же обрабатывать события и сбрасывать флаги в конце:

void loop() {
  // таймер на 50 мс
  static uint32_t tmr;
  if (millis() - tmr >= 50) {
    tmr = millis();
    // тик
    btn.tick(readSomePin());

    // разбор событий
    if (btn.click()) foo();

    // сброс флагов
    btn.clear();
  }
}

Либо можно подключить обработчик и вызывать clear() в конце функции:

void callback() {
  switch (btn.action()) {
    // ...
  }

  // сброс флагов
  btn.clear();
}

void loop() {
  // таймер на 50 мс
  static uint32_t tmr;
  if (millis() - tmr >= 50) {
    tmr = millis();
    btn.tick(readSomePin());
  }
}

В случае с вызовом по таймеру антидребезг будет частично обеспечиваться самим таймером и в библиотеке его можно отключить (поставить период 0).

Для корректной работы таймаутов, состояний и счётчика кликов нужен другой подход: буферизировать прочитанные по таймеру состояния и передавать их в тик в основном цикле. Например так:

bool readbuf = 0;  // буфер пина

void loop() {
  // таймер на 50 мс
  static uint32_t tmr;
  if (millis() - tmr >= 50) {
    tmr = millis();
    readbuf = readSomePin();  // чтение в буфер
  }

  // тик из буфера
  btn.tick(readbuf);

  if (btn.click()) foo();
}

Пропуск событий

EncButton позволяет кнопке работать в паре с энкодером для корректного отслеживания нажатых поворотов - при нажатом повороте события с кнопки будут пропущены, т.е. не обработается удержание и клик. Допустим кнопок несколько: они могут выполнять действия как сами по себе, так и в паре с энкодером (кнопка зажата и крутится энкодер, в программе меняется выбранное кнопкой значение). Чтобы при удержании кнопка не генерировала события (удержание, степ, клики...) можно включить пропуск событий. Он будет действовать до отпускания кнопки:

if (btn.pressing() && enc.turn()) {
  btn.skipEvents();  // зафиксирован поворот. Пропускаем события
  // нажатый поворот
}

if (btn.click()) {
  // просто клик
}

Мини примеры, сценарии

// меняем значения переменных

// поворот энкодера
if (enc.turn()) {
  // меняем с шагом 5
  var += 5 * enc.dir();

  // меняем с шагом 1 при обычном повороте, 10 при быстром
  var += enc.fast() ? 10 : 1;

  // меняем с шагом 1 при обычном повороте, 10 при нажатом
  var += enc.pressing() ? 10 : 1;

  // меняем одну переменную при повороте, другую - при нажатом повороте
  if (enc.pressing()) var0++;
  else var1++;

  // если кнопка нажата - доступны предварительные клики
  // Выбираем переменную для изменения по предв. кликам
  if (enc.pressing()) {
    switch (enc.getClicks()) {
      case 1: var0 += enc.dir();
        break;
      case 2: var1 += enc.dir();
        break;
      case 3: var2 += enc.dir();
        break;
    }
  }
}

// импульсное удержание на каждом шаге инкрементирует переменную
if (btn.step()) var++;

// смена направления изменения переменной после отпускания из step
if (btn.step()) var += dir;
if (btn.releaseStep()) dir = -dir;

// изменение выбранной переменной при помощи step
if (btn.step(1)) var1++;  // клик-удержание
if (btn.step(2)) var2++;  // клик-клик-удержание
if (btn.step(3)) var3++;  // клик-клик-клик-удержание

// если держать step больше 2 секунд - инкремент +5, пока меньше - +1
if (btn.step()) {
  if (btn.stepFor(2000)) var += 5;
  else var += 1;
}

// включение режима по количеству кликов
if (btn.hasClicks()) mode = btn.getClicks();

// включение режима по нескольким кликам и удержанию
if (btn.hold(1)) mode = 1;  // клик-удержание
if (btn.hold(2)) mode = 2;  // клик-клик-удержание
if (btn.hold(3)) mode = 3;  // клик-клик-клик-удержание

// или так
if (btn.hold()) mode = btn.getClicks();

// кнопка отпущена, смотрим сколько её удерживали
if (btn.release()) {
  // от 1 до 2 секунд
  if (btn.pressFor() > 1000 && btn.pressFor() <= 2000) mode = 1;
  // от 2 до 3 секунд
  else if (btn.pressFor() > 2000 && btn.pressFor() <= 3000) mode = 2;
}

Гайд по миграции с v2 на v3

Инициализация

// ВИРТУАЛЬНЫЕ
VirtEncButton eb; // энкодер с кнопкой
VirtButton b;     // кнопка
VirtEncoder e;    // энкодер

// РЕАЛЬНЫЕ
// энкодер с кнопкой
EncButton eb(enc0, enc1, btn);                    // пины энкодера и кнопки
EncButton eb(enc0, enc1, btn, modeEnc);           // + режим пинов энкодера (умолч. INPUT)
EncButton eb(enc0, enc1, btn, modeEnc, modeBtn);  // + режим пина кнопки (умолч. INPUT_PULLUP)
EncButton eb(enc0, enc1, btn, modeEnc, modeBtn, btnLevel);  // + уровень кнопки (умолч. LOW)
// шаблонный
EncButton<enc0, enc1, btn> eb;                    // пины энкодера и кнопки
EncButton<enc0, enc1, btn> eb(modeEnc);           // + режим пинов энкодера (умолч. INPUT)
EncButton<enc0, enc1, btn> eb(modeEnc, modeBtn);  // + режим пина кнопки (умолч. INPUT_PULLUP)
EncButton<enc0, enc1, btn> eb(modeEnc, modeBtn, btnLevel);  // + уровень кнопки (умолч. LOW)

// кнопка
Button b(pin);                  // пин
Button b(pin, mode);            // + режим пина кнопки (умолч. INPUT_PULLUP)
Button b(pin, mode, btnLevel);  // + уровень кнопки (умолч. LOW)
// шаблонный
ButtonT<pin> b;                 // пин
ButtonT<pin> b(mode);           // + режим пина кнопки (умолч. INPUT_PULLUP)
ButtonT<pin> b(mode, btnLevel); // + уровень кнопки (умолч. LOW)

// энкодер
Encoder e(enc0, enc1);          // пины энкодера
Encoder e(enc0, enc1, mode);    // + режим пинов энкодера (умолч. INPUT)
// шаблонный
EncoderT<enc0, enc1> e;         // пины энкодера
EncoderT<enc0, enc1> e(mode);   // + режим пинов энкодера (умолч. INPUT)

Функции

v2 v3
held() hold()
hold() holding()
state() pressing()
setPins() init()
  • Изменился порядок указания пинов (см. доку выше)
  • clearFlags() заменена на clear() (сбросить флаги событий) и reset() (сбросить системные флаги обработки, закончить обработку)

Логика работы

В v3 функции опроса событий (click, turn...) не сбрасываются сразу после своего вызова - они сбрасываются при следующем вызове tick(), таким образом сохраняют своё значение во всех последующих вызовах на текущей итерации главного цикла программы. Поэтому tick() нужно вызывать только 1 раз за цикл, иначе будут пропуски действий! Читай об этом выше.

Примеры

Остальные примеры смотри в examples!

Полное демо EncButton
// #define EB_NO_FOR           // отключить поддержку pressFor/holdFor/stepFor и счётчик степов (экономит 2 байта оперативки)
// #define EB_NO_CALLBACK      // отключить обработчик событий attach (экономит 2 байта оперативки)
// #define EB_NO_COUNTER       // отключить счётчик энкодера (экономит 4 байта оперативки)
// #define EB_NO_BUFFER        // отключить буферизацию энкодера (экономит 1 байт оперативки)

// #define EB_DEB_TIME 50      // таймаут гашения дребезга кнопки (кнопка)
// #define EB_CLICK_TIME 500   // таймаут ожидания кликов (кнопка)
// #define EB_HOLD_TIME 600    // таймаут удержания (кнопка)
// #define EB_STEP_TIME 200    // таймаут импульсного удержания (кнопка)
// #define EB_FAST_TIME 30     // таймаут быстрого поворота (энкодер)

#include <EncButton.h>
EncButton eb(2, 3, 4);
//EncButton eb(2, 3, 4, INPUT); // + режим пинов энкодера
//EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP); // + режим пинов кнопки
//EncButton eb(2, 3, 4, INPUT, INPUT_PULLUP, LOW);  // + уровень кнопки

void setup() {
    Serial.begin(115200);

    // показаны значения по умолчанию
    eb.setBtnLevel(LOW);
    eb.setClickTimeout(500);
    eb.setDebTimeout(50);
    eb.setHoldTimeout(600);
    eb.setStepTimeout(200);

    eb.setEncReverse(0);
    eb.setEncType(EB_STEP4_LOW);
    eb.setFastTimeout(30);

    // сбросить счётчик энкодера
    eb.counter = 0;
}

void loop() {
    eb.tick();

    // обработка поворота общая
    if (eb.turn()) {
        Serial.print("turn: dir ");
        Serial.print(eb.dir());
        Serial.print(", fast ");
        Serial.print(eb.fast());
        Serial.print(", hold ");
        Serial.print(eb.pressing());
        Serial.print(", counter ");
        Serial.print(eb.counter);
        Serial.print(", clicks ");
        Serial.println(eb.getClicks());
    }

    // обработка поворота раздельная
    if (eb.left()) Serial.println("left");
    if (eb.right()) Serial.println("right");
    if (eb.leftH()) Serial.println("leftH");
    if (eb.rightH()) Serial.println("rightH");

    // кнопка
    if (eb.press()) Serial.println("press");
    if (eb.click()) Serial.println("click");

    if (eb.release()) {
      Serial.println("release");

      Serial.print("clicks: ");
      Serial.print(eb.getClicks());
      Serial.print(", steps: ");
      Serial.print(eb.getSteps());
      Serial.print(", press for: ");
      Serial.print(eb.pressFor());
      Serial.print(", hold for: ");
      Serial.print(eb.holdFor());
      Serial.print(", step for: ");
      Serial.println(eb.stepFor());
    }

    // состояния
    // Serial.println(eb.pressing());
    // Serial.println(eb.holding());
    // Serial.println(eb.busy());
    // Serial.println(eb.waiting());

    // таймаут
    if (eb.timeout(1000)) Serial.println("timeout!");

    // удержание
    if (eb.hold()) Serial.println("hold");
    if (eb.hold(3)) Serial.println("hold 3");

    // импульсное удержание
    if (eb.step()) Serial.println("step");
    if (eb.step(3)) Serial.println("step 3");

    // отпущена после импульсного удержания
    if (eb.releaseStep()) Serial.println("release step");
    if (eb.releaseStep(3)) Serial.println("release step 3");

    // отпущена после удержания
    if (eb.releaseHold()) Serial.println("release hold");
    if (eb.releaseHold(2)) Serial.println("release hold 2");

    // проверка на количество кликов
    if (eb.hasClicks(3)) Serial.println("has 3 clicks");

    // вывести количество кликов
    if (eb.hasClicks()) {
        Serial.print("has clicks: ");
        Serial.println(eb.getClicks());
    }
}
Подключение обработчика
#include <EncButton.h>
EncButton eb(2, 3, 4);

void callback() {
    Serial.print("callback: ");
    switch (eb.action()) {
        case EB_PRESS:
            Serial.println("press");
            break;
        case EB_HOLD:
            Serial.println("hold");
            break;
        case EB_STEP:
            Serial.println("step");
            break;
        case EB_RELEASE:
            Serial.println("release");
            break;
        case EB_CLICK:
            Serial.println("click");
            break;
        case EB_CLICKS:
            Serial.print("clicks ");
            Serial.println(eb.getClicks());
            break;
        case EB_TURN:
            Serial.print("turn ");
            Serial.print(eb.dir());
            Serial.print(" ");
            Serial.print(eb.fast());
            Serial.print(" ");
            Serial.println(eb.pressing());
            break;
        case EB_REL_HOLD:
            Serial.println("release hold");
            break;
        case EB_REL_HOLD_C:
            Serial.print("release hold clicks ");
            Serial.println(eb.getClicks());
            break;
        case EB_REL_STEP:
            Serial.println("release step");
            break;
        case EB_REL_STEP_C:
            Serial.print("release step clicks ");
            Serial.println(eb.getClicks());
            break;
    }
}

void setup() {
    Serial.begin(115200);
    eb.attach(callback);
}

void loop() {
    eb.tick();
}
Все типы кнопок
#include <EncButton.h>

Button btn(4);
ButtonT<5> btnt;
VirtButton btnv;

void setup() {
    Serial.begin(115200);
}

void loop() {
    // Button
    btn.tick();
    if (btn.click()) Serial.println("btn click");

    // ButtonT
    btnt.tick();
    if (btnt.click()) Serial.println("btnt click");

    // VirtButton
    btnv.tick(!digitalRead(4));  // передать логическое значение
    if (btnv.click()) Serial.println("btnv click");
}
Все типы энкодеров
#include <EncButton.h>

Encoder enc(2, 3);
EncoderT<5, 6> enct;
VirtEncoder encv;

void setup() {
    Serial.begin(115200);
}

void loop() {
    // опрос одинаковый для всех, 3 способа:

    // 1
    // tick вернёт 1 или -1, значит это шаг
    if (enc.tick()) Serial.println(enc.counter);

    // 2
    // можно опросить через turn()
    enct.tick();
    if (enct.turn()) Serial.println(enct.dir());

    // 3
    // можно не использовать опросные функции, а получить направление напрямую
    int8_t v = encv.tick(digitalRead(2), digitalRead(3));
    if (v) Serial.println(v);  // выведет 1 или -1
}

Версии

Старые
  • 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. hold() и held() тоже могут принимать предварительные клики. Переделан и улучшен дебаунс
  • 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
  • v1.21 - EB_HALFSTEP_ENC теперь работает для обычного режима
  • v1.22 - улучшен EB_HALFSTEP_ENC для обычного режима
  • v1.23 - getDir() заменил на dir()
  • v2.0
    • Алгоритм EB_BETTER_ENC оптимизирован и установлен по умолчанию, дефайн EB_BETTER_ENC упразднён
    • Добавлен setEncType() для настройки типа энкодера из программы, дефайн EB_HALFSTEP_ENC упразднён
    • Добавлен setEncReverse() для смены направления энкодера из программы
    • Добавлен setStepTimeout() для установки периода импульсного удержания, дефайн EB_STEP упразднён
    • Мелкие улучшения и оптимизация
  • v3.0
    • Библиотека переписана с нуля, с предыдущими версиями несовместима!
      • Полностью другая инициализация объекта
      • Переименованы: hold()->holding(), held()->hold()
    • Оптимизация Flash памяти: библиотека весит меньше, в некоторых сценариях - на несколько килобайт
    • Оптимизация скорости выполнения кода, в том числе в прерывании
    • На несколько байт меньше оперативной памяти, несколько уровней оптимизации на выбор
    • Более простое, понятное и удобное использование
    • Более читаемый исходный код
    • Разбитие на классы для использования в разных сценариях
    • Новые функции, возможности и обработчики для кнопки и энкодера
    • Буферизация энкодера в прерывании
    • Нативная обработка двух одновременно нажимаемых кнопок как третьей кнопки
    • Поддержка 4-х типов энкодеров
    • Переписана документация
    • EncButton теперь заменяет GyverLibs/VirtualButton (архивирована)
  • v3.1
    • Расширена инициализация кнопки
    • Убраны holdEncButton() и toggleEncButton()
    • Добавлен turnH()
    • Оптимизированы прерывания энкодера, добавлена setEncISR()
    • Буферизация направления и быстрого поворота
    • Сильно оптимизирована скорость работы action() (общий обработчик)
    • Добавлено подключение внешней функции-обработчика событий
    • Добавлена обработка кнопки в прерывании - pressISR()
  • v3.2
    • Добавлены функции tickRaw() и clear() для всех классов. Позволяет проводить раздельную обработку (см. доку)
    • Улучшена обработка кнопки с использованием прерываний
  • v3.3
    • Добавлены функции получения времени удержания pressFor(), holdFor(), stepFor() (отключаемые)
    • Добавлен счётчик степов getSteps() (отключаемый)
  • v3.4
    • Доступ к счётчику кликов во время нажатого поворота
    • Добавлена функция detach()
  • v3.5
    • Добавлена зависимость GyverIO (ускорен опрос пинов)
    • Добавлена возможность задать свои функции аптайма и чтения пина
  • v3.5.2
    • Оптимизация
    • Упрощена замена кастомных функций
    • Исправлена ошибка компиляции при использовании библиотеки в нескольких .cpp файлах
  • v3.5.3
    • Добавлено количество кликов в опрос press/release/click/pressing
  • v3.5.5 - коллбэк на базе std::function для ESP
  • v3.5.8 - добавлен метод releaseHoldStep()
  • v3.5.11 - добавлен метод skipEvents() для игнорирования событий кнопки в сложных сценариях использования

Баги и обратная связь

При нахождении багов создавайте Issue, а лучше сразу пишите на почту [email protected]
Библиотека открыта для доработки и ваших Pull Request'ов!

При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:

  • Версия библиотеки
  • Какой используется МК
  • Версия SDK (для ESP)
  • Версия Arduino IDE
  • Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
  • Какой код загружался, какая работа от него ожидалась и как он работает в реальности
  • В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код

encbutton's People

Contributors

alexgyver avatar gyverlibs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

encbutton's Issues

Работа энкодера.

У меня энкодер как такой же как здесь. #10
Я прописал дефайны вот так.
#define EB_BETTER_ENC // улучшенный алгоритм опроса энкодера. Добавит 16 байт SRAM при подключении библиотеки #define EB_HALFSTEP_ENC // режим опроса полушагового энкодера (включи, если твой энкодер делает два тика за один)
Но энкодер всё равно работает без изменений 2 к 1. Что я делаю не так?

Почему два класса

В чём основное различие между EncButton и EncButton2?
Возможно я могу помочь свести в один файл.

Виртуальное нажатие кнопки

Привет, я бы хотел иметь возможность нажимать на кнопки обрабатываемые EncButton через Портал, чтобы выполнять одни и те же действия. Это было бы удобно, тк физически эти кнопки еще не подсоединены к МК. Я знаю, есть EncButton<EB_TICK, VIRT_BTN> enc; // виртуальная кнопка но такой подход не позволяет использовать долгое нажатие.

Внизу небольшой пример, того что хотелось бы получить. При нажатии кнопки на портале идёт вызов button_small.heldVirtual() , которая эмулирует нажатие button_small.held() и соответственно срабатывает условие if(button_small.held()) .

#include "EncButton.h"
#include <GyverPortal.h>
GyverPortal portal;

EncButton<INPUT, 1> button_small; 
EncButton<INPUT, 15> button_big;
bool valSwitch;
bool valSwitch2;
// билдер страницы
void build() {
  String s;
  BUILD_BEGIN(s);
  add.THEME(GP_DARK);

  add.AREA_LOG(5);

  add.BUTTON_MINI("btn1", "Открыть заслонку");
  add.BUTTON_MINI("btn2", "Закрыть заслонку");
  add.BREAK();

  add.LABEL("Включать вентилятор при открытии заслонки(долгое нажатие маленькой кнопки): ");
  add.SWITCH("sw1", valSwitch);
  add.BREAK();

  add.LABEL("Открывать заслонку при смыве(долгое нажатие большой кнопки): ");
  add.SWITCH("sw2", valSwitch2);
  add.BREAK();


  BUILD_END();
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin("", "");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  portal.attachBuild(build);
  portal.start();
  portal.log.start(30);
}

void loop() {
  portal.tick();

  if (portal.click("btn1")) button_small.clickVirtual(); //эмулирует нажатие кнопки button_small
  if (portal.click("btn2")) button_big.clickVirtual(); //эмулирует нажатие кнопки button_big
  if (portal.click("sw1")) button_small.heldVirtual(); //эмулирует долгое нажатие кнопки button_small
  if (portal.click("sw2")) button_big.heldVirtual(); //эмулирует долгое нажатие кнопки button_big
  
  //будут отрабатывать при нажатии на физические и виртуальные кнопки через Портал
  if (button_small.held())portal.log.println(Ok);
  if (button_big.held())portal.log.println(Ok);
  if (button_big.click())portal.log.println(Ok);
  if (button_small.click()) portal.log.println(Ok);
}

ArduinoOTA не работает при вызове метода enc.tick()

Проект на esp32. Функция loop () выглядит примерно следующим образом:

void loop () {
  ArduinoOTA.handle();
  enc.tick();
  getSensors();
}

В случае вызова метода enc.tick(), при работе стандартных процедур ArduinoOTA код в loop() продолжает выполняться, тем самым прерывая обновление.
Энкодер подключен без использования прерываний.

Много warning при компиляции

Подскажите - это нормально - такое количество ворнингов при компиляции?
1.8.16 софт, компилятор (5.4 или 7.3)

image

Прерывания и pcf8575

Прошу добавить пример для отлова даблкликов и холдов с использованием прерываний на esp32 с расширителем портов.
Пока не очень понятно, когда и сколько раз вызывать tick():
в лууп, в функции прерывания, в обоих случаях.

PS Приятно говорить с разработчиком на одном языке.

Unusual step counting by rotary encoder

Hey,
I am using KY-040 rotary encoder on ESP8266 microcontroller in my project. I am using the EncButton library.
The problem I am experiencing is incorrect step counting when moving the encoder knob. When I turn the knob one time, the step is not counted, only the second move of the encoder counts one step. Each one step is counted as 2 shifts of the encoder knob. Does anyone know how I can change this so that each one shift of the knob counts one encoder step?

Не работают прерывания, как следует на stm32

Добавил работу с кликами в шаблон примера и задержку в 2 секунды и все...код поплыл, тики считают только вращение энкодера.
Как быть с обработкой кнопки клика по энкодеру - ума не приложу.

// Пример с обработчиками в прерывании

#include <EncButton.h>
EncButton<EB_CALLBACK, PB4, PB3, PB10> enc;   // энкодер с кнопкой <A, B, KEY>

void setup() {
  Serial.begin(115200);
  enc.attach(TURN_HANDLER, myTurn);   // подключим поворот
  enc.attach(CLICK_HANDLER, myClick);   // подключим клик

  // прерывание обеих фаз энкодера на функцию isr
  attachInterrupt(PB4, isr, CHANGE);
  attachInterrupt(PB3, isr, CHANGE);
  attachInterrupt(PB10, isr, CHANGE);
}

void myTurn() {
  Serial.print("TURN_HANDLER: ");
  Serial.println(enc.counter);
  Serial.print("DIR_HANDLER: ");
  Serial.println(enc.dir()); // направление поворота
}

void myClick() {
  Serial.print("CLICK_HANDLER: ");  
  Serial.println(enc.click());
}

void isr() {
  enc.tickISR();  // тикер в прерывании
  // Не вызывает подключенные коллбэки внутри прерывания!!!
}

void loop() {
  enc.tick();   // дополнительный опрос таймаутов и коллбэков в loop
  // вызов подключенных функций будет здесь,
  // чтобы не грузить прерывание
  delay(2000);
}

А вообще библиотека EncButton позволяет опрашивать энкодер из структуры?
void setup() {}
Например, хочу, чтобы в секции Setup было меню с дисклеймером, и нужно согласиться с ним или не сможешь устройством пользоваться.
Ни в одном из примеров не нашел, чтобы тикер был в секции Setup.
Сам пробовал опрашивать энкодер в этой секции и ничего не работало.

Некорректная работа кнопки во время выполнения стороннего кода в loop()

Использовал пример callbackISR и tickISR. С пустым loop все работает нормально, но если в нем присутствует сторонний код (в данном случае delay) некоторые нажатия кнопки не регистрируются. Не могу разобраться, в чем смысл использования прерываний, если они все равно не помогают обновлять кнопку во время выполнения кода? Использую ESP32
`// Пример с обработчиками в прерывании

#include <EncButton.h>
EncButton<EB_CALLBACK, 12> enc; // энкодер с кнопкой <A, B, KEY>

void setup() {
Serial.begin(9600);
enc.attach(CLICK_HANDLER, myClick); // подключим поворот

// прерывание обеих фаз энкодера на функцию isr
attachInterrupt(12, isr, CHANGE);

}

void myClick() {
Serial.println("CLICK_HANDLER");
}

void isr() {
enc.tickISR(); // тикер в прерывании
// Не вызывает подключенные коллбэки внутри прерывания!!!
}

void loop() {
enc.tick(); // дополнительный опрос таймаутов и коллбэков в loop
// вызов подключенных функций будет здесь,
// чтобы не грузить прерывание
delay(5000);
}
`

Сломались клик и даблклик

На версии 1.8 в моём контроллере освещения дома с 40 выключателями работали клик, даблклик и холд.
Случайно обновил библиотеку до версии 1.11.1 перед очередным компилированием и все нажатия стали распознаваться как пресс и холдед.
Даунгрэйд на 1.8 решил проблему.
Используются прерывания и три расширителя портов, но не думаю. что дело в расширителях.

Прошу починить события.

Код примерно такой:

#ifdef button
/* для отслеживания кликов, даблкликов, лонгпрессов, v 1.8 https://github.com/GyverLibs/EncButton */
#include <EncButton.h> 


/* массив виртуальных кнопок */
`EncButton<EB_TICK, VIRT_BTN> * buttons = new EncButton<EB_TICK, VIRT_BTN> [RELAY_NUM]; `


unsigned long timing; // Переменная для хранения точки отсчета

// Функции прерываний
void   IRAM_ATTR keyChangedOnPCF1() 
{
  keyChanged1 = true;
  time_click1 = millis();
}

void  IRAM_ATTR  keyChangedOnPCF2() {
  // Interrupt called (No Serial no read no wire in this function, and DEBUG disabled on PCF library)
  keyChanged2 = true;
  time_click2 = millis();
}

void  IRAM_ATTR keyChangedOnPCF3() {
  // Interrupt called (No Serial no read no wire in this function, and DEBUG disabled on PCF library)
  keyChanged3 = true;
  time_click3 = millis();
}

void loop_buttons() {

  if (millis() - timing > 10) { // Проверяем кнопки каждые 10мс
   timing = millis();

    //если нет подключенного расширителя, то флаг состояния может сбрасываться при перезагрузке
    // чтобы исключить лишние тики неподключенных расширителей с ложными событиями, проверяем подключение

    //обработка выключателей
    if (((keyChanged1) || (millis() - time_click1 < 5000)) && (In1Con)) { //при изменении входов первого расширителя или в течение 5 секунд после
      uint16_t di = in1.readButton16(); // здесь состояния всех входов после изменения как 11100011110001, в прерывании эта функция вызывает перезагрузку
      //разбираем байты и тикаем виртуальные кнопки в цикле
      /*
         p0 = ((di & bit(0))>0)?HIGH:LOW;
         p1 = ((di & bit(1))>0)?HIGH:LOW;
         p2 = ((di & bit(2))>0)?HIGH:LOW;
         p3 = ((di & bit(3))>0)?HIGH:LOW;
         p4 = ((di & bit(4))>0)?HIGH:LOW;
         p5 = ((di & bit(5))>0)?HIGH:LOW;
         p6 = ((di & bit(6))>0)?HIGH:LOW;
         p7 = ((di & bit(7))>0)?HIGH:LOW;
      */
      for (uint8_t i = 0; i < 16; i++) { // тикаем первые 16 виртуальных кнопок, т.е. buttons[0-15]
        buttons[i].tick(((di & bit(i)) > 0) ? LOW : HIGH); // кнопки подтянуты к питанию и прижимаем к GND в нажатии, поэтому HIGH = отпущена

        // публикуем событие
        if (buttons[i].isHolded()) {
          Serial.print("holded ");
          Serial.println(i + 1);
          publishSwitchJsonState(i, "holded");
        }

        if (buttons[i].isPress()) {
          Serial.print("press ");
          Serial.println(i + 1);
          publishSwitchJsonState(i, "press");
          TargetRelay[i] = ! StatusRelay[i];
        }

        if (buttons[i].hasClicks(1)) {
          Serial.print("click ");
          Serial.println(i + 1);
          publishSwitchJsonState(i, "click");
        }
        if (buttons[i].hasClicks(2)) {
          Serial.print("2 click ");
          Serial.println(i + 1);
          publishSwitchJsonState(i, "double");
        }

        //    if (buttons[i].hasClicks()) Serial.println(buttons[i].clicks);
        //     if (buttons[i].isHold()) Serial.println("hold");

      } //for

      keyChanged1 = false; //сбрасываем флаг изменённых входов расширителя
    } //if


    if (((keyChanged2) || (millis() - time_click2 < 5000))  && (In2Con)) { //при изменении входов второго расширителя
      uint16_t di = in2.readButton16(); // здесь состояния всех входов после изменения как 11100011110001
      for (uint8_t i = 0; i < 16; i++) { // тикаем вторые 16 виртуальных кнопок, т.е. buttons[16-31]
        buttons[i + 16].tick(((di & bit(i)) > 0) ? LOW : HIGH);

        // публикуем событие
        if (buttons[i + 16].isHolded()) {
          Serial.print("holded ");
          Serial.println(i + 17);
          publishSwitchJsonState(i + 16, "holded");
        }

        if (buttons[i + 16].isPress()) {
          Serial.print("press ");
          Serial.println(i + 17);
          publishSwitchJsonState(i + 16, "press");
          TargetRelay[i + 16] = ! StatusRelay[i + 16];
        }

        if (buttons[i + 16].hasClicks(1)) {
          Serial.print("click ");
          Serial.println(i + 17);
          publishSwitchJsonState(i + 16, "click");
        }
        if (buttons[i + 16].hasClicks(2)) {
          Serial.print("2 click ");
          Serial.println(i + 17);
          publishSwitchJsonState(i + 16, "double");
        }

      } //for

      keyChanged2 = false; //сбрасываем флаг изменённых входов расширителя

    } //if

    if (((keyChanged3) || (millis() - time_click3 < 5000))  && (In3Con)) { //при изменении входов третьего расширителя
      uint16_t di = in3.readButton16(); // здесь состояния всех входов после изменения как 11100011110001
      for (uint8_t i = 0; i < 8; i++) { // тикаем последние 8 виртуальных кнопок, т.е. buttons[32-39]
        buttons[i + 32].tick(((di & bit(i)) > 0) ? LOW : HIGH);

        // публикуем событие
        if (buttons[i + 32].isHolded()) {
          Serial.print("holded ");
          Serial.println(i + 33);
          publishSwitchJsonState(i + 32, "holded");
        }

        if (buttons[i + 32].isPress()) {
          Serial.print("press ");
          Serial.println(i + 33);
          publishSwitchJsonState(i + 32, "press");
          TargetRelay[i + 32] = ! StatusRelay[i + 32];
        }

        if (buttons[i + 32].hasClicks(1)) {
          Serial.print("click ");
          Serial.println(i + 33);
          publishSwitchJsonState(i + 32, "click");
        }
        if (buttons[i + 32].hasClicks(2)) {
          Serial.print("2 click ");
          Serial.println(i + 33);
          publishSwitchJsonState(i + 32, "double");
        }
      } //for
      keyChanged3 = false; //сбрасываем флаг изменённых входов расширителя
    } //if
  }
} //loop
#endif

Не помещается в ATtiny13

Версия библиотеки: 2.0
Какой используется МК: ATtiny13A + MicroCore

Библиотека настроенная для полноценного энкодера с кнопкой фото не помещается во флеш память ATtiny13

#include <EncButton.h>
#include <stdbool.h>

#define ENCODER_KEY_PIN 2
#define ENCODER_ROT_RIGHT_PIN 3
#define ENCODER_ROT_LEFT_PIN 4
#define PWM_PIN 0

EncButton<EB_TICK, ENCODER_ROT_RIGHT_PIN, ENCODER_ROT_LEFT_PIN, ENCODER_KEY_PIN> enc;  

bool onOff = TRUE;

void setup() {
}

void loop() {
  if (enc.tick()) {
    if (enc.click())
      onOff = !onOff;
  }
}

Этот код занимает 1142 байт(111%) флеша.
Это баг библиотеки, выбранного ядра, или она в принципе не поддерживает камни меньше ATtiny85?

32767 on arduino nano

Используется arduino nano и encoder e6b2-cwz1x.
Во время использования библиотеки с инкрементарным энкодером значения растут до 32767. Далее выводится
-32768 и счетчик уменьшается, выводя отрицательные значения. Хотя направления энкодера не изменяется. Далее, после достижения -32768, счетчик снова становится положительным, однако значения продолжают уменьшаться.
С чем это может быть связано и как это исправить ?

Ke-040 и 74hc165

Добавьте пожалуйста пример, как подключать энкодеры и кнопки через сдвиговый регистр 74hc165. Я вроде догадываюсь, что это через VirtEncButton, но как?

Умеет-ли isPress() обрабатывать быстрые клики?

Здравствуйте!
Спасибо за библиотеку и канал!
Вопрос:

Можно ли при помощи isPress() обрабатывать клики, между которыми очень маленький промежуток?

Я когда кликаю пример 2-3 раза в сек. то все нормально, но если кликать быстрее, то некоторые клики пропускаются. Как это исправить?

Передать аргументы в событие.

Здравствуйте.
Во первых, спасибо за работу)

Теперь к делу. При реализации энкодера в классе, приходится делать метод события статическим, что лишает достпа к локальным переменным класса. Если будет такая возможность, то добавьте передачу аргументов в событие.

Сам я не хотел бы пул реквестить в библиотеку, без нормального понимания внутренней структуры и достаточной компетенции в плюсах.

Не понятен пример использования массива кнопок.

// объявляем массив кнопок
#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);
}

}
}

Обьявили 5 кнопок а обьекта создали 2 всего. Что это за массив кнопок он аналоговый или это матричная клавиатура?
Нельзя ли пояснить? могу нарисовать схему помочь если нужно.
Скорее всего такая клавиатура?
keypad-matrix-scheme-1024x504

Не работает на Arduino Nano 33 BLE

При использовании Arduino Nano 33 BLE не распознаются повороты энкодера, только взаимодействие с кнопкой.
Энкодер EC11 на круглой плате.
Пробовал разные режимы INPUT и INPUT_PULLUP, не помогло.

Увеличение время срабатывания между тиками

Библиотеки - зло.
Когда используешь отдельно и в чистом коде все работает как часики, но стоит проекту обрасти другими библиотеками или даже просто кодом и начинается чехарда.
В общем имею код, который опрашивает два датчика AHT10(с родной библиотекой AHTxx) и DS18B20(чистый код без библиотеки), также данные о температурах выводятся на дисплей нокия 5110 с помощью библиотеки adafruit.
Помимо этого использую микросхему PCF8575 для увеличения портов, работающую по шине I2C как и AHT10.
В общем использую библиотеку EncButton2.h и когда код "в сборе" то тики влево/вправо не отрабатывают при нормальном вращении, если вращать энкодер туда/сюда медленнее чем обычно, как бы от одного щелчка к другому, или например вращать быстро вправо несколько тиков, а потом один влево то библиотека определит поворот вправо, и наоборот.
Пробовал играться настройками(но чувствую они не для этого случая)
// ========= НАСТРОЙКИ (можно передефайнить из скетча) ==========
#define _EB_FAST 30 // таймаут быстрого поворота
#define _EB_DEB 50 // дебаунс кнопки
#define _EB_CLICK 400 // таймаут накликивания
Увеличивал/уменьшал таймайт быстрого поворота - результата ноль.
Когда использую чистый код для энкодера без библы гайвера, то срабатывание всегда.
Подскажите какие параметры "подкрутить" и вообще как понять, в чем проблема.
P.S. Железка, которую проектирую, собрана на печатной плате.
В ней я, дабы выиграть количество ножек, спецом использовал RT и TX ноги ESP12 под дисплей нокия 5110 и теперь не имею возможности использовать сериал монитор для отладки.
Вывожу конечно команды отработки на дисплей, что в принципе одно и тоже.
Но код с библиотекой гайвера не отрабатывает, хотя в том же проекте теплицы он шурует нормально.
У себя пробовал отключать опросы датчиков и разумеется сразу энкодер работал нормально, но когда не используешь библиотеку, на чистом коде все работает четко, но библиотека гайвера для большого проекта удобнее.
Мне как раз нужно вставлять обработчик в условия и менять параметры от этого.
Используя чистый код без библиотеки получается кода становиться много и его отслеживать сложно.

Ошибка в схеме энкодера

https://github.com/GyverLibs/EncButton/blob/main/doc/enc.png
Здесь в схеме допущена ошибка . Сопротивления R12 и R13 в енкодере находятся в другом месте. Между конденсатором и выходом энкодера как А так и В А пины сразу выходят с кондесаторов.
https://drive.google.com/file/d/1GKEJEZuG_lee2cvp-ihmwgnxtwhLmuWn/view?usp=sharing

Работа EncButton на матричной клавиатуре

Добрый день! Я заранее извиняюсь, если вопрос глупый, но я перед этим гуглил и искал и правда не нашел как внедрить двойной клик в матричную клавиатуру. В виртуальном режиме указано

Виртуальный режим позволяет получить все возможности библиотеки EncButton в ситуациях, когда кнопка не подключена напрямую к микроконтроллеру, либо для её опроса используется другая библиотека:
Аналоговая клавиатура (например через библиотеку [AnalogKey]
Матричная клавиатура (например через библиотеку [SimpleKeypad])"

Вопрос, как с помощью этой библиотеки сделать одну или несколько кнопок на матричной клавиатуре c двойным нажатием ?

Файл FastIO.h не защищен от повторного включения?

Здравствуйте!
Пытаюсь включить библиотеку и получаю такую ошибку:

Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/uno.html
PLATFORM: Atmel AVR (3.1.0) > Arduino Uno
HARDWARE: ATMEGA328P 16MHz, 2KB RAM, 31.50KB Flash
DEBUG: Current (avr-stub) On-board (avr-stub, simavr)
PACKAGES:
 - framework-arduino-avr 5.1.0
 - toolchain-atmelavr 1.50400.190710 (5.4.0)
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 9 compatible libraries
Scanning dependencies...
Dependency Graph
|-- <EncButton> 1.6.1
|-- <TVout> 1.0.1
Building in release mode
Compiling .pio\build\uno\src\Gamepad.cpp.o
Compiling .pio\build\uno\src\main.cpp.o
Linking .pio\build\uno\firmware.elf
.pio\build\uno\src\main.cpp.o: In function `fastRead(unsigned char)':
main.cpp:(.text._Z8fastReadh+0x0): multiple definition of `fastRead(unsigned char)'
.pio\build\uno\src\Gamepad.cpp.o:Gamepad.cpp:(.text._Z8fastReadh+0x0): first defined here
.pio\build\uno\src\main.cpp.o: In function `fastWrite(unsigned char, bool)':
main.cpp:(.text._Z9fastWritehb+0x0): multiple definition of `fastWrite(unsigned char, bool)'
.pio\build\uno\src\Gamepad.cpp.o:Gamepad.cpp:(.text._Z9fastWritehb+0x0): first defined here
.pio\build\uno\src\main.cpp.o: In function `fastShiftIn(unsigned char, unsigned char, unsigned char)':
main.cpp:(.text._Z11fastShiftInhhh+0x0): multiple definition of `fastShiftIn(unsigned char, unsigned char, unsigned char)'
.pio\build\uno\src\Gamepad.cpp.o:Gamepad.cpp:(.text._Z11fastShiftInhhh+0x0): first defined here
.pio\build\uno\src\main.cpp.o: In function `fastShiftOut(unsigned char, unsigned char, unsigned char, unsigned char)':
main.cpp:(.text._Z12fastShiftOuthhhh+0x0): multiple definition of `fastShiftOut(unsigned char, unsigned char, unsigned char, unsigned char)'
.pio\build\uno\src\Gamepad.cpp.o:Gamepad.cpp:(.text._Z12fastShiftOuthhhh+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\uno\firmware.elf] Error 1

Возникает она на таких трех файлах:

main.cpp

#include <Arduino.h>
#include "Gamepad.h"

Gamepad testObject;
void setup() {
    int a = 10;
    a = testObject.multTo5(a);
    testObject.multTo7(&a);
}

void loop() {}

Gamepad.cpp

#include "Gamepad.h"

int Gamepad::multTo5(int value) {
    return value * 5;
}

void Gamepad::multTo7(int* value) {
    *value = *value * 7;
}

Gamepad.h

#pragma once
#include <Arduino.h>
#include <EncButton.h>

class Gamepad {
    public:
        int multTo5(int value);
        void multTo7(int* value);
    private:
        EncButton<EB_TICK, 2> fireButton;
        EncButton<EB_TICK, 3> bombButton;
};

Не работает "Полушаговый" энкодер

Стоит какойто полушаговый энкодер, что бы произошел тик, надо два щелчка прокрутить. Это при стандартных настройках библиотеки.

Запустил голый пример из библиотеки под названием optimisation, и вставил #define EB_HALFSTEP_ENC, а после полез смотреть в serial. Там при прокрутке энкодер чудит. При прокрутке сначала вправа, показывает turn right, при повторной прокрутке вправо "turn left turn right". аналогично при прокрутке влево. Кнопка SW вообще реагировать перестала.

fast() всегда false

espressif8266 d1_mini platformio

fast() всегда возвращает false, если проверять в колбеке при экшене EB_TURN

EncButton eb(E_CLK, E_DT, E_SW);

void setup() {
  eb.attach(eb_cb);
}

void loop(void)  {
  eb.tick();
}

void eb_cb() {
  switch (eb.action()) {
    case EB_TURN:
          Serial.print("fast: ");
          Serial.println(eb.fast()); // !!! ALWAYS 0
        }
      }

ps: код условный, для отражения сути

3.5.1 Won't compile

Library appears to be missing.

Arduino/libraries/EncButton/src/core/utils.h:5:10: fatal error: GyverIO.h

Correct syntax for callback mode in version 2

What is the correct syntax to enable the callbacks in version2?

#define  EB_FAST  30    
#define  EB_BETTER_ENC  
#define  EB_HALFSTEP_ENC

#define  EB_DEB  50      
#define  EB_STEP  100    
#define  EB_CLICK  400   
#define  EB_HOLD  1000   

#include <EncButton2.h>
EncButton2<EB_CALLBACK> enc(11, 9, 10);

void myRight() {
  Serial.println("RIGHT_HANDLER");
}
void myLeft() {
  Serial.println("LEFT_HANDLER");
}

void setup() 
{
  Serial.begin(115200);
  while(!Serial);

  enc.attach(RIGHT_HANDLER, myRight);
  enc.attach(LEFT_HANDLER, myLeft);
}
 
void loop() 
{
  enc.tick();
}

This leads to a "crash" of the firmware, the USB port disappear and nothing works.
Instead, using version 1 of the library:

#include <EncButton.h>
EncButton<EB_CALLBACK, 11, 9, 10> enc;

Works fine.
I'm using an Arduino Leonardo-based board.

Добавить функцию attachHolds(Кол-во удержаний, функция)

Было бы неплохо добавить функцию на подобии attachHolds(Кол-во удержаний, функция), которая будет вызывать функцию которую её передадут (как attach и attachClicks) когда кнопку удержали определённое количество раз. Также можно добавить счётчик удерживаний на подобии счётчика кликов.

Работоспособность кода на ESP8266/ESP12F

/*
   Пример работы с энкодером с прерыванием. Максимальная чёткость работы
   в любом быдлокоде!
*/

#define CLK 2
#define DT 3
#define SW 4

#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);

void setup() {
  Serial.begin(9600);
  attachInterrupt(0, isrCLK, CHANGE);    // прерывание на 2 пине! CLK у энка
  attachInterrupt(1, isrDT, CHANGE);    // прерывание на 3 пине! DT у энка
}

void isrCLK() {
  enc1.tick();  // отработка в прерывании
}
void isrDT() {
  enc1.tick();  // отработка в прерывании
}

void loop() {
  enc1.tick();
  if (enc1.isRight()) Serial.println("Right");         // если был поворот
  if (enc1.isLeft()) Serial.println("Left");

  if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
  if (enc1.isLeftH()) Serial.println("Left holded");
}

Данный код перезагружает модуль ЕСП12 и выдает циклически ошибку

19:57:06.723 -> >>>stack>>>
19:57:06.723 -> 
19:57:06.723 -> ctx: cont
19:57:06.723 -> sp: 3fffff00 end: 3fffffc0 offset: 0000
19:57:06.769 -> 3fffff00:  feefeffe feefeffe feefeffe 3ffef314  
19:57:06.823 -> 3fffff10:  000000fe 00000000 00000000 00000000  
19:57:06.870 -> 3fffff20:  00000000 00000000 00000000 00ff0000  
19:57:06.923 -> 3fffff30:  5ffffe00 5ffffe00 00002580 3ffee588  
19:57:06.970 -> 3fffff40:  00000000 00000003 00000001 4020219e  
19:57:07.024 -> 3fffff50:  40100439 00000001 3ffee520 402021b0  
19:57:07.070 -> 3fffff60:  4020272c 00000003 00000001 402026b1  
19:57:07.124 -> 3fffff70:  401000f8 00000000 feefeffe feefeffe  
19:57:07.171 -> 3fffff80:  3fffdad0 00000000 3ffee574 40202750  
19:57:07.224 -> 3fffff90:  3fffdad0 00000000 3ffee574 40201072  
19:57:07.324 -> 3fffffa0:  feefeffe feefeffe feefeffe 40201d2c  
19:57:07.324 -> 3fffffb0:  feefeffe feefeffe 3ffe85d8 40100cf5  
19:57:07.425 -> <<<stack<<<
19:57:07.425 -> 
19:57:07.425 -> --------------- CUT HERE FOR EXCEPTION DECODER ---------------
19:57:07.472 -> H!⸮⸮�)⸮@H⸮⸮tH�⸮ISR not in IRAM!
19:57:07.572 -> 
19:57:07.572 -> User exception (panic/abort/assert)
19:57:07.618 -> --------------- CUT HERE FOR EXCEPTION DECODER ---------------
19:57:07.664 -> 
19:57:07.664 -> Abort called

Автор библиотеки пишет, что нужно в имя функции подставлять IRAM_ATTR void имя(){}
https://alexgyver.ru/lessons/interrupts/
Но в таком случае функция isrCLK не декларирована вообще.
В статье всего две строки по поводу wemos mini(esp8266)

На esp8266 прерывание можно настроить стандартными средствами на любом пине.

Приведите хотя бы один пример использования прерываний для этих "железок"!

Использование RX/TX (GPIO3/GPIO3) в качестве А и В в pinmode, A, B, KEY

Подскажите, использую ESP8266 в схеме управления котлом.
Почти все стандартные выхода D0...D7 на вход и выход заняты.
Остались GPIO9 и GPIO10, а также RX/TX (GPIO3/GPIO3).
Эта библиотека не может опрашивать данные ноги на вход?
Пробовал и через:

pinMode(1, FUNCTION_3);
pinMode(1, INPUT);

Часть кода такая:

#include <SPI.h>
#include <EncButton2.h>
EncButton2<EB_ENCBTN> enc(INPUT, 1, 3, D7); // энкодер с кнопкой (A, B, KEY)
void setup() {
pinMode(1, INPUT);
pinMode(3, INPUT);
Serial.begin(115200);
}

Ничего не происходит...
Не хотелось бы использовать мультиплексоры - все таки надо, чтобы устройство не потребляло много электричества и было отчасти "автономным".

Добавить поддержку энкодера

У меня есть энкодер, но прокрутка у него срабатывает только каждый второй раз. Насколько я понял, EncButton перестал поддерживать несколько типов энкодеров.

large_image-6fa5b36c-b822-4468-bed4-bdf3d3b4daad

В локальной библиотечке у себя в методе:
void poolEnc(uint8_t state)

заменяю
if (state == 0x3 && _ecount != 0) {

на
if ((state == 0x3 || state == 0x0) && _ecount != 0) {

то всё работает как требуется.
Можете ещё один дефайн сделать и поправить код, чтобы поддержку этого энкодера включить?

-- edited:
Прикрепил корректную картинку

isActive

хотелось бы иметь метод isActive который возвращал bool.

isActive() возвращает true если с энкодером/кнопкой происходит какое либо действие, в противном случае - false. Такой метод помог бы вынести всю логику связанную с обработкой действий с энкодером/кнопкой в отдельную функцию

void loop() {
    enc.tick();
    if (enc.isActive()) {
         doSomething();
    }
    .....
}

void doSomething() {
    // Здесь вся логика связанная с обработкой действий с кнопкой
    .....
}

Добавить поддержку энкодера

можно ли добавить поддержку такого энкодера с такой логикой
даташитэн

Пример кода с использованием другой библиотеки

//просто пример
#include <Encoder.h> // https://github.com/P­aulStoffregen/Encode­r/releases/tag/1.4.3

//пины энкодера
#define PIN_A D5
#define PIN_B D6

//объект энкодера
Encoder encoder(PIN_A, PIN_B);

//предыдущее значение энкодера
long oldPosition = -999;

void setup() {
//инициализация COM порта
Serial.begin(115200);
}

void loop() {
//считываем значе­ние от энкодера
long newPosition = encoder.read();

//если новое знач­ение не равно предыд­ущему,
//то выводим его на COM порт
if (newPosition != oldPo­sition) {
oldPosition = newPosition;
Serial.print("Encoder value: ");
Serial.println(newPosition);
}
delay(100); //задержка для стаб­илизации
}

Как задать #define EB_HOLD для разных кнопок разные значения?

Добрый день!

Параметр #define EB_HOLD влияет на все кнопки. А как задать этот параметр для кнопок отдельно?

Например для вращения пушки нужно значение нуль, а вот для кнопки выстрела очередями нужна небольшая задержка (чтобы была возможность стрелять отдельными пулями и очередями).

the 'eb.clear()' does not really set the encoder counter to 0

Thanks for your great work and it's a useful and elegant lib which saved me quite a lot of time.
With the EncButton, I found there is no way to clear the counter. eg, I turn to a position and need to tell the program the current position is '0', so I can double click the button to trigger that, however the 'eb.clear()' does not really set the encoder counter to 0. could you help?

Совместимость с ESP8266 (Wemos D1 Mini)

Здравствуйте! Суть проблемы: библиотека плохо работает на ESP8266, контроллер перезагружается при повороте/нажатии на кнопку энкодера. С Arduino Nano этот же энкодер работает стабильно. Может проблема в самой плате?

Конфликтует с библиотекой ArduinoJson

EncButton - версия 2.0
ArduinoJson - версия 6.21.2

Ошибка компиляции:

/Users/.../Arduino/libraries/ArduinoJson/src/ArduinoJson/Configuration.hpp:215:4: error: #error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr
 #  error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr
    ^~~~~

Migration from V2 to V3: tick() returns unreliable status

Unsuccessfully tried to migrate from V2 to v3 with troubles. Platform: STM32F401
I use the following to handle events/actions:

if( inputEnc.tick() ){
        // event handler
        bool fastRotate = inputEnc.fast();
  	if( inputEnc.right() ) {
           // do something for encoder turning right
        }
        ....
  	if( inputEnc.left() ) {
           // do something for encoder turning left
        }
 }

So, it seems that tick() is skipping events (returns true for some events, does not return true for some events even though an event is happening); as a result, the handler does not process events, behaves as event did not happen... It skips events unregularly, so, I did not notice any pattern. Should be mentioned, I also call tickISR() in the interrupt for any pin state CHANGE.
Please, check up if tick() is always returning true for any event.
Thank you.

Turn events fire only after two "ticks"

It's hard for me to understand how the library works and to search here because you don't write in English.
Anyway, using the examples provided I see it prints out a turn event only after two "ticks" of my encoder in either direction.

I'm using a common ALPS EC12D1564402.

Is there any setting to change in order to see every "tick"?

Подключение кнопки

Подключил кнопку следующим образом:
image
при нажатии на кнопку происходит просадка напряжения питания, как это можно пофиксить?

Проект на wokwi с кнопками EncButton

Использую библиотеку кнопок, для некоторых проектов, которые есть в "железе".
Над проектом работают несколько человек, и у некоторых нет доступа к настоящим микроконтроллерам, дисплеями и кнопкам.
Как писать код спрашивается?
На помощь приходит онлайн-симулятор wokwi.
Но к сожалению, библиотека EncButton там не является "по умолчанию".
Вернее "их" EncButton это вообще другая библиотека.
Решение - написать класс отдельным файлом, как это было предложено здесь.
Я попробовал реализовать такой же файл, но у меня две кнопки, и я их инициализирую так

  pinMode(34, INPUT_PULLUP);
  pinMode(35, INPUT_PULLUP); 
void loop()
{
  btn_left.tick(!digitalRead(34));
  btn_right.tick(!digitalRead(35));
}

Ввиду того, что ограничения wokwi видимо срабатывают, когда слишком много файлов добавляешь - не дает сохранить.
Но удалось скачать архив, из 16-ти файлов.
Flightinstruments_TFT_eSPI.zip
Можно их вручную создать и тогда проект скомпилируется.
Скриншот 27-08-2023 21 03 46
Вы сможете вручную установить проект на wokwi и подсказать, что не так с кнопками?
Буду вам безмерно благодарен!
P.S. bool read(); не реализована в файле btn.h и поэтому не смог сделать опрос кнопок в секции setup(){}

Описка в README

В предложении: "Например дял обработки кнопки при помощи Button нужно открыть ниже описание Button и VirtButton"

Todo?

  • не сбрасывать таймер в pressisr если уже busy. Или уже deb?

Функция isStep()

Здравствуйте. Добавьте пожалуйста функцию boolean isStep(byte clicks) как в GyverButton. Удержание после несколько кликов.

Guru Meditation Error на ESP32

Решил использовать библиотеку в проекте бортового компьютера на esp32. Если использовать тикер в loop, то все нормально, если пользоваться прерываниями, то периодически при нажатии кнопки все зависает с ошибкой в консоли: Guru Meditation Error: Core 1 panic'ed (Cache disabled but cached memory region accessed)
Core 1 register dump:
PC : 0x400d3308 PS : 0x00060034 A0 : 0x40083958 A1 : 0x3ffbec10
A2 : 0x00000011 A3 : 0x3ffc02cc A4 : 0x00000000 A5 : 0x00020000
A6 : 0x3ffae270 A7 : 0x00060323 A8 : 0x80080f08 A9 : 0x00000000
A10 : 0x00000000 A11 : 0x3ffb932c A12 : 0x80082cde A13 : 0x3ffb1a50
A14 : 0x00000001 A15 : 0x3ffb8058 SAR : 0x0000001b EXCCAUSE: 0x00000007
EXCVADDR: 0x00000000 LBEG : 0x4000c2e0 LEND : 0x4000c2f6 LCOUNT : 0x00000000
Core 1 was running in ISR context:
EPC1 : 0x4008a9c2 EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x400d3308

ELF file SHA256: 0000000000000000

Backtrace: 0x400d3308:0x3ffbec10 0x40083955:0x3ffbec30 0x4008a9bf:0x3ffb1a80 0x4008aa86:0x3ffb1ab0 0x4008b2d5:0x3ffb1ad0 0x4008

Проблема с энкодерами

Добрый день!
Подскажите, в чем может быть проблема, кнопки не считываются вообще
И на экране ничего не меняется при прокрутке энкодера, не пойму в чем ошибка, подскажите пожалуйста

#include <Arduino.h>
#include <GyverOLED.h>
#include <EncButton2.h>

GyverOLED<SSH1106_128x64, OLED_NO_BUFFER> oled(0x3C);

const int NUM_SLIDERS = 4;
int analogSliderValues[NUM_SLIDERS] = {0};

EncButton2<EB_BTN> button_pin[NUM_SLIDERS];
EncButton2<EB_ENC> encoder[NUM_SLIDERS];

int knobState[NUM_SLIDERS];
int Mute[NUM_SLIDERS];
int knobVal[NUM_SLIDERS];
int mappedvalueslider[NUM_SLIDERS];
long unsigned int remappedvalue[NUM_SLIDERS];

const uint8_t sound_8x8[] PROGMEM = {0x3c, 0x7e, 0xff, 0x00, 0x18, 0x42, 0x3c, 0x00};
const uint8_t browser_8x8[] PROGMEM = {0xff, 0x85, 0x89, 0x91, 0x91, 0x89, 0x85, 0xff};
const uint8_t other_8x8[] PROGMEM = {0x3f, 0xa1, 0xa1, 0xe1, 0xe1, 0xa1, 0xa1, 0x3f};
const uint8_t microphone_8x8[] PROGMEM = {0x1c, 0x30, 0x20, 0xef, 0xef, 0x20, 0x30, 0x1c};

void setup() {
  Serial.begin(115200);
  Wire.begin();				
  Wire.setClock(100000);

  oled.init();              // инициализация
  oled.clear();
  oled.home();
  oled.update();

  //--Кнопки (SW)--//
    button_pin[0].setPins(INPUT_PULLUP, 15); //A1
    button_pin[1].setPins(INPUT_PULLUP, 16); //A2
    button_pin[2].setPins(INPUT_PULLUP, 17); //A3
    button_pin[3].setPins(INPUT_PULLUP, 20); //A6

    //--Энкодер и Кнопки (DT & CLK)--//
    encoder[0].setPins(INPUT_PULLUP, 4, 5);
    encoder[1].setPins(INPUT_PULLUP, 6, 7);
    encoder[2].setPins(INPUT_PULLUP, 8, 9);
    encoder[3].setPins(INPUT_PULLUP, 10, 11);

    for (int i = 0; i < NUM_SLIDERS; i++) button_pin[i].tick();
    for (int i = 0; i < NUM_SLIDERS; i++){ 
      button_pin[i].click();
      analogSliderValues[i] = 1023;
      knobState[i] = 0;
      Mute[i] = 0;
      encoder[i].counter = 51;
    }
  display_draw();
}

void checkButtons() {
  for (int i = 0; i < NUM_SLIDERS; i++) {
    if (digitalRead(button_pin[i].click()) == 0 && knobState[i] == 0) {
      Mute[i] = !Mute[i];
      knobState[i] = 1;
      delay(25);
    }
    if (digitalRead(button_pin[i].click()) == 1) {
      knobState[i] = 0;
    }
  }
}

void checkEncoders() {
  for (int i = 0; i < NUM_SLIDERS; i++) {
    knobVal[i] = encoder[i].counter;
    if (digitalRead(encoder[i].left()) || digitalRead(encoder[i].right()) && knobVal[i] > 0 && knobVal[i] < 102 && Mute[i] == 0) {
      analogSliderValues[i] = map(knobVal[i], 0, 102, 0, 1023);
      } else if (Mute[i] == 0 && (knobVal[i] >= 102)) {
        analogSliderValues[i] = 1023;
        encoder[i].counter = 102;
      } else if (Mute[i] == 1) {
        analogSliderValues[i] = 0;
      } else {
        analogSliderValues[i] = 0;
        encoder[i].counter = 0; 
    }
  }
}

void mappingvalues(){
  for(int i = 0; i < 4; i++){
      mappedvalueslider[i] = map(analogSliderValues[i], 0, 1023, 0, 97);
      remappedvalue[i] = map(analogSliderValues[i], 0, 1023, 0, 100);
  }
}

void display_draw(){
  oled.clear();    // Очистка буфера
  oled.home();
  oled.setScale(1);

  byte textPos1 = 2;
  byte textPos2 = 19;
  byte textPos3 = 36;
  byte textPos4 = 53;

  //рисует 4 рамоки
  /*for(int i = 2; i < 54; i= i + 17){
    oled.rect(20, i, 97, i + 6, OLED_STROKE);
  }*/

  //рисует знаки %) рядом с 4 рамками
  for(int i = 2; i < 54; i = i + 17){
    oled.setCursorXY(120, i);
    oled.print("%");
  }

  //заполняет каждый из 4 индикаторов выполнения их значениями
  for(int i = 0; i < 4; i++) {
    oled.rect(20, 2, 97, mappedvalueslider[i] + 8, OLED_FILL);
    oled.rect(20, 19, 97, mappedvalueslider[i] + 25, OLED_FILL);
    oled.rect(20, 36, 97, mappedvalueslider[i] + 42, OLED_FILL);
    oled.rect(20, 53, 97, mappedvalueslider[i] + 59, OLED_FILL);  
  }

  //Выведите процентное значение каждого ползунка
  for(int i = 0; i < 4; i++) {
    if(remappedvalue[i] < 10){
      oled.setCursorXY(113, 2 + (17 * i));
    } else if(remappedvalue[i] > 9 && remappedvalue[i] < 100) {
      oled.setCursorXY(107, 2 + (17 * i));
    } else if(remappedvalue[i] == 100) {
      oled.setCursorXY(101, 2 + (17 * i));
    }
    oled.print(remappedvalue[i]);
  }

  oled.drawBitmap(6, textPos1, sound_8x8, 8, 8);
  oled.drawBitmap(6, textPos2, browser_8x8, 8, 8);
  oled.drawBitmap(6, textPos3, other_8x8, 8, 8);
  oled.drawBitmap(6, textPos4, microphone_8x8, 8, 8);

  oled.update();
}

void sendSliderValues() {
  String builtString = String("");
  for (int i = 0; i < NUM_SLIDERS; i++) {
    builtString += String((int)analogSliderValues[i]);
    if (i < NUM_SLIDERS - 1) {
      builtString += String("|");
    }
  }
  Serial.println(builtString);
}

void loop(){
  mappingvalues();
  for (int i = 0; i < NUM_SLIDERS; i++) encoder[i].tick();
  for (int i = 0; i < NUM_SLIDERS; i++) button_pin[i].tick();
  for (int i = 0; i < NUM_SLIDERS; i++) {
    knobVal[i] = encoder[i].counter;
  }
  checkButtons();
  checkEncoders();
  sendSliderValues();
}

Опрос кнопок в момент перезагрузки устройства

Возможно сделать опрос состояния кнопок в цикле setup(){}?
Что то вроде такого

void setup() {
	btn.setButtonLevel(HIGH);
    Serial.begin(115200);
    btn.tick();
    if (btn.click()) { // или hold()
        Serial.println("Правая кнопка удержана при перезагрузке");
    }
}

У меня этот код не заработал в setup.
Код индусов свободно опрашивает кнопку, если ее удерживать во время перезапуска МК, путем нажатия RESET на отладочной плате.

  if (Btn.read())
    Serial.begin(115200);
    Serial.println("Правая кнопка удержана при перезагрузке");
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.