Switch DeBounce: Why is a switch not
ideal and why does it bounce (generate oscillating output)? What affects
the amount of bounce you
get from a specific type of switch? Three methods to deal with switch
bouncing resulting in ideal switch action.
Switch Debounce or "how to stop switch bounce".
Eliminate oscillating button switch inputs.
Three methods from simple to advanced examined here.
One method uses a time delay
One method uses interrupts and smoothing.
The shift register methodis unique and very clever.
Switch bouncing occurs due to the mechanical nature of physical
switches. When a switch is toggled, the contacts inside bounce against
each other briefly as they change position. This bouncing causes
unwanted electrical noise known as switch bounce.
The bouncing happens as the contacts separate and touch multiple times
rapidly before settling into their final open or closed resting
position. A spring helps the contacts return to their original alignment
but can prolong the bouncing effect. Even momentary presses induce
bouncing that lasts several milliseconds.
The degree and duration of bouncing varies depending on the switch
design and amount of force used. Harder presses tend to cause more
numerous and longer-lasting bounces compared to softer presses.
Different manufacturers' switches also exhibit a range of bounce
characteristics.
When you push a momentary button it feels like it is a clean on-off
operation but in fact there is enough time for the contacts to bounce a
great deal as you push and release the button. It is just unavoidable.
Different switches cause
different bounce characteristics, and even how hard you press the switch
changes the number of bounces and bounce periods.
When you write code for the microcontroller to read an input switch
you probably expect it to go low only once (the ideal switch!), so you
may have thought
that a simple read of the pin would be enough - its not!
In fact the
switch bounce can last for several milliseconds and the bounce periods
can be 10's of nanoseconds or even microseconds so the microcontroller will
think that the button is pushed multiple times. You can see a typical bounce in the above image.
Warning: Physical switch characteristics determine the bounce seen.
If you are trying to increase a variable by 1 you
don't want it to increase by a random amount!
Oscilloscope results
Here are the push buttons used (the smaller is 6x6mm):
The outputs from two push buttons are shown - they produce very different results.
Physically small push button
The following oscilloscope output is for a small button (6x6mm) i.e. a physically
smaller device. More often than not there is no bounce (but there can
be). It only really bounces near the rising edge.
Each column is worth 1ms.
Physically large push button
The following oscilloscope output is for large push button. This is for a push
button pulled high by ~100k in an Arduino input pin. Each column is
worth 20ms:
Push Button connection
A push button is usually connected from the microcontroller input to
ground. This is because R1 is usually the internal pullup provided by
the microcontroller (they are always pull-ups).
When pushed the microcontroller input is pulled low by the connection
to ground, and when released the input value is pulled high by the
resistor.
So the input is LOW when the button is pressed which is a bit counter
intuitive as it means Zero is active! (You can easily invert this in
software if needed).
Button press detection
A microcontroller is capable of detecting many transitions on an input, so using the unprocessed 'large button' signal results in 100s of button press detections, when only one should be
flagged! Even for the small button there are multiple transitions.
However you
can see that there is a low period in the middle after the button has
been pushed. This is one key to debouncing the signal i.e. eventually the signal settles down.
There are basically two things to to do
Filter the input
Digitally process the input.
Or both!
Switch Bounce Demonstration Code
The program below lets you see switch bounce transitions when an
input button is pressed and released i.e. the output shows you what the
microcontroller is actually seeing as an input signal.
The variable b is set to read the input every time round the loop
(since the input is pulled up by the internal pullup) the button pulls it
low so pressing a key sends zero to the microcontroller input.
If this value is not the same as the last read of b (then b is
printed out). So this prints out every change of input that is read by
the microcontroller. The text "Key Stable" is only output once the value
of b has not changed for greater than 500ms i.e. if you push and hold
the key, the routine detects that the input is high and not changed
after 500 ms, and outputs that text.
The results of the program are shown below the code.
Note: Further down the page you can find an interrupt driven button
push detector. Since it operates faster it shows there are many more
transitions i.e. as in the oscilloscope image above.
Code for the Arduino Uno with a button connected across pin 8 and ground.
#define BUTTON_PIN 8
voidsetup(void){
pinMode(BUTTON_PIN,INPUT_PULLUP);Serial.begin(250000);
}
voidloop(void){
staticbytelastb=0,bstate=0;
staticbyteprintNextStable=0;
staticuint32_tlastUnstable=millis();
byte b=!digitalRead();// Pulled up so zero = hit.
if(lastb!=b){
Serial.println(b);
printNextStable=1;
lastUnstable=millis();
}
lastb=b;
if(printNextStable&&millis()-lastUnstable>500){
Serial.println("Key Stable");printNextStable=0;
}
}
These results are for pressing the button ONCE and holding,
then releasing the button ONCE! As you can see there is lot of
switch bounce. If you try this for yourself you will see a lot of
variation - sometimes no bounce and sometimes a lot.
Since there are a different number of bounces each time it means that
the button is taking time to settle and this bounce time can vary a lot.
Some switches bounce more than others so looking at the output using a
DSO will allow you to see how long the bounce lasts.
Observing the switch output on an oscilloscope shows how long
bouncing persists before the contact stops oscillating. Measurement
reveals individual switches can bounce with significantly different
durations due to construction.
Delay Switch Debouncers
The simplest way of implementing switch debounce is to detect the
first key press, wait a while and see if is still the same. If it is
then the key is valid, and if not you ignore it. This is an extremely common method for switch debouncing.
It sounds like the ideal debouncer but there is a problem with it!
Switch Debounce with delay() function
Problem with Delay(): Switch Debounce
It's that word 'delay', and the simplest delay method is to use the
delay function but that means that the processor is held up doing
nothing since the delay function is a do-nothing function.
The more subtle problem with this decode method is that you need to
vary the delay time based on the switching characteristics that the
switch exhibits, so you may end up with a delay time of 100ms just
because of the type of switch you used!
This is time that could be spent doing something else, and in
some cases you can not afford delays in a program.
Code for a delay(): Switch Debounce
#define BUTTON_PIN 8 #define LED_PIN LED_BUILTIN
voidsetup(void){
pinMode(BUTTON_PIN,INPUT_PULLUP);
pinMode(LED_PIN,OUTPUT);
Serial.begin(250000);
}
voidprocessor_operation(void){}
voidloop(void){
bytelastb=0,b;
// Check for keypress
if(b=!digitalRead(BUTTON_PIN)){// Pulled up so zero = hit.
delay(50);
if(b==!digitalRead(BUTTON_PIN))
Serial.println("Key Stable");
while(!digitalRead(BUTTON_PIN));// wait for low
}
// Processor can operate here but
// only after the above delays finish if triggered by a button press.
processor_operation();
}
Note how the microcontroller can do nothing while waiting for the delay time 'delay(50)'.
Timer alternative to delay(): Switch Debounce
One way around this problem is to use a non blocking delay - this is a
delay method that uses the millis() timer to record when events happen.
Since the delay times are stored your program can exit that function
and return to it later - 'non blocking'. To make it work you will need a
state machine similar to the one below.
The code works by storing a future time
to
re-check the key input. The loop function below is continually
re-entered so a state machine is needed to keep track of the required
program actions. These actions depend on the stored time - in this case
30ms after the 1st key press.
Code for Timed Switch Debounce
In state 0 you are waiting for the 1st keypress. In state 1 you are
waiting for a delay time after the first keypress and then re-checking
the key.
If the key remaind pressed then the output "Key pressed" is
generated. The last state (2) is entered to stop multiple "Key pressed"
outputs and simply waits for the key to be released, after which the
state is set to 0 - back to the start.
voidsetup(void){
pinMode(BUTTON_PIN,INPUT_PULLUP);
pinMode(LED_PIN,OUTPUT);
Serial.begin(250000);
}
#define STATE_WAIT_KEY 0
#define STATE_CHECK_KEY 1
#define STATE_WAIT_KEY_RELEASE 2
voidloop(void){
staticbytestate=STATE_WAIT_KEY;
staticuint32_tstartKeyPress,stopKeyPress;
byteb=!digitalRead(BUTTON_PIN);// Pulled up so zero = hit.
if(state==STATE_WAIT_KEY&&b==1){
startKeyPress=millis();
state=STATE_CHECK_KEY;
}
// After n milliseconds, is the key still pressed?
if(state==STATE_CHECK_KEY&&(millis()-startKeyPress)>30){
if(b==1){
Serial.print("Key pressed ");Serial.println(millis());
state=STATE_WAIT_KEY_RELEASE;
}elsestate=STATE_WAIT_KEY;
}
if(state==STATE_WAIT_KEY_RELEASE&&b==0)state=STATE_WAIT_KEY;
}
Note: b is the inverted input value, so (1=pressed & 0=released).
Originally I set the debounce delay to 5 (5ms) but the key bounce caused
multiple output so I set it to 10ms. Then that was not good enough
(multiple keypress detected) so I set it to 30ms. Other switches behave
better and you can get away with 1ms!
This is the problem with delay type methods - you have to characterise
the switch in use and adjust the code to suit it. The other problem is
that the routine is getting complicated.
The reason is that the code can
be placed in a function and that function will not block the processor
from doing other work.
Shift Register debounce
I came across this debouncing method from university notes (source
Jack Gansel : Guide to Debouncing) and it is really a very clever piece
of code and the essential part of it is this:
In one go, a button is reliably debounced and there's no delay
function in sight! (you place the function in a loop that is regularly
refreshed). as long as the loop delay is smaller than the total button
press time then it works.
It is really a shift register that collects snapshots of the button
press at regular intervals. When the output, btndbc, reaches a specific
value it means that the key press is valid.
Shift register operation: Switch Debounce
First of all the line above is designed to be called regularly so you
either put it into the loop() function or call it regularly via a timer
interrupt.
At each call of that line the storage element is shifted left by one
bit and a new value of the input pin is 'ored' into the lowest bit. So
those two commands are creating a shift register with the lowest bit fed
in as input.
After that the result is 'ored' with 0xe000.
Note that the digital input pin is low for a key-press.
The next part of the code reads as follows:
if (btndbc==0xf000)
button_press_action();
So this is saying that if the store 'btndbc' reads 0xf000 then the
button has been debounced and is valid. This can only be true if the
following hex number has been accumulated in btndbc:
0x1000 i.e. the input has had a 'one' followed by 12 'zeros' accumulated
around the loop. When ored with 0xe000 you get the value 0xf000.
(0xe000 is masking off the top 3 bits as not used)
If at any time around the loop during the accumulation phase you get a
one then the debouncer will not trigger since the value is not 0xf000.
e.g. 0x1004 would fail i.e. the input bounced while accumulating.
When bouncing has stopped there will be a first 'one' followed by 12 'zeros' i.e. debounced.
If you use this code in the loop() function then you have to have
some time spent doing other work so that you don't call the debouncer
too fast - either processing or add some delay! This delay will be the
time between accumulation of input data in the debouncer (time between calls to the debouncer routine).
Code for Shift Register: Switch Debouncer
Here is the code for real, in action. Note how simple it looks compared to the delay timer non-blocking method, and it works!
#define BUTTON_PIN 8
#define LED_PIN LED_BUILTIN
voidsetup(void){
pinMode(BUTTON_PIN,INPUT_PULLUP);
pinMode(LED_PIN,OUTPUT);
Serial.begin(250000);
}
/////////////////////////////////////////////////////////////////
voidbutton_press_action(void){
Serial.println("BUTTON PRESS");
}
/////////////////////////////////////////////////////////////////
// Debounce a button.
voidbutton_press_and_action(void){
staticuint16_tbtndbc=0;
btndbc=(btndbc<<1)|digitalRead(BUTTON_PIN)|0xe000;
if(btndbc==0xf000){// High for 1 bits low for 12 (each bit is 1 loop thru this code).
button_press_action();
}
}
voidloop(void){
button_press_and_action();
delay(1);
}
The above debouncer does not itself use delays but actually
needs a delay (or timer) to function. The debouncer is very useful for
quickly and reliably debouncing an input and uses a very small code
footprint.
Demo of Output of Shift Register
The following code shows the output states of btndbc showing how the
shift register (btndbc) is filled as the loop is iterated during switch
debounce.
Here is the output it produces showing btndbc filling with zero
from FFFF to F000. The next value is E000 when the button press is detected and the text "BUTTON PRESS" is generated.
Then btndbc fills with ones from E000 to FFFF when the push button is released again.
One of the problems with the previous code is that the time round the
loop is undefined and actually depends on how much processing is going
on. It means that the time between updates to the shift register could
vary depending on what your code is doing.
To solve this problem the following code only updates the shift
register if enough time has passed since the last update. It does this
by using a non-blocking delay operation and the period of update is set
to be greater than the value of
SHIFT_DEBOUNCE_DETECT_TIME
The code performs the same operation as the previous one except for the delay period.
#define LED_PIN LED_BUILTIN
#define BUTTON_PIN 8
#define SHIFT_DEBOUNCE_DETECT_TIME 10
staticunsignedlongshift_debounce_old_time=millis();
voidsetup(void){
pinMode(BUTTON_PIN,INPUT_PULLUP);
pinMode(LED_PIN,OUTPUT);
Serial.begin(250000);
}
/////////////////////////////////////////////////////////////////
voidbutton_press_action(void){
Serial.println("BUTTON PRESS");
}
/////////////////////////////////////////////////////////////////
// Debounce a button.
uint8_tbutton_press_and_action(void){
staticuint16_tbtndbc=0,lastb=0;
// Timer to space out calls when this function is in loop.
if(millis()-shift_debounce_old_time>SHIFT_DEBOUNCE_DETECT_TIME){shift_debounce_old_time=millis();// Restart timer.
btndbc=(btndbc<<1)|digitalRead(BUTTON_PIN)|0xe000;
if(btndbc!=lastb)Serial.println(btndbc,HEX);
lastb=btndbc;
if(btndbc==0xf000)button_press_action();
}
}
voidloop(void){
button_press_and_action();
// delay(1);
}
An interesting thing to do is set the 'SHIFT_DEBOUNCE_DETECT_TIME'
variable to 100. This really slows things down. You can now release the
button before the sequence finishes - simulating a bounce. The key press
won't be detected until you hold it for ~1500ms!
Interrupt switch debounce
Yes I know, everyone says never do this because you will generate an
unknown number of interrupts that interfere with the operation of the
processor. This is true and without a filter you can get 10s or 100s of interrupts (depending on the switch characteristics).
If you add a filter you get one or two interrupts (for a very bouncy
switch) and probably only one for a switch that was good in the first
place.
The
advantage of this method is that minimal code is required and adding a
filter means minimal interrupts. This means less processing i.e. you can do more useful stuff.
Switch Debounce Interrupt test code
Use the program below to investigate a button connected to an
interrupt pin. Here we are using pin 2, so it will use INT0.
// By John Main (C) best-microcontroller-projects.com
// Simple LED flash and button interrupt smoothing test.
#define LED LED_BUILTIN
#define interruptPin 2 // Can only be pin 2 or 3 on the Uno.
#define TONE_PIN A3
volatileuint16_tdelayTime=500;
volatilebyteflgTone=0,intsFound=0;
//////////////////////////////////////////////
voidsetup(void){
pinMode(LED,OUTPUT);
// pinMode(interruptPin,INPUT); //No pullup to allow external smoothing.
pinMode(interruptPin,INPUT_PULLUP);
pinMode(TONE_PIN,OUTPUT);
Serial.begin(115200);
attachInterrupt(digitalPinToInterrupt(interruptPin),myisr,RISING);
}
//////////////////////////////////////////////
// Arduino Delay LED flash.
voidloop(){
delay(delayTime);
digitalWrite(LED,HIGH);
delay(delayTime);
digitalWrite(LED,LOW);
if(flgTone){
Serial.print("Found ");Serial.print(intsFound);
Serial.print(" interrupts, using: ");Serial.println(flgTone);
if(intsFound>1)
tone(TONE_PIN,600,100);
else
tone(TONE_PIN,300,100);
intsFound=flgTone=0;
}
}
//////////////////////////////////////////////
// Interrupt Service Routine
voidmyisr(){
staticbytestate=1;
staticuint32_tlastButtonTime=0;
// Filter out too quick buttons = errors.
if(millis()-lastButtonTime>300){
state=!state;
if(state)delayTime=500;elsedelayTime=50;
flgTone++;
lastButtonTime=millis();
}
intsFound++;
}
[file: flash_led_interrupt_switch_debounce_test]
The rising edge of the input triggers the interrupt. The
number of interrupts occuring is reported to the loop via the variable
intsFound. Additionally, if you add a sounder to pin A3 a low sound is
generated for single
interrupts and a high for >1 interrupt.
Note how the delay timeout in the ISR, stops multiple
interrupts triggering keypress detection i.e. it waits for 300ms before
accepting another interrupt.
Without filtering you will see the following results (typical):
4:44:24.839 -> Found 134 interrupts, using: 1
14:47:12.219 -> Found 12 interrupts, using: 1
14:47:59.882 -> Found 144 interrupts, using: 1
14:48:58.332 -> Found 81 interrupts, using: 1
14:49:37.445 -> Found 3 interrupts, using: 1
14:52:13.397 -> Found 52 interrupts, using: 1
14:52:26.068 -> Found 79 interrupts, using: 1
Each time the button was pressed a lot of bouncing was observed - as
the Gansel document talks about this could have been because of the 0.8 to
2.0V undefined area however the digital pins in an Arduino have a
schmitt trigger built in. the schmitt trigger is specifically there to
get round the 0.8 to 2.0V undefined region (and slow input signals) so it
probably is bouncing this much!
Bouncing switch screenshot
Here's a screen shot of the 134 bounce button press
You can see that the button was down for 188ms and bounces a lot.
Add Smoothing Capacitor: Switch Debounce
Now simply add a 100nF capacitor to the interrupt pin and one end to
ground. This forms a low pass filter with the 100k pullup internal to
the Arduino pin.
Note: The capacitor will only smooth the rising edge as it is shorted
when pulled low by the switch - that's ok since the interrupt is set to
fire on the rising edge.
Note: The internal schmitt trigger takes care of slowly rising signals.
You will see the following typical results:
Found 1 interrupts, using: 1
Found 1 interrupts, using: 1
Found 1 interrupts, using: 1
Found 1 interrupts, using: 1
Found 2 interrupts, using: 1
Found 1 interrupts, using: 1
Found 2 interrupts, using: 1
Found 1 interrupts, using: 1
Found 1 interrupts, using: 1
Smoothed switch debounce screenshot
Here's a screenshot for the smoothed bouncing switch input:
Only one interrupt is taken as valid; Determined by the 300ms timeout in the ISR.
Conclusion
Delay method
Advantage: Simple and quick. Disadvantage: Although commonly used but could be unreliable (depends on switch characteristics).
[link]
Shift Register method
Advantage: Works (very well), Does not need extra components. Disadvantage: Uses more processing time, not so easy to understand, timing loop will affect detection.
[ link ]
Note: The timer version of this code allows more predictable operation.[ link ]
Smoothing method
Advantage: Smoothing the input does work and uses less processing time. Disadvantage: Uses extra component(s), still needs s/w timeout for reliability.
[ link ]
Written by John Main who has a degree in Electronic Engineering.
Unlock the secrets of Arduino scrolling displays! This beginner-friendly guide shows you how to create real-time, dynamic graphics using an SSD1306 OLED, perfect for tracking sensor data and building…
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.