What is switch bounce & how to implement debounce

Switch bounce is a common issue in electronics, particularly when working with physical switches. If you’ve ever noticed multiple signals from a single switch press, youā€™ll likely need to implement debounce. 

This phenomenon can happen to any switch, button or relay contact which uses mechanical force to connect two bits of metal to form a complete circuit and allow electricity to flow. That means it can happen in a keyboard, control panel, user interface or relay as they all use the same mechanical metal contacts to create the circuit. 

What is switch bounce?

Switch bounce occurs when the contacts inside a switch donā€™t close cleanly. When you press a switch, the internal components can momentarily bounce off each other instead of making immediate and stable contact. This brief bouncing can generate multiple electrical signals, even though you pressed the switch just once.

To visualize this, imagine two wires touching to close a circuit. Ideally, they meet and stay connected but in reality, they may bounce rapidly making and breaking contact before settling. Each bounce that goes over the threshold of an input in a microcontroller or processor can register an extra input pulse.

Without a way to handle this bouncing, systems can behave unpredictably, registering one press as several. This leads to errors in counting, toggling, or any action the switch is supposed to control.

The effects of switch bounce

To demonstrate the impact of switch bounce, we need a simple switch circuit that we can test and measure. In this example circuit, we have the switch to ground and a pull-up resistor going into a microcontroller. 

The microcontroller is running a program that is continuously checking the switch position and adding 1 to a counter when the switch is triggered, followed by waiting until the switch is released (to prevent counter spam when the button is held down). 

int buttonPin = 2;  // the number of the pushbutton pin
int buttonState = 0;  // variable for reading the pushbutton status
int counter = 0;

void setup() {
  Serial.begin(9600); // Begin serial interface
  pinMode(buttonPin, INPUT); // Setup button input
}
void loop() {
  buttonState = digitalRead(buttonPin);   // Read the button state
  if(buttonState == 0) {        // If the button is pressed
    if(pressed == false) {      // and if the button hasn't been held down (preventing spam)
      counter = counter + 1;    // Add 1 to the counter
      Serial.println(counter);  // Output counter to Serial bus
      pressed = true;     // Stop counting till the button is let go
    }
  }
  else {                  // When the button is let go
    pressed = false;      // Reset the 'pressed' boolean
  }
}

When observing the behavior on an oscilloscope, you’ll likely see that sometimes a single press results in two or more counts. This happens because the bouncing signal crosses the microcontroller’s voltage threshold multiple times, registering each bounce as a press.

The goal of adding debounce is to eliminate these extra signals and ensure the system only registers one input per switch press.

Two approaches can be employed to eliminate bounce from a switch. The first is a hardware debounce that utilizes electronic parts to reduce the bounce beneath the threshold. The second is a firmware debounce that incorporates a delay in the firmware to disregard the bouncing switch.

You can test and verify each of these methods using an oscilloscope to view the input and output signal. 

Implementing hardware debounce

One approach to solving switch bounce is through hardware debounce, where components like resistors and capacitors smooth out the electrical signal. By adding a capacitor and resistor to the circuit, you can filter out the high-frequency bouncing, leaving just the low-frequency button press. This configuration of capacitor and resistor functions as a low-pass filter, so you can use all the calculations of an RC filter circuit to calculate the debounce of the circuit.

Below weā€™re using the same circuit example we used earlier. This features a switch to ground and a pull-up resistor to 5 V.

To implement a hardware debounce you first need to test how long the initial bouncing is. Using an oscilloscope allows you to measure the length of the bouncing to be able to calculate the resistor and capacitor values to add to the circuit. In this example we measured a bouncing time of around 300 Ī¼s.

To determine the appropriate resistor and capacitor values, youā€™ll need a combination of values that fully discharge the capacitor longer than the maximum bouncing of the switch.

Working out the discharge time is simple, first you need to calculate the time constant (Ļ„) of the RC circuit – this will give you roughly 37% discharge of the capacitor. Then you will need to multiply this constant by 5 to have a ā€œfullyā€ discharged capacitor.

In the example below, the switch bounce stops after 300 Ī¼s. Therefore our RC full discharge needs to be over that. Weā€™ve selected the values 1 kĪ© resistor and a 100 nF capacitor, if we put those values into the formula below, we should get a value over 300 Ī¼s.

    \[\tau = \frac{1}{RC} = \frac{1}{1\,k \times 100\,n} = 100\,\mu s\]

    \[t_{discharge} = 5 \times \tau = 500\,\mu s\]

    \[500\,\mu s > 300\,\mu s\]

With a discharge time of around 500 Āµs, our pull-down button will take that much time to fall to 0 V, suppressing any high-frequency switch bounce in our circuit. 

As the button is a pull-down, weā€™ll need our pull-up resistor to bring the voltage back to 5 V and for this weā€™re using a 10 kĪ© to reduce quiescent current draw and our capacitor will slow down the rise time of our pull-up. We can calculate the rise time using the same formula.

    \[t_{charge} = 5 \times \tau = 5 \times \frac{1}{RC} = 5 \times \frac{1}{10\,k \times 100\,n} = 5\,ms\]

