Advanced Example: Software Debouncing of GPIO Input
Software debouncing GPIO data for more stable results
At the risk of moving forward too quickly, we thought that it might be helpful to some readers to offer some more advanced (or 'real-world') examples in some of these tutorials. These examples might not be helpful to everyone, but in the interest of trying to help people continually improve the quality of their software, you may at some point benefit from seeing how some of these concepts could possibly be used in real-world devices.
One problem that may be encountered when working with GPIO, for example, is that the you may need to make sure that a button's state really has changed before performing any actions. It's possible that you may get a faulty reading once (which can occur for a number of reasons), and it's generally a good idea to 'confirm' the values you receive from the IOPIN register. The task of 'double-checking' your input is called debouncing. There are hardware solutions for this, but this can also be done (and often is) in software. A commented example of how you might 'debounce' the button input in the previous exercise is presented here. It's out of the scope of this tutorial to explain this code (or debouncing in general) in detail, but you may benefit from comparing the example below with the more simplistic code provided in the exercise in Lesson 2. They perform identical tasks, but in a very different way:
#include "lpc214x.h"
#define LED1 (1 << 10)
#define LED2 (1 << 11)
#define BUTTON1 (1 << 15)
#define BUTTON2 (1 << 16)
// Method prototypes
void processButtons(unsigned int keys);
void delay(unsigned char ticks);
// 'Debouncing' variables
unsigned int buttonState;
unsigned int newButtonState, oldButtonState;
int main(void)
{
GPIO0_IODIR &= ~(BUTTON1 | BUTTON2); // Set buttons as input
GPIO0_IODIR |= (LED1 | LED2); // Set LEDs as output
GPIO0_IOSET = (LED1 | LED2); // Turn both LEDs off
// Set default debouncing values
buttonState = newButtonState = oldButtonState = 0;
buttonState = (BUTTON1 | BUTTON2);
// Constantly monitor the button's current state
while (1)
{
// Get state of all pins, masking results for button1 and button2
newButtonState = (~GPIO0_IOPIN & (BUTTON1 | BUTTON2));
// Note: By inverting IOPIN (with the '~'), we end up with
// a hex value showing the pins that ARE selected, rather
// than all the pins that are NOT.
// Check if the button state has changed since it was last read
if (oldButtonState != newButtonState)
{
// Cause a brief delay ...
delay(1);
// ... before testing the value again ("debouncing")
buttonState = (~GPIO0_IOPIN & (BUTTON1 | BUTTON2));
// Confirm that the new state is stable
if(buttonState == newButtonState)
{
// The button state has changed, and the values have been
// 'debounced' (checking the values twice over a fixed
// period of time), so we can confidently change the
// LED state knowing it isn't just a 'glitch' or noise
// Refresh LEDs to reflect current button state
processButtons(buttonState);
// Note: The button values ("buttonState") are passed in the
// method call above to avoid having to read IOPIN again.
// The reason for this is because the button's state may
// change before another read attempt is made
}
// Set oldButtonState to the current state for next detection
oldButtonState = buttonState;
}
}
}
// Check button state and adjust the LEDs accordingly
void processButtons(unsigned int buttons)
{
// Check if P0.15 is pressed (Button1)
if (buttons & (BUTTON1))
{
// Turn LED1 on
GPIO0_IOCLR |= (LED1);
}
else
{
// Turn LED1 off
GPIO0_IOSET |= (LED1);
}
// Check if P0.16 is pressed (Button2)
if (buttons & (BUTTON2))
{
// Turn LED2 on
GPIO0_IOCLR |= (LED2);
}
else
{
// Turn LED2 off
GPIO0_IOSET |= (LED2);
}
}
// A quick and dirty 'delay' routine
void delay(unsigned char ticks)
{
unsigned long us = 1000*ticks;
while (us--)
{
__asm volatile("nop");
}
}