Kinetek Systems Inc.

Software Button Debouncing and Ergonomics

Author: Nathaniel W. Crutcher
Date: June 2nd, 2005

Debouncing button presses is a common element in embedded devices. Although it is a relatively easy problem to solve, there are many different issues and solutions. The following is a discussion of the goals of button debouncing and some debounce algorithms.

This article does not discuss using an electronic circuit to debounce a button. Although this is possible and sometimes useful, it is generally unnecessary and adds expense. This discussion also covers ergonomics and usability concerns that go beyond preventing pure contact bounce.

Problems

For purposes of this analysis, there are five distinct problems being addressed:

  • Contact bounce when a button is pressed, giving the appearance that the button was pressed, then released and pressed additional times in a very short interval. Example: you are adjusting your alarm clock and press the hour button once, but the hour increases by two. The duration of this type of bounce is generally less than 5ms.
  • Contact bounce when a button is released, giving the appearance of extra press/release cycles. In many cases, "bounce" isn't really the right word, since the phenomenon is more of a gradual scratching disconnect, more like the sound you hear when unplugging a headphone jack. The symptoms are essentially the same as for button press bounce.
  • Accidental button press due to impact or inadvertent button contact. Although this can happen with almost any button, it is not normally a concern except with buttons whose actions cannot be undone or have a long recovery time. Example: you bump your PC case with your foot and the computer resets because the reset button made momentary contact.
  • Accidental double press of a button by a human operator. Again, this is only a concern for certain types of buttons, where there is virtually never a good reason to press the button twice within a very short period of time. Example: you press the eject button on your CD player to open the drawer, but you accidentally double tap the button, so the drawer opens a bit and then immediately closes.
  • Very noisy contacts due to low button quality or corrosion. In many ways, this is just a variation on the first two problems, but it's actually a more severe problem, because the noise may last for as long as the button is depressed. Depending on the debounce algorithm, this could result in several problems, including unintended presses or failure to detect a press.

Button Types

  • General - This covers most common buttons that do not fall into any of the following more specific categories. Most buttons on a remote control would fall into this category. These buttons may be pressed multiple times, but not normally at a high repeat rate.
  • Repeat Action - This refers to buttons that are commonly pressed in a repeating fashion to reach a desired set point, such as the minutes button on an alarm clock.
  • Smooth Adjustment - This refers to buttons (or joysticks) that are used to smoothly adjust something. These are most commonly used for visual adjustments, such as when you adjust the size and position of the video on a CRT monitor. The actual adjustment generally occurs in small steps, but appears smooth while the button is pressed. Interestingly, this is the one type of button that doesn't generally need to be debounced.
  • Reset - This refers to buttons such as the reset button on a PC or the stop button on an assembly line. Reset buttons are only meant to be pressed at long intervals and there is a high cost associated with accidental presses.
  • Toggle - This covers power buttons, play/pause buttons, and more generally any button where subsequent presses do not have the same effect as the first press. Other more complex examples are the tray open/close buttons on DVD players, garage door opener buttons, etc.
  • Cycle - Cycle type buttons are similar to toggle, but refer to buttons used to cycle through several different choices or modes or operation, such as the input selector on a stereo.

Goals

  • The main goal of button debouncing is to filter out these extra apparent press/release cycles due to contact bounce or noise on press and release.
  • The debounce algorithm should be designed so that every actual press of the button is correctly detected and counted. It is common to encounter devices that limit button presses to one or two presses per second or that do not detect quick button presses, because of long debounce periods in software. Very few buttons require such a long debounce interval and using such a long interval makes the device less convenient to use.
  • The initial press of the button should be detected as quickly as is reasonably possible to provide a feeling of responsiveness for users. This means that it is normally preferable to react quickly to button presses and put the majority of the debouncing on the button release. In cases where the button auto repeats or smoothly adjusts a value, it's also important to respond quickly to button releases.
  • Depending on the button design and the application, it may be necessary to filter out brief button presses. This is valuable for buttons like the reset on a computer, where it is probably best to require a solid 250 - 500 millisecond press, so that a brief kick of the case doesn't accidentally reset the computer.
  • For buttons that don't require repeat action, the debouncing should filter out accidental double presses.
  • In general, you should tune the debounce period based on the intended use of a button. For buttons that won't be pressed in rapid succession, use longer debounce periods to provide more immunity to button corrosion, accidental presses, etc. For buttons that will be pressed in rapid succession (the minutes on an alarm clock), use shorter debounce periods.

