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. You can contact Myke by E-mail at: myke@passport.ca or visit his web site at: http://www.myke.com

Button Debouncing

first.jpg (23199 bytes)

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:

butconfg.gif (3919 bytes)

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:

butbounc.gif (3718 bytes)

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:

WaitForButton:

   While (Button == High);           // Wait while the Button isn't Pressed

   Dlay = 20msecs;                   // Poll the button for 20 msecs before
                                     // Acknowledging Button is Debounced
   while ((Button == Low) && (Dlay != 0))
   Dlay--;                           // Decrement through each Loop

if (Dlay != 0) then goto WaitForButton;
                                     // If Dlay not Zero, Bounce

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:

Circuit

Bill of Materials

R1
R2-R3 
C1
C2
U1
U2
CR1-CR2
SW1
--
--

--
Y1
4.7K 0.25 Watt Resistor
220 Ohm 0.25 Watt Resistor
10 uF 25 Volt Electrolytic Capacitor
0.1 uF 16 Volt Tantalum Capacitor
78L05 Voltage Regulator
PIC16F84 Microcontroller
5 mm Red LEDs
"Momentary On" SPST Pushbutton
Prototype Card
Wiring
Power Supply (> 6 Volts if Voltage Regulator used)

4MHz Ceramic Resonator (With Internal Capacitors)
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:

main()
{

    LED.Port = Output;            // Initialize the LED Output Bits
    Always.Port = Output;

    Always.Port = on;             // Set the "always" on LED

    while (1 == 1) {

    Debounce( Button, High );     // Wait for the Button to Go High
                                  // (Not Pressed)
    Debounce( Button, Low );      // Wait for the Button to Go Low
                                  // (Pressed)
    if (LED == on)                // Toggle LED State
      LED = off;
    else
      LED = on

  }
}  // End Button Debounce

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:

LowLoop                       ; Return Here until "0" Debounced
  btfsc    Button             ; Is the Button Pressed?
  goto     LowLoop            ; Yes - Wait for it to be Released

  movlw    0x00C              ; Wait for Release to be Debounced
  movwf    Dlay               ; Have to Delay 20 msecs
  movlw    0x0D7

LowPollLoop                   ; Poll while Button is Low
  btfsc    Button             ; If Button Pressed, Wait Again for it
  goto     LowLoop            ; to be Released
  addlw    1                  ; Increment the Delay Count
  btfsc    STATUS, Z          ; If Low Byte (in w) Not Equal to
  decfsz   Dlay               ; Zero, then Loop Around
  goto     LowPollLoop

This code doesn't actually follow the psuedo code listed above. It really operates like:

WaitForButton:

   While (Button == High);    // Wait while the Button isn't Pressed

   Dlay = 20msecs;            // Poll the button for 20 msecs before
                              // Acknowledging Button is Debounced
   while (Dlay != 0) {
     if (Button == High)      // Restart if Button is High
     goto WaitForButton;
   Dlay--;                    // Decrement through each Loop
  }

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
if HiLo == Lo
  btfss   Port,Bit        ; Is the Button Pressed?
else
  btfsc   Port,Bit
endif
  goto    $ - 1           ; Yes - Wait for it to be Released
  movlw   InitDlay        ; Wait for Release to be Debounced
  movwf   Dlay            ; Have to Delay 20 msecs
  movlw   0
if HiLo == Lo
  btfss   Port,Bit        ; If Button Pressed, Wait Again for it
else
  btfsc   Port,Bit
endif
  goto    $ - 6           ; to be Released
ifndef    Debug           ; Skip Small Loop if "Debug" Defined
  addlw   1               ; Increment the Delay Count
  btfsc   STATUS, Z;If Low Byte (in w) Not Equal to Zero,then Loop Around
else
  nop                     ; Match the Number of Instructions
  nop
endif
  decfsz  Dlay
  goto    $ - 5
endm


The advantages of this macro are:

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)

