Библиотека для работы с кнопкой shButton

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

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

 

shButton - библиотека для отработки нажатия кнопки. Возможности:

- Работа с подключением PULL_UP и PULL_DOWN

- Опрос кнопки как с программным антидребезгом контактов, так и без него

- Отработка нажатия, удержания, отпускания кнопки, двойного клика

- Настройка интервалов антидребезга, двойного клика и удержания кнопки

 

Результат опроса кнопки заносится в поле btnState и может иметь следующие значения

BTN_RELEASED - кнопка отпущена

BTN_UP - кнопка только что отпущена

BTN_PRESSED - кнопка нажата, но время удержания не вышло

BTN_HOLDED - кнопка удерживается нажатой дольше времени удержания

BTN_DOWN - кнопка только что нажата

BTN_DBLCLICK - кнопка только что нажата, двойной клик

Файл shButton.h

#pragma once
#include <Arduino.h>
/*
  shButton - библиотека для отработки нажатия кнопки. Возможности:
  - Работа с подключением PULL_UP и PULL_DOWN
  - Опрос кнопки как с программным антидребезгом контактов, так и без него
  - Отработка нажатия, удержания, отпускания кнопки, двойного клика 
  - Настройка интервалов антидребезга, двойного клика и удержания кнопки
*/

// типы подключения кнопки
#define PULL_UP 0   // кнопка подтянута к VCC
#define PULL_DOWN 1 // кнопка подтянута к GND
// состояние кнопки
#define BTN_RELEASED 0 // кнопка отпущена
#define BTN_UP 1       // кнопка только что отпущена
#define BTN_PRESSED 2  // кнопка нажата, но время удержания не вышло
#define BTN_HOLDED 3   // кнопка удерживается нажатой дольше времени удержания
#define BTN_DOWN 4     // кнопка только что нажата
#define BTN_DBLCLICK 5 // двойной клик

class shButton
{
private:
  byte _PIN = 0;               // пин, на который посажена кнопка
  word _debounce = 50;         // интервал подавления дребезга контактов
  word _timeout = 500;         // интервал удержания кнопки нажатой
  word _dblclck = 300;         // интервал двойного клика
  byte _clckcount = 0;         // счетчик кликов
  byte _type = PULL_UP;        // тип подключения
  bool _flag = false;          // сохраненное состояние кнопки - нажата/не нажата
  bool _deb = false;           // флаг включения подавления дребезга - пока включено, изменения состояния не принимаются
  unsigned long btn_timer = 0; // таймер удержания кнопки нажатой
  unsigned long deb_timer = 0; // таймер подавления дребезга контактов
  unsigned long dbl_timer = 0; // таймер двойного клика

  // получение мгновенного состояния кнопки - нажата/не нажата с учетом типа подключения и без учета дребезга контактов
  bool getButtonFlag();
  // установка кнопке состояния "только что нажата" или "только что отпущена"
  void setBtnUpDown(bool flag, unsigned long thisMls);

public:
  // Варианты инициализации:
  // shButton btn(пин);	 - с привязкой к пину и без указания типа (по умолч. PULL_UP)
  // shButton btn(пин, тип подключ.);	- с привязкой к пину и указанием
  // типа подключения (PULL_UP / PULL_DOWN)
  shButton(byte pin, byte mode = PULL_UP);
  // получение состояния кнопки - отпущена/нажата/удерживается
  void getButtonState();
  // установка времени антидребезга (по умолчанию 50 мс)
  void setDebounce(word debounce);
  // установка таймаута удержания (по умолчанию 500 мс)
  void setTimeout(word new_timeout);
  // установка интервала двойного клика
  void setDblClickTimeout(word new_timeout);
  // установка типа кнопки (PULL_UP - подтянута к VCC, PULL_DOWN - к GND)
  void setType(byte type);
  // текущее состояние кнопки
  byte btnState = BTN_RELEASED;
};

Файл shButton.cpp

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

shButton::shButton(byte pin, byte mode)
{
  _PIN = pin;
  setType(mode);
}

void shButton::getButtonState()
{
  bool flag = getButtonFlag();
  unsigned long thisMls = millis();
  // состояние кнопки не поменялось с прошлого опроса
  if (flag == _flag)
  { // и не поднят флаг подавления дребезга
    if (!_deb)
    {
      if (!flag)
      { // кнопка находится в отжатом состонии
        btnState = BTN_RELEASED;
        if (millis() - dbl_timer > _dblclck)
        { // обнулить счетчик кликов, если период двойного клика закончился
          _clckcount = 0;
        }
      }
      else if (millis() - btn_timer < _timeout)
      { // кнопка находится в нажатом состоянии, но время удержания еще не вышло
        btnState = BTN_PRESSED;
      }
      else
      { // кнопка удерживается нажатой дольше времени удержания
        btnState = BTN_HOLDED;
      }
    }
  }
  // состояние кнопки поменялось с прошлого опроса
  else
  { // если задано подавление дребезга контактов
    if (_debounce > 0)
    { // если флаг подавления еще не поднят - поднять и больше ничего не делать
      if (!_deb)
      {
        deb_timer = thisMls;
        _deb = true;
      } // иначе, если поднят, и интервал вышел - установить состояние кнопки
      else if (millis() - deb_timer >= _debounce)
      {
        setBtnUpDown(flag, thisMls);
      }
    }
    else // если подавление вообще не задано, то сразу установить состояние кнопки
    {
      setBtnUpDown(flag, thisMls);
    }
  }
}

void shButton::setType(byte type)
{
  _type = type;
  switch (type)
  {
  case PULL_UP:
    pinMode(_PIN, INPUT_PULLUP);
    break;
  default:
    pinMode(_PIN, INPUT);
    break;
  }
}

void shButton::setDebounce(word debounce)
{
  _debounce = debounce;
}

void shButton::setTimeout(word new_timeout)
{
  _timeout = new_timeout;
}

void shButton::setDblClickTimeout(word new_timeout)
{
  _dblclck = new_timeout;
}

bool shButton::getButtonFlag()
{
  bool val = digitalRead(_PIN);
  if (_type == PULL_UP)
  {
    val = !val;
  }
  return val;
}

void shButton::setBtnUpDown(bool flag, unsigned long thisMls)
{
  _deb = false;
  _flag = flag;

  if (flag)
  { // если кнопка только что нажата, то запустить таймер удержания
    btn_timer = thisMls;
    if (_clckcount == 0)
    { // если это первый клик, запустить таймер двойного клика и увеличить счетчик кликов
      btnState = BTN_DOWN;
      _clckcount++;
      dbl_timer = thisMls;
    }
    else if (millis() - dbl_timer <= _dblclck)
    { 
      btnState = BTN_DBLCLICK;
      _clckcount = 0;
    }
  }
  else
  {
    btnState = BTN_UP;
  }
}

