From d392b4b4aaa353326b533430b0393b6a494d0d02 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Tue, 19 Jan 2021 11:11:01 +0100 Subject: [PATCH 01/16] SimpleOneButton example for NodeMCU working. --- examples/SimpleOneButton/SimpleOneButton.ino | 28 +++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/SimpleOneButton/SimpleOneButton.ino b/examples/SimpleOneButton/SimpleOneButton.ino index 3ce5022..0e92746 100644 --- a/examples/SimpleOneButton/SimpleOneButton.ino +++ b/examples/SimpleOneButton/SimpleOneButton.ino @@ -18,19 +18,26 @@ #include "OneButton.h" -// Example for Arduino UNO with input button on A1 +#if defined(ARDUINO_AVR_UNO) +// Example for Arduino UNO with input button on A1 and builtin LED on pin 13 #define PIN_INPUT A1 #define PIN_LED 13 -// Example for ESP8266 with input button on D5 and using the led on -12 module. -// #define PIN_INPUT D5 -// #define PIN_LED D4 +#else if defined(ESP8266) +// Example for NodeMCU with input button using FLASH button on D3 and using the led on -12 module (D4). +#define PIN_INPUT D3 +#define PIN_LED D4 + +#endif // Setup a new OneButton on pin PIN_INPUT // The 2. parameter activeLOW is true, because external wiring sets the button to LOW when pressed. OneButton button(PIN_INPUT, true); +// current LED state, staring with LOW (0) +bool ledState = LOW; + // In case the momentary button puts the input to HIGH when pressed: // The 2. parameter activeLOW is false when the external wiring sets the button to HIGH when pressed. // The 3. parameter can be used to disable the PullUp . @@ -46,9 +53,7 @@ void setup() pinMode(PIN_LED, OUTPUT); // sets the digital pin as output // link the doubleclick function to be called on a doubleclick event. - button.attachDoubleClick(doubleclick); - - + button.attachDoubleClick(doubleClick); } // setup @@ -64,12 +69,11 @@ void loop() // this function will be called when the button was pressed 2 times in a short timeframe. -void doubleclick() + +void doubleClick() { - static int m = LOW; - // reverse the LED - m = !m; - digitalWrite(PIN_LED, m); + ledState = ! ledState; // reverse the LED + digitalWrite(LED_BUILTIN, ledState); } // doubleclick // End \ No newline at end of file From 42d56ce52ac1648b430cea9c6426f6d4eee1f416 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Tue, 19 Jan 2021 11:13:26 +0100 Subject: [PATCH 02/16] use enum stateMachine_t instead of numbers. --- src/OneButton.cpp | 36 +++++++++++++++---------------- src/OneButton.h | 55 ++++++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/OneButton.cpp b/src/OneButton.cpp index d398807..108c478 100644 --- a/src/OneButton.cpp +++ b/src/OneButton.cpp @@ -34,7 +34,7 @@ OneButton::OneButton() * @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. */ -OneButton::OneButton(int pin, boolean activeLow, bool pullupActive) +OneButton::OneButton(const int pin, const boolean activeLow, const bool pullupActive) { // OneButton(); _pin = pin; @@ -60,14 +60,14 @@ OneButton::OneButton(int pin, boolean activeLow, bool pullupActive) // explicitly set the number of millisec that have to pass by before a click is // assumed as safe. -void OneButton::setDebounceTicks(int ticks) +void OneButton::setDebounceTicks(const int ticks) { _debounceTicks = ticks; } // setDebounceTicks // explicitly set the number of millisec that have to pass by before a click is // detected. -void OneButton::setClickTicks(int ticks) +void OneButton::setClickTicks(const int ticks) { _clickTicks = ticks; } // setClickTicks @@ -75,7 +75,7 @@ void OneButton::setClickTicks(int ticks) // explicitly set the number of millisec that have to pass by before a long // button press is detected. -void OneButton::setPressTicks(int ticks) +void OneButton::setPressTicks(const int ticks) { _pressTicks = ticks; } // setPressTicks @@ -176,7 +176,7 @@ int OneButton::getPressedTicks() void OneButton::reset(void) { - _state = 0; // restart. + _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. _startTime = 0; _stopTime = 0; _isLongPressed = false; @@ -204,22 +204,22 @@ void OneButton::tick(bool activeLevel) // Implementation of the state machine - if (_state == 0) { // waiting for menu pin being pressed. + if (_state == OneButton::WAIT_FOR_INITIAL_PRESS) { // waiting for menu pin being pressed. if (activeLevel) { - _state = 1; // step to state 1 + _state = OneButton::DEBOUNCE_OR_LONG_PRESS; // step to state 1 _startTime = now; // remember starting time } // if - } else if (_state == 1) { // waiting for menu pin being released. + } else if (_state == OneButton::DEBOUNCE_OR_LONG_PRESS) { // waiting for menu pin being released. if ((!activeLevel) && ((unsigned long)(now - _startTime) < _debounceTicks)) { // button was released to quickly so I assume some debouncing. // go back to state 0 without calling a function. - _state = 0; + _state = OneButton::WAIT_FOR_INITIAL_PRESS; } else if (!activeLevel) { - _state = 2; // step to state 2 + _state = OneButton::DETECT_CLICK; // step to state 2 _stopTime = now; // remember stopping time } else if ((activeLevel) && @@ -236,7 +236,7 @@ void OneButton::tick(bool activeLevel) _duringLongPressFunc(); if (_paramDuringLongPressFunc) _paramDuringLongPressFunc(_duringLongPressFuncParam); - _state = 6; // step to state 6 + _state = OneButton::LONG_PRESS; // step to state 6 } else { // Button was pressed down. wait. Stay in this state. // if a pressStart event is registered, call it: @@ -244,7 +244,7 @@ void OneButton::tick(bool activeLevel) _pressStartFunc(); } // if - } else if (_state == 2) { + } else if (_state == OneButton::DETECT_CLICK) { // waiting for menu pin being pressed the second time or timeout. if ((_doubleClickFunc == NULL && _paramDoubleClickFunc == NULL) || (unsigned long)(now - _startTime) > _clickTicks) { @@ -253,15 +253,15 @@ void OneButton::tick(bool activeLevel) _clickFunc(); if (_paramClickFunc) _paramClickFunc(_clickFuncParam); - _state = 0; // restart. + _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. } else if ((activeLevel) && ((unsigned long)(now - _stopTime) > _debounceTicks)) { - _state = 3; // step to state 3 + _state = OneButton::COUNT_CLICKS; // step to state 3 _startTime = now; // remember starting time } // if - } else if (_state == 3) { // waiting for menu pin being released finally. + } else if (_state == OneButton::COUNT_CLICKS) { // waiting for menu pin being released finally. // Stay here for at least _debounceTicks because else we might end up in // state 1 if the button bounces for too long. if ((!activeLevel) && @@ -272,10 +272,10 @@ void OneButton::tick(bool activeLevel) _doubleClickFunc(); if (_paramDoubleClickFunc) _paramDoubleClickFunc(_doubleClickFuncParam); - _state = 0; // restart. + _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. } // if - } else if (_state == 6) { + } else if (_state == OneButton::LONG_PRESS) { // waiting for menu pin being release after long press. if (!activeLevel) { _isLongPressed = false; // Keep track of long press state @@ -284,7 +284,7 @@ void OneButton::tick(bool activeLevel) _longPressStopFunc(); if (_paramLongPressStopFunc) _paramLongPressStopFunc(_longPressStopFuncParam); - _state = 0; // restart. + _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. } else { // button is being long pressed _stopTime = now; // remember stopping time diff --git a/src/OneButton.h b/src/OneButton.h index f566a38..6379ba4 100644 --- a/src/OneButton.h +++ b/src/OneButton.h @@ -29,7 +29,7 @@ extern "C" { typedef void (*callbackFunction)(void); -typedef void (*parameterizedCallbackFunction)(void*); +typedef void (*parameterizedCallbackFunction)(void *); } @@ -45,38 +45,38 @@ class OneButton * @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. */ - OneButton(int pin, boolean activeLow = true, bool pullupActive = true); + OneButton(const int pin, const boolean activeLow = true, const bool pullupActive = true); // ----- Set runtime parameters ----- /** * set # millisec after safe click is assumed. */ - void setDebounceTicks(int ticks); + void setDebounceTicks(const int ticks); /** * set # millisec after single click is assumed. */ - void setClickTicks(int ticks); + void setClickTicks(const int ticks); /** * set # millisec after press is assumed. */ - void setPressTicks(int ticks); + void setPressTicks(const int ticks); /** * Attach an event to be called when a single click is detected. * @param newFunction */ void attachClick(callbackFunction newFunction); - void attachClick(parameterizedCallbackFunction newFunction, void* parameter); + void attachClick(parameterizedCallbackFunction newFunction, void *parameter); /** * Attach an event to be called after a double click is detected. * @param newFunction */ void attachDoubleClick(callbackFunction newFunction); - void attachDoubleClick(parameterizedCallbackFunction newFunction, void* parameter); + void attachDoubleClick(parameterizedCallbackFunction newFunction, void *parameter); /** * @deprecated Replaced by longPressStart, longPressStop, and duringLongPress. @@ -95,21 +95,21 @@ class OneButton * @param newFunction */ void attachLongPressStart(callbackFunction newFunction); - void attachLongPressStart(parameterizedCallbackFunction newFunction, void* parameter); + void attachLongPressStart(parameterizedCallbackFunction newFunction, void *parameter); /** * Attach an event to fire as soon as the button is released after a long press. * @param newFunction */ void attachLongPressStop(callbackFunction newFunction); - void attachLongPressStop(parameterizedCallbackFunction newFunction, void* parameter); + void attachLongPressStop(parameterizedCallbackFunction newFunction, void *parameter); /** * Attach an event to fire periodically while the button is held down. * @param newFunction */ void attachDuringLongPress(callbackFunction newFunction); - void attachDuringLongPress(parameterizedCallbackFunction newFunction, void* parameter); + void attachDuringLongPress(parameterizedCallbackFunction newFunction, void *parameter); // ----- State machine functions ----- @@ -144,12 +144,12 @@ class OneButton void reset(void); private: - int _pin; // hardware pin number. + int _pin; // hardware pin number. unsigned int _debounceTicks = 50; // number of ticks for debounce times. - unsigned int _clickTicks = 600; // number of ticks that have to pass by - // before a click is detected. - unsigned int _pressTicks = 1000; // number of ticks that have to pass by - // before a long button press is detected + unsigned int _clickTicks = 600; // number of ticks that have to pass by + // before a click is detected. + unsigned int _pressTicks = 1000; // number of ticks that have to pass by + // before a long button press is detected int _buttonPressed; @@ -158,33 +158,44 @@ class OneButton // These variables will hold functions acting as event source. callbackFunction _clickFunc = NULL; parameterizedCallbackFunction _paramClickFunc = NULL; - void* _clickFuncParam = NULL; + void *_clickFuncParam = NULL; callbackFunction _doubleClickFunc = NULL; parameterizedCallbackFunction _paramDoubleClickFunc = NULL; - void* _doubleClickFuncParam = NULL; + void *_doubleClickFuncParam = NULL; callbackFunction _pressFunc = NULL; callbackFunction _pressStartFunc = NULL; callbackFunction _longPressStartFunc = NULL; parameterizedCallbackFunction _paramLongPressStartFunc = NULL; - void* _longPressStartFuncParam = NULL; + void *_longPressStartFuncParam = NULL; callbackFunction _longPressStopFunc = NULL; parameterizedCallbackFunction _paramLongPressStopFunc = NULL; - void* _longPressStopFuncParam; + void *_longPressStopFuncParam; callbackFunction _duringLongPressFunc = NULL; parameterizedCallbackFunction _paramDuringLongPressFunc = NULL; - void* _duringLongPressFuncParam = NULL; + void *_duringLongPressFuncParam = 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. - int _state = 0; + + // define FiniteStateMachine + enum stateMachine_t : int { + WAIT_FOR_INITIAL_PRESS = 0, + DEBOUNCE_OR_LONG_PRESS = 1, + DETECT_CLICK = 2, + COUNT_CLICKS = 3, + LONG_PRESS = 6 + }; + + stateMachine_t _state = WAIT_FOR_INITIAL_PRESS; + unsigned long _startTime; // will be set in state 1 - unsigned long _stopTime; // will be set in state 2 + unsigned long _stopTime; // will be set in state 2 }; #endif From 478620ed6a412ee4ad6ca7a15b55a24ac958c94f Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Tue, 19 Jan 2021 11:15:10 +0100 Subject: [PATCH 03/16] docu --- src/OneButton.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OneButton.h b/src/OneButton.h index 6379ba4..7abf2ba 100644 --- a/src/OneButton.h +++ b/src/OneButton.h @@ -18,6 +18,7 @@ // sources of input. // 26.09.2018 Initialization moved into class declaration. // 26.09.2018 Jay M Ericsson: compiler warnings removed. +// 29.01.2020 improvements from ShaggyDog18 // ----- #ifndef OneButton_h From 77674860ebf6360b01a5dd8d73895fbf0797b1a6 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 22 Jan 2021 18:12:26 +0100 Subject: [PATCH 04/16] Interrupt example without UNO-register mods. --- .../InterruptOneButton/InterruptOneButton.ino | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/examples/InterruptOneButton/InterruptOneButton.ino b/examples/InterruptOneButton/InterruptOneButton.ino index 551f2e7..2364146 100644 --- a/examples/InterruptOneButton/InterruptOneButton.ino +++ b/examples/InterruptOneButton/InterruptOneButton.ino @@ -17,50 +17,89 @@ The Sketch shows how to setup the library and bind a special function to the doubleclick event. By using interrupts the doubleclick function will be called not from the main program. */ - + // 03.03.2011 created by Matthias Hertel // 01.12.2011 extension changed to work with the Arduino 1.0 environment // 04.11.2017 Interrupt version created. +// 04.11.2017 Interrupt version using attachInterrupt. #include "OneButton.h" -// Setup a new OneButton on pin A1. -OneButton button(A1, true); +#if defined(ARDUINO_AVR_UNO) +// Example for Arduino UNO with input button on pin 2 and builtin LED on pin 13 +// attachInterrupt only supports pin 2 and 3 on UNO. +// See https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/ +#define PIN_INPUT 2 +#define PIN_LED 13 + +#else if defined(ESP8266) +// #error This Example uses interrupts NodeMCU with input button using FLASH button on D3 and using the led on -12 module (D4). +#define PIN_INPUT D3 +#define PIN_LED D4 + +#endif + + +// Setup a new OneButton on pin A1. +OneButton button(PIN_INPUT, true); + + +// This function is called when the signal on the PIN_INPUT has changed. +void checkTicks() +{ + // include all buttons here to be checked + button.tick(); // just call tick() to check the state. +} // setup code here, to run once: -void setup() { - // enable the standard led on pin 13. - pinMode(13, OUTPUT); // sets the digital pin as output - - // link the doubleclick function to be called on a doubleclick event. +void setup() +{ + Serial.begin(115200); + Serial.println("One Button Example with interrupts."); + + pinMode(PIN_LED, OUTPUT); // sets the digital pin for LED as output + + // setup interrupt routine + pinMode(PIN_INPUT, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(PIN_INPUT), checkTicks, CHANGE); + + // link the doubleclick function to be called on a doubleclick event. button.attachDoubleClick(doubleclick); + // A1-Option: + // it is possible to use e.g. A1 but then some direct register modifications and an ISR has to be used: // You may have to modify the next 2 lines if using another pin than A1 - PCICR |= (1 << PCIE1); // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C. - PCMSK1 |= (1 << PCINT9); // This enables the interrupt for pin 1 of Port C: This is A1. -} // setup + // PCICR |= (1 << PCIE1); // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C. + // PCMSK1 |= (1 << PCINT9); // This enables the interrupt for pin 1 of Port C: This is A1. +} // setup +// A1-Option: // The Interrupt Service Routine for Pin Change Interrupt 1 // This routine will only be called on any signal change on A1: exactly where we need to check. -ISR(PCINT1_vect) { - // keep watching the push button: - button.tick(); // just call tick() to check the state. -} +// ISR(PCINT1_vect) +// { +// // keep watching the push button: +// button.tick(); // just call tick() to check the state. +// } -// main code here, to run repeatedly: -void loop() { - // You can implement other code in here or just wait a while +// main code here, to run repeatedly: +void loop() +{ + // You can implement other code in here or just wait a while delay(10); } // loop // this function will be called when the button was pressed 2 times in a short timeframe. -void doubleclick() { +void doubleclick() +{ + Serial.println("x2"); + static int m = LOW; - // reverse the LED + // reverse the LED m = !m; digitalWrite(13, m); } // doubleclick From c8da5fd0f3b4c03e1272762b57f64aa8eaaced7f Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 22 Jan 2021 18:24:09 +0100 Subject: [PATCH 05/16] Minor example code improvements. --- .../InterruptOneButton/InterruptOneButton.ino | 33 ++++++++++++------- examples/SimpleOneButton/SimpleOneButton.ino | 18 +++++----- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/examples/InterruptOneButton/InterruptOneButton.ino b/examples/InterruptOneButton/InterruptOneButton.ino index 2364146..e5e3f5e 100644 --- a/examples/InterruptOneButton/InterruptOneButton.ino +++ b/examples/InterruptOneButton/InterruptOneButton.ino @@ -33,18 +33,27 @@ #define PIN_LED 13 #else if defined(ESP8266) -// #error This Example uses interrupts NodeMCU with input button using FLASH button on D3 and using the led on -12 module (D4). +// Example for NodeMCU with input button using FLASH button on D3 and using the led on -12 module (D4). #define PIN_INPUT D3 #define PIN_LED D4 #endif - -// Setup a new OneButton on pin A1. +// Setup a new OneButton on pin PIN_INPUT +// The 2. parameter activeLOW is true, because external wiring sets the button to LOW when pressed. OneButton button(PIN_INPUT, true); +// current LED state, staring with LOW (0) +int ledState = LOW; + +// In case the momentary button puts the input to HIGH when pressed: +// The 2. parameter activeLOW is false when the external wiring sets the button to HIGH when pressed. +// The 3. parameter can be used to disable the PullUp . +// OneButton button(PIN_INPUT, false, false); + -// This function is called when the signal on the PIN_INPUT has changed. +// This function is called from the interrupt when the signal on the PIN_INPUT has changed. +// do not use Serial in here. void checkTicks() { // include all buttons here to be checked @@ -58,14 +67,16 @@ void setup() Serial.begin(115200); Serial.println("One Button Example with interrupts."); - pinMode(PIN_LED, OUTPUT); // sets the digital pin for LED as output + // enable the led output. + pinMode(PIN_LED, OUTPUT); // sets the digital pin as output + digitalWrite(LED_BUILTIN, ledState); // setup interrupt routine pinMode(PIN_INPUT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(PIN_INPUT), checkTicks, CHANGE); // link the doubleclick function to be called on a doubleclick event. - button.attachDoubleClick(doubleclick); + button.attachDoubleClick(doubleClick); // A1-Option: // it is possible to use e.g. A1 but then some direct register modifications and an ISR has to be used: @@ -94,14 +105,12 @@ void loop() // this function will be called when the button was pressed 2 times in a short timeframe. -void doubleclick() +void doubleClick() { Serial.println("x2"); - static int m = LOW; - // reverse the LED - m = !m; - digitalWrite(13, m); -} // doubleclick + ledState = !ledState; // reverse the LED + digitalWrite(PIN_LED, ledState); +} // doubleClick // End diff --git a/examples/SimpleOneButton/SimpleOneButton.ino b/examples/SimpleOneButton/SimpleOneButton.ino index 0e92746..a84fbf1 100644 --- a/examples/SimpleOneButton/SimpleOneButton.ino +++ b/examples/SimpleOneButton/SimpleOneButton.ino @@ -19,8 +19,8 @@ #include "OneButton.h" #if defined(ARDUINO_AVR_UNO) -// Example for Arduino UNO with input button on A1 and builtin LED on pin 13 -#define PIN_INPUT A1 +// Example for Arduino UNO with input button on pin 2 and builtin LED on pin 13 +#define PIN_INPUT 2 #define PIN_LED 13 #else if defined(ESP8266) @@ -31,12 +31,11 @@ #endif // Setup a new OneButton on pin PIN_INPUT - // The 2. parameter activeLOW is true, because external wiring sets the button to LOW when pressed. OneButton button(PIN_INPUT, true); // current LED state, staring with LOW (0) -bool ledState = LOW; +int ledState = LOW; // In case the momentary button puts the input to HIGH when pressed: // The 2. parameter activeLOW is false when the external wiring sets the button to HIGH when pressed. @@ -48,9 +47,11 @@ bool ledState = LOW; void setup() { Serial.begin(115200); + Serial.println("One Button Example with polling."); // enable the standard led on pin 13. pinMode(PIN_LED, OUTPUT); // sets the digital pin as output + digitalWrite(PIN_LED, ledState); // link the doubleclick function to be called on a doubleclick event. button.attachDoubleClick(doubleClick); @@ -69,11 +70,12 @@ void loop() // this function will be called when the button was pressed 2 times in a short timeframe. - void doubleClick() { - ledState = ! ledState; // reverse the LED - digitalWrite(LED_BUILTIN, ledState); -} // doubleclick + Serial.println("x2"); + + ledState = !ledState; // reverse the LED + digitalWrite(PIN_LED, ledState); +} // doubleClick // End \ No newline at end of file From def9742d6e41f204666aa41f59b6bc3df071473c Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 22 Jan 2021 18:42:50 +0100 Subject: [PATCH 06/16] remove DEPRECATED functions --- src/OneButton.cpp | 11 +---------- src/OneButton.h | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/OneButton.cpp b/src/OneButton.cpp index 108c478..a76d4f1 100644 --- a/src/OneButton.cpp +++ b/src/OneButton.cpp @@ -111,14 +111,6 @@ void OneButton::attachDoubleClick(parameterizedCallbackFunction newFunction, voi } // attachDoubleClick -// save function for press event -// DEPRECATED, is replaced by attachLongPressStart, attachLongPressStop, -// attachDuringLongPress, -void OneButton::attachPress(callbackFunction newFunction) -{ - _pressFunc = newFunction; -} // attachPress - void OneButton::attachPressStart(callbackFunction newFunction) { _pressStartFunc = newFunction; @@ -226,8 +218,7 @@ void OneButton::tick(bool activeLevel) ((unsigned long)(now - _startTime) > _pressTicks)) { _stopTime = now; // remember stopping time _isLongPressed = true; // Keep track of long press state - if (_pressFunc) - _pressFunc(); + if (_longPressStartFunc) _longPressStartFunc(); if (_paramLongPressStartFunc) diff --git a/src/OneButton.h b/src/OneButton.h index 7abf2ba..33ffe2d 100644 --- a/src/OneButton.h +++ b/src/OneButton.h @@ -165,7 +165,6 @@ class OneButton parameterizedCallbackFunction _paramDoubleClickFunc = NULL; void *_doubleClickFuncParam = NULL; - callbackFunction _pressFunc = NULL; callbackFunction _pressStartFunc = NULL; callbackFunction _longPressStartFunc = NULL; From 7df3c8d77ede86510c1306c500e2afe645d2dfba Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 22 Jan 2021 19:00:53 +0100 Subject: [PATCH 07/16] double click debouncing code --- src/OneButton.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/OneButton.cpp b/src/OneButton.cpp index a76d4f1..73a2b55 100644 --- a/src/OneButton.cpp +++ b/src/OneButton.cpp @@ -255,8 +255,13 @@ void OneButton::tick(bool activeLevel) } else if (_state == OneButton::COUNT_CLICKS) { // waiting for menu pin being released finally. // Stay here for at least _debounceTicks because else we might end up in // state 1 if the button bounces for too long. - if ((!activeLevel) && - ((unsigned long)(now - _startTime) > _debounceTicks)) { + + if ((!activeLevel) && ((unsigned long)(now - _startTime) < _debounceTicks)) { + // button was released to quickly so I assume some debouncing. + // go back to state DETECT_CLICK + _state = OneButton::DETECT_CLICK; + + } else if ((!activeLevel) && ((unsigned long)(now - _startTime) > _debounceTicks)) { _stopTime = now; // remember stopping time // this was a 2 click sequence. if (_doubleClickFunc) From f88f867b3e9fd9c6c285b071a388fb79661f8dc1 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 22 Jan 2021 19:45:23 +0100 Subject: [PATCH 08/16] isIdle() added from pull request #73 --- src/OneButton.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/OneButton.h b/src/OneButton.h index 33ffe2d..bcde79e 100644 --- a/src/OneButton.h +++ b/src/OneButton.h @@ -144,6 +144,13 @@ class OneButton */ 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 == 0; } + private: int _pin; // hardware pin number. unsigned int _debounceTicks = 50; // number of ticks for debounce times. @@ -196,6 +203,7 @@ class OneButton unsigned long _startTime; // will be set in state 1 unsigned long _stopTime; // will be set in state 2 + int _numberOfClicks; // count the number of clicks with this variable }; #endif From 642badd88c8aa58895fb5720e2fbf8c68bf27778 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sat, 23 Jan 2021 19:28:41 +0100 Subject: [PATCH 09/16] first implementation 2.0.0 finished --- CHANGELOG.md | 49 ++++ README.md | 5 +- .../InterruptOneButton/InterruptOneButton.ino | 81 ++++-- examples/SimpleOneButton/SimpleOneButton.ino | 11 +- src/OneButton.cpp | 230 ++++++++++-------- src/OneButton.h | 77 +++--- 6 files changed, 294 insertions(+), 159 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ad22ea2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog + +All notable changes to this project will be documented in this file starting 2021. + +## [2.0.0] - 2021-01-22 + +* CHANGELOG created. +* include improvements from #27 (aslobodyanuk), #59 (ShaggyDog18) and #73 (geeksville) + +This is a major update with breaking changes. + +The **states** are re-factored to support counting the clicks. + +By design only one of the events (click, doubleClick, MultiClick) are triggered within one interaction. +As a consequence a single-click interaction is detected after waiting some milliseconds (see setClickTicks()) without another click happening; +Only if you have not attached any double-click event function the waiting time can be skipped. + +Detecting a long 'down' not only works with the first but always as the last click. + +The number of actual clicks can be retrieved from the library any time. + +The function **getPressedTicks()** was removed. See example SimpleOneButton on how to get that time by using attachLongPressStart to save starting time. + +The function **attachPressStart()** is removed as **attachLongPressStart()** does the same but also supports parameters. + +One additional feature has been added not to call the event functions from the interrupt routine and detect +the need for event functions to be called only when the tick() function is called from the main loop() method. +This is because some boards and processors do not support timing or Serial functions (among others) from interrupt routines. + +The function **isIdle()** was added to allow detect a current interaction. + +The library now supports to detect multiple (>2) clicks in a row using **attachMultiClick()** . + + +* The internal _state is using enum instead of plain numbers to make the library more readable. +* functions that had been marked deprecated are now removed. (attachPress->attachLongPressXXX) +* added const to constant parameters to enable meaningful compiler warnings. +* added code for de-bouncing double clicks from pull 27. +* added isIdle() function to find out that the internal state is `init`. + + +### Examples + +* Examples run on NodeMCU boards. (the library worked already). + +* The **SimpleOneButton.ino** got some cleanup and definition to be used with ESP8266 boards as well. + +* The **InterruptOneButton.ino** now is using attachInterrupt instead of UNO specific register modifications. + diff --git a/README.md b/README.md index 29f73eb..2ad06f6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Arduino OneButton Library -=== +# Arduino OneButton Library This Arduino library is improving the usage of a singe button for input. It shows how to use an digital input pin with a single pushbutton attached @@ -11,6 +10,8 @@ This is also a sample for implementing simple finite-state machines by using the You can find more details on this library at http://www.mathertel.de/Arduino/OneButtonLibrary.aspx +Many thanks to **ShaggyDog18** and **aslobodyanuk** for contributions to the library. + ## Getting Started diff --git a/examples/InterruptOneButton/InterruptOneButton.ino b/examples/InterruptOneButton/InterruptOneButton.ino index e5e3f5e..5ed63fd 100644 --- a/examples/InterruptOneButton/InterruptOneButton.ino +++ b/examples/InterruptOneButton/InterruptOneButton.ino @@ -14,8 +14,9 @@ * The pin 13 (StatusPin) is used for output attach a led and resistor to ground or see the built-in led on the standard arduino board. - The Sketch shows how to setup the library and bind a special function to the doubleclick event. - By using interrupts the doubleclick function will be called not from the main program. + The sketch shows how to setup the library and bind the functions (singleClick, doubleClick) to the events. + In the loop function the button.tick function must be called as often as you like. + By using interrupts the internal state advances even when for longer time the button.tick is not called. */ // 03.03.2011 created by Matthias Hertel @@ -46,6 +47,9 @@ OneButton button(PIN_INPUT, true); // current LED state, staring with LOW (0) int ledState = LOW; +// save the millis when a press has started. +unsigned long pressStartTime; + // In case the momentary button puts the input to HIGH when pressed: // The 2. parameter activeLOW is false when the external wiring sets the button to HIGH when pressed. // The 3. parameter can be used to disable the PullUp . @@ -60,6 +64,51 @@ void checkTicks() button.tick(); // just call tick() to check the state. } +// this function will be called when the button was pressed 1 time only. +void singleClick() +{ + Serial.println("singleClick() detected."); +} // singleClick + + +// this function will be called when the button was pressed 2 times in a short timeframe. +void doubleClick() +{ + Serial.println("doubleClick() detected."); + + ledState = !ledState; // reverse the LED + digitalWrite(PIN_LED, ledState); +} // doubleClick + + +// this function will be called when the button was pressed 2 times in a short timeframe. +void multiClick() +{ + Serial.print("multiClick("); + Serial.print(button.getNumberClicks()); + Serial.println(") detected."); + + ledState = !ledState; // reverse the LED + digitalWrite(PIN_LED, ledState); +} // multiClick + + +// this function will be called when the button was pressed 2 times in a short timeframe. +void pressStart() +{ + Serial.println("pressStart()"); + pressStartTime = millis() - 1000; // as set in setPressTicks() +} // pressStart() + + +// this function will be called when the button was pressed 2 times in a short timeframe. +void pressStop() +{ + Serial.print("pressStop("); + Serial.print(millis() - pressStartTime); + Serial.println(") detected."); +} // pressStop() + // setup code here, to run once: void setup() @@ -69,16 +118,22 @@ void setup() // enable the led output. pinMode(PIN_LED, OUTPUT); // sets the digital pin as output - digitalWrite(LED_BUILTIN, ledState); + digitalWrite(PIN_LED, ledState); // setup interrupt routine - pinMode(PIN_INPUT, INPUT_PULLUP); + // when not registering to the interrupt the sketch also works when the tick is called frequently. attachInterrupt(digitalPinToInterrupt(PIN_INPUT), checkTicks, CHANGE); - // link the doubleclick function to be called on a doubleclick event. + // link the xxxclick functions to be called on xxxclick event. + button.attachClick(singleClick); button.attachDoubleClick(doubleClick); + button.attachMultiClick(multiClick); + + button.setPressTicks(1000); // that is the time when pressStart is called + button.attachLongPressStart(pressStart); + button.attachLongPressStop(pressStop); - // A1-Option: + // A1-Option for UNO: // it is possible to use e.g. A1 but then some direct register modifications and an ISR has to be used: // You may have to modify the next 2 lines if using another pin than A1 // PCICR |= (1 << PCIE1); // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C. @@ -86,7 +141,7 @@ void setup() } // setup -// A1-Option: +// A1-Option for UNO: // The Interrupt Service Routine for Pin Change Interrupt 1 // This routine will only be called on any signal change on A1: exactly where we need to check. // ISR(PCINT1_vect) @@ -99,18 +154,12 @@ void setup() // main code here, to run repeatedly: void loop() { + // keep watching the push button, even when no interrupt happens: + button.tick(); + // You can implement other code in here or just wait a while delay(10); } // loop -// this function will be called when the button was pressed 2 times in a short timeframe. -void doubleClick() -{ - Serial.println("x2"); - - ledState = !ledState; // reverse the LED - digitalWrite(PIN_LED, ledState); -} // doubleClick - // End diff --git a/examples/SimpleOneButton/SimpleOneButton.ino b/examples/SimpleOneButton/SimpleOneButton.ino index a84fbf1..04350f5 100644 --- a/examples/SimpleOneButton/SimpleOneButton.ino +++ b/examples/SimpleOneButton/SimpleOneButton.ino @@ -9,8 +9,8 @@ * The pin 13 (StatusPin) is used for output attach a led and resistor to ground or see the built-in led on the standard arduino board. - The Sketch shows how to setup the library and bind a special function to the doubleclick event. - In the loop function the button.tick function has to be called as often as you like. + The sketch shows how to setup the library and bind the functions (singleClick, doubleClick) to the events. + In the loop function the button.tick function must be called as often as you like. */ // 03.03.2011 created by Matthias Hertel @@ -34,14 +34,13 @@ // The 2. parameter activeLOW is true, because external wiring sets the button to LOW when pressed. OneButton button(PIN_INPUT, true); -// current LED state, staring with LOW (0) -int ledState = LOW; - // In case the momentary button puts the input to HIGH when pressed: // The 2. parameter activeLOW is false when the external wiring sets the button to HIGH when pressed. // The 3. parameter can be used to disable the PullUp . // OneButton button(PIN_INPUT, false, false); +// current LED state, staring with LOW (0) +int ledState = LOW; // setup code here, to run once: void setup() @@ -51,6 +50,8 @@ void setup() // enable the standard led on pin 13. pinMode(PIN_LED, OUTPUT); // sets the digital pin as output + + // enable the standard led on pin 13. digitalWrite(PIN_LED, ledState); // link the doubleclick function to be called on a doubleclick event. diff --git a/src/OneButton.cpp b/src/OneButton.cpp index 73a2b55..d6e0aff 100644 --- a/src/OneButton.cpp +++ b/src/OneButton.cpp @@ -58,23 +58,21 @@ OneButton::OneButton(const int pin, const boolean activeLow, const bool pullupAc } // OneButton -// explicitly set the number of millisec that have to pass by before a click is -// assumed as safe. +// explicitly set the number of millisec that have to pass by before a click is assumed stable. void OneButton::setDebounceTicks(const int ticks) { _debounceTicks = ticks; } // setDebounceTicks -// explicitly set the number of millisec that have to pass by before a click is -// detected. + +// explicitly set the number of millisec that have to pass by before a click is detected. void OneButton::setClickTicks(const int ticks) { _clickTicks = ticks; } // setClickTicks -// explicitly set the number of millisec that have to pass by before a long -// button press is detected. +// explicitly set the number of millisec that have to pass by before a long button press is detected. void OneButton::setPressTicks(const int ticks) { _pressTicks = ticks; @@ -100,6 +98,7 @@ void OneButton::attachClick(parameterizedCallbackFunction newFunction, void *par void OneButton::attachDoubleClick(callbackFunction newFunction) { _doubleClickFunc = newFunction; + _maxClicks = max(_maxClicks, 2); } // attachDoubleClick @@ -108,13 +107,26 @@ void OneButton::attachDoubleClick(parameterizedCallbackFunction newFunction, voi { _paramDoubleClickFunc = newFunction; _doubleClickFuncParam = parameter; + _maxClicks = max(_maxClicks, 2); } // attachDoubleClick -void OneButton::attachPressStart(callbackFunction newFunction) +// save function for multiClick event +void OneButton::attachMultiClick(callbackFunction newFunction) +{ + _multiClickFunc = newFunction; + _maxClicks = max(_maxClicks, 100); +} // attachMultiClick + + +// save function for parameterized MultiClick event +void OneButton::attachMultiClick(parameterizedCallbackFunction newFunction, void *parameter) { - _pressStartFunc = newFunction; -} // attachPressStart + _paramMultiClickFunc = newFunction; + _multiClickFuncParam = parameter; + _maxClicks = max(_maxClicks, 100); +} // attachMultiClick + // save function for longPressStart event void OneButton::attachLongPressStart(callbackFunction newFunction) @@ -122,6 +134,7 @@ void OneButton::attachLongPressStart(callbackFunction newFunction) _longPressStartFunc = newFunction; } // attachLongPressStart + // save function for parameterized longPressStart event void OneButton::attachLongPressStart(parameterizedCallbackFunction newFunction, void *parameter) { @@ -129,12 +142,14 @@ void OneButton::attachLongPressStart(parameterizedCallbackFunction newFunction, _longPressStartFuncParam = parameter; } // attachLongPressStart + // save function for longPressStop event void OneButton::attachLongPressStop(callbackFunction newFunction) { _longPressStopFunc = newFunction; } // attachLongPressStop + // save function for parameterized longPressStop event void OneButton::attachLongPressStop(parameterizedCallbackFunction newFunction, void *parameter) { @@ -142,12 +157,14 @@ void OneButton::attachLongPressStop(parameterizedCallbackFunction newFunction, v _longPressStopFuncParam = parameter; } // attachLongPressStop + // save function for during longPress event void OneButton::attachDuringLongPress(callbackFunction newFunction) { _duringLongPressFunc = newFunction; } // attachDuringLongPress + // save function for parameterized during longPress event void OneButton::attachDuringLongPress(parameterizedCallbackFunction newFunction, void *parameter) { @@ -155,23 +172,20 @@ void OneButton::attachDuringLongPress(parameterizedCallbackFunction newFunction, _duringLongPressFuncParam = parameter; } // attachDuringLongPress -// function to get the current long pressed state -bool OneButton::isLongPressed() -{ - return _isLongPressed; -} -int OneButton::getPressedTicks() +void OneButton::reset(void) { - return _stopTime - _startTime; + _state = OneButton::OCS_INIT; + _lastState = OneButton::OCS_INIT; + _nClicks = 0; + _startTime = 0; } -void OneButton::reset(void) + +// ShaggyDog ---- return number of clicks in any case: single or multiple clicks +int OneButton::getNumberClicks(void) { - _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. - _startTime = 0; - _stopTime = 0; - _isLongPressed = false; + return _nClicks; } @@ -188,110 +202,126 @@ void OneButton::tick(void) /** - * @brief Advance the finite state machine (FSM) using the given level. + * @brief Advance to a new state and save the last one to come back in cas of bouncing detection. + */ +void OneButton::_newState(stateMachine_t nextState) +{ + _lastState = _state; + _state = nextState; +} // _newState() + + +/** + * @brief Run the finite state machine (FSM) using the given level. */ void OneButton::tick(bool activeLevel) { unsigned long now = millis(); // current (relative) time in msecs. + unsigned long waitTime = (now - _startTime); // Implementation of the state machine - - if (_state == OneButton::WAIT_FOR_INITIAL_PRESS) { // waiting for menu pin being pressed. + switch (_state) { + case OneButton::OCS_INIT: + // waiting for level to become active. if (activeLevel) { - _state = OneButton::DEBOUNCE_OR_LONG_PRESS; // step to state 1 + _newState(OneButton::OCS_DOWN); _startTime = now; // remember starting time + _nClicks = 0; } // if + break; - } else if (_state == OneButton::DEBOUNCE_OR_LONG_PRESS) { // waiting for menu pin being released. + case OneButton::OCS_DOWN: + // waiting for level to become inactive. - if ((!activeLevel) && - ((unsigned long)(now - _startTime) < _debounceTicks)) { - // button was released to quickly so I assume some debouncing. - // go back to state 0 without calling a function. - _state = OneButton::WAIT_FOR_INITIAL_PRESS; + if ((!activeLevel) && (waitTime < _debounceTicks)) { + // button was released to quickly so I assume some bouncing. + _newState(_lastState); } else if (!activeLevel) { - _state = OneButton::DETECT_CLICK; // step to state 2 - _stopTime = now; // remember stopping time - - } else if ((activeLevel) && - ((unsigned long)(now - _startTime) > _pressTicks)) { - _stopTime = now; // remember stopping time - _isLongPressed = true; // Keep track of long press state - - if (_longPressStartFunc) - _longPressStartFunc(); - if (_paramLongPressStartFunc) - _paramLongPressStartFunc(_longPressStartFuncParam); - if (_duringLongPressFunc) - _duringLongPressFunc(); - if (_paramDuringLongPressFunc) - _paramDuringLongPressFunc(_duringLongPressFuncParam); - _state = OneButton::LONG_PRESS; // step to state 6 - } else { - // Button was pressed down. wait. Stay in this state. - // if a pressStart event is registered, call it: - if (_pressStartFunc) - _pressStartFunc(); + _newState(OneButton::OCS_UP); + _startTime = now; // remember starting time + + } else if ((activeLevel) && (waitTime > _pressTicks)) { + if (_longPressStartFunc) _longPressStartFunc(); + if (_paramLongPressStartFunc) _paramLongPressStartFunc(_longPressStartFuncParam); + _newState(OneButton::OCS_PRESS); } // if + break; - } else if (_state == OneButton::DETECT_CLICK) { - // waiting for menu pin being pressed the second time or timeout. - if ((_doubleClickFunc == NULL && _paramDoubleClickFunc == NULL) || - (unsigned long)(now - _startTime) > _clickTicks) { - // this was only a single short click - if (_clickFunc) - _clickFunc(); - if (_paramClickFunc) - _paramClickFunc(_clickFuncParam); - _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. - - } else if ((activeLevel) && - ((unsigned long)(now - _stopTime) > _debounceTicks)) { - _state = OneButton::COUNT_CLICKS; // step to state 3 - _startTime = now; // remember starting time + case OneButton::OCS_UP: + // level is inactive + + if ((activeLevel) && (waitTime < _debounceTicks)) { + // button was pressed to quickly so I assume some bouncing. + _newState(_lastState); // go back + + } else if (waitTime >= _debounceTicks) { + // count as a short button down + _nClicks++; + _newState(OneButton::OCS_COUNT); } // if + break; + + case OneButton::OCS_COUNT: + // dobounce time is over, count clicks - } else if (_state == OneButton::COUNT_CLICKS) { // waiting for menu pin being released finally. - // Stay here for at least _debounceTicks because else we might end up in - // state 1 if the button bounces for too long. - - if ((!activeLevel) && ((unsigned long)(now - _startTime) < _debounceTicks)) { - // button was released to quickly so I assume some debouncing. - // go back to state DETECT_CLICK - _state = OneButton::DETECT_CLICK; - - } else if ((!activeLevel) && ((unsigned long)(now - _startTime) > _debounceTicks)) { - _stopTime = now; // remember stopping time - // this was a 2 click sequence. - if (_doubleClickFunc) - _doubleClickFunc(); - if (_paramDoubleClickFunc) - _paramDoubleClickFunc(_doubleClickFuncParam); - _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. + if (activeLevel) { + // button is down again + _newState(OneButton::OCS_DOWN); + _startTime = now; // remember starting time + + } else if ((waitTime > _clickTicks) || (_nClicks == _maxClicks)) { + // now we know how many clicks have been made. + + if (_nClicks == 1) { + // this was 1 click only. + if (_clickFunc) _clickFunc(); + if (_paramClickFunc) _paramClickFunc(_clickFuncParam); + + } else if (_nClicks == 2) { + // this was a 2 click sequence. + if (_doubleClickFunc) _doubleClickFunc(); + if (_paramDoubleClickFunc) _paramDoubleClickFunc(_doubleClickFuncParam); + + } else { + // this was a multi click sequence. + if (_multiClickFunc) _multiClickFunc(); + if (_paramMultiClickFunc) _paramMultiClickFunc(_doubleClickFuncParam); + } // if + + reset(); } // if + break; - } else if (_state == OneButton::LONG_PRESS) { + case OneButton::OCS_PRESS: // waiting for menu pin being release after long press. + if (!activeLevel) { - _isLongPressed = false; // Keep track of long press state - _stopTime = now; // remember stopping time - if (_longPressStopFunc) - _longPressStopFunc(); - if (_paramLongPressStopFunc) - _paramLongPressStopFunc(_longPressStopFuncParam); - _state = OneButton::WAIT_FOR_INITIAL_PRESS; // restart. + _newState(OneButton::OCS_PRESSEND); + _startTime = now; + } else { - // button is being long pressed - _stopTime = now; // remember stopping time - _isLongPressed = true; // Keep track of long press state - if (_duringLongPressFunc) - _duringLongPressFunc(); - if (_paramDuringLongPressFunc) - _paramDuringLongPressFunc(_duringLongPressFuncParam); + // still the button is pressed + if (_duringLongPressFunc) _duringLongPressFunc(); + if (_paramDuringLongPressFunc) _paramDuringLongPressFunc(_duringLongPressFuncParam); } // if + break; + + case OneButton::OCS_PRESSEND: + // button was released. + if ((activeLevel) && (waitTime < _debounceTicks)) { + // button was released to quickly so I assume some bouncing. + _newState(_lastState); // go back + + } else if (waitTime >= _debounceTicks) { + if (_longPressStopFunc) _longPressStopFunc(); + if (_paramLongPressStopFunc) _paramLongPressStopFunc(_longPressStopFuncParam); + reset(); + } + break; } // if + } // OneButton.tick() diff --git a/src/OneButton.h b/src/OneButton.h index bcde79e..dd3ec0f 100644 --- a/src/OneButton.h +++ b/src/OneButton.h @@ -67,29 +67,24 @@ class OneButton /** * Attach an event to be called when a single click is detected. - * @param newFunction + * @param newFunction This function will be called when the event has been detected. */ void attachClick(callbackFunction newFunction); void attachClick(parameterizedCallbackFunction newFunction, void *parameter); /** * Attach an event to be called after a double click is detected. - * @param newFunction + * @param newFunction This function will be called when the event has been detected. */ void attachDoubleClick(callbackFunction newFunction); void attachDoubleClick(parameterizedCallbackFunction newFunction, void *parameter); /** - * @deprecated Replaced by longPressStart, longPressStop, and duringLongPress. - * @param newFunction - */ - void attachPress(callbackFunction newFunction); - - /** - * Attach an event to fire as soon as the button is pressed down. - * @param 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 attachPressStart(callbackFunction newFunction); + void attachMultiClick(callbackFunction newFunction); + void attachMultiClick(parameterizedCallbackFunction newFunction, void *parameter); /** * Attach an event to fire when the button is pressed and held down. @@ -120,6 +115,7 @@ class OneButton */ 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 @@ -127,17 +123,6 @@ class OneButton */ void tick(bool level); - /** - * Detect whether or not the button is currently inside a long press. - * @return - */ - bool isLongPressed(); - - /** - * Get the current number of ticks that the button has been held down for. - * @return - */ - int getPressedTicks(); /** * Reset the button state machine. @@ -145,11 +130,23 @@ class OneButton void reset(void); -/** + /* + * return number of clicks in any case: single or multiple clicks + */ + int getNumberClicks(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 == 0; } + bool isIdle() const { return _state == OCS_INIT; } + + /** + * @return true when a long press is detected + */ + bool isLongPressed() const { return _state == OCS_PRESS; }; + private: int _pin; // hardware pin number. @@ -161,8 +158,6 @@ class OneButton int _buttonPressed; - bool _isLongPressed = false; - // These variables will hold functions acting as event source. callbackFunction _clickFunc = NULL; parameterizedCallbackFunction _paramClickFunc = NULL; @@ -172,7 +167,9 @@ class OneButton parameterizedCallbackFunction _paramDoubleClickFunc = NULL; void *_doubleClickFuncParam = NULL; - callbackFunction _pressStartFunc = NULL; + callbackFunction _multiClickFunc = NULL; + parameterizedCallbackFunction _paramMultiClickFunc = NULL; + void *_multiClickFuncParam = NULL; callbackFunction _longPressStartFunc = NULL; parameterizedCallbackFunction _paramLongPressStartFunc = NULL; @@ -192,18 +189,26 @@ class OneButton // define FiniteStateMachine enum stateMachine_t : int { - WAIT_FOR_INITIAL_PRESS = 0, - DEBOUNCE_OR_LONG_PRESS = 1, - DETECT_CLICK = 2, - COUNT_CLICKS = 3, - LONG_PRESS = 6 + OCS_INIT = 0, + OCS_DOWN = 1, + OCS_UP = 2, + OCS_COUNT = 3, + OCS_PRESS = 6, + OCS_PRESSEND = 7, + UNKNOWN = 99 }; - stateMachine_t _state = WAIT_FOR_INITIAL_PRESS; + /** + * Advance to a new state and save the last one to come back in cas of bouncing detection. + */ + void _newState(stateMachine_t nextState); + + stateMachine_t _state = OCS_INIT; + stateMachine_t _lastState = OCS_INIT; // used for debouncing - unsigned long _startTime; // will be set in state 1 - unsigned long _stopTime; // will be set in state 2 - int _numberOfClicks; // count the number of clicks with this variable + unsigned long _startTime; // start of current input change to checking debouncing + int _nClicks; // count the number of clicks with this variable + int _maxClicks = 1; // max number (1, 2, multi=3) of clicks of interest by registration of event functions. }; #endif From c0bc5ae85ed3745e6e3475e771508b8bbf449f91 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 12:09:18 +0100 Subject: [PATCH 10/16] Examples cleanup --- examples/BlinkMachine/BlinkMachine.ino | 35 +++++++++++++------ .../InterruptOneButton/InterruptOneButton.ino | 12 +++++++ examples/SimpleOneButton/SimpleOneButton.ino | 1 + examples/TwoButtons/TwoButtons.ino | 4 +-- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/examples/BlinkMachine/BlinkMachine.ino b/examples/BlinkMachine/BlinkMachine.ino index cd88ccf..462327d 100644 --- a/examples/BlinkMachine/BlinkMachine.ino +++ b/examples/BlinkMachine/BlinkMachine.ino @@ -11,8 +11,8 @@ http://www.mathertel.de/Arduino/OneButtonLibrary.aspx Setup a test circuit: - * Connect a pushbutton to pin A1 (ButtonPin) and ground. - * The pin 13 (StatusPin) is used for output attach a led and resistor to ground + * Connect a pushbutton to pin A1 (Uno) or D3 (ESP8266) and ground. + * The pin 13 (UNO) or D4 (ESP8266) is used for output attach a led and resistor to ground or see the built-in led on the standard arduino board. The Sketch shows how to setup the library and bind a "machine" that can blink the LED slow or fast. @@ -58,8 +58,21 @@ typedef enum { } MyActions; +#if defined(ARDUINO_AVR_UNO) +// Example for Arduino UNO with input button on pin 2 and builtin LED on pin 13 +#define PIN_INPUT A1 +#define PIN_LED 13 + +#else if 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. +#define PIN_INPUT D3 +#define PIN_LED D4 + +#endif + // Setup a new OneButton on pin A1. -OneButton button(A1, true); +OneButton button(PIN_INPUT, true); MyActions nextAction = ACTION_OFF; // no action when starting @@ -67,7 +80,7 @@ MyActions nextAction = ACTION_OFF; // no action when starting // setup code here, to run once. void setup() { // enable the standard led on pin 13. - pinMode(13, 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. button.attachClick(myClickFunction); @@ -91,26 +104,26 @@ void loop() { if (nextAction == ACTION_OFF) { // do nothing. - digitalWrite(13, LOW); + digitalWrite(PIN_LED, LOW); } else if (nextAction == ACTION_ON) { // turn LED on - digitalWrite(13, HIGH); + digitalWrite(PIN_LED, HIGH); } else if (nextAction == ACTION_SLOW) { // do a slow blinking if (now % 1000 < 500) { - digitalWrite(13, LOW); + digitalWrite(PIN_LED, LOW); } else { - digitalWrite(13, HIGH); + digitalWrite(PIN_LED, HIGH); } // if } else if (nextAction == ACTION_FAST) { // do a fast blinking if (now % 200 < 100) { - digitalWrite(13, LOW); + digitalWrite(PIN_LED, LOW); } else { - digitalWrite(13, HIGH); + digitalWrite(PIN_LED, HIGH); } // if } // if } // loop @@ -139,4 +152,4 @@ void myDoubleClickFunction() { } // myDoubleClickFunction // End - + diff --git a/examples/InterruptOneButton/InterruptOneButton.ino b/examples/InterruptOneButton/InterruptOneButton.ino index 5ed63fd..0479150 100644 --- a/examples/InterruptOneButton/InterruptOneButton.ino +++ b/examples/InterruptOneButton/InterruptOneButton.ino @@ -35,6 +35,7 @@ #else if 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. #define PIN_INPUT D3 #define PIN_LED D4 @@ -58,12 +59,23 @@ unsigned long pressStartTime; // This function is called from the interrupt when the signal on the PIN_INPUT has changed. // do not use Serial in here. +#if defined(ARDUINO_AVR_UNO) void checkTicks() { // include all buttons here to be checked button.tick(); // just call tick() to check the state. } +#else if defined(ESP8266) +ICACHE_RAM_ATTR void checkTicks() +{ + // include all buttons here to be checked + button.tick(); // just call tick() to check the state. +} + +#endif + + // this function will be called when the button was pressed 1 time only. void singleClick() { diff --git a/examples/SimpleOneButton/SimpleOneButton.ino b/examples/SimpleOneButton/SimpleOneButton.ino index 04350f5..d0eb6d6 100644 --- a/examples/SimpleOneButton/SimpleOneButton.ino +++ b/examples/SimpleOneButton/SimpleOneButton.ino @@ -25,6 +25,7 @@ #else if 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. #define PIN_INPUT D3 #define PIN_LED D4 diff --git a/examples/TwoButtons/TwoButtons.ino b/examples/TwoButtons/TwoButtons.ino index b18c532..f0ec8b7 100644 --- a/examples/TwoButtons/TwoButtons.ino +++ b/examples/TwoButtons/TwoButtons.ino @@ -46,7 +46,7 @@ OneButton button2(A2, true); // setup code here, to run once: void setup() { // Setup the Serial port. see http://arduino.cc/en/Serial/IfSerial - Serial.begin(9600); + Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } @@ -139,4 +139,4 @@ void longPressStop2() { // End - + From d086edbd940e67e932b3c9cfa76b3ac391688918 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 12:35:55 +0100 Subject: [PATCH 11/16] Special-Input Example added. --- CHANGELOG.md | 1 + examples/SpecialInput/SpecialInput.ino | 39 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 examples/SpecialInput/SpecialInput.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index ad22ea2..a37a5f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,3 +47,4 @@ The library now supports to detect multiple (>2) clicks in a row using **attachM * The **InterruptOneButton.ino** now is using attachInterrupt instead of UNO specific register modifications. +* The **SpecialInput.ino** example was added to shpow how to use the OneButton algorythm and input pattern recognition with your own source of input. diff --git a/examples/SpecialInput/SpecialInput.ino b/examples/SpecialInput/SpecialInput.ino new file mode 100644 index 0000000..09a104f --- /dev/null +++ b/examples/SpecialInput/SpecialInput.ino @@ -0,0 +1,39 @@ +#include "OneButton.h" + +// This is an example on how to use the OneClick library on other input sources than standard digital pins. +// 1. do not use a pin in the initialization of the OneClick library. +// 2. pass the input state to the tick function. + +#if defined(ARDUINO_AVR_UNO) +#define PIN_INPUT 2 + +#else if defined(ESP8266) +#define PIN_INPUT D3 + +#endif + + +// create a OneButton instance without pin. +OneButton *button = new OneButton(); + +void setup() { + Serial.begin(115200); + Serial.println("One Button Example with custom input."); + + // Here is an example on how to use an inline function as parameter: + button->attachClick([]() { Serial.println("Click"); }); + button->attachDoubleClick([]() { Serial.println("DoubleClick"); }); + + // setup your own source of input: + pinMode(PIN_INPUT, INPUT_PULLUP); + +} // setup() + +void loop() { + // read your own source of input: + bool isPressed = (digitalRead(PIN_INPUT) == LOW); + + // call tick frequently with current push-state of the input + button->tick(isPressed); +} // loop() + From a034704aa45a6ac06822ea7dad7172b8568567c1 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 16:22:34 +0100 Subject: [PATCH 12/16] examples for Arduino nano every --- examples/BlinkMachine/BlinkMachine.ino | 4 +- .../InterruptOneButton/InterruptOneButton.ino | 8 +-- examples/SimpleOneButton/SimpleOneButton.ino | 2 +- examples/SpecialInput/SpecialInput.ino | 49 +++++++++++++++---- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/examples/BlinkMachine/BlinkMachine.ino b/examples/BlinkMachine/BlinkMachine.ino index 462327d..02f8502 100644 --- a/examples/BlinkMachine/BlinkMachine.ino +++ b/examples/BlinkMachine/BlinkMachine.ino @@ -58,12 +58,12 @@ typedef enum { } MyActions; -#if defined(ARDUINO_AVR_UNO) +#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 -#else if defined(ESP8266) +#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. #define PIN_INPUT D3 diff --git a/examples/InterruptOneButton/InterruptOneButton.ino b/examples/InterruptOneButton/InterruptOneButton.ino index 0479150..d726f8b 100644 --- a/examples/InterruptOneButton/InterruptOneButton.ino +++ b/examples/InterruptOneButton/InterruptOneButton.ino @@ -26,14 +26,14 @@ #include "OneButton.h" -#if defined(ARDUINO_AVR_UNO) +#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 // attachInterrupt only supports pin 2 and 3 on UNO. // See https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/ #define PIN_INPUT 2 #define PIN_LED 13 -#else if defined(ESP8266) +#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. #define PIN_INPUT D3 @@ -59,14 +59,14 @@ unsigned long pressStartTime; // This function is called from the interrupt when the signal on the PIN_INPUT has changed. // do not use Serial in here. -#if defined(ARDUINO_AVR_UNO) +#if defined(ARDUINO_AVR_UNO) || defined (ARDUINO_AVR_NANO_EVERY) void checkTicks() { // include all buttons here to be checked button.tick(); // just call tick() to check the state. } -#else if defined(ESP8266) +#elif defined(ESP8266) ICACHE_RAM_ATTR void checkTicks() { // include all buttons here to be checked diff --git a/examples/SimpleOneButton/SimpleOneButton.ino b/examples/SimpleOneButton/SimpleOneButton.ino index d0eb6d6..8607dac 100644 --- a/examples/SimpleOneButton/SimpleOneButton.ino +++ b/examples/SimpleOneButton/SimpleOneButton.ino @@ -18,7 +18,7 @@ #include "OneButton.h" -#if defined(ARDUINO_AVR_UNO) +#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 2 #define PIN_LED 13 diff --git a/examples/SpecialInput/SpecialInput.ino b/examples/SpecialInput/SpecialInput.ino index 09a104f..335df36 100644 --- a/examples/SpecialInput/SpecialInput.ino +++ b/examples/SpecialInput/SpecialInput.ino @@ -1,10 +1,29 @@ +/* + * SpecialInput.ino - Example for the OneButtonLibrary library. + * This is a sample sketch to show how to use the OneClick library on other input sources than standard digital pins. + * + * The library internals are explained at + * http://www.mathertel.de/Arduino/OneButtonLibrary.aspx + * + * Setup a test circuit: + * * Connect a pushbutton to pin 2 (ButtonPin) and ground. + * + * The sketch shows how to setup the library and bind the functions (singleClick, doubleClick) to the events. + * In the loop function the button.tick function must be called as often as you like. + * + * * 22.01.2021 created by Matthias Hertel +*/ + #include "OneButton.h" // This is an example on how to use the OneClick library on other input sources than standard digital pins. // 1. do not use a pin in the initialization of the OneClick library. // 2. pass the input state to the tick function. -#if defined(ARDUINO_AVR_UNO) +// You can also find how to create an instance in setup and not by declaration. +// You can also find how to use inline callback functions. + +#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY) #define PIN_INPUT 2 #else if defined(ESP8266) @@ -13,15 +32,27 @@ #endif -// create a OneButton instance without pin. -OneButton *button = new OneButton(); +// OneButton instance will be created in setup. +OneButton *button; + +void fClicked(void *s) +{ + Serial.print("Click:"); + Serial.println((char *)s); +} -void setup() { +void setup() +{ Serial.begin(115200); Serial.println("One Button Example with custom input."); - // Here is an example on how to use an inline function as parameter: - button->attachClick([]() { Serial.println("Click"); }); +// create the OneButton instance without a pin. + button = new OneButton(); + + // Here is an example on how to use a parameter to the registered function: + button->attachClick(fClicked, "me"); + + // Here is an example on how to use an inline function: button->attachDoubleClick([]() { Serial.println("DoubleClick"); }); // setup your own source of input: @@ -29,11 +60,11 @@ void setup() { } // setup() -void loop() { +void loop() +{ // read your own source of input: bool isPressed = (digitalRead(PIN_INPUT) == LOW); - // call tick frequently with current push-state of the input + // call tick frequently with current push-state of the input button->tick(isPressed); } // loop() - From 3f98996c26c02699854dd6ba90f3333aeb33e1fd Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 16:23:26 +0100 Subject: [PATCH 13/16] timing adjusted. --- src/OneButton.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/OneButton.h b/src/OneButton.h index dd3ec0f..b1e4a8a 100644 --- a/src/OneButton.h +++ b/src/OneButton.h @@ -151,10 +151,8 @@ class OneButton private: int _pin; // hardware pin number. unsigned int _debounceTicks = 50; // number of ticks for debounce times. - unsigned int _clickTicks = 600; // number of ticks that have to pass by - // before a click is detected. - unsigned int _pressTicks = 1000; // number of ticks that have to pass by - // before a long button press is detected + unsigned int _clickTicks = 400; // number of msecs before a click is detected. + unsigned int _pressTicks = 800; // number of msecs before a long button press is detected int _buttonPressed; From caf60561792b6735cd2b080bf5f84b17b8b6c7af Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 16:25:01 +0100 Subject: [PATCH 14/16] prepare version 2.0, docu --- CHANGELOG.md | 6 +++--- README.md | 15 +++++++++------ library.properties | 4 ++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a37a5f5..4f6b09b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file starting 202 ## [2.0.0] - 2021-01-22 * CHANGELOG created. -* include improvements from #27 (aslobodyanuk), #59 (ShaggyDog18) and #73 (geeksville) +* Many thanks to the improvements included from #27 (**aslobodyanuk**), #59 (**ShaggyDog18**) and #73 (**geeksville**). This is a major update with breaking changes. @@ -43,8 +43,8 @@ The library now supports to detect multiple (>2) clicks in a row using **attachM * Examples run on NodeMCU boards. (the library worked already). -* The **SimpleOneButton.ino** got some cleanup and definition to be used with ESP8266 boards as well. +* The **SimpleOneButton.ino** example got some cleanup and definition to be used with ESP8266 boards as well. -* The **InterruptOneButton.ino** now is using attachInterrupt instead of UNO specific register modifications. +* The **InterruptOneButton.ino** example now is using attachInterrupt instead of UNO specific register modifications. * The **SpecialInput.ino** example was added to shpow how to use the OneButton algorythm and input pattern recognition with your own source of input. diff --git a/README.md b/README.md index 2ad06f6..1193592 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This is also a sample for implementing simple finite-state machines by using the You can find more details on this library at http://www.mathertel.de/Arduino/OneButtonLibrary.aspx -Many thanks to **ShaggyDog18** and **aslobodyanuk** for contributions to the library. +The change log of this library can be found in [CHANGELOG](CHANGELOG.md). ## Getting Started @@ -124,11 +124,14 @@ the following functions to change the timing. click event is not attached, the library will assume a valid single click after one click duration, otherwise it must wait for the double click timeout to pass. -| Function | Default (ms) | Description | -| ----------------------- | ------------ | ------------------------------------------------------------- | -| `setDebounceTicks(int)` | `50` | Period of time in which to ignore additional level changes. | -| `setClickTicks(int)` | `600` | Timeout used to distinguish single clicks from double clicks. | -| `setPressTicks(int)` | `1000` | Duration to hold a button to trigger a long press. | +| Function | Default | Description | +| ----------------------- | ---------- | ------------------------------------------------------------- | +| `setDebounceTicks(int)` | `50 msec` | Period of time in which to ignore additional level changes. | +| `setClickTicks(int)` | `500 msec` | Timeout used to distinguish single clicks from double clicks. | +| `setPressTicks(int)` | `800 msec` | Duration to hold a button to trigger a long press. | + +You may change these default values but be aware that when you specify too short times +it is hard to click twice or you will create a press instead of a click. ### Additional Functions diff --git a/library.properties b/library.properties index a675be7..eca4f30 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=OneButton -version=1.6.0 +version=2.0.0 author=Matthias Hertel, mathertel@hotmail.com maintainer=Matthias Hertel sentence=Arduino library for improving the usage of a singe input button. -paragraph=It supports detecting events like single clicks, double clicks and long-time pressing. This enables you to reuse the same button for multiple functions and lowers the hardware invests. +paragraph=It supports detecting events like single, double, multiple clicks and long-time pressing. This enables you to reuse the same button for multiple functions and lowers the hardware invests. category=Signal Input/Output url=https://github.com/mathertel/OneButton architectures=* From 18217e9eec9e96f0ef3aff6b3b7cb9fb8bac2a25 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 16:25:28 +0100 Subject: [PATCH 15/16] git settings --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index af56f61..4048809 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store .idea/ +.vscode +_* \ No newline at end of file From 53d7ccac286f9b2eb36f9cb3983e65722b632db0 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 24 Jan 2021 16:31:13 +0100 Subject: [PATCH 16/16] doku --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6b09b..4da4241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file starting 202 ## [2.0.0] - 2021-01-22 * CHANGELOG created. -* Many thanks to the improvements included from #27 (**aslobodyanuk**), #59 (**ShaggyDog18**) and #73 (**geeksville**). +* Many thanks to the improvements included from #27 (**@aslobodyanuk**), #59 (**@ShaggyDog18**) and #73 (**@geeksville**). This is a major update with breaking changes.