Become a subscriber (Free)

Join 29,000 other subscribers to receive subscriber sale discounts and other free resources.
:
:
Don't worry -- youre-mail address is totally secure. I promise to use it only to send you MicroZine.

Switch DeBouncing

Switch Debounce or "how to stop switch bounce".

Switch bounces are unwanted signal transitions generated when the mechanical contacts bounce off each other (and of course there's a spring in there which adds more bounce). As the bouncing settles down the switch comes to rest at the correct state.

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.

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

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!

The diagram below shows an input push button switch connected to a microcontroller input. Normally the input button closes to ground and a pullup is used at the input - microcontrollers usually provide an internal pullup so you won't always see the resistor shown below (some pins don't have internal pullups).

When pushed the microcontroller input is low, and when released the input value is high.

Example switch bounce signal at microcontroller input

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(BUTTON_PIN);  // 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 (or you  could add a millis() display code to the above code).

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!

Problem with Delay Time 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 to allow delays in a program.

Code for a delay switch debouncer

#define BUTTON_PIN 8

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() 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 Debouncer

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 has remain 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 debounce operation

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

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 States from Shift Register Decoder

The following code shows the output states of btndbc showing how the shift register (btndbc) is filled as the loop is iterated.

#define BUTTON_PIN 8

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

Interrupt switch debouncing

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.

Bounce 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 © 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

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 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 ]

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 ]


New! Comments

Have your say about what you just read! Leave me a comment in the box below.




Privacy Policy | Contact | About Me

Site Map | Terms of Use



Visit our Facebook Page:
To Visit Click Here


Recent Articles

  1. Arduino EEPROM - how it works and how to use it - with examples.

    Arduino EEPROM: How to use it and How to presrve the life of EEPROM. Two examples sketches to save multiple values to EEPROM.

    Read more

  2. How to use the ADS1115

    A tutorial on using the ADS1115 precision 16 bit ADC for low power use.

    Read more

  3. The TP4056: Lithium Ion/polymer Battery Charger IC

    Learn how to use the TP4056 properly. There's a right, and a wrong way, to use it to safely charge Lithium Ion batteries.

    Read more

  4. DW01A Battery Protector IC

    The DW01A chip is a Lithium Ion battery protector commonly used on TP4056 boards.  Find out Exactly how it works and how to use it the correct way.

    Read more

  5. Arduino String: How to read commands from the serial port.

    For Arduino string operations you can use Object Class Strings or C style strings but which should you use? Also find out how to decode commands and control variables in your programs using strings.

    Read more

  6. A Real Time Clock design (DS1307) with a PIC microcontroller

    Real Time Clock Design (FREE): A Free and Complete RTC design using the DS1307 and a PIC micro (16F88) also re-targetable. This PIC project uses an I2C Clock chip and 7-segment display to create a fou…

    Read more

Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's

SUPERB and FANTASTIC."

- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan

bursach<at>gmail.com<

"Awesome site,
very, very easy and nice
to navigate!"


- Matt
matt_tr<at>
wolf359.cjb.net


Learn Microcontrollers

"Interested in
Microcontrollers?"

Sign up for The
Free 7 day guide:

FREE GUIDE : CLICK HERE


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."


- Dave

de_scott<at>bellsouth.net

"Your site is a great
and perfect work.
congratulations."


- Suresh

integratredinfosys<at>
yahoo.com

"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.
Regards."


- Anon

Back to Top