Файл keywords.txt

#######################################
# Syntax Coloring Map For shButton
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################

shButton	KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################

setDebounce	KEYWORD2
setTimeout	KEYWORD2
setType	KEYWORD2
setDblClickTimeout	KEYWORD2
btnState	KEYWORD2
getButtonState	KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################

PULL_UP	LITERAL1
PULL_DOWN	LITERAL1
BTN_RELEASED	LITERAL1
BTN_UP	LITERAL1
BTN_PRESSED	LITERAL1
BTN_HOLDED	LITERAL1
BTN_DOWN	LITERAL1
BTN_DBLCLICK	LITERAL1

Демо-скетч

#include <shButton.h>

// инициализация кнопки (пин, режим кнопки: PULL_UP (значение по умолчанию) - кнопка подтянута
// к VCC; PULL_DOWN - кнопка подтянута к GND)
shButton but(10, PULL_UP);

void setup()
{
  // режим пина устанавливается автоматически

  // необязательные установки
  but.setDebounce(80); // установка времени антидребезга, мс (по умолчанию 50 мс)
  but.setTimeout(800); // установка времени удержания кнопки нажатой, мс (по умолчанию 500 мс)

  Serial.begin(9600);
}

void loop()
{
  static int8_t bt = -1;
  // опрос кнопки, для уверенной обработки состояния кнопки опросы нужно делать как можно чаще
  but.getButtonState();
  if (bt != but.btnState)
  {
    bt = but.btnState;
    switch (bt)
    {
      case BTN_RELEASED:
      //  Serial.println("button released");
        break;
      case BTN_UP:
        Serial.println("button up");
        break;
      case BTN_PRESSED:
      //  Serial.println("button pressed");
        break;
      case BTN_HOLDED:
        Serial.println("button holded");
        break;
      case BTN_DOWN:
        Serial.println("button down");
        break;
      case BTN_DBLCLICK:
        Serial.println("button dblclick");
        break;
    }
  }
}

Скачать одним файлом можно здесь

 

Просьба к опытным товарищам покритиковать, желательно конструктивно )))

b707
Offline
Зарегистрирован: 26.05.2017

сильно лучше, чем первый вариант скетча для часов. Так сходу прям и поругать не за что :))

продолжайте развиваться, у вас получается

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

v258 пишет:

BTN_HOLDED

Исправьте, пожалуйста, глаза режет. Это неправильный глагол, его формы: hold, held, held.

Это "конструктивно"?

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

ЕвгенийП пишет:

Исправьте, пожалуйста, глаза режет. Это неправильный глагол, его формы: hold, held, held.

Это "конструктивно"?

Вполне. У меня с английским туговато, это я подсмотрел, кажется, у Гайвера. BTN_WITHHELD будет нормально?

Logik
Offline
Зарегистрирован: 05.08.2014

//Просьба к опытным товарищам покритиковать, желательно конструктивно )))

Это можно.

1. Пример, строки 22-23. Логично возвращать состояние как результат  but.getButtonState(); В конце концев она ж так и называется. А but.btnState; можно убрать, но и оставить не вредно.

2. Состояния кнопок BTN_хххх. В принципе подход верный, но там не все - именно состояния. Например BTN_UP, BTN_DBLCLICK - скорей события возникающие единоразово в процессе обработки нажатий. В теории это грех. На практике почти пофиг, но и порядок их нумерации тоже задан произвольно. Отсюда простой лайфхак - состояния все подряд нумеруем от 0, а события за ними.

3. Явно не хватает "логического клика" - событие из числа BTN_хххх, возникает разово, после отпускания кнопки через интервал времени исключающий возможность дальнейшего даблклика. На него тогда вешается обработчик обычного нажатия и он тогда не срабатывает в других случаях.

4. Иногда по ходу программы возникает нужда просто узнать нажата кнопка или нет. Ну просто проверить 0 или 1, игноря все остальные подробности. Удобно это делать через тот же but.getButtonState(), но указав ему параметром специфику.  Или завести еще метод.

5. Даблклик есть, а где длинное нажатие? Не совсем понялBTN_HOLDED, может оно? 

6. Настройки таймингов. Ну подумайте, кто по ходу работы будет их менять? Никто и никогда. Максимум при ините зададут, если умолчания не устроят. Экономим ресурс, делаем их константами. Шаблонами можно поигратся, если не страшно ;)

7.Формирование временных интервалов. Их много, миллисов да еще с лонгами тоже. Это усложняет проверки и забирает ресурсы. Введите внутри либы дискретный интервал, чаще которого она не отрабатывает. Чего ей ловить тысячи раз в сек? Например в примере ниже такт 20мсек. Тогда нужные интервалы типа word _timeout = 500;, превратятся в байтовые счетчики uint8_t cnt_timeout. Когда надо его запустить то присвоим ему значение  500/2=25. На каждом такте 20мсек вычитаем 1. Как получится ноль - интервал завершен. Да, точность снизится, но никто не заметит что вместо 500мсек прошло 519.

8. В примере логика проверки повторного состояния на переменной bt портит впечатления. 

Мой аналогичный код. Не из библиотеки, прямо из скетча, кнопка енкодера обрабатывается.

.....


#define BTN_TIME_CLICK 4  //4*20=80msec
#define BTN_TIME_LONG  40 //40*20=800msec
#define BTN_TIME_PAUSE 10 //10*20=200msec
enum BTN
{
  PRESS,      //удерживается нажатой короткое время, 1 раз на нажатие
  LONG_PRESS, //удерживается нажатой длинное время, 1 раз на нажатие
  CLICK,      //отпущена после нажатия в течении времени больше короткого но меньше длинного, 1 раз на нажатие
  DONE,       //нажата, 1 раз на нажатие
  UP,         //отпущена но не CLICK и не DBLCLICK, 1 раз на нажатие
  PRESSED,    //удерживается нажатой, но не PRESS, не LONG_PRESS и не DBLPRESS, ***** многократно
  TOP,        //не нажата, ***** многократно
  DBLPRESS,   //повторно нажата и удерживается нажатой короткое время, 1 раз на нажатие
  DBLCLICK,   //повторный CLICK, ранее был CLICK с паузой менее заданой, 1 раз на нажатие
  ONE_CLICK   //отпущена и истекла пауза для повторного нажатия, 1 раз на нажатие
  
};


