AVR Tutorial: Switch Input and Debounce

Though an input can be from any number of sources, one of the mose common and the easiest to implement, would be a swith. Due to mechanical properties of a switch, when a switch is closed, there is a period of time in which the electrical connection "bounces" between open and closed. To a microcontroller, this "bouncing" can be interpreted as multiple button pushes. Therefore, we must either build a circuit to supress the "bouncing" or we must do so in our software. This is known as "debouncing a switch". Download the project file: tutorial_3.tar.gz

This Tutorial Introduces...

  • Using a pin on the AVR as a digital input.
  • Using the internal pull-up resistors on an AVR digital input pin.
  • Testing if a bit is clear or set using the avr-libc macros
  • Toggling the state of an output pin using C bitwise operators
  • "De-bouncing" a switch connected to an input pin using the "polled" method.

Introduction

There are typically 2 methods employed to de-bounce a swtich: one using timers and interrupts and one which polls the state of the switch. In this tutorial, we will be debouncing the swith using the polling method. We'll also be learning about pull-up resistors (or "pull-ups") and the internal pull-ups in AVR microcontrollers.

Switch bounce shown on oscilloscope

"Bouncing" of a switch shown on an oscilloscope

This project will toggle an LED on and off with each push of a push-button (switch).

Prerequisites

  • You should have either read Tutorial #1 or have equivelent knowledge (avr-libc includes, set/clear bits, digital output, compiling with gcc).
  • You should either know how to adapt the code to your AVR's clock frequency (see Tutorial #2) or be using an 8 MHz clock source.

Circuit

This circuit uses an 8MHz crystal oscillator. In order to use a crystal oscillator with an AVR, you must first change the fuse bytes for the AVR as was discussed in Tutorial #2. Once again, the RESET pin is "pulled up" to VCC through a 10k resistor and there is an LED connected to PC0 through a 1k resistor. We have added a N.O. (normally open) push button switch connected to PD2. When we press the switch button, we want the pin to change from a high to low. This way we are not "sinking current" when the switch is not being used and thus we save power. This makes the switch "active-low" in that a digital low represents the action of pressing the button. In order for our switch to represent a digital high when open and digital low when pressed (+5V/Ground), we would usually connect the switch using a pull-up resistor as show in Fig. 1. With the switch open, the input is "pulled" high to VCC through the 10k resistor. This is known as a pull-up resistor (the same concept is also being employed on the RESET pin). However, when the button is pressed, current takes the path of least resistance and the input is grounded. With AVR microcontrollers, the I/O pins can be configured to use internal pull-up resistors when configured as inputs. Therefore, we do not need to include a pull-up resistor in our circuit (Fig. 3)--however, it is VERY important to understand their purpose. Without an internal pull-up enabled in our circuit, the button would be "floating" when open and ground when pushed. We want it to be a digital high when open. We need the pull-up resistor.

Fig. 1

Pull-up Resistor Schematic

Fig. 2 Photo of circtuit on my breadboard

Fig. 3

Schematic of AVR debounce circuit

Click to Enlarge

Source Code

The project consists of just one file of C code, main.c, the first few lines of which we have some compiler defines. F_CPU should be familiar and is used by the avr-libc delay routines in <util/delay.h>. Now, in order to make our code more readable and easy to modify, we use defines to declare our button and LED ports and pins. For the button, we will be using the PORT and PIN registers for writing and reading. With AVRs, we read a pin using it's PINx register and we write to a pin using it's PORTx register. We need to write to the button register to enable the pull-ups. For the LED we only need to use the PORT register to write to, however, we also need the data direction register (DDR) as the I/O pins are setup as inputs by default. Using the #define statements like this allows us to only need to modify 3 easy-to-find lines if we move the LED to a different I/O pin or use a different AVR. The final 2 #define statements setup times, in ms, to debounce the switch and the time to wait before allowing another press of the button. The debounce time needs to be adjusted to the time it takes the switch to go from a digital high to a digital low after all the bouncing. The bounce behavior will differ from switch to switch, but 20-30 ms is typically quite sufficient.
#define F_CPU 8000000UL         /* 8MHz crystal oscillator */

#define BUTTON_PORT PORTD       /* PORTx - register for button output */
#define BUTTON_PIN PIND         /* PINx - register for button input */
#define BUTTON_BIT PD2          /* bit for button input/output */

#define LED_PORT PORTC          /* PORTx - register for LED output */
#define LED_BIT PC0             /* bit for button input/output */
#define LED_DDR DDRC            /* LED data direction register */

