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

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

Sonologist пишет:

какой из меня тетильщик? 

Ничо-ничо! Главное, чтобы костюмчик сидел!

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

По итогам эксплуатации слегка доработал библиотеку.

  + в методе isButtonClosed() добавлен параметр toChecked; при значении true автоматически вызывает процедуру getButtonState() перед определением положения кнопки; при значении false (принято по умолчанию) определение положения кнопки выполняется по результату последнего опроса;
  + добавлен метод resetButtonState(), сбрасывающий счетчик кликов кнопки (_clckcount); метод позволяет исключить возникновение событий BTN_ONECLICK и BTN_DBLCLICK, если они по каким-то причинам в данный момент не желательны;
  + все булевы и часть байтовых полей упакованы в один байт, что позволило уменьшить размер занимаемой кнопкой памяти;
  + добавлен пример обработки одновременного нажатия на две кнопки
 

Файл shButton.h

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

  Версия 1.1
  + в методе isButtonClosed() добавлен параметр toChecked; при значении true автоматически вызывает процедуру getButtonState() перед определением положения кнопки; при значении false (принято по умолчанию) определение положения кнопки выполняется по результату последнего опроса;
  + добавлен метод resetButtonState(), сбрасывающий счетчик кликов кнопки (_clckcount); метод позволяет исключить возникновение событий BTN_ONECLICK и BTN_DBLCLICK, если они по каким-то причинам в данный момент не желательны;
  + все булевы и часть байтовых полей упакованы в один байт, что позволило уменьшить размер занимаемой кнопкой памяти на 4 байта;
*/

// флаги свойств и состояния кнопки - биты поля _flags
#define FLAG_BIT 0         // сохраненное состояние кнопки - нажата/не нажата
#define INPUTTYPE_BIT 1    // тип подключения - PULL_UP/ PULL_DOWN
#define BTNTYPE_BIT 2      // тип кнопки - нормально разомкнутая или нормально замкнутая
#define DEBOUNCE_BIT 3     // флаг включения подавления дребезга
#define VIRTUALCLICK_BIT 4 // режим виртуального клика