uint8_t GetBtnCmdLow(uint8_t scan = false)
{
 static uint8_t b_old;
 uint8_t b;

 if(scan)
 {
  b=bit_is_set(ARDUINO_PIN(DRV_BUTTON_PIN), ARDUINO_PIN_NUM(DRV_BUTTON_PIN));  // digitalRead(DRV_BUTTON_PIN);

  if(b_old!=b)
  {
    b_old=b;
    return b?BTN::UP:BTN::DONE;
  }
 }
 return b_old?BTN::TOP:BTN::PRESSED;
}

uint8_t GetBtnCmd()
{
 static uint8_t t;
 static uint8_t btn_press;
 static uint8_t FlDBL;
 uint8_t i;
 uint8_t r;

 t++;
 i=t-btn_press;
 r=GetBtnCmdLow(true);
 switch(r)
 {
  case BTN::UP:                                                        //отпускание
      if((i<BTN_TIME_LONG) && (i>=BTN_TIME_CLICK))
      {
        btn_press=t;
        r=FlDBL?BTN::DBLCLICK:BTN::CLICK;
        FlDBL=!FlDBL;
      }
      break;
  case BTN::DONE:   btn_press=t;      break;                          //обнаружено нажатие
  case BTN::TOP:                                                      // не нажата
      if(i>BTN_TIME_PAUSE)
      {
        if(FlDBL) 
          r=BTN::ONE_CLICK;
        FlDBL=false;
      }
      break;          
  case BTN::PRESSED:                                                  //удерживается нажата
      if(i==BTN_TIME_CLICK) return FlDBL?BTN::DBLPRESS:BTN::PRESS;
      if(i==BTN_TIME_LONG) return BTN::LONG_PRESS;
      break;
  
 }
 return r ;

}

void loop() {
 uint8_t n;
  static uint8_t takt;
 
 uint8_t t=millis();

.......

 if(uint8_t(t-takt)>=20)
 {
   takt+=20;
........

   n=GetBtnCmd();
   switch (n)
   {
    case BTN::DONE:
            Rotation(false);  //стоп
        break;

    case BTN::LONG_PRESS:
            fl^=1;//реверс
            fl&=~2; 
            fl|=4;
        break;

    case BTN::ONE_CLICK:
            fl^=2; //старт-стоп
            Rotation(fl&2);
        break;
  case BTN::DBLCLICK:
            SetCountPulse(1000, 700);
        break;
   }
 }

.......

  if(GetBtnCmdLow()==BTN::PRESSED) //вращение энкодера при нажатой
  {
.....
    }
  }

 

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Попробую отвертеться )))

 

1. Возвращать, конечно, можно, где-то это будет экономить одну строку кода. Но убирать but.btnState тоже не стоит, имхо. Почему? Потому, что это значение может понадобиться не один раз за итерацию и тогда либо каждый раз вызывать  but.getButtonState(), либо заводить отдельную переменную, типа byte state = but.getButtonState(). А зачем, если есть but.btnState?

2. Изначально так и было. Но потом поменял. Логика была следующая - если нужно проверить именно состояние контактов - замкнуты или нет, то достаточно сделать проверку

if (but.btnState > BTN_UP)
  {
    // контакты замкнуты
  }
  else
  {
    // контакты разомкнуты
  }

3.  Это чтобы отфильтровывать одиночные и двойные клики? Честно говоря, я противник запихивания таких вещей в библиотеки, если кому-то нужно отрабатывать и то, и другое, разделение нужно делать в скетче. 

Например, так:

#include <shButton.h>

#define DBLCLICKTIME 250 // интервал двойного клика, мс (по умолчанию 300 мс)

// Пример раздельной реакции на одинарный и двойной клик кнопкой

// инициализация кнопки (пин, режим кнопки: PULL_UP (значение по умолчанию) - кнопка подтянута
// к VCC; PULL_DOWN - кнопка подтянута к GND)
shButton but(10, PULL_UP);

void setup()
{
  // режим пина устанавливается автоматически

  // необязательные установки
  but.setDblClickTimeout(DBLCLICKTIME); // установка интервала двойного клика

  Serial.begin(9600);
}

void loop()
{
  static byte clckcount = 0;              // счетчик кликов
  static unsigned long dblclck_timer = 0; // таймер двойного клика

  but.getButtonState();
  switch (but.btnState)
  {
  case BTN_DOWN: // если пришел клик, установить счетчик кликов и запустить таймер двойного клика
    clckcount = 1;
    dblclck_timer = millis();
    break;
  case BTN_DBLCLICK: // если пришел двойной клик, увеличить счетчик кликов и отработать нужное действие
    clckcount = 2;
    // действие по двойному клику
    Serial.println("onDblClick");
    break;
  default:
    if ((clckcount == 1) && (millis() - dblclck_timer > DBLCLICKTIME))
    { // если счетчик кликов равен единице, и таймер двойного клика истек, отработать одиночный клик
      clckcount = 0;
      // действие по одиночному клику
      Serial.println("onClick");
    }
  }
}

Этот пример я уже засунул в архив с библиотекой

4. Кажется, на это я ответил в п.2 ))

5. Этого есть у меня, даже два ))

BTN_PRESSED // кнопка нажата, но время удержания не вышло

BTN_HOLDED // кнопка удерживается нажатой дольше времени удержания

BTN_HOLDED - действительно неудачное имя, заменил на BTN_WITHHELD

6. Тут не согласен. Естественно, я не предполагаю, что тайминги будут меняться по ходу выполнения, но если сделать их константами, то как их менять-то хотя бы и в ините?))

7. Честно говоря, не уверен, что правильно понял, но, имхо, это некоторое усложнение. Впрочем, над этим стоит подумать, вы меня натолкнули на немного другую мысль))

8. А тут точно не понял)) Имеете в виду вот так?

  if (bt != but.btnState)
  {
    bt = but.btnState;
    switch (but.btnState)
    {
......
    }

 

Logik
Offline
Зарегистрирован: 05.08.2014

1. Так уж принято, если функцм GetЧегототам, то от нее и ожидают значение Чегототам. Тем более что и в Вашем примере и как правило по жизни сразу же это значение и востребовано. Ну и сыкономить не лишне. but.btnState - дело вкуса, можно убрать, можно оставить. "заводить отдельную переменную" выглядит предпочтительней т.к. на её поведение класс уже не повлияет. Смотрите свой пример, в стр 25 вы её всеравно присваиваете.

2. Ну вот это условие "but.btnState > BTN_UP" - как раз то, о чем писал. "В теории это грех. На практике почти пофиг" Как бы это плохо, но для квалифицированного пользователя... сойдет ;)))  Потому различать таким путем состояния и события, еще ладно квалифицированный поймет. А вот нажата или нет - это любому профану может понадобится.

