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 method is 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.

switch bounce waveform

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):


pushbuttons red and 6x6

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.

smd switch bounce
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:


switch bounce example DSO

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).


Example switch bounce signal at microcontroller input

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

void setup(void) {

   pinMode(BUTTON_PIN, INPUT_PULLUP);   

   Serial.begin(250000);
}

void loop(void) {
static byte lastb = 0, bstate = 0;
static byte printNextStable=0;
static uint32_t lastUnstable = 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;
   }
}

Key Stable
1
0
1
0
1
0
1
Key Stable
0
1
0
1
0
1
0
1
0
1
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

void setup(void) { pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN,OUTPUT); Serial.begin(250000); } void processor_operation(void) {} void loop(void) { byte lastb = 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.


void setup(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

void loop(void) {
static byte state = STATE_WAIT_KEY;
static uint32_t startKeyPress,stopKeyPress;

   byte b = !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;
       } else state = 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:

uint16_t btndbc;

btndbc=(btndbc<<1) | digitalRead(BUTTON_PIN) | 0xe000;

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

void setup(void) { pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN,OUTPUT); Serial.begin(250000); } ///////////////////////////////////////////////////////////////// void button_press_action(void) { Serial.println("BUTTON PRESS"); } ///////////////////////////////////////////////////////////////// // Debounce a button. void button_press_and_action(void) { static uint16_t btndbc = 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(); } } void loop(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.

#define BUTTON_PIN 8
#define LED_PIN LED_BUILTIN

void setup(void) { pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN,OUTPUT); Serial.begin(250000); } ///////////////////////////////////////////////////////////////// void button_press_action(void) { Serial.println("BUTTON PRESS"); } ///////////////////////////////////////////////////////////////// // Debounce a button. uint8_t button_press_and_action(void) { static uint16_t btndbc = 0, lastb = 0; btndbc=(btndbc<<1) | digitalRead(BUTTON_PIN) | 0xe000; if (btndbc!=lastb) Serial.println(btndbc,HEX); lastb = btndbc; if (btndbc==0xf000) button_press_action(); } void loop(void) { button_press_and_action(); delay(1); }

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.

FFFF
FFFE
FFFC
FFF8
FFF0
FFE0
FFC0
FF80
FF00
FE00
FC00
F800
F000
BUTTON PRESS
E000
E001
E003
E007
E00F
E01F
E03F
E07F
E0FF
E1FF
E3FF
E7FF
EFFF
FFFF

Shift Register Debounce with timer

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

static unsigned long shift_debounce_old_time = millis();

void setup(void) {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN,OUTPUT);

  Serial.begin(250000);
}

/////////////////////////////////////////////////////////////////
void button_press_action(void) {
  Serial.println("BUTTON PRESS");
}

/////////////////////////////////////////////////////////////////
// Debounce a button.
uint8_t button_press_and_action(void) {
static uint16_t btndbc = 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();
  }
}

void loop(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

volatile uint16_t delayTime= 500;
volatile byte flgTone = 0,intsFound = 0;

//////////////////////////////////////////////
void setup(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.
void loop(){

  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
void myisr() {
static byte state = 1;
static uint32_t lastButtonTime=0;

  // Filter out too quick buttons = errors.
  if (millis()-lastButtonTime > 300) {
    state = !state;
    if (state) delayTime = 500; else delayTime = 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

switch bounce example DSO

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:

smoothed interrput bounce input switch

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.



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.




Privacy Policy | Contact | About Me

Site Map | Terms of Use