Вопросы по библиотеке EncButton

alex_kart

✩✩✩✩✩✩✩
4 Ноя 2021
1
0
Всем привет. Хочу использовать библиотеку EncButton для работы с несколькими кнопками. Каким образом можно объявить массив кнопок EncButton? EncButton ведь не класс а template, не получится просто объявить массив объектов. Почему кстати передача номера пина и режим сделан через шаблоны, можно ведь было просто сделать их параметрами конструктора..
 

Lumenjer

★★★✩✩✩✩
10 Дек 2020
220
112
Почему кстати передача номера пина и режим сделан через шаблоны
Как бы я хотел этот вопрос Гайверу задать, правда, ответ на него я знаю)
По гениальной задумке эту либу можно использовать как кнопку, как энкодер(просто крутилску) и как энкодер с кнопкой.
В зависимости от нужного шаблона - компилятор отрезает ненужный код. И это как бы минимизация и оптимизация кода.

Каким образом можно объявить массив кнопок EncButton?
Самый обычный вариант - отрезать ненужное и оставить нужное в библиотеке, добавив конструктор и переменные, где будут храниться пины
Второй вариант (гипотетически рабочий), объявить 10 кнопок через шаблоны и упаковать их в массив указателей, что-то вроде типа этого
Пример:
EncButton<EB_CALLBACK, DT, CLK, SW> enc2;
EncButton<EB_CALLBACK, DT, CLK, SW> enc3;
EncButton<EB_CALLBACK, DT, CLK, SW> enc4;

EncButton<EB_CALLBACK, DT, CLK, SW> *enc[] = {&enc2, &enc3, &enc4};
Если вам нужна только кнопка и ничего больше, то собственно я уже покромсал библиотеку для себя

Button без Enc:
#ifndef EncButton_h
#define EncButton_h
/*
    Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки
    - Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13)
    - Оптимизированный вес
    - Быстрые и лёгкие алгоритмы кнопки и энкодера
    - Энкодер: поворот, нажатый поворот, быстрый поворот, счётчик
    - Кнопка: антидребезг, клик, несколько кликов, счётчик кликов, удержание, режим step
    - Подключение - high pull
    - Опциональный режим callback (+22б SRAM на каждый экземпляр)
*/

// =========== НАСТРОЙКИ (можно передефайнить из скетча) ============
#define _EB_FAST 30     // таймаут быстрого поворота
#define _EB_DEB 80      // дебаунс кнопки
#define _EB_HOLD 1000   // таймаут удержания кнопки
#define _EB_STEP 500    // период срабатывания степ
#define _EB_CLICK 400    // таймаут накликивания

// =========== НЕ ТРОГАЙ ============
#include <Arduino.h>
//#include "fastIO.h"
// флаг макро
#define _setFlag(x) (flags |= 1 << x)
#define _clrFlag(x) (flags &= ~(1 << x))
#define _readFlag(x) ((flags >> x) & 1)

#ifndef EB_FAST
#define EB_FAST _EB_FAST
#endif
#ifndef EB_DEB
#define EB_DEB _EB_DEB
#endif
#ifndef EB_HOLD
#define EB_HOLD _EB_HOLD
#endif
#ifndef EB_STEP
#define EB_STEP _EB_STEP
#endif
#ifndef EB_CLICK
#define EB_CLICK _EB_CLICK
#endif

enum eb_callback {
    TURN_HANDLER,
    RIGHT_HANDLER,
    LEFT_HANDLER,
    RIGHT_H_HANDLER,
    LEFT_H_HANDLER,
    CLICK_HANDLER,
    HOLDED_HANDLER,
    STEP_HANDLER,
    HOLD_HANDLER,
    CLICKS_HANDLER,
};

#define EB_TICK 0
#define EB_CALLBACK 1

// класс
class EncButton {
public:
    EncButton(uint8_t pin) : btnPin{pin} {
        pinMode(pin, INPUT_PULLUP);
    }