3. Неее... это явно попытка отвертется ))) Без этого как различить одинарное и двойное нажатия. Тем более что в коде по сути все есть, просто выплюнуть наружу нужно. С практической точки зрения пользователя из всех проблем это наибольшая. Вот тот код, что "Например, так:" - он жеж так и просится в либу.

5. ИМХО новое название хуже. Но речь не о нем. Нужно событие которое генерируется только один раз, если кнопку удерживали долго. Это чисто с практической стороны удобно. Логика такая. На три действия пользователя: короткое нажатие, долгое нажатие и двойное нажатие, без шаманства снаружи либы, просто в свичь, вешаются 3 обработчика, например отправка транзакции на сервер. И отправка один раз на одно действие.

6. Шаблон класса - один из путей, но есть и другие.

7. Не. Не сложней. Найдите в моем коде работу с временными интервалами для даблклика например. А она есть! Я её сам еле нашел)) Ну понятно  if(uint8_t(t-takt)>=20) задает период вызова основной функции и в ней 

049  t++;
050  i=t-btn_press;

 да пару btn_press=t; Куда уж проще. Все байтовое. t - текущее "время" в библиотеке, единица равна 20мсек, i-интервал от прошлого какого либо события. Ну и сравнения прошло ли нужное время но не более - типа if((i<BTN_TIME_LONG) && (i>=BTN_TIME_CLICK)) А можно и жестко проверять завершение интервала if(i==BTN_TIME_LONG), пропуска не будет.

8. Да. Человек который впервые увидит эту либу и пример смутится. Потом конечно поймет, если не сбежит стр 26-27 из примера #5 куда приятней.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

1. Согласен. Сделаю return. Про btnState таки подумаю.

2. Видимо да, стоит сделать отдельный метод, типа isBtnClosed() (или isBtnPressed() ), в котором уже и проверять флаги

3. Вообще-то это стандартное (по крайней мере по меркам ПК) поведение - сначала onClick, потом onDblClick. Если делать разделение в библиотеке, то клик всегда будет идти с сильным запаздыванием. У Гайвера, кажется, так сделано - честно говоря, напрягает, когда реакция на нажатие кнопки следует через полсекунды, когда кнопка уже отпущена )) Если только действительно к BTN_DOWN добавить еще и BTN_CLICK, но не запутает ли это неискушенного пользователя?

5. С названиями у меня беда. Я в школе учил французский ))) И было это еще в прошлом веке. Если предложите что-то более подходящее, буду благодарен. Новое название, по крайней мере, гуглопереводчик переводит, старое игнорировал ))

По поводу однократного события. Наверное, можно сделать статус типа BTN_LONGCLICK со сменой на следующей итерации на BTN_WITHHELD. Может быть даже с возможностью настройки - нет, однократно, серийно через заданный интервал. Нужно подумать.

6. Все-таки тут я оставлю как есть))

7. Буду думать ))

8. Исправил )) 

 

Logik
Offline
Зарегистрирован: 05.08.2014

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

//Если только действительно к BTN_DOWN добавить еще и BTN_CLICK, но не запутает ли это неискушенного пользователя?

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

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Обновил библиотеку, в том числе с учетом замечаний. Что изменилось:

- Добавил возможность работы с нормально замкнутыми кнопками, может кому понадобится.

- Процедура getButtonState() теперь возвращает значение, поле btnState убрал в private.

- Добавил процедуру isButtonClosed(), возвращающую true, если кнопка по результатам последнего опроса нажата.

- Добавил виртуальный клик. Генерирует событие BTN_ONECLICK в случае, если по истечении интервала двойного клика собственно двойной клик не последовал. Кроме того, это событие не будет сгенерировано, если кнопка в этот момент удерживается нажатой. Т.е. появилась возможность раздельно обрабатывать одиночный, двойной клик и удержание кнопки. По умолчанию этот режим выключен, включается процедурой setVirtualClickOn(true).

- Состояние BTN_WITHHELD удалено и заменено состоянием BTN_LONGCLICK. По умолчанию оно так же генерируется при удержании кнопки нажатой дольше интервала удержания кнопки.

- Расширена обработка удержания кнопки, добавлено три режима: LCM_CONTINUED - режим по умолчанию, LCM_ONLYONCE - переводит BTN_LONGCLICK из состояния кнопки в событие, которое генерируется один раз по истечении интервала удержания кнопки. LCM_CLICKSERIES - то же событие, но регулярное - после истечения интервала удержания оно начинает генерироваться через равные интервалы времени. Это позволяет, например, организовать ускоренное изменение значений в каких-нибудь настройках. Режим удержания кнопки устанавливается процедурой setLongClickMode(mode). Интервал выдачи событий в режиме LCM_CLICKSERIES настраивается процедурой setLongClickTimeout(timeout), по умолчанию - 200 мс.

Файл shButton.h

#pragma once
#include <Arduino.h>
/*
  shButton - библиотека для отработки нажатия кнопки. Возможности:
  - Работа с кнопками с нормально разомкнутыми и нормально замкнутыми контактами;
  - Работа с подключением PULL_UP и PULL_DOWN;
  - Опрос кнопки как с программным антидребезгом контактов, так и без него; возможность найстройки интервала антидребезга;
  - Отработка нажатия, отпускания кнопки, двойного клика; возможность настройки интервала двойного клика;
  - Возможность использования виртуального клика; в этом режиме событие клика (BTN_ONECLICK) генерируется по истечении интервала двойного клика, если не наступило событие двойного клика и в это же время кнопка не удерживается нажатой; таким образом появляется возможность организовать раздельную реакцию на однократный клик, двойной клик и удержание кнопки без написания дополнительного кода в скетче;
  - Отработка удержания кнопки; возможность настройки интервала удержания; 
  - Возможность настройки генерируемого при удержании кнопки свыше интервала удержания события (BTN_LONGCLICK) - непрерываная выдача события (т.е. фактически - состояния кнопки), однократная подача события или циклическая подача события через равные интервалы времени, пока кнопка нажата; возможность настройки этого интервала;
*/

// значения по умолчанию
#define DEBOUNCETIMEOUT 50
#define PRESSEDTIMEOUT 500
#define DBLCLICKTIMEOUT 300
#define LONGCLICKTIMEOUT 200

// типы кнопки
#define BTN_NO 0  // кнопка с нормально разомкнутыми контактами
#define BTN_NC 1  // кнопка с нормально замкнутыми контактами

// типы подключения кнопки
#define PULL_UP 0   // кнопка подтянута к VCC
#define PULL_DOWN 1 // кнопка подтянута к GND

// состояние кнопки
#define BTN_RELEASED 0 // кнопка отпущена
#define BTN_PRESSED 1  // кнопка нажата, но время удержания не вышло