There are more complex button types, like the clock button on appliances that must be held pressed for 2+ seconds to change the time, buttons that accelerate the incrementing of a variable the longer they are held down, or buttons that must be pressed in pairs to achieve results, but these are not covered in this discussion.

Data

Button Behavior

Study of a variety of buttons shows a variety of bounce behaviors. The small momentary buttons with a snap or click type action tend to have no little or no bounce on press, but occasionally show bounce of up to 1 millisecond. On release, these buttons show anywhere from 100 microseconds up to 5 milliseconds of bounce or noise. Very cheap buttons without a click action and with old corroded contacts can show much more extensive bounce or noise, generally up to 30 milliseconds but sometimes lasting for as long as the button is pressed, depending on how decisively the button is pressed.

Human Behavior

Humans can press a pushbutton as fast as 10 - 15 times per second. A good rule of thumb is that the minimum press time will be 30 - 40 milliseconds and the minimum release time will be 30 - 40 milliseconds. A more typical relaxed rate is 3 - 5 times per second, with press and release times of 100 - 150 milliseconds. The fast rate is only seen when pressing a button in rapid succession, while single presses tend to fall into the second category of 100 milliseconds or longer.

Based on this data, the recommended press and release intervals for different button types follow. The times are all in milliseconds. The release time refers to the time after one press before another press will be detected.

Button Type Min / Max Press Period
(milliseconds)
Min / Max Release Period
(milliseconds)
General 50 / 100 50 / 100
Repeat Action 20 / 40 20 / 40
Reset 250 / 500 50 / 100
Toggle 50 / 100 500 / 1000
Cycle 50 / 100 50 / 100

There are two notable features in the table above. Reset type buttons have a long press time to prevent accidental presses. Toggle type buttons have a long release time so that they cannot be pressed twice in a short period of time.

Debounce Algorithms

Overview

At a very general level, embedded devices can sample or poll button inputs or can configure interrupts to occur when a button input changes states. Polling algorithms are generally preferred, for reasons explained in the Interrupt section below. Even polling algorithms commonly use a timer interrupt to control the polling interval.

The following algorithms show code for a single button, but these can be generalized with arrays or other techniques to work for multiple buttons.

Uniform Periodic Sampling

If all buttons can be polled with the same press and release intervals, as is the case for general type buttons, a very simple algorithm can be used, which just polls all buttons at the minimum press and release interval. For example, if the press/release interval is 50 milliseconds, you would poll all buttons every 50 milliseconds. Any button that goes from released to pressed would be treated as a new button press.

This algorithm uses the least RAM and ROM. It does not actually require that the button be pressed for the minimum interval, since it's possible to press the button for just one millisecond right when the code is polling the button, but in practice, it works well anyway because the duration of the bounce is much less than the sampling interval and humans cannot press a button for extremely brief periods.

#define RELEASED 0
#define PRESSED  1
/* Call this function every 50 milliseconds */
void SampleButton1(void)
{
  static int oldState = RELEASED;
  int newState;

  newState = button1;      /* Read the hardware port */
  if (oldState == RELEASED && newState == PRESSED)
    Button1PressAction();  /* Call the button press routine */

  oldState = newState;
}
            

This routine can be optimized for multiple buttons by reading several button values into one integer variable and then using the exclusive-OR operator to check for changes of state. It can also be used to check for button releases.

Mixed Periodic Sampling

If different buttons have different press/release intervals, a more complex algorithm is necessary to poll the buttons and count up to the required press and release intervals. This approach can also be used for buttons with the same press and release intervals to add oversampling (sample every 1 - 10ms) for more accurate measurement of the actual press and release times.