// значения по умолчанию
#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 _longclickmode = LCM_CONTINUED;       // режим удержания кнопки;
  word _longclicktimeout = LONGCLICKTIMEOUT; // интервал следования события BTN_LONGCLICK, если установлен режим LCM_CLICKSERIES, мс
  byte _btnstate = BTN_RELEASED;             // текущее состояние кнопки

  byte _flags = 0; // набор флагов свойств и состояния кнопки
  /*
* 0 бит - сохраненное состояние кнопки - нажата(1)/не нажата(0)
* 1 бит - тип подключения - PULL_UP(0)/PULL_DOWN(1)
* 2 бит - тип кнопки - нормально разомкнутая (BTN_NO) или нормально замкнутая (BTN_NC)
* 3 бит - флаг включения подавления дребезга - пока флаг поднят (1), изменения состояния не принимаются
* 4 бит - режим виртуального клика, 0 - выключен, 1 - включен
  */

  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);
  // получение состояния бита
  bool getFlag(byte _bit);
  // установка состояния бита
  void setFlag(byte _bit, bool x);

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, если контакты кнопки по результату последней проверки замкнуты; если toChecked == true будет выполнен опрос кнопки
  bool isButtonClosed(bool toChecked = false);
  // принудительный сброс состояния кнопки; может понадобиться, если по каким-то причинам нужно исключить возникновение событий BTN_ONECLICK и BTN_DBLCLICK
  void resetButtonState();
  // установка типа подключения кнопки (PULL_UP - подтянута к VCC, PULL_DOWN - к GND)
  void setInputType(byte inputtype);
  // установка типа кнопки (BTN_NO - нормально разомкнутая, BTN_NC - нормально замкнутая)
  void setButtonType(byte btntype);
  // установка времени антидребезга (по умолчанию 50 мс); для отключения антидребезга нужно задать 0 мс
  void setDebounce(word debounce);
  // установка таймаута удержания кнопки (по умолчанию 500 мс)
  void setTimeout(word new_timeout);
  // установка интервала двойного клика
  void setDblClickTimeout(word new_timeout);
  // включение режима "Виртуальный клик"
  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 == getFlag(FLAG_BIT))
  { // и не поднят флаг подавления дребезга
    if (!getFlag(DEBOUNCE_BIT))
    {
      if (!flag)
      { // кнопка находится в отжатом состоянии
        _btnstate = BTN_RELEASED;
        if (millis() - dbl_timer > _dblclck)
        { // если период двойного клика закончился, проверить на виртуальный клик и обнулить счетчик кликов
          if (getFlag(VIRTUALCLICK_BIT) && _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 (!getFlag(DEBOUNCE_BIT))
      {
        deb_timer = thisMls;
        setFlag(DEBOUNCE_BIT, true);
      } // иначе, если поднят, и интервал вышел - установить состояние кнопки
      else if (millis() - deb_timer >= _debounce)
      {
        setBtnUpDown(flag, thisMls);
      }
    }
    else // если подавление вообще не задано, то сразу установить состояние кнопки
    {
      setBtnUpDown(flag, thisMls);
    }
  }
  return _btnstate;
}

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

void shButton::resetButtonState()
{
  _clckcount = 0;
}

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

void shButton::setButtonType(byte btntype)
{
  setFlag(BTNTYPE_BIT, 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)
{
  setFlag(VIRTUALCLICK_BIT, virtualclick);
}

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

void shButton::setLongClickTimeout(word longclicktimeout)
{
  _longclicktimeout = longclicktimeout;
  // если установлено нулевое значение, то перевести режим на однократное событие
  if (_longclicktimeout == 0)
  {
    _longclickmode = LCM_ONLYONCE;
  }
}

bool shButton::getButtonFlag()
{
  bool val = digitalRead(_PIN);
  if (getFlag(INPUTTYPE_BIT) == PULL_UP)
  {
    val = !val;
  }
  if (getFlag(BTNTYPE_BIT) == BTN_NC)
  {
    val = !val;
  }
  return val;
}

void shButton::setBtnUpDown(bool flag, unsigned long thisMls)
{
  setFlag(DEBOUNCE_BIT, false);
  setFlag(FLAG_BIT, 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;
  }
}

bool shButton::getFlag(byte _bit)
{
  bool result;
  (_bit < 8) ? result = (((_flags) >> (_bit)) & 0x01) : result = false;
  return (result);
}

void shButton::setFlag(byte _bit, bool x)
{
  if (_bit < 8)
  {
    (x) ? (_flags) |= (1UL << (_bit)) : (_flags) &= ~(1UL << (_bit));
  }
}

Файл keywords.txt

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

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

shButton	KEYWORD1

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

getButtonState	KEYWORD2
isButtonClosed	KEYWORD2
resetButtonState	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 - (pin -> кнопка -> GND); 
	PULL_DOWN - кнопка подтянута к GND - (pin -> кнопка -> VCC;
* тип кнопки: 
	BTN_NO - с нормально разомкнутыми контактами (по умолчанию);
	BTN_NC - с нормально замкнутыми контактами);
*/
shButton but_1(10); // равнозначно shButton but_1(10, PULL_UP, BTN_NO)
shButton but_2(11);

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

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

  Serial.begin(9600);
}

void loop()
{
  byte _but_1 = but_1.getButtonState();
  byte _but_2 = but_2.getButtonState();
  if ((_but_1 == BTN_DOWN && but_2.isButtonClosed()) || (_but_2 == BTN_DOWN && but_1.isButtonClosed()))
  { // действие по одновременно нажатым двум кнопкам
    Serial.println("Two buttons pressed");
    // сбросить состояние обеих кнопок
    but_1.resetButtonState();
    but_2.resetButtonState();
  }
  else
  {
    switch (_but_1)
    { 
    case BTN_ONECLICK:
      if (!but_2.isButtonClosed())
      { // действие по нажатию первой кнопки
        Serial.println("Button No1 pressed");
      }
      break;
    }

    switch (_but_2) // кнопка "-"
    {
    case BTN_ONECLICK:
      if (!but_1.isButtonClosed())
      { // действие по нажатию второй кнопки
        Serial.println("Button No2 pressed");
      }
      break;
    }
  }  
}

Скачать одним файлом

Клапауций 12345
Offline
Зарегистрирован: 17.05.2020

т.е. до того оно работало с одной кнопкой, а теперь может работать аж с двумя?
ок. но Логику не понравится - у него на ногах 10-ть пальцев...
о_О

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

У меня с тремя работает. По отдельности. Но здесь не раз поднимался вопрос про одновременное нажатие, вот я пример и добавил. Да и сам у себя, если честно, в одном приборе использую ))