// события кнопки
#define BTN_UP 2       // кнопка только что отпущена
#define BTN_DOWN 3     // кнопка только что нажата
#define BTN_DBLCLICK 4 // двойной клик

// виртуальные события кнопки
#define BTN_ONECLICK 5 // одиночный клик, следует через некоторый интервал после нажатия кнопки, если за это время не последовал двойной клик или длительное удержание кнопки нажатой; по умолчанию событие отключено
#define BTN_LONGCLICK 6// событие, соответствующее удержанию кнопки дольше времени удержания; может быть однократным, следовать через определенные интервалы или быть непрерывным (по умолчанию)

// режимы отработки удержания кнопки
#define LCM_CONTINUED 0   // непрерывное событие, генерируется постоянно, пока кнопка удерживается нажатой, если интервал удержания превышен; значение по умолчанию
#define LCM_ONLYONCE 1    // однократное событие, генерируется только один раз по истечении интервала удержания кнопки
#define LCM_CLICKSERIES 2 // по истечении интервала удержания кнопки событие генерируется постоянно через равные интервалы времени

class shButton
{
private:
  byte _PIN = 0;       // пин, на который посажена кнопка
  word _debounce = DEBOUNCETIMEOUT; // интервал подавления дребезга контактов, мс
  word _timeout = PRESSEDTIMEOUT;   // интервал удержания кнопки нажатой, мс
  word _dblclck = DBLCLICKTIMEOUT;  // интервал двойного клика, мс
  byte _clckcount = 0;      // счетчик кликов
  byte _longclickcount = 0; // счетчик длинных кликов
  byte _inputtype = PULL_UP; // тип подключения
  byte _btntype = BTN_NO; // тип кнопки - нормально разомкнутая или нормально замкнутая
  bool _flag = false;   // сохраненное состояние кнопки - нажата/не нажата
  bool _deb = false;    // флаг включения подавления дребезга - пока включено, изменения состояния не принимаются
  bool _virtualclick = false;          // режим виртуального клика
  byte _longclickmode = LCM_CONTINUED; // режим удержания кнопки; 
  word _longclicktimeout = LONGCLICKTIMEOUT; // интервал следования события BTN_LONGCLICK, если установлен режим LCM_CLICKSERIES, мс
  byte _btnstate = BTN_RELEASED; // текущее состояние кнопки

  unsigned long btn_timer = 0;   // таймер удержания кнопки нажатой
  unsigned long deb_timer = 0;   // таймер подавления дребезга контактов
  unsigned long dbl_timer = 0;   // таймер двойного клика
  unsigned long lclck_timer = 0; // таймер серийного BTN_LONGCLICK

  // получение мгновенного состояния кнопки - нажата/не нажата с учетом типа подключения и без учета дребезга контактов
  bool getButtonFlag();
  // установка кнопке состояния "только что нажата" или "только что отпущена"
  void setBtnUpDown(bool flag, unsigned long thisMls);

public:
  // Варианты инициализации:
  // shButton btn(пин);	 - с привязкой к пину и без указания типа подключения (по умолч. PULL_UP) и типа контактов (по умолчанию BTN_NO - нормально разомкнутые контакты)
  // shButton btn(пин, тип подключ.);	- с привязкой к пину и указанием типа подключения (PULL_UP / PULL_DOWN)
  // shButton btn(пин, тип подключ., тип кнопки);	- с привязкой к пину и указанием типа подключения (PULL_UP / PULL_DOWN) и типа кнопки (BTN_NO/BTN_NC)
  shButton(byte pin, byte inputtype = PULL_UP, byte btntype = BTN_NO);
  // получение состояния кнопки - отпущена/нажата/удерживается
  byte getButtonState();
  // возвращает true, если контакты кнопки в данный момент замкнуты
  bool isButtonClosed();
  // установка времени антидребезга (по умолчанию 50 мс); для отключения антидребезга нужно задать 0 мс
  void setDebounce(word debounce);
  // установка таймаута удержания (по умолчанию 500 мс)
  void setTimeout(word new_timeout);
  // установка интервала двойного клика
  void setDblClickTimeout(word new_timeout);
  // установка типа подключения кнопки (PULL_UP - подтянута к VCC, PULL_DOWN - к GND)
  void setInputType(byte inputtype);
  // установка типа кнопки (BTN_NO - нормально разомкнутая, BTN_NC - нормально замкнутая)
  void setButtonType(byte btntype);
  // включение режима "Виртуальный клик"
  void setVirtualClickOn(bool virtualclick);
  // установка режима обработки удержания кнопки нажатой
  void setLongClickMode(byte longclickmode);
  // установка интервала выдачи события BTN_LONGCLICK в режиме LCM_CLICKSERIES
  void setLongClickTimeout(word longclicktimeout);
};

Файл shButton.cpp

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

shButton::shButton(byte pin, byte inputtype, byte btntype)
{
  _PIN = pin;
  setInputType(inputtype);
  setButtonType(btntype);
}

byte shButton::getButtonState()
{
  bool flag = getButtonFlag();
  unsigned long thisMls = millis();
  // состояние кнопки не изменилось с прошлого опроса
  if (flag == _flag)
  { // и не поднят флаг подавления дребезга
    if (!_deb)
    {
      if (!flag)
      { // кнопка находится в отжатом состонии
        _btnstate = BTN_RELEASED;
        if (millis() - dbl_timer > _dblclck)
        { // если период двойного клика закончился, проверить на виртуальный клик и обнулить счетчик кликов
          if (_virtualclick && _clckcount == 1)
          {
            _btnstate = BTN_ONECLICK;
          }
          _clckcount = 0;
          _longclickcount = 0;
        }
      }
      else if (millis() - btn_timer < _timeout)
      { // кнопка находится в нажатом состоянии, но время удержания еще не вышло
        _btnstate = BTN_PRESSED;
      }
      else
      { // если кнопка удерживается нажатой дольше времени удержания, то дальше возможны варианты
        switch (_longclickmode)
        {
        case LCM_ONLYONCE:
          if (_longclickcount == 0)
          {
            _longclickcount++;
            _btnstate = BTN_LONGCLICK;
          }
          else
          {
            _btnstate = BTN_PRESSED;
          }
          break;
        case LCM_CLICKSERIES:
          if (millis() - lclck_timer >= _longclicktimeout)
          {
            lclck_timer = thisMls;
            _longclickcount++;
            _btnstate = BTN_LONGCLICK;
          }
          else
          {
            _btnstate = BTN_PRESSED;
          }
          break;
        default:
          _btnstate = BTN_LONGCLICK;
          break;
        }
        _clckcount = 0;
      }
    }
  }
  // состояние кнопки изменилось с прошлого опроса
  else
  { // если задано подавление дребезга контактов
    if (_debounce > 0)
    { // если флаг подавления еще не поднят - поднять и больше ничего не делать
      if (!_deb)
      {
        deb_timer = thisMls;
        _deb = true;
      } // иначе, если поднят, и интервал вышел - установить состояние кнопки
      else if (millis() - deb_timer >= _debounce)
      {
        setBtnUpDown(flag, thisMls);
      }
    }
    else // если подавление вообще не задано, то сразу установить состояние кнопки
    {
      setBtnUpDown(flag, thisMls);
    }
  }
  return _btnstate;
}