#define DEBOUNCE_TIME 25        /* time to wait while "de-bouncing" button */
#define LOCK_INPUT_TIME 250     /* time to wait after a button press */
The includes are the same as they were explained in Tutorial #1. However, I now have a section for function prototypes. This is so the compiler knows about functions which will be defined later. This is a standard C programming concept and should not need much explaining. We'll talk more about each of these functions when we get to their respective definitions.
#include <avr/io.h>
#include <inttypes.h>
#include <util/delay.h>

/* function prototypes */

void delay_ms(uint16_t ms);
void init_io();
int button_is_pressed();
void toggle_led();
You'll notice right away how simple our mainline code looks. This is why we use functions and name them descriptively. By looking at the source code alone, we can see what is happening:
  • Initialize I/O pins being used
  • Enter infinate loop and...
    • If the button is pressed, toggle the LED state and delay for 250ms (#define LOCK_INPUT_TIME)
int 
main (void)
{
        init_io();

        while (1)                       
        {
                if (button_is_pressed())
                {
                        toggle_led();
                        delay_ms(LOCK_INPUT_TIME);
                }
        }
}
So let's take a look at how we're implementing each of those functions used in main(). I will not be explaining the delay_ms() function as that was done in Tutorial #1. Let's start with init_io(). This function is called just once in the beginning of our program to initialize I/O pins that we will be using. First, we're setting the LED I/O pin as an output using it's data direction register. Pins are input by default so we have to explicitly set the LED pin as an output but do not have to explicitly set the button pin as an input. Remember we defined LED_DDR and LED_BIT up in the beginning of our main.c file. Next, the LED pin is set high (+5V) to turn it off. The output pin is initially low, and since our LED is wired active-low, it will be turned on unless we explicitly turn it off. We do this using the bit set method described in Tutorial #1. And finally, we enable the internal pull-up resistor on the input pin we're using for our button. This is done simply by outputting a 1 to the port. When configured as an input, doing so results in enabling pull-ups and when configured as an output, doing so would simply output a high voltage.
void 
init_io() 
{
        /* set LED pin as digital output */
        LED_DDR = _BV (LED_BIT); 

        /* led is OFF initially (set pin high) */         
        LED_PORT |= _BV(LED_BIT);

        /* turn on internal pull-up resistor for the switch */
        BUTTON_PORT |= _BV(BUTTON_BIT);
}
The button_is_pressed() function returns a boolean value indicating whether or not the button was pressed. This is the block of code with is continually being executed in the infinate loop and thus is polling the state of the button. This is also where we debounce the switch. Now, remember that when we press the switch, the I/O pin is pulled to ground. Thus, we're waiting for the pin to go low. We do so by checking if the bit is clear using the bit_is_clear() avr-libc macro. If the bit is clear, indicating that the button is depressed, we first delay for the amount of time defined by DEBOUNCE_TIME which is 25ms and then check the state of the button again. If the button is depressed after the 25ms then the switch is considered to be debounced and ready to trigger an event and so we return 1 to our calling routine. If the button is not depressed, we return 0 to our calling routine.
int 
button_is_pressed()
{
        /* the button is pressed when BUTTON_BIT is clear */
        if (bit_is_clear(BUTTON_PIN, BUTTON_BIT))
        {
                delay_ms(DEBOUNCE_TIME);
                if (bit_is_clear(BUTTON_PIN, BUTTON_BIT)) return 1;
        }

        return 0;
}
When button_is_pressed() returns 1 indicating that the button was pressed and debounced, toggle_led() is then called. This function uses C bitwise operators again. However, this time it's using the exclusive OR (XOR) operator ^.
void
toggle_led()
{
        LED_PORT ^= _BV(LED_BIT);
}
When you XOR the PORT with the bit value of the bit you want to toggle, that one bit is changed without effecting the other bits.
  10101010 0xAA
^ 00000001 0x01
  -------- ----
  10101011 0xAB Bit 0 is set, other bits uneffected.

  01010101 0x55
^ 00000001 0x01
  -------- ----
  01010100 0x54 Bit 0 is cleared, other bits uneffected.
So now, when you run this program, you should be able to press the push-button to turn on the LED, and push the button again to turn it off. Due to our delay defined by LOCK_INPUT_TIME, you can press and hold the button which will cause the LED to turn off and on at a consistant rate (little more than every 275ms).
Did you enjoy AVR Tutorial: Switch Input and Debounce? If you would like to help support my work, A donation of a buck or two would be very much appreciated.
blog comments powered by Disqus
Linux Servers on the Cloud IN MINUTES