Клапауций 12345
Offline
Зарегистрирован: 17.05.2020

v258 пишет:

У меня с тремя работает. По отдельности. Но здесь не раз поднимался вопрос про одновременное нажатие, вот я пример и добавил. Да и сам у себя, если честно, в одном приборе использую ))


ок.
откуда, вообще, у кого-то должны возникать сомнения, что оно не будет работать со 100 кнопками, например...
одновременно/неодновременно, отжатие/нажатие - какая вжопу разница.

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

Аминь, бро ))

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

v258 пишет:

Аминь, бро ))

)))

То у него старая рана в нижней части спины ноет. На погоду наверное ;)

Он забульбенил  развесистый класс для кнопки. Ну и когда речь зашла о поддержке сотни кнопок на устройство то фигонул этот класс в массив. На том ОЗУ контроллера и кончилось. До обработки одновременного нажатия дело кажись и не дошло)))) А я ему намекнул что все это делается сильно проще, в полсотни байт ОЗУ влазит, с даблкликами, комбинациями одновременно нажатых кнопок и прочим. Но будет ограничение по кол-ву одновременно нажатых кнопок, например десяток. Так он долго у меня код клянчил. Три года уже прошло, гнали его с форума его пару раз, а ему все чешется )))

Клапа, там все просто! Очередь клавиатурных событий ведется. Типа циклический массив записей время-код события-номер кнопки. А по ней уже и даблклики, и комбинации определяются... А ограничение на кол-во одновременных нажатий - оно из ограничения длины очереди следует. Легко понять что чем больше очередь больше кнопок одновременно приймется. Считай, Клапа, этот абзац гуманитаркой ;)

Клапауций 12345
Offline
Зарегистрирован: 17.05.2020

Logik пишет:

Очередь клавиатурных событий ведется.

снова про жопу и клавиатуру...

КНОПКИ, упоротый норкоман...

...не клавиатура - кнооопки...

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

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

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

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

shButton btn;

И в метод getButtonState нужно передавать состояние контактов кнопки (нажата/не нажата), определенное внешним кодом, например

word res = analogRead(A0);
btn.getButtonState(res == 512);

В остальном работа с виртуальной кнопкой не отличается от работы с обычной

Скачать можно здесь

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Клапауций 12345 пишет:

снова про жопу и клавиатуру...

КНОПКИ, упоротый норкоман...

...не клавиатура - кнооопки...

Вот с этим хокку 1024 раза согласен. Ни разу не встречал проблем с кнопками. Все эти дребезги-хуебезги для холериков. А остальное решается на аппаратном уровне.

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

Еще немного допилил библиотеку:

- Слегка оптимизировал код, объем динамической памяти, занимаемой кнопкой, уменьшен c 30 до 20 байт;
- Добавил примеры работы с виртуальной кнопкой на примере матричной клавиатуры и расширителя портов MCP23017;

Скачать можно здесь

Land
Offline
Зарегистрирован: 24.02.2022

Green пишет:

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

А не поделитесь библиотекой с одновременным нажатием кнопок?

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

Land пишет:

Green пишет:

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

А не поделитесь библиотекой с одновременным нажатием кнопок?

https://github.com/VAleSh-Soft/shButton/tree/master/examples/PressTwoButton

Land
Offline
Зарегистрирован: 24.02.2022

Спасибо большое.