bool shButton::isButtonClosed()
{
  return _btnstate != BTN_RELEASED && _btnstate != BTN_UP;
}

void shButton::setInputType(byte inputtype)
{
  _inputtype = inputtype;
  switch (inputtype)
  {
  case PULL_UP:
    pinMode(_PIN, INPUT_PULLUP);
    break;
  default:
    pinMode(_PIN, INPUT);
    break;
  }
}

void shButton::setButtonType(byte btntype)
{
  _btntype = btntype;
}

void shButton::setDebounce(word debounce)
{
  _debounce = debounce;
}

void shButton::setTimeout(word new_timeout)
{
  _timeout = new_timeout;
}

void shButton::setDblClickTimeout(word new_timeout)
{
  _dblclck = new_timeout;
}

void shButton::setVirtualClickOn(bool virtualclick)
{
  _virtualclick = virtualclick;
}

void shButton::setLongClickMode(byte longclickmode)
{
  _longclickmode = longclickmode;
  if (_longclickmode == LCM_CLICKSERIES && _longclicktimeout == 0)
  {
    _longclicktimeout = LONGCLICKTIMEOUT;
  }
}

void shButton::setLongClickTimeout(word longclicktimeout)
{
  _longclicktimeout = longclicktimeout;
}

bool shButton::getButtonFlag()
{
  bool val = digitalRead(_PIN);
  if (_inputtype == PULL_UP)
  {
    val = !val;
  }
  if (_btntype == BTN_NC)
  {
    val = !val;
  }
  return val;
}

void shButton::setBtnUpDown(bool flag, unsigned long thisMls)
{
  _deb = false;
  _flag = flag;

  if (flag)
  { // если кнопка только что нажата, то запустить таймер удержания
    btn_timer = thisMls;
    if (_clckcount == 0)
    { // если это первый клик, запустить таймер двойного клика и увеличить счетчик кликов
      _btnstate = BTN_DOWN;
      _clckcount++;
      dbl_timer = thisMls;
    }
    else if (millis() - dbl_timer <= _dblclck)
    {
      _btnstate = BTN_DBLCLICK;
      _clckcount = 0;
    }
  }
  else
  {
    _btnstate = BTN_UP;
  }
}

Файл keywords.txt

#######################################
# Syntax Coloring Map For shButton
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################

shButton	KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################

getButtonState	KEYWORD2
isButtonClosed	KEYWORD2
setDebounce	KEYWORD2
setTimeout	KEYWORD2
setDblClickTimeout	KEYWORD2
setInputType	KEYWORD2
setButtonType	KEYWORD2
setVirtualClickOn	KEYWORD2
setLongClickMode	KEYWORD2
setLongClickTimeout	KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################

PULL_UP	LITERAL1
PULL_DOWN	LITERAL1
BTN_NO	LITERAL1
BTN_NC	LITERAL1
BTN_RELEASED	LITERAL1
BTN_PRESSED	LITERAL1
BTN_UP	LITERAL1
BTN_DOWN	LITERAL1
BTN_DBLCLICK	LITERAL1
BTN_ONECLICK	LITERAL1
BTN_LONGCLICK	LITERAL1
LCM_CONTINUED	LITERAL1
LCM_ONLYONCE	LITERAL1
LCM_CLICKSERIES	LITERAL1

Демо-скетч:

#include <shButton.h>

// демонстрация основных возможностей для работы с тактовой кнопкой

// инициализация кнопки (пин, режим подключения кнопки: PULL_UP (значение по умолчанию) - кнопка подтянута к VCC; PULL_DOWN - кнопка подтянута к GND, тип кнопки: BTN_NO - с нормально разомкнутыми контактами или BTN_NC - с нормально замкнутыми контактами)
shButton but(10, PULL_UP);

void setup()
{
  // режим пина устанавливается автоматически

  // необязательные установки
  but.setDebounce(80); // установка времени антидребезга, мс (по умолчанию 50 мс)
  but.setTimeout(800); // установка времени удержания кнопки нажатой, мс (по умолчанию 500 мс)

  Serial.begin(9600);
}

void loop()
{
  static int8_t oldState = -1;
  // опрос кнопки, для уверенной обработки состояния кнопки опросы нужно делать как можно чаще
  int8_t curState = but.getButtonState();
  if (oldState != curState)
  {
    oldState = curState;
    switch (curState)
    {
      case BTN_RELEASED:
      //  Serial.println("button released");
        break;
      case BTN_UP:
        Serial.println("button up");
        break;
      case BTN_PRESSED:
      //  Serial.println("button pressed");
        break;
      case BTN_LONGCLICK :
        Serial.println("button hold");
        break;
      case BTN_DOWN:
        Serial.println("button down");
        break;
      case BTN_DBLCLICK:
        Serial.println("button dblclick");
        break;
    }
  }
}

Скетч - пример обработки одинарного и двойного клика:

#include <shButton.h>

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

// инициализация кнопки
shButton but(10);

void setup()
{
  // режим пина устанавливается автоматически

  but.setVirtualClickOn(true); // включение режима виртуального клика

  Serial.begin(9600);
}

void loop()
{

  switch (but.getButtonState())
  {
  case BTN_ONECLICK:
    // действие по одинарному клику
    Serial.println("onClick");
    break;
  case BTN_DBLCLICK:
    // действие по двойному клику
    Serial.println("onDblClick");
    break;
  }
}

Скетч - пример обработки удержания кнопки:

#include <shButton.h>

// Пример обработки удержания кнопки нажатой - получение одиночного события или серии событий, пока кнопка нажата

// инициализация кнопки
shButton but_1(10);
shButton but_2(11);

void setup()
{
  // режим пина устанавливается автоматически

  but_1.setLongClickMode(LCM_ONLYONCE);    // включение однократного события при удержании кнопки
  but_2.setLongClickMode(LCM_CLICKSERIES); // включение серии событий при удержании кнопки

  Serial.begin(9600);
}