Due to our pull-up resistor selection, the resultant time of charge is 5 ms which is quite large in comparison to 500 Ī¼s. But in comparison to how much a user presses the button, itā€™s a very short amount of time making it acceptable. 

Now that you have chosen the R and C values using the above formula, you can add them to a button input and test the setup with an oscilloscope to ensure that the bouncing is removed.

The downside of hardware debounce is that once you build the circuit, changing the debounce time becomes difficult without physically modifying the components.

Implementing firmware debounce

Firmware debounce offers a more flexible solution, allowing you to handle switch bounce entirely in software. Instead of adding components to the circuit, you build code that processes the input signal and filters out the bounce.

In a simple firmware debounce implementation, after the microcontroller has detected a switch input, a delay of a few milliseconds is added, followed by a switch check that checks if the switch is still depressed. If the switch is still pressed, the controller registers the input and effectively ignores any bouncing signals.

The code button reading code may look like this:

buttonState = digitalRead(buttonPin);   // Read the button state
delaymicroseconds(500);                 // delay for 500 microseconds

And if we output that straight to an Arduino output, the result would look like this:

While effective, this approach can cause the microcontroller to hold-up briefly while waiting for the delay. In cases where the microcontroller needs to perform a time critical task, this delay can be problematic. 

To avoid this, you can optimize firmware debounce using interrupts which work using background timers and logic, or use the microcontroller clock to test for a button press/release instead of delays. These techniques allow the microcontroller to continue running other tasks while still handling the debounce process in the background.

Hereā€™s an example of a debounce button read that uses ā€˜millis()ā€™ as a timer:

// constants won't change. They're used here to set pin numbers:
const int buttonPin = 2;    // Pin connected to the pushbutton
const int outputPin = 3;    // Pin used for output signal


// variables will change:
int buttonState = 0;        // Variable to store the state of the pushbutton (HIGH or LOW)


// previous millis counter
unsigned long prev_millis = 0; // Stores the last time the button was checked, used for debounce timing


void setup() {
  Serial.begin(9600);             // Start serial communication for debugging (9600 baud rate)
 
  // initialize the button pin as an input
  pinMode(buttonPin, INPUT);       // Sets pin 2 as input to read the pushbutton
  // initialize the output pin as an output
  pinMode(outputPin, OUTPUT);      // Sets pin 3 as output to control something (e.g., an LED)
}


void loop() {
  // Read the current state of the pushbutton (HIGH when pressed, LOW when not)
  // If the button's current state matches the stored state (buttonState), continue
  if (digitalRead(buttonPin) == buttonState) {
    buttonState = digitalRead(buttonPin);   // Update the buttonState to the current reading
    prev_millis = millis();                 // Reset the millis counter for debounce timing
  }


  // Check if more than 1 millisecond has passed since the last time the button state was recorded
  // This prevents the button press from being detected too quickly (debounce logic)
  if (millis() - prev_millis > 1) {
    // Read the button state again after the debounce delay
    if (digitalRead(buttonPin) == buttonState) {
      // Write the current button state to the output pin (i.e., turn something on or off)
      digitalWrite(outputPin, buttonState);  // If button is pressed, outputPin will match the button state
    }
  }
 
  // Loop repeats continuously
}

Firmware debounce is more flexible than hardware debounce because you can adjust the debounce time in the code. However, it relies on the microcontrollerā€™s processing power, which may be a concern in resource-limited systems.

Hardware vs software debounce, which should you use?

Both hardware and firmware debounce methods are effective, but the best choice depends on your specific application. Hardware debounce is simple and reliable and it doesnā€™t require the microcontrollerā€™s resources. However, once implemented, itā€™s not easy to adjust the debounce time.

Firmware debounce, on the other hand, offers more adaptability and developers can make adjustments quickly in the code.  The downside is that it can use up microcontroller resources, especially if not optimized.

Regardless of the method you choose, it’s important to test your debounce implementation thoroughly using an oscilloscope. This ensures your system registers inputs accurately and reliably, avoiding issues with switch bounce.

Engineers Notes

When creating these two examples, Iā€™ve learnt how simple it is to implement a hardware debounce with a simple resistor and capacitor. Although it may seem simpler to fix it in software, I always find itā€™s better to add provisions in a PCB design for a hardware debounce. 

I find it best practice to add Do Not Fit (DNF) small, surface mount RC components on the switch trace of a PCB. This provides the flexibility to test the button for debounce using an oscilloscope, then I can decide if I have the capacity in the code to add debounce – this may be a limit to interrupts or a constraint of time-critical code. If itā€™s not possible, I simply add the RC components and be happy with a reliable, consistent hardware debounce. 

For testing the debounce, I used a PicoScope 5444D along with PicoScope 7 software. The built-in rulers made it straightforward to measure the debounce for this article. It could capture a switch press lasting just a few milliseconds and thanks to the PicoScopeā€™s large sample memory, I could zoom in and measure the bouncing down to hundredths of a millisecond.