From 5c6d3e311a1279e3b57036b79d304dbc94af6f3b Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sat, 2 Dec 2023 12:59:42 +0100 Subject: [PATCH] OneButtonTiny added --- README.md | 24 +++ examples/BlinkMachine/BlinkMachine.ino | 68 +++++--- src/OneButtonTiny.cpp | 221 +++++++++++++++++++++++++ src/OneButtonTiny.h | 172 +++++++++++++++++++ 4 files changed, 458 insertions(+), 27 deletions(-) create mode 100644 src/OneButtonTiny.cpp create mode 100644 src/OneButtonTiny.h diff --git a/README.md b/README.md index 6969a0c..27e9047 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,30 @@ a copy of this library. You can find more detail about installing libraries Each physical button requires its own `OneButton` instance. You can initialize them like this: + +### OneButton Tiny version + +The OneButton Library was extended over time with functionality that was requested for specific +use cases. This makes the library growing over time too and therefore was limiting use cases using very small processors like attiny84. + +Staring with version 2.5 the OneButton Library starts supporting these processors with limited +memory and low cpu frequencies by introducing the `OneButtonTiny` class that offers a subset of +the features of the complete `OneButton` class by exposing the following events as callbacks: + +* Click event +* DoubleClick event +* LongPressStart event +* Callbacks without parameters + +This saves up to 1k of binary program space that is a huge amount on these processors. + +With Version 2.5 the `OneButtonTiny` class is now in a beta state. + +* Any Issues or pull requests fixing problems are welcome. +* Any new feature request for the `OneButtonTiny` class will be rejected to keep size small. +* New, reasonable functionality will be added to the OneButton class only. + + ### Initialize a Button to GND ```CPP diff --git a/examples/BlinkMachine/BlinkMachine.ino b/examples/BlinkMachine/BlinkMachine.ino index 05a54a7..9c91608 100644 --- a/examples/BlinkMachine/BlinkMachine.ino +++ b/examples/BlinkMachine/BlinkMachine.ino @@ -28,9 +28,9 @@ -------- ------ | | OFF |<--click-+->| ON | | -------- | ------ | - | | | - | d-click | - | | | + ^ | | | + | | d-click | + longpress | | | | V | | ------ | +- | SLOW | | @@ -47,22 +47,27 @@ // 06.10.2012 created by Matthias Hertel // 26.03.2017 state diagram added, minor changes -#include "OneButton.h" +// #include "OneButton.h" +#include "OneButtonTiny.h" // This example also works with reduced OneButtonTiny class saving. // The actions I ca do... typedef enum { - ACTION_OFF, // set LED "OFF". - ACTION_ON, // set LED "ON" - ACTION_SLOW, // blink LED "SLOW" - ACTION_FAST // blink LED "FAST" -} -MyActions; + ACTION_OFF, // set LED "OFF". + ACTION_ON, // set LED "ON" + ACTION_SLOW, // blink LED "SLOW" + ACTION_FAST // blink LED "FAST" +} MyActions; #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY) // Example for Arduino UNO with input button on pin 2 and builtin LED on pin 13 #define PIN_INPUT A1 #define PIN_LED 13 +#elif defined(ARDUINO_attiny) +// Example for Arduino UNO with input button on pin 2 and builtin LED on pin 13 +#define PIN_INPUT A1 +#define PIN_LED 13 + #elif defined(ESP8266) // Example for NodeMCU with input button using FLASH button on D3 and using the led on -12 module (D4). // This LED is lighting on output level LOW. @@ -78,36 +83,40 @@ MyActions; #endif -// Setup a new OneButton on pin PIN_INPUT. -OneButton button(PIN_INPUT, true); +// Setup a new OneButton on pin PIN_INPUT. +// OneButton button(PIN_INPUT, true); +OneButtonTiny button(PIN_INPUT, true); // This example also works with reduced OneButtonTiny class saving. -MyActions nextAction = ACTION_OFF; // no action when starting +MyActions nextAction = ACTION_OFF; // no action when starting // setup code here, to run once. void setup() { // enable the standard led on pin 13. - pinMode(PIN_LED, OUTPUT); // sets the digital pin as output + pinMode(PIN_LED, OUTPUT); // sets the digital pin as output - // link the myClickFunction function to be called on a click event. + // link the myClickFunction function to be called on a click event. button.attachClick(myClickFunction); - // link the doubleclick function to be called on a doubleclick event. + // link the doubleclick function to be called on a doubleclick event. button.attachDoubleClick(myDoubleClickFunction); + // link the doubleclick function to be called on a doubleclick event. + button.attachLongPressStart(myDoubleClickFunction); + // set 80 msec. debouncing time. Default is 50 msec. button.setDebounceMs(80); -} // setup +} // setup -// main code here, to run repeatedly: +// main code here, to run repeatedly: void loop() { unsigned long now = millis(); // keep watching the push button: button.tick(); - // You can implement other code in here or just wait a while + // You can implement other code in here or just wait a while if (nextAction == ACTION_OFF) { // do nothing. @@ -123,7 +132,7 @@ void loop() { digitalWrite(PIN_LED, LOW); } else { digitalWrite(PIN_LED, HIGH); - } // if + } // if } else if (nextAction == ACTION_FAST) { // do a fast blinking @@ -131,9 +140,9 @@ void loop() { digitalWrite(PIN_LED, LOW); } else { digitalWrite(PIN_LED, HIGH); - } // if - } // if -} // loop + } // if + } // if +} // loop // this function will be called when the button was pressed 1 time and them some time has passed. @@ -142,7 +151,7 @@ void myClickFunction() { nextAction = ACTION_ON; else nextAction = ACTION_OFF; -} // myClickFunction +} // myClickFunction // this function will be called when the button was pressed 2 times in a short timeframe. @@ -155,8 +164,13 @@ void myDoubleClickFunction() { } else if (nextAction == ACTION_FAST) { nextAction = ACTION_ON; - } // if -} // myDoubleClickFunction + } // if +} // myDoubleClickFunction -// End +// this function will be called when a long press was detected. +void myLongPressFunction() { + nextAction = ACTION_OFF; +} // myLongPressFunction + +// End diff --git a/src/OneButtonTiny.cpp b/src/OneButtonTiny.cpp new file mode 100644 index 0000000..2197a33 --- /dev/null +++ b/src/OneButtonTiny.cpp @@ -0,0 +1,221 @@ +/** + * @file OneButton.cpp + * + * @brief Library for detecting button clicks, doubleclicks and long press + * pattern on a single button. + * + * @author Matthias Hertel, https://www.mathertel.de + * @Copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. + * Ihor Nehrutsa, Ihor.Nehrutsa@gmail.com + * + * This work is licensed under a BSD style license. See + * http://www.mathertel.de/License.aspx + * + * More information on: https://www.mathertel.de/Arduino/OneButtonLibrary.aspx + * + * Changelog: see OneButtonTiny.h + */ + +#include "OneButtonTiny.h" + +// ----- Initialization and Default Values ----- + +/** + * Initialize the OneButton library. + * @param pin The pin to be used for input from a momentary button. + * @param activeLow Set to true when the input level is LOW when the button is pressed, Default is true. + * @param pullupActive Activate the internal pullup when available. Default is true. + */ +OneButtonTiny::OneButtonTiny(const int pin, const boolean activeLow, const bool pullupActive) { + _pin = pin; + + if (activeLow) { + // the button connects the input pin to GND when pressed. + _buttonPressed = LOW; + + } else { + // the button connects the input pin to VCC when pressed. + _buttonPressed = HIGH; + } + + if (pullupActive) { + // use the given pin as input and activate internal PULLUP resistor. + pinMode(pin, INPUT_PULLUP); + } else { + // use the given pin as input + pinMode(pin, INPUT); + } +} // OneButton + + +// explicitly set the number of millisec that have to pass by before a click is assumed stable. +void OneButtonTiny::setDebounceMs(const unsigned int ms) { + _debounce_ms = ms; +} // setDebounceMs + + +// explicitly set the number of millisec that have to pass by before a click is detected. +void OneButtonTiny::setClickMs(const unsigned int ms) { + _click_ms = ms; +} // setClickMs + + +// explicitly set the number of millisec that have to pass by before a long button press is detected. +void OneButtonTiny::setPressMs(const unsigned int ms) { + _press_ms = ms; +} // setPressMs + + +// save function for click event +void OneButtonTiny::attachClick(callbackFunction newFunction) { + _clickFunc = newFunction; +} // attachClick + + +// save function for doubleClick event +void OneButtonTiny::attachDoubleClick(callbackFunction newFunction) { + _doubleClickFunc = newFunction; +} // attachDoubleClick + + +// save function for longPressStart event +void OneButtonTiny::attachLongPressStart(callbackFunction newFunction) { + _longPressStartFunc = newFunction; +} // attachLongPressStart + + +void OneButtonTiny::reset(void) { + _state = OneButtonTiny::OCS_INIT; + _nClicks = 0; + _startTime = 0; +} + + +/** + * @brief Debounce input pin level for use in SpecialInput. + */ +int OneButtonTiny::debounce(const int value) { + now = millis(); // current (relative) time in msecs. + if (_lastDebouncePinLevel == value) { + if (now - _lastDebounceTime >= _debounce_ms) + debouncedPinLevel = value; + } else { + _lastDebounceTime = now; + _lastDebouncePinLevel = value; + } + return debouncedPinLevel; +}; + + +/** + * @brief Check input of the configured pin, + * debounce input pin level and then + * advance the finite state machine (FSM). + */ +void OneButtonTiny::tick(void) { + if (_pin >= 0) { + _fsm(debounce(digitalRead(_pin)) == _buttonPressed); + } +} // tick() + + +void OneButtonTiny::tick(bool activeLevel) { + _fsm(debounce(activeLevel)); +} + + +/** + * @brief Advance to a new state and save the last one to come back in cas of bouncing detection. + */ +void OneButtonTiny::_newState(stateMachine_t nextState) { + _state = nextState; +} // _newState() + + +/** + * @brief Run the finite state machine (FSM) using the given level. + */ +void OneButtonTiny::_fsm(bool activeLevel) { + unsigned long waitTime = (now - _startTime); + + // Implementation of the state machine + switch (_state) { + case OneButtonTiny::OCS_INIT: + // waiting for level to become active. + if (activeLevel) { + _newState(OneButtonTiny::OCS_DOWN); + _startTime = now; // remember starting time + _nClicks = 0; + } // if + break; + + case OneButtonTiny::OCS_DOWN: + // waiting for level to become inactive. + + if (!activeLevel) { + _newState(OneButtonTiny::OCS_UP); + _startTime = now; // remember starting time + + } else if ((activeLevel) && (waitTime > _press_ms)) { + if (_longPressStartFunc) _longPressStartFunc(); + _newState(OneButtonTiny::OCS_PRESS); + } // if + break; + + case OneButtonTiny::OCS_UP: + // level is inactive + + // count as a short button down + _nClicks++; + _newState(OneButtonTiny::OCS_COUNT); + break; + + case OneButtonTiny::OCS_COUNT: + // dobounce time is over, count clicks + + if (activeLevel) { + // button is down again + _newState(OneButtonTiny::OCS_DOWN); + _startTime = now; // remember starting time + + } else if ((waitTime >= _click_ms) || (_nClicks == 2)) { + // now we know how many clicks have been made. + + if (_nClicks == 1) { + // this was 1 click only. + if (_clickFunc) _clickFunc(); + + } else if (_nClicks == 2) { + // this was a 2 click sequence. + if (_doubleClickFunc) _doubleClickFunc(); + + } // if + + reset(); + } // if + break; + + case OneButtonTiny::OCS_PRESS: + // waiting for pin being release after long press. + + if (!activeLevel) { + _newState(OneButtonTiny::OCS_PRESSEND); + _startTime = now; + } // if + break; + + case OneButtonTiny::OCS_PRESSEND: + // button was released. + reset(); + break; + + default: + // unknown state detected -> reset state machine + _newState(OneButtonTiny::OCS_INIT); + break; + } // if + +} // OneButton.tick() + + +// end. diff --git a/src/OneButtonTiny.h b/src/OneButtonTiny.h new file mode 100644 index 0000000..4a1990b --- /dev/null +++ b/src/OneButtonTiny.h @@ -0,0 +1,172 @@ +// ----- +// OneButtonTiny.h - Library for detecting button clicks, doubleclicks and long +// press pattern on a single button. This class is implemented for use with the +// Arduino environment. Copyright (c) by Matthias Hertel, +// http://www.mathertel.de This work is licensed under a BSD style license. See +// http://www.mathertel.de/License.aspx More information on: +// http://www.mathertel.de/Arduino +// ----- +// 01.12.2023 created from OneButtonTiny to support tiny environments. +// ----- + +#ifndef OneButtonTiny_h +#define OneButtonTiny_h + +#include "Arduino.h" + +// ----- Callback function types ----- + +extern "C" { + typedef void (*callbackFunction)(void); +} + + +class OneButtonTiny { +public: + // ----- Constructor ----- + + /** + * Initialize the OneButtonTiny library. + * @param pin The pin to be used for input from a momentary button. + * @param activeLow Set to true when the input level is LOW when the button is pressed, Default is true. + * @param pullupActive Activate the internal pullup when available. Default is true. + */ + explicit OneButtonTiny(const int pin, const boolean activeLow = true, const bool pullupActive = true); + + /** + * set # millisec after safe click is assumed. + */ + void setDebounceMs(const unsigned int ms); + + /** + * set # millisec after single click is assumed. + */ + void setClickMs(const unsigned int ms); + + /** + * set # millisec after press is assumed. + */ + void setPressMs(const unsigned int ms); + + // ----- Attach events functions ----- + + /** + * Attach an event to be called when a single click is detected. + * @param newFunction This function will be called when the event has been detected. + */ + void attachClick(callbackFunction newFunction); + + /** + * Attach an event to be called after a double click is detected. + * @param newFunction This function will be called when the event has been detected. + */ + void attachDoubleClick(callbackFunction newFunction); + + /** + * Attach an event to be called after a multi click is detected. + * @param newFunction This function will be called when the event has been detected. + */ + void attachMultiClick(callbackFunction newFunction); + + /** + * Attach an event to fire when the button is pressed and held down. + * @param newFunction + */ + void attachLongPressStart(callbackFunction newFunction); + + + // ----- State machine functions ----- + + /** + * @brief Call this function every some milliseconds for checking the input + * level at the initialized digital pin. + */ + void tick(void); + + /** + * @brief Call this function every time the input level has changed. + * Using this function no digital input pin is checked because the current + * level is given by the parameter. + * Run the finite state machine (FSM) using the given level. + */ + void tick(bool level); + + + /** + * Reset the button state machine. + */ + void reset(void); + + + /** + * @return true if we are currently handling button press flow + * (This allows power sensitive applications to know when it is safe to power down the main CPU) + */ + bool isIdle() const { + return _state == OCS_INIT; + } + + +private: + int _pin = -1; // hardware pin number. + unsigned int _debounce_ms = 50; // number of msecs for debounce times. + unsigned int _click_ms = 400; // number of msecs before a click is detected. + unsigned int _press_ms = 800; // number of msecs before a long button press is detected + + int _buttonPressed = 0; // this is the level of the input pin when the button is pressed. + // LOW if the button connects the input pin to GND when pressed. + // HIGH if the button connects the input pin to VCC when pressed. + + // These variables will hold functions acting as event source. + callbackFunction _clickFunc = NULL; + callbackFunction _doubleClickFunc = NULL; + callbackFunction _longPressStartFunc = NULL; + + // These variables that hold information across the upcoming tick calls. + // They are initialized once on program start and are updated every time the + // tick function is called. + + // define FiniteStateMachine + enum stateMachine_t : int { + OCS_INIT = 0, + OCS_DOWN = 1, // button is down + OCS_UP = 2, // button is up + OCS_COUNT = 3, + OCS_PRESS = 6, // button is hold down + OCS_PRESSEND = 7, + }; + + /** + * Run the finite state machine (FSM) using the given level. + */ + void _fsm(bool activeLevel); + + /** + * Advance to a new state. + */ + void _newState(stateMachine_t nextState); + + stateMachine_t _state = OCS_INIT; + + int debouncedPinLevel = -1; + int _lastDebouncePinLevel = -1; // used for pin debouncing + unsigned long _lastDebounceTime = 0; // millis() + unsigned long now = 0; // millis() + + unsigned long _startTime = 0; // start of current input change to checking debouncing + int _nClicks = 0; // count the number of clicks with this variable + +public: + int pin() const { + return _pin; + }; + stateMachine_t state() const { + return _state; + }; + int debounce(const int value); + int debouncedValue() const { + return debouncedPinLevel; + }; +}; + +#endif