void loop()
{
  static word count = 0;
  switch (but_1.getButtonState())
  {
  case BTN_LONGCLICK:
    // действие по одиночному событию
    Serial.println("Only Once LongClickEvent");
    break;
  }
  switch (but_2.getButtonState())
  {
  case BTN_DOWN:
    // обнуление счетчика
    count = 0;
    break;
  case BTN_LONGCLICK:
    // действие по серийному событию
    Serial.print(++count);
    Serial.println(" - LongClickEventSeries");
    break;
  }
}

Скачать одним архивом можно здесь

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

Загрузил демку. Пи компиляции сразу сообщение об ошибке на строке "oldState = but.btnState;":

'class shButton' has no member named 'btnState'; did you mean '_btnstate'?

Где в библе капкан искать?

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

В библе все нормально, это в скетче поправить забыл. Нужно так

    oldState = curState;

 

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

v258 пишет:

В библе все нормально, это в скетче поправить забыл. Нужно так

    oldState = curState;

 

Ага, понил. Тока перезалей скетч, а то такие дуболомы, как я, впадают в ступор :)

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Да уже перезалил, спасибо ))

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

v258 пишет:

Да уже перезалил, спасибо ))

Попробовал. Подключил 4 кнопки. Отрабатывает (совершенно адекватно) только одна. К каждой надо свою библиотеку цеплять?

b707
Offline
Зарегистрирован: 26.05.2017

Sonologist пишет:

Попробовал. Подключил 4 кнопки. Отрабатывает (совершенно адекватно) только одна. К каждой надо свою библиотеку цеплять?

приведите код

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Sonologist пишет:

v258 пишет:

Да уже перезалил, спасибо ))

Попробовал. Подключил 4 кнопки. Отрабатывает (совершенно адекватно) только одна. К каждой надо свою библиотеку цеплять?

Для каждой создавать свой экземпляр класса. Давайте свой код

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

b707 пишет:

приведите код

#include <shButton.h>

volatile int pin_but_hea = 50; //Пин кнопки энкодера нагрева
volatile int pin_but_del = 53; //Пин кнопки энкодера дельты 
volatile int pin_but_col = 52; //Пин кнопки энкодера охлаждения
volatile int pin_but_val = 51; //Пин кнопки энкодера клапана 

// инициализация кнопок
shButton but_hea (pin_but_hea);
shButton but_del (pin_but_del);
shButton but_col (pin_but_col);
shButton but_val (pin_but_val);

// Значенния переменных кнопок
volatile int but_hea_val = 0; 
volatile int but_del_val = 0;
volatile int but_col_val = 0;
volatile int but_val_val  = 0;


void setup()
{
  // режим пина устанавливается автоматически
  // включение режима виртуального клика
  but_hea.setVirtualClickOn(true); 
  but_hea.setVirtualClickOn(true); 
  but_hea.setVirtualClickOn(true); 
  but_hea.setVirtualClickOn(true); 
  
  
  
  Serial.begin(115200);
}

void loop()
{
  if (but_hea.getButtonState()==BTN_ONECLICK) 
    {
        but_hea_val++;
    }
  if (but_del.getButtonState()==BTN_ONECLICK) 
    {
        but_del_val++;
    }
  if (but_col.getButtonState()==BTN_ONECLICK) 
    {
        but_col_val++;
    }
  if (but_val.getButtonState()==BTN_ONECLICK) 
    {
        but_val_val++;
    }
  Serial.print  (but_hea_val);
  Serial.print  (but_del_val);
  Serial.print  (but_col_val);
  Serial.println  (but_val_val);

}

 

b707
Offline
Зарегистрирован: 26.05.2017

а что значит "адекватно срабатывает одна кнопка"?

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Неадекватность в чем заключается?

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

b707 пишет:

а что значит "адекватно срабатывает одна кнопка"?

Адекватно - значит безотказно и четко.

 

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

v258 пишет:

Неадекватность в чем заключается?

В том, что меняется значение только переменной but_hea_val. Остальные переменные не меняются при нажатии соответствующих кнопок.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Как подключены кнопки?

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

Все подтянуты к +5v. То есть, при нажатии на пине кнопки имеем LOW. Это имеется ввиду?

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Да, это. В принципе в коде все верно. Если кнопки действительно посажены на обозначенные пины, то должны работать.

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

Кнопки сидят именно на этих пинах. В моей программе все они работают нормально. Просто хотел с помощью shButton немного упростить код. А вот фигу :)

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Щас попробую ваш код воспроизвести. Только у меня три кнопки в наличии, но, думаю, это непринципиально ))

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

v258 пишет:

Щас попробую ваш код воспроизвести. Только у меня три кнопки в наличии, но, думаю, это непринципиально ))

Канешшна не принципиально. Достаточно для пробы и 2 кнопок.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Хе-хе, а вот и не буду пробовать, нашел ваш косяк - посмотрите, какие кнопки вы настроили в сетапе? ))

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Подсказываю - что настроили, то и работает )))

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

v258 пишет:

Хе-хе, а вот и не буду пробовать, нашел ваш косяк - посмотрите, какие кнопки вы настроили в сетапе? ))

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

И спасибо!

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Sonologist пишет:

И спасибо!

Бывает. Не за что ))

Green
Offline
Зарегистрирован: 01.10.2015

Мне нравятся библиотеки на все случаи жизни для ОДНОЙ кнопки. А вот если нужно фиксировать одновременные нажатия?

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Green пишет:

Мне нравятся библиотеки на все случаи жизни для ОДНОЙ кнопки. А вот если нужно фиксировать одновременные нажатия?

Одновременность с точностью до микросекунды? В противном случае никто не мешает вам проводить опрос хоть десяти кнопок за одну итерацию

Green
Offline
Зарегистрирован: 01.10.2015

Боюсь что комбинации нажатий таким способом не получить. Либо ещё нужна будет дополнительная возня.)

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Дык, это библиотека для одиночной кнопки, клавиатуры, что матричные, что резистивные, тут идут мимо ))

Green
Offline
Зарегистрирован: 01.10.2015

Причём здесь матричная. К примеру, у меня 3 "одиночных" кнопки, но я использую комбинации, т.к. без них совсем скучненько бегать по меню.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Тут без дополнительных движений никак ))

btn1.getButtonState();
btn2.getButtonState();
if (btn1.isButtonClosed() && btn2.isButtonClosed())
{
// ваша комбинация
}

 

Green
Offline
Зарегистрирован: 01.10.2015

Дык, кнопки разные и ведут себя по разному, и нажимаются не абсолютно одновременно, со всеми отсюда вытекающими. Вы понимаете?

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Тут понимать нечего - любая клавиатурная комбинация есть КЛИК основной кнопки при условии удерживаемой нажатой кнопки-модификатора. Причем кнопка-модификатор должна быть нажата заранее. Стало быть вам нужно ловить событие клика основной кнопки и проверять, не нажата ли при этом кнопка-модификатор. Примерно так:

