It’s a fact of life that mechanical switches don’t open and close perfectly. The contacts can “bounce” on opening or closing resulting in a short period of intermittent contact before settling into their final state. This typically occurs over a timescale of milliseconds.
Figure 1 shows a ‘scope trace of a typical momentary switch with a pull-up resistor closing. The vertical resolution is 1V per division and the horizontal resolution is 1ms per division. You can see that the transition from high to low is far from clean. A digital circuit will probably interpret this as a series of transitions, not one single high-to-low edge as we intend.
This waveform was captured when a momentary switch with pull-up to 3.3V was closed. The horizontal timescale in 1ms per division. The switch does not close cleanly, but “bounces” between closed an open a number of times before settling in its final state. A digital circuit will see this as a series of transitions not a single high-to-low edge as expected.
A simple hardware solution as shown in Figure 2. When the switch is closed, the capacitor C is discharged via R2, and when it is opened it is charged via R1 and R2 in series. If we select the RC time constants appropriately, they will filter out the switching transition artifacts and present a relatively smooth waveform to the digital circuit as shown on the right. Ideally, the digital input should be Schmitt trigger type to completely eliminate the possibility of any uncertainty in the digital signal, however this circuit will work reliably if the components are carefully chosen.
When the switch is closed, the capacitor C is discharged via R2, and when it is opened it is charged via R1 and R2 in series. If we select the RC time constants appropriately, they will filter out the switching transition artefacts and present a relatively smooth waveform to the digital circuit as shown on the right.
Very often, if these switches are connected to a microcontroller, the pull-up will be internal, so the circuit of Figure 2 needs to be adapted to that of Figure 3. This time the capacitor discharged via R2 when the button is pressed and charged via R1 when it is released. There is one other important difference – when the button is pressed, R1 and R2 form a voltage divider so the voltage at the microcontroller input is some non-zero value shown in the waveform at right. You must choose R2 to be low enough that this voltage is below the microcontroller’s logic zero threshold.
If the microcontroller has a built-in pull-up you will need to use this circuit. The capacitor discharged via R2 when the button is pressed and charged via R1 when it is released. Note that when the button is pressed, R1 and R2 form a voltage divider so the voltage at the microcontroller input is some non-zero value. You must choose R2 to be low enough that this voltage is below the microcontroller’s logic zero threshold.
We can perform the debouncing entirely in software if we choose. This has the advantage of being more flexible and requiring a minimum of external components. In the case of a microcontroller input with programmable pull-ups you don’t need any external components apart from the switch itself.
There are a few ways to do this. One way is to use an interrupt-on-change input to indicate there has been a level change on the input, then to sample the pin after some time delay as shown in Figure 4. If the time delay period is chosen to be longer than the contact bounce time, we can be sure the value we sample is the switch’s final state.
— ADVERTISMENT—
—Advertise Here—
Debouncing in software can be achieved by using an interrupt-on-change input to indicate there has been a level change on the input, then to sample the pin after some time delay. If the time delay period is chosen to be longer than the contact bounce time, we can be sure the value we sample is the switch’s final state.
Code Snippet 1 shows a typical example of how this might be done. It is a pretty effective technique but requires us to disable further interrupts during the time delay. If several switch inputs share one interrupt, we could miss a transition on one pin while waiting for another.
Another approach is to use a polling technique and a shift register. We read each input on a regular basis and push the result into a shift register, effectively giving us a record of the last few samples. Figure 5 shows how this might work. We can then compare the last n samples with a suitable mask to determine whether or not a button has been pressed or released. For example, if we look for a high sample followed by two low samples, as shown in the Figure, we can safely assume we have seen a high-to-low transition. The power of this technique is that we can adjust the mask to adjust the “time constant” of the debouncing.
We can poll each input on a regular basis and push the result into a shift register, effectively giving us a record of the last few samples. We can then compare the last n samples with a suitable mask to determine whether or not a button has been pressed or released. In this example, we look for a high sample followed by two low samples, to unambiguously identify a high-to-low transition.
Code Snippet 2 shows a simple implementation of this technique in C. I like this method because it lends itself to more complex behaviour such as the detection of short or long presses, double-taps, or key repeat, with the addition of a simple state machine.
Debouncing mechanical switch inputs is an important part of producing reliable designs which behave in the way that users expect. Depending on the application, you may wish to implement debouncing in hardware or software or perhaps a combination of both.
References
Nuvation Engineering. “Switch Debouncing for Electronic Product Designs.” Accessed October 7, 2022. https://www.nuvation.com/resources/article/switch-debouncing-electronic-product-designs.
Horowitz, Paul, and Winfield Hill. The Art of Electronics. Third edition, 11th printing, with Corrections. Cambridge New York, NY: Cambridge University Press, 2017.
— ADVERTISMENT—
—Advertise Here—
Andrew Levido (andrew.levido@gmail.com) earned a bachelor’s degree in Electrical Engineering in Sydney, Australia, in 1986. He worked for several years in R&D for power electronics and telecommunication companies before moving into management roles. Andrew has maintained a hands-on interest in electronics, particularly embedded systems, power electronics, and control theory in his free time. Over the years he has written a number of articles for various electronics publications and occasionally provides consulting services as time allows.