Arduino switch library with Short/Long Press, Double Click
- Questions
- Download the Switch library
- New: beep
- New: software deglitch
- Connecting switches to the Arduino
- Using the Switch library
- Transparent signal filtering
- Using callback functions to be triggered by button events
- process
- Notes
- Simple example
- How to use successive button events
- More information
- Software debounce, how does it work
- Timing diagram
- Using an interrupt service routine for polling the buttons
- If polling is interrupted by a delay function
I made this switch library years ago when decent switch libraries didn't exist, and over the years it has become more and more extended. It is now the best out there according to people who have compared them.
void beepCallbackFunction(void* s) // optional { tone(3, 2400, 5); // is non-blocking //Serial.print("BeepCallback: "); Serial.println((char*)s); }
The advantages of the Switch library are:
- Beep when a button is pressed.
- Performs not just de-bouncing, but also de-glitching against EMC pulses.
- External pull-up resistors are not required.
- Supports also long press, double click, and single click detection.
- Callback functions.
Questions
Post questions on the Arduino board and let me know, I will reply there.
Arduino forum
Download the Switch library
Download the library at GitHub.
New: beep
The arduino beeps when a button is pressed. To make it universal, the beep function is not integrated in the library. You can use any beep function you want, for example:
void beepCallbackFunction(void* s) // optional { tone(3, 2400, 5); // is non-blocking //Serial.print("BeepCallback: "); Serial.println((char*)s); }
Set the beep function in setup:
void setup() { toggleSwitch.setBeepAllCallback(&beepCallbackFunction/*, "Beep done"*/); // needed only for one switch because of static }
To make it easy, the setBeepAllCallback () function only needs to be used once. There is no code yet to disable buttons for the beep, I think that is necessary for two-position switches. The beep function uses static function pointers.
New: software deglitch
Normally, just debouncing is used. But debouncing doesn't make the software insensitive to noise pulses, which could lead to dangerous situations: if there is a poll() at the moment of a noise pulse, the software can detect an activated switch. To prevent this, a deglitch function is added now: during a "deglitchPeriod", all input values have to be equal.
Connecting switches to the Arduino
There is nothing needed beyond the switches, just connect the switches between the ground and a digital pin:
Switch between GND and digital pin
This is technically speaking the best solution and is taken by the library as default. An external pull-up resistor is not needed but allowed.
Switch between 5V and digital pin
For switches connected to the Arduino power supply, the settings are: polarity = HIGH and pinmode = INPUT, which disables the internal pull-up resistor. Note that we need external pull-down resistors of about 10k here.
Using the Switch library
Declare each switch or button, by indicanting its GPIO number:
Switch buttonGND = Switch(4); // GPIO 4
The library sets GPIO in input mode with an internal pull-up resistor by default. The constructor accepts several arguments including the particular input mode (e.g. without pull-up), whether voltage level high must be interpreted as on or off (argument polarity must indicate the pushed voltage level), and the duration of the time periods that define the behaviour.
All switches have to be repeatedly polled individually to update the status. This is usually done in the loop() function.
buttonGND.poll();
After refreshing the status, it can be checked with get functions. For example:
if(buttonGND.pushed()) Serial.print("pushed");
Any event occurs based on the deglitched and debounced pin signal. See below for details on this function of the library that happens transparently.
Each possible event (each switch-status) has its own get function:
switched()
It returns "true" if the pin voltage level changed, no matter the direction (from low to high or from high to low).
on()
It returns "true" if the pin voltage agrees with the one defined by polarity (which is LOW by default). Use only for toggle switches. It returns "true" as long as the switch is in the "on" position. The polarity of the switch in the "on" position has to be filled in correctly. There is no off() function, this is simply !on().
pushed()
It returns "true" if a button was pushed (switched towards the on position). Use only for push buttons. Note that this provides instant response and can be used as "single-click" if "double-click" and "long-press" are not watched events. "Pushed" occurs simultaneously with "double-click" and "long-press".
released()
It returns "true" if a push button was released (switched towards the off position), this will however rarely be used.
longPress()
It returns "true" if a push button is pressed longer than 300ms (by default). Note that a longPress() always will be preceded by pushed() from the first push. You can change the longPressPeriod with:
yourButton.longPressPeriod = 500; // instead of the default 300ms
doubleClick()
It returns "true" if a push button is double clicked within 250ms (by default). Note that a doubleClick() always will be preceded by pushed() from the first push.
In situations where, for example, you want to count the number of clicks, the doubleClick () function is undesirable. You can turn this off with
yourButton.doubleClickPeriod = 0;
The anteceding pushed() event can't be avoided, because to restrict the pushed() function to single clicks it would have to wait for a possible second click, which would introduce an annoying delay. So, the action on doubleClick() has to undo the previous action on pushed().
You can change the doubleClickPeriod with:
yourButton.doubleClickPeriod = 350; // instead of the default 250ms
singleClick()
It returns "true" if a push button is clicked once and the requirements for doubleClick and longPress are not met. The event thus occur several miliseconds after the actual push, because it depends on the other events not happening after the push. Use this if three different interactions are needed in combination with doubleClick and longPress. Note that a singleClick() always will be preceded by pushed(). Use pushed() instead if immediate response is required and there is no interest in monitoring doubleClick and longPress.
Example
See the example in the library for a complete working program.
Transparent signal filtering
Deglitching
Sudden short changes in the microcontroler power supply may create inexistent switched() events if the pin is polled in that instant. Deglitching is achieved because the software will not believe that a voltage change has happened (a swithed event) until the voltage remains in the new level for a minimum period. A voltage change will be ignored if it returns to the original level before that period.
Debouncing
Due to the elastic properties of the metals in the contacts they bounce away from each other after the initial contact creating false switched() events after an initial true switch, until the voltage stabilizes in the new level. Debouncing forces a refractary period after an initial switch, during which any voltage level change is ignored.
Using callback functions to be triggered by button events
Callbacks can be used, instead of needing to check with an if statement whether the relevant event has occurred after calling switch.poll().
With a callback registered, Switch will run the defined function when the conditions are met at the time poll() is called. Calling poll() periodically is the only requirement.
First, define a function with the action you want to perform, with no return type (void) and one argument, a pointer to some reference (or use nullptr if you don't need it.):
void foo(void* ref) { //your code; }
... and then call one of the "set callback" methods in the setup() function, like so:
void setup() { ... // Other setup code ... int reference; switch.setPushedCallback(&foo, &reference); ... }
The available callback setting functions are setPushedCallback(), setReleasedCallback(), setLongPressCallback(), setDoubleClickCallback(), and setSingleClickCallback() which allow defining the functions that will be called on such events. If using a toggle switch and not a push button, the "pushed" event will be of interest when the switch is turned on, and "released" when it is turned off.
If the conditions for more than one event occur simultaneously and there are callback functions registered for them, they will be executed in the order of the functions above.
See the example in the library for a complete working program.
process
The member function process() makes it possible to use the library in case the buttons are not directly connected to the microcontroller, but for example via an I2C expander PCF8574. I have also successfully used process() in the two-wire current-controlled bus. Up to version 1.0.5 the function process has been tested.
Notes
- The poll instruction must be called at least 50 times per second, i.e. every 20ms; it takes about 20us in Arduino.
- The button states are saved till the next poll. Then all previous button states will be cleared, so the button states must be read every poll interval, after calling the poll function.
- Reading a button several times within a poll interval gives the same value, so reading doesn't clear the button state.
Simple example
#include "Arduino.h" #include <Streaming.h> // https://github.com/kachok/arduino-libraries/blob/master/Streaming/Streaming.h #include "avdweb_Switch.h" const byte toggleSwitchpin = 3; const byte buttonGNDpin = 4; const byte ButtonVCCpin = 6; const byte Button10mspin = 8; int i; Switch buttonGND = Switch(buttonGNDpin); // button to GND, use internal 20K pullup resistor Switch toggleSwitch = Switch(toggleSwitchpin); Switch buttonVCC = Switch(ButtonVCCpin, INPUT, HIGH); // button to VCC, 10k pull-down resistor, no internal pull-up resistor, HIGH polarity Switch button10ms = Switch(Button10mspin, INPUT_PULLUP, LOW, 1); // debounceTime 1ms void setup() { Serial.begin(9600); } void loop() { buttonGND.poll(); if(buttonGND.switched()) Serial << "switched "; if(buttonGND.pushed()) Serial << "pushed " << ++i << " "; if(buttonGND.released()) Serial << "released\n"; if(toggleSwitch.poll()) Serial << toggleSwitch.on() << endl; if(toggleSwitch.longPress()) Serial << "longPress1 "; if(toggleSwitch.longPress()) Serial << "longPress2\n"; if(toggleSwitch.doubleClick()) Serial << "doubleClick1 "; if(toggleSwitch.doubleClick()) Serial << "doubleClick2\n"; }
How to use successive button events
With the Switch library, you can use all button events at the same time with the same button. For example pushed(), released(), doubleClick() and longPress(). To see how several button events can be used together, run Windows Explorer: a single click selects a file and a double click opens it.
Button pushed event followed by a longPress event
A long-press generates first a pushed event and after 300ms the longPress event. This is not a shortcoming but a logical consequence. We can, of course, not always wait 300ms to see if a push might be a long push.
Button pushed event followed by a doubleClick event
The same happens with doubleClick, which also generates two pushed() events. When doubleClick is used, ignore the second pushed() result or don't call pushed(). When doubleClick is not needed, simply don't call doubleClick().
More information
On GitHub you can read more about the library.
Software debounce, how does it work
The software debounce algorithm is based on the following assumptions; the 50ms is the so-called debounce delay:
- The switch bounce time is less than 50ms.
- The time between successive keystrokes is larger than 50ms.
- A reaction time of 50ms is acceptable.
If the above assumptions are met, the software debounce algorithm can be quite simple: a switch event is only accepted when the elapsed time since the last switch event is larger than the debounce delay.
A debounce delay of 50ms is a safe value; it doesn't hurt the reaction time, and will handle even bad switches.
if((newlevel != level) & (millis() - _switchedTime >= debounceDelay))
Timing diagram
The debounce timing diagram is included in the Switch.cpp file at GitHub.
..........................................DEGLITCHING.............................. ________________ _ on | | | | _ | | | | | | | |_| |___| |__ analog off_____|_____________________________|____________________________ ________________ _ _ input _____| |_| |___| |_______________________________ poll ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ equal 0 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 deglitchPeriod <--------><-- <-- <- <--------><--------><-------- ___________________________ deglitched _________________| |__________________ deglitchTime ^ ^ ^ ^ ^ ^ ^ ..........................................DEBOUNCING............................. debouncePeriod <--------------------------------> _________________________________ debounced _________________| |____________ _ _ _switched _________________| |_______________________________| |__________ switchedTime ^ ^ ********************************************************************************** ........................................DOUBLE CLICK.............................. __________ ______ debounced ________| |_______| |_____________________________ poll ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ _ _ pushed _________| |________________| |__________________________________ pushedTime ^ ^ doubleClickPeriod <-------------------------------------> _ _doubleClick ___________________________| |__________________________________ ........................................LONG PRESS................................ ___________________________ debounced ________| |___________________________ poll ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ longPressPeriod <---------------> _ _ _switched _________| |_________________________| |________________________ __________ longPressDisable ___________________________| |_________________________ _ _longPress ___________________________| |__________________________________
Using an interrupt service routine for polling the buttons
Polling buttons has a high priority, slow functions such as Serial.print() may disrupt the timing. See here how to use an ISR for polling the buttons:
#include <Arduino.h> #include "Switch.h" #include <FrequencyTimer2.h> Switch speedUpBtn(1); Switch speedDownBtn(2); Switch buttonLeft(3); Switch buttonRight(4); void setup(void) { Serial.begin(9600); FrequencyTimer2::setPeriod(1000); FrequencyTimer2::setOnOverflow(timer2ISR); } void loop(void) { printAll(); // run slow functions in loop() } void timer2ISR() { pollAll(); // polling buttons has priority buttonActions(); }
If polling is interrupted by a delay function
During a delay function in a program, requesting the buttons is interrupted for a moment. This is undesirable, the buttons are not read during the interruption. Moreover, if polling is started again after the delay, the library sometimes returns incorrect values. It is therefore better to use virtualDelay instead of delay that doesn't block the Arduino.
If it is ok that the buttons are not read during a delay then there is a solution: use dummy poll instructions, see the following example:
void weldCyclus() { pollAll(); pollAll(); // reset all buttons because polling was interrupted during delay functions weldPulse(menuItems[0].upDownValueTable); pollAll(); pollAll(); delay(menuItems[1].upDownValueTable); pollAll(); pollAll(); weldPulse(menuItems[2].upDownValueTable); pollAll(); pollAll(); }
Links