Using oversampling is desirable if you have buttons which may develop corrosion or have noisy contacts. With Uniform Periodic Sampling, if a button is pressed for 300 milliseconds, but contact corrosion causes continual noise on the button input, a different value may be seen for the button input each time the input is sampled. This can fool the software into thinking the button has been pressed 2-3 times during the 300 milliseconds. Increasing the sampling period is not really a good solution since it prevents rapid button presses and doesn't solve the problem if the button is pressed for longer intervals. Using oversampling solves this problem, since the full release interval (50 - 100 milliseconds) has to elapse before another button press can be detected.

If you are trying to handle noisy or corroded buttons, you may want to allow one of two approaches for the minimum press interval. The first is described next, the other in Advanced Mixed Periodic Sampling. If you oversample and the button is noisy, it may be difficult for the user to get a clean press signal for enough samples to trigger a button press. Instead, the press interval can be made equal to just one sample interval and the release interval can be lengthened to compensate.

#define RELEASED 0
#define PRESSED  1
#define PRESS_COUNT 1      /* 10 milliseconds */
#define RELEASE_COUNT 9    /* 90 milliseconds */
/* Call this function every 10 milliseconds */
void SampleButton1(void)
{
  static int debounceCount = 0;
  static int currentState = RELEASED;
  int inputState;

  inputState = button1;            /* Read the hardware port */
  if (inputState != currentState)  /* If the button is in a new state */
    debounceCount++;               /* Increment the count for that state */
  else                             /* Otherwise */
    debounceCount = 0;             /* Reset the count */

  if (inputState == PRESSED && debounceCount >= PRESS_COUNT) {
    Button1PressAction();          /* Call the button press routine */
    currentState = PRESSED;
  }
  else if (inputState == RELEASED && debounceCount >= RELEASE_COUNT) {
    currentState = RELEASED;
  }
}
            

Advanced Mixed Periodic Sampling

This is the same as the previous algorithm, but with a change to handle noisy reset type buttons. The problem is as follows: suppose we want to require a constant press for 250 milliseconds or longer for a reset button. We also want to allow for noisy or corroded button contacts, such that it will be difficult to achieve 250 milliseconds of contact without some noise. In this case, it becomes difficult or impossible for the user to operate the button. To solve this, we need to define "pending pressed" to mean the following: if we oversample the button, it is considered pending pressed if we see the input in the pressed state at least once every N samples (perhaps one out of five samples). It must stay in this pending state for the required press interval (say 250 milliseconds) before it is considered truly pressed.

#define RELEASED 0
#define PRESSED 1
#define PENDING_PRESSED  2
#define PENDING_COUNT 5    /* 50  milliseconds */
#define PRESS_COUNT 25     /* 250 milliseconds */
#define RELEASE_COUNT 10   /* 100 milliseconds */
/* Call this function every 10 milliseconds */
void SampleResetButton(void)
{
  static int debounceCount = 0;
  static int pendingCount;
  static int currentState = RELEASED;
  int inputState;

  inputState = button1;             /* Read the hardware port */

  if (currentState == RELEASED) {
    if (inputState == PRESSED) {    /* If we see a press, go to PENDING_PRESSED */
      pendingCount = PENDING_COUNT;
      debounceCount = 1;
      currentState = PENDING_PRESSED;
    }
  }
  else if (currentState == PENDING_PRESSED) {
    if (inputState == PRESSED)      /* So long as we're pressed, */
      pendingCount = PENDING_COUNT; /* keep pendingCount at PENDING_COUNT */
    else if (--pendingCount == 0)   /* If we go for PENDING_COUNT without seeing */
        currentState == RELEASED;   /*  PRESSED, go back to RELEASED state. */

    if (++debounceCount == PRESS_COUNT) {  /* If we make it to PRESS_COUNT */
      ResetButtonPressAction();     /* Call the button press routine */
      currentState = PRESSED;
      debounceCount = 0;            /* Reset the count */
    }
  }
  else {      /* currentState == PRESSED */
    if (inputState != RELEASED)
      debounceCount = 0;
    else if (++debounceCount == RELEASE_COUNT)
      currentState = RELEASED;
  }
}
            

Interrupts