    void tick(bool hold = 0) {
        uint32_t thisMls = millis();
        uint32_t debounce = thisMls - _debTimer;
        // обработка кнопки (компилятор вырежет блок если не используется)
        _btnState = !FastRead(btnPin);                           // обычная кнопка
        if (_btnState) {                                                    // кнопка нажата
            if (!_readFlag(3)) {                                              // и не была нажата ранее
                if (debounce > EB_DEB) {                                       // и прошел дебаунс
                    _setFlag(3);                                            // флаг кнопка была нажата
                    _debTimer = thisMls;                                    // сброс таймаутов
                    EBState = 0;                                               // сброс состояния
                }
                if (debounce > EB_CLICK) {                                    // кнопка нажата после EB_CLICK
                    clicks = 0;                                                // сбросить счётчик и флаг кликов
                    flags &= ~0b01100000;
                }
            } else {                                                          // кнопка уже была нажата
                if (!_readFlag(4)) {                                        // и удержание ещё не зафиксировано
                    if (debounce < EB_HOLD) {                                  // прошло меньше удержания
                        if (EBState != 0) _setFlag(2);                         // но энкодер повёрнут! Запомнили
                    } else {                                                // прошло больше времени удержания
                        if (!_readFlag(2)) {                                // и энкодер не повёрнут
                            EBState = 6;                                       // значит это удержание (сигнал)
                            _setFlag(4);                                    // запомнили что удерживается
                            _debTimer = thisMls;                            // сброс таймаута
                        }
                    }
                } else {                                                    // удержание зафиксировано
                    if (debounce > EB_STEP) {                                  // таймер степа
                        EBState = 7;                                           // сигналим
                        _debTimer = thisMls;                                // сброс таймаута
                    }
                }
            }
        } else {                                                            // кнопка не нажата
            if (_readFlag(3)) {                                               // но была нажата
                if (debounce > EB_DEB && !_readFlag(4) && !_readFlag(2)) {    // энкодер не трогали и не удерживали - это клик
                    EBState = 5;
                    clicks++;
                }
                flags &= ~0b00011100;                                       // clear 2 3 4
                _debTimer = thisMls;                                        // сброс таймаута
            } else if (clicks > 0 && debounce > EB_CLICK && !_readFlag(5)) flags |= 0b01100000;     // флаг на клики
        }
    }   
    
    bool isRight() { return checkState(1); }
    bool isLeft() { return checkState(2); }
    bool isRightH() { return checkState(3); }
    bool isLeftH() { return checkState(4); }

    uint8_t getState() { return EBState; }
    void resetState() { EBState = 0; }
    bool isFast() { return _readFlag(1); }
    bool isTurn() { return (EBState > 0 && EBState < 5); }
    bool isClick() { return checkState(5); }
    bool isHolded() { return checkState(6); }
    bool isHold() { return _readFlag(4); }
    bool isStep() { return checkState(7); }
    bool state() { return !FastRead(btnPin); }
    bool hasClicks(uint8_t numClicks) {
        if (clicks == numClicks && _readFlag(6)) {
            _clrFlag(6);
            return 1;
        }
        return 0;
    }
    uint8_t hasClicks() {
        if (_readFlag(6)) {
            _clrFlag(6);
            return clicks;
        } return 0;   
    }

    int counter = 0;
    uint8_t clicks = 0;

private:
    bool FastRead(const uint8_t pin) {
    #if defined([B]AVR_ATmega328P[/B]) || defined([B]AVR_ATmega168[/B])
    if (pin < 8) return bitRead(PIND, pin);
    else if (pin < 14) return bitRead(PINB, pin - 8);
    else if (pin < 20) return bitRead(PINC, pin - 14);

    #elif defined([B]AVR_ATtiny85[/B]) || defined([B]AVR_ATtiny13[/B])
    return bitRead(PINB, pin);

    #elif defined(AVR)
    uint8_t *_pin_reg = portInputRegister(digitalPinToPort(pin));
    uint8_t _bit_mask = digitalPinToBitMask(pin);
    return bool(*_pin_reg & _bit_mask);

    #else
    return digitalRead(pin);

    #endif
    return 0;
    }
    uint8_t btnPin;
    bool checkState(uint8_t val) {
        if (EBState == val) {
            EBState = 0;
            return 1;
        } return 0;
    }
    uint32_t _debTimer = 0;
    uint8_t _lastState = 0, EBState = 0;
    bool _btnState = 0;
    uint8_t flags = 0;
    

