|
... Kinetek Systems, Inc. Phone: Toll free: Fax: |
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 AlgorithmsOverviewAt 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 SamplingIf 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 SamplingIf 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 SamplingThis 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;
}
}
InterruptsAlthough it is popular to use interrupts, there are problems with this approach and interrupts should probably only be used in certain cases:
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 NotesStuck ButtonsDepending 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 - IntentionalSome devices require two or more buttons to be pressed together to trigger some special action. This introduces a few complexities.
Two Buttons Pressed Together - AccidentalWith 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:
If you have any questions or comments, please email me at |
|||||||||||||||||||