Skip to main content

Arduino switch library with Short/Long Press, Double Click

Published: 06 December 2012
Last updated: 30 August 2024

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.

Arduino Triple Function Button: Short / Long Press, Double Click with BeepArduino Triple Function Button: Short / Long Press, Double Click with Beep

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.

Arduino Triple Function Button: Short / Long Press, Double Click with BeepArduino Triple Function Button: Short / Long Press, Double Click with Beep

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

Very poor switch contact bounce measured with Arduino oscilloscopeVery poor switch contact bounce measured with Arduino oscilloscope

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

Other articles from Interfacing with hardware