void setup()
{
  btnA.setVirtualClickOn(true);
  btnB.setVirtualClickOn(true);
}

void loop()
{
  byte btnA_state = btnA.getButtonState();
  byte btnB_state = btnB.getButtonState();

  switch (btnA_state)
  {
    case BTN_ONECLICK:
      if (btnB.isButtonClosed())
      {
        // здесь клик кнопки А при зажатой В
      } else
      {
        // здесь просто клик кнопки А
      }
  }

  switch (btnB_state)
  {
    case BTN_ONECLICK:
      if (btnA.isButtonClosed())
      {
        // здесь клик кнопки B при зажатой A
      } else
      {
        // здесь просто клик кнопки B
      }
  }
}

 

Green
Offline
Зарегистрирован: 01.10.2015

))Какой ещё модификатор при 3-х кнопках! Нет у меня никакого модификатора. Это непозволительная роскошь. 3 кнопки дают 7 комбинаций, при этом ложных быть не должно. А в вашем варианте они будут, т.к. каждая кнопка сама по себе.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Green пишет:

))Какой ещё модификатор при 3-х кнопках! Нет у меня никакого модификатора. Это непозволительная роскошь. 3 кнопки дают 7 комбинаций, при этом ложных быть не должно. А в вашем варианте они будут, т.к. каждая кнопка сама по себе.

В моем варианте две кнопки дают четыре комбинации. И ничего ложного ))

И да, все кнопки сами по себе - это же не клавиатура

Green
Offline
Зарегистрирован: 01.10.2015

Ничего ложного - это если вы нажимаете их СТРОГО одновременно, и дребезг у всех одинаковый. Иначе сначала будет одна, а затем уже комбинация.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Я взращен ПК и твердо знаю, что одновременно нажимать кнопки - плохая идея. Например, если мне нужно вызвать диалог поиска текста, я должен сначала нажать Ctrl, а затем F. Если я сделаю наоборот, нужный мне диалог не появится. А, нажимая эти клавиши одновременно, я не могу быть уверен, что они сработают в нужной последовательности. И даже когда последовательность вроде бы не важна, все равно лучше ее придерживаться. Например, что Alt+Shift, что Shift+Alt одинаково переключат раскладку клавиатуры, но во втором случае активируются акселераторы главного меню, и вместо продолжения набора текста я внезапно попаду в какой-то из пунктов меню редактора. Если сильно не повезет, то этот пункт еще и сработает. Т.е. и тут последовательность нажатий тоже имеет значение.

Поэтому повторю - ОДНОВРЕМЕННОЕ нажатие кнопок есть ОЧЕНЬ плохая идея. Последовательность действий должна быть строго определенной, а результат - прогнозируемый ))

ЗЫ: в приведенном мною варианте никто не помешает вам поставить в строках 17 и 29 один и тот же обработчик

Green
Offline
Зарегистрирован: 01.10.2015

Так и МК тоже не очень хорошая идея, в некоторых так совсем не развернёшься. И кнопок много иной раз не поставишь, да и по 34 байта ОЗУ на каждую кнопку тоже как то не очень... Но это так, лирика, что б поговорить.)

Logik
Offline
Зарегистрирован: 05.08.2014

Вполне терпимая идея. Есть у меня тоже девайсик на три кнопки. Контроллер самогонного аппарата. Экран, менюшки, переферии куча. Проект в развитии лет 5 уже. По ходу чет добавляется. Удобства тоже. Наращивать клавиатуру вообще нет желания, размер корпуса не позволяет. Для навигации по многоуровневому меню предостаточно. Но есть несколько случаев, когда меню не удобно, а комбинации кнопок - то что надо. Самое банальное - аварийное выключение. Сейчас это сделано как все три кнопки нажато. Это намного быстрей чем в меню лазить. Другой пример - диагностика. Если ниче не нажато работаем сразу, если что зажато - проверяем соответствующую часть. Также частые действия (вкл/выкл звуковой сигнализации) или вызовы редких веток меню на комбинации иногда просятся.

Реализуется это довольно не сложно. И без особых затрат ресурса. Кстати, заодно с поддержкой матриц кнопок легко делается. Абстрактно выглядит так- пишем обработчик 3-х событий: на очередном такте обнаружено нажатие, на очередном такте обнаружено отжатие, такт прошел но состояние не менялось (это событие одно, без номера кнопки и последнее).  И этот обработчик в качестве параметра принимает и номер кнопки к которому относится. Считаем это физические события. Номер кнопки, событие и условное его время запоминаем в очереди событий. Больше нигде. Затем в этой очереди разбираем для данной кнопки какие и когда происходили физические события. И генерим логические события (сразу простейшие, типа кнопка удержана дольше дребезга, затем более сложные вплоть до даблклика). Их тоже в очередь. И сверху ложим разбор комбинаций. Если, к примеру, есть все три физических нажатия за небольшой интервал времени то генерим событие для тройной комбинации и не генерим три логических события нажатия 3-х кнопок. В конце обработки - поток событий валим на обработчик. В общем такая хитрая архитектурка. Не сложно но непревычно.

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

 

 

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Нет, я не утверждаю, что комбинации кнопок - зло. Я писал, что ОДНОВРЕМЕННОЕ нажатие кнопок - плохая идея, т.к. не гарантирует предсказуемого результата. Тем более, что МК все равно опрашивает их по очереди, т.е. неодновременно. А по поводу отработки одновременного нажатия кнопок - см. #39, в строках 17 и 29 ставится одинаковый обработчик и все, даже очередность нажатия влиять не будет. Только двойное срабатывание обработчика придется исключить

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

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

PS: Не знаю, почему получилось таким жирным и крупным шрифтом. Модераторы, если есть возможность, исправьте на штатный шрифт. Заранее спасибо.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Sonologist пишет:

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

Пожалуйста. Кстати, и спасибо за тестирование ))

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Sonologist пишет:

PS: Не знаю, почему получилось таким жирным и крупным шрифтом. Модераторы, если есть возможность, исправьте на штатный шрифт. Заранее спасибо.

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

Sonologist
Sonologist аватар
Offline
Зарегистрирован: 08.06.2018

v258 пишет:

Кстати, и спасибо за тестирование ))

Да пожалуйста, только какой из меня тетильщик? Просто тупо применил и посмотрел на результат.

"Не кочегары мы, не плотники

Для нас наука - целый лес чудес

А мы научные работники

И не хватаем звезд с небес.

В цел'ях природы обуздания

В цел'ях рассеять неученья тьму

Берем картину мироздания

И тупо смотрим, что к чему"

                                                    А. и Б.   Стругацкие

Как-то так и у меня :)