or

   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.

I should point out that this macro will not work with low-end PICMicros because of the "addlw 1" instruction. This code can be modified with my "Increment w" routine that can be found on my PICMicro Snippet Page, but if you are going to use "w increment" code, then make sure you increase the number of nops to 4 in the Macro and multiply by "9" instead of "7" when calculating "InitDlay".

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.

ButtonLoop:

   Button Pin, Downstate, Delay, Rate, Variable, Targetstate, Address

   Delay

   goto ButtonLoop

Address:                     ' Jump Here When Debounced.

I have defined the following "Button" Macro for the PICMicro with the Parameters:

Button Port, Pin, Downstate, Delay, Rate, Variable, Targetstate, Address
  Where:
   Port, Pin - the Button Pin (ie "PORTA, 0") and can be defined with a
    "#define" Statement
   Downstate - The State When the Button is Pressed
   Delay - The number of iterations of the Macro code before the "address"
    is jumped to (to 127). If Set to 0, then Jump if "targetstate" met
    without any debouncing. If Bit 7 of "Delay" is set, then no
    auto-repeats
   Rate - After the Initial jump to "address", the number of cycles (to 127)
    before autorepeating.
   Targetstate - The state ("1" or "0") to respond to.
   Address - The Address to Jump to when the Button is pressed or Auto-repeats

The Macro itself probably looks quite complex at first glance:

Button macro Port, Pin, Downstate, Delay, Rate, Variable, Targetstate, Address
    local   ButtonEnd
    incf    Variable, w        ; Increment the Counter Variable
  if ((Downstate == 0) && (Targetstate == 0))||((Downstate == 1) && (Targetstate == 1))
    btfsc   Port, Pin          ; If Low, then Valid Pin
  else
    btfss   Port, Pin          ; If High, then Valid Pin
  endif
    clrw                       ; Not Pressed, Clear the Counter
    movwf    Variable          ; Save the Counter Value
    movlw   Delay & 0x07F
    subwf   Variable, w        ; Button Debounced?
    btfsc   STATUS, Z
    goto    Address            ; If Equal, then "Yes"
  if    ((Delay & 0x080) != 0) ; Is Autorepeat used?
    btfsc   STATUS, C
    decf    Variable           ; No - Decrement if > "Delay"
  else
    btfss   STATUS, C
    goto    ButtonEnd          ; Less than Expected - End
    xorlw   Rate               ; At the Autorepeat Point yet?
    btfsc   STATUS, Z
    goto    ButtonEnd          ; No - Keep Incrementing
    movlw   Delay              ; Yes, Reset back to the Original Count and Repeat
    movwf   Variable
    goto    Address
  endif
 ButtonEnd                     ; Macro Finished
  endm

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:

Loop

   btfss  Hi       ; Wait for the Button to go high
   goto   $ - 1

   btfss  Lo       ; Wait for the Button to go low
   goto   $ - 1

   btfsc  PORTB, 2 ; Is the Button Pressed?
   goto   ButtonUp ; No, Light Off

   bsf    PORTB, 2 ; Turn Light On

   goto   Loop

ButtonUp           ; Button Up, Turn Light Off

   bcf    PORTB, 2

   goto   Loop

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:

    org     4
Int                         ; Depending on Interrupt

    movwf   _w              ; Save the Context Registers
    movf    STATUS, w
    movwf   _status

    movlw   256 - 0x09C     ; Reset the Timer to Overflow in 20 msec
    movwf   TMR0           

    btfss   INTCON, RBIF    ; Button Low Received
    goto    IntTimerOF      ; Else, Overflow - Button Pressed

IntTMR0Reset                ; Reset the Interrupt OverFlow Timer

    movf    PORTB, w        ; PORTB Read To Reset Interrupt Requests

    bcf     INTCON, RBIF    ; Reset the Interrupts

    clrf    flagLo          ; Reset the Flags Registers
    clrf    flagHi

    goto    IntEnd

