Button de-bouncing

If you are dealing with a physical button in one of your hardware project, you'll probably end up having a issue with mechanical bounces of the button when changing state.

Because a button is often build with a spring (or some elastic material) to provide tactile feedback (or simply to maintain its position), you'll get what is called "bounces" when the button toggle to the other state. This happens because the energy to move the button from one state to another has to dissipate somewhere.

It can be dissipated as heat (all energy is dissipated as heat in the end), but on a mechanical device, it'll try to first dissipate as momentum.

The potential energy of the button will cause it to bounce back in the opposite direction once it's reached its mechanical stop (it'll bounce back to a smaller distance than its initial position).

But since you (the user) is still pressing the button, you'll force it again back to the mechanical stop and so on until the bouncing energy is absorbed. All of this is very fast (from a user perspective) but not fast enough to be ignored by the electronic of your projects.

Bouncing signal

Why is it a problem ?

If you want to link some actions to the button press, you'll need to make sure the action matches the user's intent. If you execute 3 actions per user's press of the button, you are failing to match the user's expectations. So, if, in your code, you have the button press detected as an interrupt, you'll need to filter out the spurious bounces in your interrupt or action handler.

If you don't do that, then you'll get 3 drinks ordered when the user pressed on the Coffee button, it'll be impossible for the user to type her PIN code to validate the order and so on.

How to solve this problem ?

There are multiple ways to solve this problem. One solution is hardware based.

Hardware solution

You want to dissipate the energy of the button state change efficiently. In order to do that, one simple solution is to use a capacitor to store this energy. But since capacitor are usually very reactive, you'll need to also slow the reaction time, by using a resistor to limit the charging current of the capacitor.

This is known as a RC filter.

Typically, when the button toggle the first time, it'll start to pass current into a resistor + capacitor that'll start to charge. When it bounces, if the resistor and capacitor value are correctly chosen, the amount of charges stored in the capacitor is not enough to trigger the input detection circuit of the MCU. After the second bounce, more charges are saved in the capacitor and so on until the charge are enough to reach the input's trigger voltage of the MCU.

This solution depends on 2 parameters: R (the resistance) and C (the capacitance). The higher the R x C (proportional to time, also called tau τ), the longer it takes for the MCU to register the press but many bounces will be absorbed. Also, the system will not be able to register button release until the capacitor has emptied its charges.

So you'll need to find a good compromise between the mechanical bouncing time and the electrical constants.

This is fine if you master the hardware schematic. But if you don't (for example, when working with some modules with exposed GPIO), you'll need to find another solution.

Software solution

A software solution is often better than the hardware solution because:

  1. You can react as soon as you detect the first contact of the button (unlike the hardware solution where you need to wait for τ)
  2. You can adjust the constants dynamically. So a strong mechanical button might have a smaller constant than a tactile switch.
  3. You can prefetch the action.

And it's worst because:

  1. If you use interrupt on the button's GPIO, you'll interrupt your CPU multiple time so it increases the load on the CPU
  2. If you don't use interrupt, you might miss the button press if your CPU is busy when it happened.
  3. It takes binary space to add the debouncing code.

There are two types of software debouncing algorithm. The most common algorithm is called "delay" or "history" and it's using a small accumulator that'll store the state of the button vs time. When the accumulator is full of button pressed state, it'll trigger the action linked to this button.


uint32 buttonState = 0;
bool buttonLastState = false;

void readButton(Button button) {
    buttonState = (buttonState << 1) | (getGPIOState(button) ? 1 : 0);
    if (buttonState == 0xFFFFFFFF) {
         if (!buttonLastState) 
             triggerPressActionForButton(button);
        buttonLastState = true;
    }
    else if (buttonState == 0x00000000) {
        if (buttonLastState)
            triggerReleaseActionForButton(button);
        buttonLastState = false;
    }
}

So this kind of algorithm imitate the hardware solution (with a capacitor). It need to be called at regular interval so it doesn't fit well with interrupt based code. Also, it'll only be able to filter actions that bounce shorter than the accumulator size * polling period = τ. It'll only trigger the action after τ so it might not be the best solution in terms of latency.

This kind of code in an interrupt handler is trickier to do, since you don't know beforehand the number of bounce you'll see. If your accumulator is too large, you'll miss some bounce to actually trigger the action and if it's too small, you risk triggering the action twice (creating a software bounce).

The second kind of algorithm imitate our neurons, that is, once the button first state change is detected, the button handler will be inactive for a small period of time (so any state change during this period will be ignored). This has the advantage to react as fast as possible to the button press, but the inactivity period need to be carefully chosen not to be too short (in that case a long bounce will be detected twice) or too long (you might miss the button release event).

It's typically written like this:

const uint32 inactivityPeriod;
uint32 lastButtonStateChange = 0;
void buttonInterruptHandler(Button button) {
    uint32 curTime = getTime();
    if ((curTime - lastButtonStateChange) > inactivityPeriod) {
        lastButtonStateChange = curTime;
        if (getGPIOState(button)) 
            triggerPressActionForButton(button);
        else 
            triggerReleaseActionForButton(button);
    }
}

Conclusion

To sum up, here's a comparative table of the solutions for debouncing:

Method Hardware or Software Low latency Robustness Code space Interrupt based Mis-detection risk
RC filter Hardware No Usually high 0 Yes High
Delay accumulator Software No Usually high Low No High
Neurons Software Yes Medium Low Yes Low

Previous Post Next Post

Related Posts