    uint8_t _dir = 0;
    uint8_t _amount = 0;


    // flags
    // 0 - enc reset
    // 1 - enc fast
    // 2 - enc был поворот
    // 3 - флаг кнопки
    // 4 - hold
    // 5 - clicks flag
    // 6 - clicks get
    // 7 - reserved

    // EBState
    // 0 - idle
    // 1 - right
    // 2 - left
    // 3 - rightH
    // 4 - leftH
    // 5 - click
    // 6 - holded
    // 7 - step
};

#endif
 

bort707

★★★★★★✩
21 Сен 2020
3,058
910
Самый обычный вариант - отрезать ненужное и оставить нужное в библиотеке, добавив конструктор и переменные, где будут храниться пины
самый простой вариант, вообще-то - это взять другую библиотеку. Библиотек для работы с кнопками сотни
 
  • Лойс +1
Реакции: te238s и Nikanor

Lumenjer

★★★✩✩✩✩
10 Дек 2020
220
112
@bort707, не спорю, но вопрос был именно о EncButton. Но и сам вопрос по себе интересный, реально ли по человечески упаковать экземпляры шаблонного класса в массив, с учетом того, что пины задаются шаблоном. Правда я не понимаю, зачем надо было делать именно так, ведь если нужен был именно шаблон, то в нем можно было указывать только mode (tick, callback) и тип (кнопка, энкодер, энкодер с кнопкой), а пины уже назначать через конструктор. Нахрена пины указывать через шаблон - не ясно
 

bort707

★★★★★★✩
21 Сен 2020
3,058
910
Нахрена пины указывать через шаблон - не ясно
Просто кое-кто недавно освоил шаблоны и теперь пихает их всюду
Это не единственный пример. В шаблонной версии либы микроЛЕД того же автора :) через шаблон задется число светодиодов в ленте, что делает невозможным написание функций, принимающих ссылку на ленту как параметр...
 
  • Лойс +1
Реакции: Vaqtincha и technotrasher

Lumenjer

★★★✩✩✩✩
10 Дек 2020
220
112

rkit

★★★✩✩✩✩
5 Фев 2021
508
127
Нахрена пины указывать через шаблон - не ясно
Чтобы работать с ними через регистры без лишней нагрузки. Что вполне оправдано, когда работаем только с одним энкодером. По уму рядом с этим классом достаточно положить специализацию с указанием пина через конструктор.

что делает невозможным написание функций, принимающих ссылку на ленту как параметр...
Функцию тоже можно сделать шаблоном.
 

Slenk

★★★★★★✩
21 Янв 2020
382
591
34
Краснодар
Может и мне кто подскажет по этой библиотеке. Не хочется опять велосипед изобретать.

Задача такая:
Нужно управлять устройством при помощи двух кнопок.
Кнопка А и кнопка В.
Стандартный Клик, Двойной клик и долгий клик. (тут всё ок)
Но дополнительно еще клик и двойной клик во время удерживания второй кнопки. То есть удерживаем А и кликаем В. (тут тоже ок)

Проблема возникает после того как поклацать В во время удержания А. Если отпустить А, то это распознаётся как долгий клик А. Этого возможно избежать по средствам этой библиотеки?

C++:
  if (BTN_A.hasClicks(1)) {
    if (digitalRead(BBB)) {
      if (++mode_arr[0] > 9) mode_arr[0] = 1;
    } else {
      RemoteFunc(BUTT_RIGHT);
    }

  }
  if (BTN_A.hasClicks(2)) {
    if (digitalRead(BBB)) {
      if (--mode_arr[0] < 1) mode_arr[0] = 9;
    } else {
      RemoteFunc(BUTT_LEFT);
    }
  }
 
  if (BTN_A.releaseStep()) RemoteFunc(BUTT_0);     //

  if (BTN_B.hasClicks(1)) {
    if (digitalRead(AAA)) {
      RemoteFunc(BUTT_HASH);
    } else {
      RemoteFunc(BUTT_UP);
    }
  }
  if (BTN_B.hasClicks(2)) {
    if (digitalRead(AAA)) {
      RemoteFunc(BUTT_STAR);
    } else {
      RemoteFunc(BUTT_DOWN);
    }
  }
  if (BTN_B.releaseStep()) RemoteFunc(BUTT_OK);
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,302
Москва
Боюсь без доп. ухищрений не выйдет. можно попробовать при отработке клика кнопки Б при удержании А скинуть статус кнопки А используя resetStates

