Chapter 6: User Interface Experiments Part 2 - Switch Bounce
08/233/2015 | 01:11 PM
Switch Bounce
Another issue in the design of button interfaces is switch bounce, also known as contact bounce or chatter. When a mechanical switch is pressed, the metal contacts inside actually bounce on the surface of each other for some amount of time before they settle down and remain in contact. The Stater Kit has two pushbuttons which are “debounced” via an RC filter. The resistor and capacitor create a low-pass filter that doesn’t allow the sharp rise and fall waveforms of switching bouncing to occur. The change in voltage passing through the pushbutton is smoothed to create a single button press event. There are more robust debouncing circuits that involve flip-flops and even integrated circuits dedicated to solve this problem in hardware. That may be right for your solution if the response time is critical, but for most applications, you can handle this solely in software without any external components at all.
Keep in mind that other user actions can cause indeterminate states. Switches can bounce when they are pressed and again as they are released. Sometimes, they don’t bounce at all and work perfectly. As switches are used in the field and age, they can perform differently than the new switches that you test for your prototype. In addition, you will get similar bouncing waveforms from a plug being inserted into a socket. If your design is trying to detect that event, you will need to debounce the plug event. To handle all of these situations, you will need to develop a software algorithm to detect the initial event and then wait for some amount of time until the bouncing has stopped before you allow another input event.
Since the pushbuttons on the Starter Kit are brand new and already debounced in hardware, I used a jumper wire attached to the GPIO pin PF9 and then touched it against the VMCU power rail to introduce a nasty switch bounce effect. The following code will count the number of times the jumper has been touched to VMCU, which is the pin right next to PF9.
#include "em_device.h"
#include "em_chip.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "utilities.h"
#define TEST_JUMPER_PORT gpioPortF
#define TEST_JUMPER_PIN 9
/**************************************************************************//**
* @brief Main function
*****************************************************************************/
int main(void)
{
// Chip errata
CHIP_Init();
setup_utilities();
CMU_ClockEnable(cmuClock_GPIO, true);
// Set up the user interface buttons
GPIO_PinModeSet(TEST_JUMPER_PORT, TEST_JUMPER_PIN, gpioModeInput, 0);
bool pressed = false;
int number_of_presses __attribute__((unused)) = 0;
while (1)
{
if (GPIO_PinInGet(TEST_JUMPER_PORT, 9))
{
if (pressed == false)
{
number_of_presses++;
pressed = true;
}
}
else
{
pressed = false;
}
}
}
I am keeping track of the number of times the jumper has been touched against VMCU with the number_of_presses variable. I have added a special message to the compiler in the declaration of this variable with __attribute__((unused)) that I don’t want to see any compiler warnings if this variable is unused. I like to do this whenever I declare a variable that I will use strictly for debugging purposes, and it keeps my build console clean.
In order to use this code, compile and run the debugger, then put a break point on the if (GPIO_PinInGet…) line, and the code should immediately break in a that line. Examine the value of the number_of_presses variable is zero by hovering your mouse over it. Remove the breakpoint, and click on the resume button. Then touch the jumper from PF9 to VMCU once and then remove the jumper from VMCU. Set the breakpoint on the if line again, which should again cause the debugger to break in and allow you to examine the value of the number_of_presses. If you only touched the jumper one time, it should contain a count of one, but it will sometimes be higher than one. It depends on how cleanly you touched the jumper to the pin.
To debounce this switch, we need to start a timer when the jumper is touched to the pin, and not allow another touch or release until the timer expires. Then, we need to do the same thing when the jumper is released from the pin.
#define DEBOUNCE_TIME 300 // ms
bool pressed = false;
int number_of_presses __attribute__((unused)) = 0;
int debounce_pressed_timeout = 0;
int debounce_released_timeout = 0;
while (1)
{
if (GPIO_PinInGet(TEST_JUMPER_PORT, 9))
{
if (pressed == false && expired_ms(debounce_released_timeout))
{
// You could start some process here on the initial event
// and it would be immediate
number_of_presses++;
pressed = true;
debounce_pressed_timeout = set_timeout_ms(DEBOUNCE_TIME);
}
}
else
{
if (pressed == true && expired_ms(debounce_pressed_timeout))
{
// You could start some process here on the release event
// and it would be immediate
pressed = false;
debounce_released_timeout = set_timeout_ms(DEBOUNCE_TIME);
}
}
}
I found that a DEBOUNCE_TIME of 100ms was not sufficient for this jumper wire switch, and I had to look at an oscilloscope waveform to figure out why that was so. Sure enough, attaching a jumper to pin with a wire actually connects and disconnects over a longer period of time than a switch, as much as 300ms. Sometimes, it can toggle back and forth between VMCU and ground many times before settling on VMCU, so I set DEBOUNCE_TIME to 300ms to be safe. More characterization and test is needed to ensure that this value will work for every user that interacts with your device and with your users.
Keep in mind when using GPIO interrupts that switch bounce will cause many rapid interrupts to occur. If you were to always trigger an interrupt handler to do something on each button press, you will find that you get more button presses than what the user actually presses. Therefore, you will either need to disable the interrupts for a time after a button is pressed, or allow the interrupt to trigger but always check a timer to see if the time for switch bounce has passed before taking any action.
This jumper wire should perform much more poorly than an SMT switch, but you never know until you test it.
More research on this topic can be found in this article from The Embedded Muse. An analysis of multiple switches are performed, and hardware and software solutions to the problem are explained.
One more thing to consider: whenever a user connects power to your device, whether it be through a battery or a connector, the MCU will see power for a few milliseconds and then power will be lost, due to the same phenomenon we are studying in this lesson. Therefore, I always recommend putting a delay in the beginning of your program to allow everything to settle down before you start to process any of your inputs or sensors. You don’t want to do a one-time only event such as the first ever power-on initialization only to see power be removed a few milliseconds after it was applied. While debugging, I will usually inject a delay(400) statement in my programs as soon as I enable the clocks and the SysTick interrupt before the MCU tries to initialize anything else.
My preferred way to do switches (one or a lot) is to read the switches in a timer ISR with the timer set to bounce time + safety. then if you read the same twice your switch read is valid
Chapter 6: User Interface Experiments Part 2 - Switch Bounce
Switch Bounce
Another issue in the design of button interfaces is switch bounce, also known as contact bounce or chatter. When a mechanical switch is pressed, the metal contacts inside actually bounce on the surface of each other for some amount of time before they settle down and remain in contact. The Stater Kit has two pushbuttons which are “debounced” via an RC filter. The resistor and capacitor create a low-pass filter that doesn’t allow the sharp rise and fall waveforms of switching bouncing to occur. The change in voltage passing through the pushbutton is smoothed to create a single button press event. There are more robust debouncing circuits that involve flip-flops and even integrated circuits dedicated to solve this problem in hardware. That may be right for your solution if the response time is critical, but for most applications, you can handle this solely in software without any external components at all.
Keep in mind that other user actions can cause indeterminate states. Switches can bounce when they are pressed and again as they are released. Sometimes, they don’t bounce at all and work perfectly. As switches are used in the field and age, they can perform differently than the new switches that you test for your prototype. In addition, you will get similar bouncing waveforms from a plug being inserted into a socket. If your design is trying to detect that event, you will need to debounce the plug event. To handle all of these situations, you will need to develop a software algorithm to detect the initial event and then wait for some amount of time until the bouncing has stopped before you allow another input event.
Since the pushbuttons on the Starter Kit are brand new and already debounced in hardware, I used a jumper wire attached to the GPIO pin PF9 and then touched it against the VMCU power rail to introduce a nasty switch bounce effect. The following code will count the number of times the jumper has been touched to VMCU, which is the pin right next to PF9.
I am keeping track of the number of times the jumper has been touched against VMCU with the number_of_presses variable. I have added a special message to the compiler in the declaration of this variable with __attribute__((unused)) that I don’t want to see any compiler warnings if this variable is unused. I like to do this whenever I declare a variable that I will use strictly for debugging purposes, and it keeps my build console clean.
In order to use this code, compile and run the debugger, then put a break point on the if (GPIO_PinInGet…) line, and the code should immediately break in a that line. Examine the value of the number_of_presses variable is zero by hovering your mouse over it. Remove the breakpoint, and click on the resume button. Then touch the jumper from PF9 to VMCU once and then remove the jumper from VMCU. Set the breakpoint on the if line again, which should again cause the debugger to break in and allow you to examine the value of the number_of_presses. If you only touched the jumper one time, it should contain a count of one, but it will sometimes be higher than one. It depends on how cleanly you touched the jumper to the pin.
To debounce this switch, we need to start a timer when the jumper is touched to the pin, and not allow another touch or release until the timer expires. Then, we need to do the same thing when the jumper is released from the pin.
I found that a DEBOUNCE_TIME of 100ms was not sufficient for this jumper wire switch, and I had to look at an oscilloscope waveform to figure out why that was so. Sure enough, attaching a jumper to pin with a wire actually connects and disconnects over a longer period of time than a switch, as much as 300ms. Sometimes, it can toggle back and forth between VMCU and ground many times before settling on VMCU, so I set DEBOUNCE_TIME to 300ms to be safe. More characterization and test is needed to ensure that this value will work for every user that interacts with your device and with your users.
Keep in mind when using GPIO interrupts that switch bounce will cause many rapid interrupts to occur. If you were to always trigger an interrupt handler to do something on each button press, you will find that you get more button presses than what the user actually presses. Therefore, you will either need to disable the interrupts for a time after a button is pressed, or allow the interrupt to trigger but always check a timer to see if the time for switch bounce has passed before taking any action.
This jumper wire should perform much more poorly than an SMT switch, but you never know until you test it.
More research on this topic can be found in this article from The Embedded Muse. An analysis of multiple switches are performed, and hardware and software solutions to the problem are explained.
One more thing to consider: whenever a user connects power to your device, whether it be through a battery or a connector, the MCU will see power for a few milliseconds and then power will be lost, due to the same phenomenon we are studying in this lesson. Therefore, I always recommend putting a delay in the beginning of your program to allow everything to settle down before you start to process any of your inputs or sensors. You don’t want to do a one-time only event such as the first ever power-on initialization only to see power be removed a few milliseconds after it was applied. While debugging, I will usually inject a delay(400) statement in my programs as soon as I enable the clocks and the SysTick interrupt before the MCU tries to initialize anything else.
PREVIOUS | NEXT
My preferred way to do switches (one or a lot) is to read the switches in a timer ISR with the timer set to bounce time + safety. then if you read the same twice your switch read is valid