Although it is popular to use interrupts, there are problems with this approach and interrupts should probably only be used in certain cases:

  1. To wake the device from sleep mode when a button is pressed. After waking, the device should disable the interrupt for the button input and use polling until just before going to sleep again.
  2. To implement a function such as a stop button, that must function even if the main code has become stuck in an endless loop. In practice, reset and stop buttons should probably be implemented in hardware, so this situation is not likely to arise.
  3. For scientific or other purposes such as measuring a person's reaction time, where the exact time of the press must be recorded with great precision. In this case, you may also want to use a capture/compare type input to eliminate variable delays in responding to the interrupt.

There are two problems with using interrupts on button inputs. First, a noisy or corroded button can cause a very rapid succession of interrupts, preventing other code from running. Second, the algorithms that use interrupts tend to be unnecessarily complex compared to the polling algorithms, without a corresponding improvement in performance.

It is commonly argued that using interrupts on the button inputs will reduce CPU consumption by freeing the processor from periodically polling the button. While it is true this reduces CPU consumption, particularly in devices where user input is rare, this may not be valuable. On most microcontrollers, polling will probably take between 0.1% and 1% of the available CPU time; it's difficult to image an embedded system where an extra 1% available CPU time would make a difference. If the CPU is overloaded and at risk of not completing critical tasks on time, allowing random unpredictable delays due to button interrupts is more dangerous than polling.

It is possible and reasonable to poll buttons within a periodic timer interrupt. This is not the same as actually triggering an interrupt in response to a button press.

Because interrupts are not a preferred way to capture and debounce button inputs and because the algorithm depends more on processor specific details, I will not develop an algorithm here. The basic approach is to record the time of each press and release or set a timer interrupt to expire some time after each button event, so that the required debouncing interval can be measured.

Other Notes

Stuck Buttons

Depending on the application and the design of the buttons, it may be desirable to gracefully handle stuck buttons. A stuck button can be detected as a button being pressed for more than 5 or 10 seconds (unless the button is normally held to scroll a number or position, in which case a much longer timeout should be used). Once marked as stuck, it will be treated as unpressed. If it is ever released, it will be marked as unstuck unless it stays pressed for another long interval; this ensures that a user holding a button for a long time won't permanently disable a button. Generally, it would be nice to keep track of the fact that a button is stuck across sleep or power down, although this could be simulated by looking for stuck buttons on power up.

An interesting point about stuck buttons: if you only take action when a button is pressed (there is no release action or repeat action) and if you don't use combinations of buttons pressed together to perform actions, there is no need for special handling of stuck buttons, since the time a button is held pressed is irrelevant. When first pressed (or on power-up) the normal action will occur, and after that, nothing else will happen until after the button is released and pressed again.

Two Buttons Pressed Together - Intentional

Some devices require two or more buttons to be pressed together to trigger some special action. This introduces a few complexities.

  1. You must allow for the buttons being pressed at slightly different times.
  2. You should increase the debounce interval significantly, so that accidental dual-presses do not trigger the special action. People tend to be very slow and deliberate when pressing two buttons together, so the debounce interval could be > 100ms (except for combinations like the SHIFT key on keyboard, which require fast debounce).
  3. If either of the buttons does something significant, then you may have to make the normal behavior occur when the button is released (instead of pressed), so that if two buttons are pressed, you can skip the normal actions for the buttons. If you act on the button press instead of the release, then you will take the normal action for the button, followed by the special action for the two buttons.

Two Buttons Pressed Together - Accidental

With things like keyboards, keypads, etc. where buttons are positioned near each other, there is a risk that someone will accidentally press the intended button AND accidentally press an adjacent button. If there is little harm in pressing an extra button, this risk can probably be ignored. However, for better usability, there are other choices:

  1. Only count the first key pressed, ignoring any second keys pressed while the first key is depressed. Even if the first key is released while the second is still pressed, the second should be ignored. In other words, as soon as a key is pressed, lock out subsequent keypresses until all keys have been released for some interval, perhaps 20ms.
  2. Don't count either keypress if two keys are pressed within a short interval of each other (50ms) unless there is special behavior associated with pressing two keys together. This behavior is a bit more unusual, since most people expect that if they hit two keys, at least one of them should register.

If you have any questions or comments, please email me at