Есть еще вариант при отрабатывании такой же ситуации возводить флаг в истину и следующую обработку кнопки А просто игнорировать.

Но я бы начал с 1-го варианта.

Если digitalRead(AAA) это проверка состояния кнопки а, то почему не проверять state ?
 

Slenk

★★★★★★✩
21 Янв 2020
382
591
34
Краснодар
@Старик Похабыч, первый вариант не прокатил.

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

Через digitalRead потому что через state работать нормально не хотело, а так завелось сразу.
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
Здравствуйте!
Вопросов собственно пока два:
1. возможно ли библиотеку EncButton использовать в проектах с STM32? В частности, с STM32F103C8T6 в среде arduino IDE.
2. если "да", то потребуются ли какие-либо корректировки в собственно "теле" библиотеки?
 

mihey78

✩✩✩✩✩✩✩
31 Мар 2022
2
0
Здравствуйте, Александр.
Пользуюсь библиотекой EncButton. Возникла необходимость проверять удержание кнопки
при загрузке, а вместе с ней и необходимость сочинять костыли, типа следующих:

Кнопка:
EncButton<EB_TICK, 12> btnSel;

1. Из setup() вызываю для этой кнопки функцию, возвращающую
True в случае удержании кнопки btnSel при загрузке:

boolean PrePrssedSel() {
//*********** если кнопка не нажата
btnSel.tick();
if (!btnSel.state()) {
return false;
}
//*********** если кнопка нажата при сбросе/загрузке
while (true) {
btnSel.tick();
//************ если кнопка удерживалась недостаточно долго
if (!btnSel.state()) {
btnSel.resetState();
return false;
}
//*********** если кнопка удерживалась достаточно долго
if (btnSel.held()) {
//btnSel.resetState();
return true;
}
}
}

2. Получив True из функции PrePressedSel() выводим на
дисплей сервисное меню и ждем отпускания кнопки, так как кнопка еще нажата:

void WaitUnprerssSel() {
//******* ждем отпускания кнопки
while (true) {
btnSel.tick();
if (!btnSel.state()) {
btnSel.resetState();
break;
}
}
}

3. Дождавшись отпускания, обрабатываем последующие
нажатия кнопок, уже внутри сервисного меню.

Работает, но как-то некрасиво выглядит...
Нельзя-ли подобный функционал добавить в библиотеку?
Спасибо.
С уважением, Михаил.
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
612
146
как-то некрасиво выглядит
А чем не красивость?
Нельзя-ли подобный функционал добавить в библиотеку?
Ну так никто не препятствует собственноручно добавить свои функции в библиотеку и пользоваться.
 

mihey78

✩✩✩✩✩✩✩
31 Мар 2022
2
0
Некрасивость в том, что для шести кнопок нужно шесть раз написать код PrePrssedMyBtn() и шесть раз написать код WaitUnprerssMyBtn() в ситуации "Удерживайте любую кнопку при загрузке". А по-делитантски улучшать написанное профессионалом - это неправильно.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,302
Москва
Задача в чем ? Выполнить какое то действие , если кнопка (кнопки ) при включении удерживается как минимум определенное время ?
Так тут даже не надо использовать никакие библиотеки.
Если вызвать это в setup, то функция будет крутиться до тех пор пока нажата кнопка на любом из пинов 2 , 3 или 4. Нажатый уровень при этом 1. Если есть подтяжка к +, то надо чуть изменить условие.
C++:
boolean PrePressedSel()
{
    tmr=millis();
    while (digitalRead(2) or digitalRead(3) or digitalRead(4))
    {
        delay(2);
        if (millis()-tmr>3000) return true;
    }
    return false;
}
далее в setup
C++:
if PrePressedSel()
{  
  showSetMenu(); // показать меню, что бы пользователь понял, что кнопки можно отпускать
  while(digitalRead(2) or digitalRead(3) or digitalRead(4)) delay(2); // ждать отжатия кнопок.
}
 