IntTimerOF                  ; Overflow - Debounce Completed

    bcf     INTCON, T0IF    ; Reset the Timer Interrupt

    movf    PORTB, w        ; Get the current Button State
    xorlw   0x0F0
    andlw   0x0F0
    movwf   flagLo          ; Save the Current Low Values

    movf    PORTB, w        ; Save the Current High Values
    andlw   0x0F0       
    movwf   flagHi

IntEnd

    movf    _status, w      ; Restore Interrupt Registers before Return
    movwf   STATUS
    swapf   _w
    swapf   _w, w

    retfie                  ; Return from Interrupt

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 speed to 1 MHz on the YAP, there wasn't any problems.

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.

Coming up and other News

yapwin.jpg (29348 bytes)

This past week, I created a Visual Basic Front end for the YAP programmer that is presented in "Programming and Customizing the PIC Microcontroller". This front end is available for download from Wirz Electronics.

The file to be downloaded is a 1.6 MB "zip" file. During download you can "open" it with "WinZip" and then execute "setup.exe". The set up operation will load the correct device drivers as well as the application on your PC. To execute the application, click on "YAP Windows" from "Start" on your PC's taskbar.

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:

- Train Sensing
- Train Control (Forward, Backwards, Speed)
- Track Switching

Is there anything else you would like to see? Based on the number and complexity of Macros in this week's article, I suspect that people would like more information about them. In the next few weeks, I'll prepare an article on Macros.

This week on myke.com

Since it has been a few weeks since my last article, I thought I should give you a quick list of the past books and movies of the week. If you love movies, then you should probably take a look at Ed Sikov's "On Sunset Boulevard" which is a biography of Billy Wilder and a discussion of his movies. It is a fascinating look at the man that tried to push Hollywood to become more adventurous and then discovered that mainstream Hollywook movies passed him by. If you are interested in "true" history, then you would probably be interested in "Black Hawk Down", the harrowing story of the 99 Army Rangers caught having to spend the night in downtown Mogadishu after a raid to arrest clan leaders. The battle is described as the "biggest firefight since Vietnam" and will give you a much greater respect for the men (and women) who go overseas to help others. Lastly, this week I have "Hannibal", Thomas Harris' long awaited sequel to "Silence of the Lambs". It is a compelling, shocking story that you will be thinking about for a long time. Find out more at my Book of the Week Page.

In response to the popularity of the latest Austin Power's movie, I thought I'd put in a movie that <i>I</i> loved as a teenager; "Hooper" starring Burt Reynolds and Sally Fields. My thirteen year old grudgingly admits that it is as good as Austin Powers (which he's seen twice so far). Previously, the video of the week was Mike Nichol's "Primary Colors". A few weeks ago I presented "The Newsroom" as one of the best shows ever on TV because of it's final, one hour episode, "The Election" in which the laid off news team get their stupid, vain and sexually disfunctional anchor elected as an Ontario Liberal MPP. Well, since I saw this episode for the first time, two things have happened; the first is, we had an election here in Ontario that was more ridiculous than anything you could put on a screen and secondly, I saw "Primary Colors". I am not a big fan of John Travolta, but he does do an excellent job as a Bill Clinton clone that can't keep his pants zipped up. Emma Thompson does her typically excellent job as his continually humiliated wife that you spend the whole movie wondering how much she knows. Take a look at it at my Video Corner.

This week's survey is "Who is the funniest guy in the movies"? Drop by my Survey Page  and tell me if I'm all wet about Mike Meyers.

I hope this article is useful for you and if you have any questions, please send them to me at: myke@passport.ca

Myke

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.

If you have problems with the circuit or software, I would be happy to talk with you about it, but I cannot guarantee that it will work for you under all circumstances. The text, drawings, circuit and software are Copyright © Myke Predko, 1999.

For technical support with this project please contact Myke Predko at: myke@passport.ca

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