A Special "Thanks" to Myke Predko for submitting the following project/article.
About the Author
Myke Predko is the author of "Programming and Customizing the PIC Microcontroller", the "Handbook of Microcontrollers" and "Programming and Customizing the 8051 Microcontroller" as well as the soon to be released "PC PhD" and "PC Interfacing Pocketbook" which are all published by McGraw-Hill.
As well as writing books on electronics and programming, Myke works for Celestica, Inc. in the area of New Products Test Engineering. His wife, Patience, and he have three children, Joel, Elliot and Marya.
I apologise for the tardiness of this article; for the next little while, I will probably have to scale them back to once every two weeks or so. The reason is that I am proof reading the "Galley Proofs" of my new book, "PC PhD". This book discusses the PC's hardware and how to develop interfaces (along with the required software) for it. I'll be putting more information about it on my web page as the August release date comes up.
In my last article, I presented you with a simple circuit as a first application. I want to continue with this circuit to demonstrate five different methods of software based "debouncing".
A typical switch connection used in a digital application is:
In this circuit, the Microcontroller input pin is held high until the button is pressed (the switch is "closed"). When the switch is closed, then a direct path to ground is made with the Microcontroller Pin being "pulled" to Ground.
"Bouncing" is the term used to describe what happens when the switch is closed or opened. Instead of a single, square edge as you may expect, a switch change consists of many short spikes which are a result of the button's contacts "bouncing" during the transition. This is shown in the diagram below:
These spikes can cause a microcontroller to detect many button pressing events. As is shown in the diagram above, when "Button Pressed", the signal actually received by the microcontroller's input pin looks like the series of "spikes" that I have shown. These spikes are characteristic to the button and are generally present for about 10 milliseconds.
In the diagram above, I have shown the Logic "High", "Low" and "Threshold" levels. The "High" and "Low" values should be easy to understand, but I wanted to spend a moment on the "Threshold" level. This is the voltage level in which the microcontroller detects the difference between a "High" or a "Low" input. For TTL circuits, this value is generally about 1.4 Volts while for CMOS circuits (as are used in the PICMicro), the threshold is normally one half Vcc, or 2.4 to 2.6 Volts.
While 10 milliseconds is quite short for us humans, it is actually quite long for a digital circuit and the period between spikes is often enough for the application to respond to the request and set up to wait for another button press, which comes as another keystroke.
To the user, it will seem as if the Microcontroller is responding to multiple button presses for each individual one. From the Microcontroller's perspective, this is exactly what is happening.
To prevent this rapid repeat of button press operations a "debounce" routine like the one shown in psuedo-code below is used to wait for the button transitions to stop for 20 msecs before acknowledging that a button has been pressed:
In this code, first the button going low (being pressed) is polled. When it goes low, a 20 msec delay is set up and then the button is polled until either the button goes high again or the 20 msec delay times out. If the button goes high again (the timeout has not occured), then the whole process starts again until the button is low for 20 msecs.
To demonstrate how debouncing works, I decided to use the same circuit as last week:
|In the application code that I present here,
instead of turning on one of the LEDs when the button is pressed, I toggle the state of
the LED each time the button is pushed.
The psuedo-code for the application is:
In this code, the PICMicro waits for the button to stabilize at a "high" condition before waiting for a valid press ("Low"). Note that I have an LED always on; I use this as an indicator for me to know the application is in fact running.
In "bounce.asm", I used the following code for debouncing the button going low:
; Return Here until "0" Debounced
This code doesn't actually follow the psuedo code listed above. It really operates like:
Which obviously doesn't follow structured programming techniques and could cause problems with some "C" compilers.
The PICMicro assembly language code works quite well and to make it more "portable" to other applications, I rewrote it as a Macro in "bounce2.asm":
Debounce macro HiLo, Port, Bit
1. It can be used for waiting for the "up stroke" as well as the "down stroke". In the "Bounce" code, I had to repeate the code twice based on whether or not I was debouncing the wait for high or the wait for low. In Bounce2, I could use the same macro for the same function.
2. The Macro can be used for any Pin (set for "input") in the PICMicro. The "Bounce" code has to be edited for use with other pins.
3. I put in conditional code for debugging. If the "Debug" label is "defined", then a short loop (which takes the same number of instruction words) is used. This is useful when working with simulators like "MPLAB" in which the 20 msec delay can be quite onerous to wait through.
The "InitDlay" constant is calculated using the formula:
TimeDelay = (((InitDlay - 1 ) * 256) * 7) / (Frequency / 4)
InitDlay = ((TimeDelay * (Frequency / 4)) / (256 * 7)) + 1
This means that with an "InitDlay" value of 12, a 19.7 msec debounce delay will be produced for a PICMicro running a 4 MHz Clock.
A very interesting way of debouncing is used by the Parallax Basic Stamp. The "Button" instruction checks the defined pin each time the instruction is executed. If the pin is active ("Downstate" and "Targetstate"), then a counter ("Variable") is incremented. If the Pin is not active, then the counter is cleared.
When the counter reaches the "Delay" value, execution jumps to the button response address, else the execution flows right through to the next location.
The most typical way of implementing this function is before a delay which then loops back to the function to check the button again.
I have defined the following "Button" Macro for the
PICMicro with the Parameters:
The Macro itself probably looks quite complex at first glance:
Button macro Port, Pin, Downstate, Delay, Rate,
Variable, Targetstate, Address
But it really is quite simple to use and allows you to create applications that can provide an interrupt-like response to buttons and debouncing without using interrupts.
I should point out that the "Button" macro will work for low-end as well as mid-range PICMicros.
With the different ways of implementing polling (interruptless) debounce loops pretty well exhausted, I want to take a look at debouncing switches using interrupts. My first look at this avenue was to create debounce code that would cause an interrupt when the "RBO/Int" Pin changed state to "low" or the timer timed out after 20 msecs. This is "bounce3.asm".
I'm not going to discuss Bounce3 too much other than to say that it converts the work done by the first two polling debounce routines into interrupt handlers. As such, it works quite well, but I don't feel that this method is that optimal because it uses more resources (the timer, interrupt controller and more instructions) to provide the same level of function as was present before.
The last two Interrupt based Debounce Routines do have a lot to add to the function of the code. The first "bounce4.asm" sets one of two flags if the button state has stabilized to the point where it can beconsidered "debounced".
This simplifies the code to just polling the "Hi" and "Lo" bits set by the Interrupt handler to:
When you look at the "Button4" interrupt handler, you'll see that it is actually quite complex with it working through which level change is going to happen next.
To try and simplify that, I modified "Button4" into "bounce5.asm" which uses the "Port Change Interrupt". In this mode, if any of the PORTB pins 4 through 7 are used for "input", then an interrupt request will be made if the pins change state. This eliminates the need to "swap" the RB0/Int pin Interrupt direction bits in the "OPTION" Register and also gives up to four buttons to play with instead of two.
The actual code uses two "flag" registers ("Hi" and "Lo") which are polled for each of the four possible buttons. In my code I used "#defines" for the "Lo" and "Hi" bits, so the code presented above is exactly the same for Bounce5. The interrupt handler code is:
Note that when you implement this code that you have to change the button's position from RB0 (pin 6) to RB4 (pin 10) on the PIC16F84. Also note that when using the Port Change Interrupt, PORTB should never be read except in the interrupt handler body. Reads outside the interrupt handler body could result in missed Port Change Interupt requests. This is why I use a variable kept current with the PORTB output values instead of reading PORTB directly for the LED toggles as I do in the other applications.
So, with this (rather long) article, you now have six different ways of implementing software button debouncing, each with it's own advantages and disadvantages.
A few comments about testing out this code. The first one is, I used a YAP (along with the "YAP Windows" code discussed below) for the circuit. This worked out very well because I was able to test and debug all six applications with only having to change the button between Bounce5 and the other applications. If you're going to use the "YAP", make sure you always use the correct speed. I found that running at the incorrect speeds made the debounce routines work strangely (causing some short lived panic until I understood what I screwed up on).
Secondly, as you look through the code you'll see that I
included a "Debug" "define" for the delay loops. This was done to
speed through the debounce loops while simulating in MPLAB. For the Button Input, I used
the "Asynchronous Input" to MPLAB and this slows it down considerably. If you
are going to use the "Asynchronous Input" debug dialog box for your application,
I highly recommend that you add this to your applications if you have significant delays
that you want to step through. A couple of times I found that I forgot to take out the
"Debug" define (which means I rename it so it isn't recognized by the
conditional code) and the operating code work strangely - although if I reduced
The last point I want to make is, I didn't include any hardware debouncing. In the past I haven't had a lot of luck with these, but I am going to try a 0.1 uF cap and a 100K resistor to filter button input into a Schmidt trigger input. When I get some results, I'll post them here.
One of the big features of the YAP and one that can really be taken advantage of with the "YAP Windows" code is the ability to send and receive serial messaging to and from the application. In my next article, I will present a number of ways in which the 16F84 can communicate with the "YAP Windows" application using "NRZ" ("Non-Return to Zero") serial communications. These NRZ routines can also be used outside of the YAP in applications for communicating with other devices, such as your PC using RS-232.
I recently bought a model train set for my daughter and was surprised to discover how primitive the electronic controls are for trains; unless you are willing to spend huge bucks on a "DCC" system. Looking at the traditional controls which are probably the same as what your parents played with, I wondered if there was some opportunities for me to come up with better ways of controlling and monitoring the trains and the tracks.
What I will probably start with includes:
Copyright and Warranty Statement
This article is presented on an "AS IS" basis. I have tested the circuit and code that I have presented here and I am confident that it works on the hardware that I have used. Different hardware may result in different results.
The information contained here cannot be reproduced without the author's permission.
Program the PIC in simple BASIC using the PicBasic Compiler. Visit: http://www.rentron.com/PicBasic2.htm