viktor1703

★★★✩✩✩✩
9 Дек 2021
612
146
для шести кнопок нужно шесть раз написать код PrePrssedMyBtn() и шесть раз написать код WaitUnprerssMyBtn()
Так библиотека написана только для опроса кнопок и возврата результата опроса, а вот, что дальше с этими результатами делать каждый решает как он хочет.
улучшать написанное профессионалом - это неправильно
Возьми за основу код профессионала, попили под себя, и пользуйся свой личной библиотекой, Gyver, вроде, не запрещал вносить изменения в свои библиотеки.
 

Scopex

✩✩✩✩✩✩✩
3 Фев 2021
31
2
Ребят, подскажите бестолковому, как при двойном клике в данной конструкции отсечь сработку одинарного?

C++:
  enc.attach(CLICK_HANDLER, myClick);
  enc.attach(CLICKS_HANDLER, myClicks);

void myClick() {
  Serial.println("1 CLICK");
}
void myClicks() {
  Serial.print("2 CLICKS: ");
}
при двойном нажатии в монитор всё равно лезет - 1 CLICK
 

PiratFox

★★★★★✩✩
13 Фев 2020
1,706
474
@Scopex, ну так и будет лезть потому, что он присутствует. Первый клик фиксируется как один независимо от того, последует ли второй. Отсекать по условию if (myClicks) сбросить myClick;. Хотя, по логике, myClick должен бы сброситься сам, если вначале вызвать myClicks. Попробуйте поменять очерёдность.
 
Изменено:
  • Лойс +1
Реакции: Scopex

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,302
Москва
Надо смотреть примеры, скорее всего для одинарного клика будет еще одно событие, которое отличает одинарный от двойного и тройного.
Примерно так было в ранней версии библиотеки.
 

Scopex

✩✩✩✩✩✩✩
3 Фев 2021
31
2
@PiratFox,ничего не меняет
img-2023-03-09-20-22-41.png

будет еще одно событие
конечно оно есть! однако проблема в том, что в обоих вызовах есть одинаковое событие, по которому первым срабатывает одинарный клик.

в принципе я сделал код который их различает, вкратце:
завожу переменную, при каждом клике увеличиваю её на 1в вызове myClick() и засекаю время, в LOOP жду когда пройдёт 400мс (максимальное время для повторного клика)
по окончании времени анализирую переменную и вывожу результат...

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

poty

★★★★★★✩
19 Фев 2020
3,238
943
Функция из enc.attach, случайно, не в прерывании обрабатывается? Тогда нужно избавиться от вывода serial внутри прерывания.
 

Scopex

✩✩✩✩✩✩✩
3 Фев 2021
31
2
з.ы. только сейчас дошло, можно и без переменной, анализируй enc.clicks но время засекать всё равно надо

@poty, фиг её знает, это стандартный пример из описания
 

poty

★★★★★★✩
19 Фев 2020
3,238
943
Попробуйте вывод сделать вне функций обработки.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,302
Москва
Я же говорю, надо смотреть примеры:
C++:
 // проверка на количество кликов
  if (enc.hasClicks(1)) Serial.println("action 1 clicks");
  if (enc.hasClicks(2)) Serial.println("action 2 clicks");
  if (enc.hasClicks(3)) Serial.println("action 3 clicks");
  if (enc.hasClicks(5)) Serial.println("action 5 clicks");
Т.е. 1-ая обработка вообще не нужна, это обрабока быстрого клика, б, без задержки в ожидании следующих возможных кликов. Надо обрабатывать только
enc.attach(CLICKS_HANDLER, myClicks);
и внутри смотреть сколько было кликов при вызове этой функции.

Даже еще проще:
enc.attachClicks(5, fiveClicks);
}
 
  • Лойс +1
Реакции: Scopex