Debouncing a switch in software using a PIC16F72

Thread Starter

b1u3sf4n09

Joined May 23, 2014
113
Intro: It has been a long time since I have worked with microcontrollers, and I was looking at getting back into it. So, naturally, I chose a project that is way beyond my skill level and started at it. I decided to break my project into parts and write them out into individual programs, that way I could test them individually and put it all together when it was working properly.

Problem: I'm currently on the input side of the project, which is the simplest part, basically just two switches which need to be debounced in software to ensure no erratic behavior. The test program uses an array of values that will change the output port of the micro to indicate the successful debouncing of the switch by energizing an LED on that output.

Initially, I wrote a for loop with a specific time delay to handle debouncing a single switch, and I wrote an interrupt to flash an LED to ensure the loop was not locking up my program. My initial code worked in debouncing the switch, but locked up my program. I changed it to a while loop, and included a boolean function to prevent multiple state changes from a single button press, but this has produced erratic behavior from my circuit. For example, the ISR LED will not begin flashing until I have pressed the switch, and then it will stop flashing after a few switch presses.

Rich (BB code):
/******************************************************************************/
/* User Global Variable Declaration                                           */
/******************************************************************************/

unsigned char LED_Number = 0;                                  // 8-bit variable
const unsigned char LED_LookupTable[3] = {0x01, 0x08, 0x80};   // LED array
bool switchState = false;
unsigned char debounceDelay = 20;

/******************************************************************************/
/* Main Program                                                               */
/******************************************************************************/

void main(void)
{
    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();

    while(1)
    {

        // use lookup table to energize one LED based on LED_Number value
        PORTC = LED_LookupTable[LED_Number];

        if (RC6 == 0 && switchState == false)     // button press loop
        {
            uint8_t db_cnt = 0;
            
            LED_Number++;                            // rotate display by 1
            if (LED_Number >= 3)
            {
                LED_Number = 0;                      // go back to LED 0.
            }

            while(db_cnt<=debounceDelay)             // button debounce loop
            {
                wait_ms(1);
                db_cnt++;
                if (RC6 == 1)                        // if button press
                {
                    db_cnt = 0;
                }
            }
            switchState = true;
        }
        if (RC6 == 1)
        {
            switchState = false;
        }
    }

}
I have also attached the files in a .zip file for examination. Does anyone have any suggestions as to what I can do to fix the issues I am seeing.
 

Attachments

JohnInTX

Joined Jun 26, 2012
4,787
You have a potential lockup issue in the while loop, I think:
Code:
            while(db_cnt<=debounceDelay)             // button debounce loop
            {
                wait_ms(1);
                db_cnt++;
                if (RC6 == 1)                        // if button press
                {
                    db_cnt = 0;
                }
            }
After entering, if the switch goes back open before db_cnt is reached and stays open, it will lock up here. You could mess with the code but main is not the place to do switch debouncing. My personal preference for a few buttons is to:
1) Make a system tik using a timer interrupt. You have this already.
2) On each system tik, one of its jobs is to sample the switch(es) and run the debounce there.
3) PRESENT the results to the main code for sampling.

This code snippet illustrates one implementation - I just grabbed it from an AAC thread I was working with a guy on and deleted project-specific stuff so it may not be compilable but here it is:
Code:
        //-----DEBOUNCE 1 BUTTON-----//
        //A COUNTER COUNTS UP AND DOWN BETWEEN 0 AND ButtonDebK.  THE CURRENT
        //STATE OF THE BUTTON IS INSPECTED AND WE SEE IF IT ACCUMULATES TO
        //THE OTHER STATE.  THE OUTPUTS OF THIS ARE THEN INSPECTED BY THE
        //MAIN PROGRAM TO DETERMINE WHAT TO DO.
        if(BUTTON_HELD == FALSE)        //BUTTON NOT HELD
        {
            if(buttonOPEN)              //NO BUTTON INPUT -buttonOPEN is an IO macro  i.e. #define buttonOPEN (RB6==1)
            {
                if(ButtonDebK != 0)     //IF BUTTON ACCUMULATED TIKS
                    ButtonDebK--;       //DECREMENT BUTTON COUNTER
                else                    //IF NO TIKS ON BUTTON COUNTER
                    ;                   //DO NOTHING
            }                           //END IF(BUTTONOPEN)
            else                        //IF BUTTON IS PRESSED
            {
                ButtonDebK++;           //INCREMENT BUTTON COUNTER
                if(ButtonDebK == BUTTON_DEBOUNCE_TIKS)  //COUNTER REACHES DEBOUNCED STATE
                {
                    BUTTON_HELD = TRUE; //SET BUTTON PRESSED STATE TO TRUE
                    BUTTON_REQUEST = TRUE; //SET ONCE PER PUSH/RELEASE

                }                       //END DEBOUNCED STATE
            }                           //END BUTTON CLOSED STATE
        }                               //END BUTTON_HELD == FALSE
        else                            //BUTTON IS CURRENTLY HELD
        {
            if(buttonCLOSED)            //BUTTON IS CURRENTLY PRESSED
            {
                if(ButtonDebK < BUTTON_DEBOUNCE_TIKS)   //COUNTER HAS NOT REACHED DEBOUNCED
                    ButtonDebK++;       //INCREMENT BUTTON COUNTER
                else                    //COUNTER HAS REACHED DEBOUNCED
                    ;                   //DO NOTHING
            }                           //END BUTTON PRESSED
            else                        //BUTTON NOT CURRENTLY PRESSED
            {
                ButtonDebK--;           //DECREMENT BUTTON COUNTER
                if(ButtonDebK == 0)     //WHEN BUTTON COUNTER IS 0
                    BUTTON_HELD = FALSE;    //SET BUTTON AS FALSE
            }           //ELSE BUTTON IS OPEN
        }               //ELSE BUTTON_HELD
The code is run every 1ms (or whatever is convenient). A 1 byte counter (for each button) increments if the switch is closed and decrements if the switch is open. Boundary conditions (zero for open, some number of tiks for closed) determine when the switch is debounced TRUE (k==some count) or FALSE (k==0). The switch is debounced both ways.

Two conditions are indicated by flags:
1) buttonHELD - TRUE when button is pushed and debounced, FALSE when debounced open. buttonHELD may be written only by the debouncer as it is used as a state indicator - it is read-only by rules.
2) buttonREQUEST - optional. This is a flag that is set ONCE when the switch is first debounced TRUE. Its most useful in cases when one action per push is required and eliminates extra processing on main's part. When a REQUEST is 'used' by main, main resets the REQUEST flag.

Something to take away from all of this (even if you don't use it) is that IO should be processed in lower levels and the results (debounced switch or ADC result) passed to the routines that use them. A producer->consumer paradigm relieves the main program logic of the details of how things actually work but preserving the logic of what they do.

Hope this helps..
Have fun!
 
Last edited:

Thread Starter

b1u3sf4n09

Joined May 23, 2014
113
Thank you for not just offering a solution to my issue, but also giving suggestions for good coding practice. These are the sorts of things I want to keep stored in the memory banks.
 

MMcLaren

Joined Feb 14, 2010
861
Question for JohnInTx, if you please?

John, while that's a nice example for debouncing a single switch, how would you handle multiple switches?
 

John P

Joined Oct 14, 2008
2,025
People go nutty over debounce routines, but I really think it's wasted effort. All you need to do is read the input(s) in a recurring interrupt at some fixed time interval, selecting a rate that's slower than the bouncing time of the switch can ever be. Maybe 30 times a second (33msec) would be a good choice. Very often, you want a fixed timer in your code anyway, so there's no need to add an extra interrupt; if the timer runs faster than the polling rate for the button(s), just use a counter, so if you had a 1msec timer and you wanted to read the buttons at 30 times a second, count to 33 and when the counter hits that level, do the test and reset the counter to 0.

Alternatively, you can avoid using interrupts at all; just poll the rollover flag for an internal timer. When you find it set, clear it and read the buttons. This has problems if the loop that the program is executing is very long, but often it's good enough. The key point is not to read the buttons too frequently--and it's OK because nobody can push them very fast anyway.
 

JohnInTX

Joined Jun 26, 2012
4,787
Question for JohnInTx, if you please?
John, while that's a nice example for debouncing a single switch, how would you handle multiple switches?
Good question. I guess I don't have any hard fast rules but the implementations I've used fall into a few broad categories.
For a few switches I just replicate the code. Crude (especially on the source level) but it doesn't compile to that much code space.

For more, I've used one debounce engine that parses an array of structures in ROM whose elements contain the port,bit to sample, whether its active high or low, and a pointer to the debounce counter/flags byte in RAM (still one RAM per switch). I've also used an array of pointers to a bunch of small functions that sample the bit and manage the RAM either as a function return value or use a passed pointer to it.

Which particular implementation is best depends on the compiler/processor and I look at the compiler output to see which source constructs make the best code. The most I've debounced with the replicate method was maybe 8-10 in a combination of panel buttons and machine sense/limit switches. Despite the sorry-looking source, it compiled to just a few btfsc/s and incf/decf and one RAM per switch so did the job and executed quickly. The one I did using the array of structures method was elegant at the source level but (for a PIC18) but the engine was bigger due to the indirects an parameter passing and took longer to execute per switch than the brute force method. I tried it as an experiment. It worked just fine but wasn't a next-level improvement at the execution level.

There are lots of variations on the same theme. If you are hard-pressed for interrupt service time and you haven't bogged down MAIN with lots of dumb delays, you can just set a flag that says 'run one debounce tik' and let MAIN get around to it when it can.

As I noted, the code posted above was done for a midrange PIC and was written off the cuff for instructional purposes. It likely could be improved both at the semantic and code generation levels. As shown it generates 29 instructions using XC8-PRO for 12F629. Not wonderful but not bad maybe? considering the functionality.

I'd never say my method is the best or for every application but I use some implementation of it every time. I like the ability to sample a switch in one cycle (by polling the _HELD flag) and also being able to consume individual pushes via the _REQUEST flag). Most programs have many occasions to use switches like this and I like it to be on a lower level with an consistent interface rather than repeated ad-hoc approaches buried in the logic of the main program, which also can be code-intensive. Just my .03

John P has an interesting approach to debouncing as well. Something to consider.
 
Last edited:

MMcLaren

Joined Feb 14, 2010
861
JohnInTx,

Thank you for the explanation. I asked because there are so many examples (simple to exotic) for debouncing a single switch while examples for multiple switches are rather lacking.

Gannsel conflates switch contact bounce and electrical noise in his Switch Debounce document and promotes the use of a software low pass filter. Unfortunately, implementing a low pass filter for multiple switches often results in rather awkward and inelegant code. If speed, code size, and overhead are important, using an eight bit wide 2-bit vertical counter as independent low pass filters for up to eight switches (on the same port) can be handy . In this case a debounced latch is toggled only after sampling a switch at the opposite state four times spanning three intervals. An 8-msec interval would be adaquate for a 24-msec debounce period.

Code:
;
;  Mike McLaren's version of Scott Dattalo's vertical counter demo'
;
;  input  ___-----_____------______-_-------_-________
;  latch  _______-----_____------________---------____
;  delta  ___----_----_----__----__-_----___-_----____
;
;  vcnt0  ---_-_--_-_--_-_---_-_---_-_-_----_-_-_-----
;  vcnt1  ----__---__---__----__------__-------__-----
;
;  delta  ______-____-____-_____-_______-________-____
;  flags  ______-----_____------________---------_____
;
;
;       delta = latch ^ ~portb;         // changes, press or release
;       vcnt0 &= delta; vcnt0 = ~vcnt0; // clear or count vc bit 0
;       vcnt1 &= delta; vcnt1 ^= vcnt0; // clear or count vc bit 1
;       delta &= vcnt0 & vcnt1;         // collect counter overflows
;       latch ^= delta;                 // update switch state latch
;       delta &= latch;                 // filter out 'release' bits
;       flags ^= delta;                 // toggle flag bits for main
;
        comf    PORTB,W                 ; sample active lo switches
        xorwf   latch,W                 ; changes, press or release
        movwf   vmask                   ; save temporarily
        andwf   vcnt0,F                 ; reset inactive counters
        andwf   vcnt1,F                 ; reset inactive counters
        comf    vcnt0,F                 ; bump vc0 unconditionally
        movf    vcnt0,W                 ;
        xorwf   vcnt1,F                 ; bump vc1 (conditionally)
        movf    vmask,W                 ; changes, press or release
        andwf   vcnt0,W                 ; collect counter overflows
        andwf   vcnt1,W                 ;   "
        xorwf   latch,F                 ; update switch state latch
        andwf   latch,W                 ; filter out 'release' bits
        xorwf   flags,F                 ; toggle flag bits for main
While you could simply 'OR' the "new press" bits into the "flags" variable (used by MAIN), the 'XOR' operation provides a simple method for using a regular momentary push button to implement a "toggle switch" function. That is, each "new press" will toggle the flag bit from on-to-off or from off-to-on. For a regular "momentary" switch you would test and then clear the associated "flags" bit while for a "toggle" switch you would simply test the associated "flags" bit.

The method Mike Szczys posted in the Debounce Code Collection on Hackaday also uses a 2-bit vertical counter. It's practically the same as the code listed above;

Code:
ISR(TIM0_OVF_vect)  // interrupt every 10ms
{
   static unsigned char ct0, ct1;
   unsigned char i;

// TCNT0 is where TIMER0 starts counting. This calculates a value based on
// the system clock speed that will cause the timer to reach an overflow
// after exactly 10ms TCNT0 = (unsigned char)(signed short)-(((F_CPU / 1024) * .01) + 0.5);
// preload for 10ms interrupts

   i = key_state ^ ~KEY_PIN;    // key changed ?
   ct0 = ~( ct0 & i );          // reset or count ct0
   ct1 = ct0 ^ (ct1 & i);       // reset or count ct1
   i &= ct0 & ct1;              // count until roll over ?
   key_state ^= i;              // then toggle debounced state
   key_press |= key_state & i;  // 0->1: key press detect
}

Like JohnP, I've come to realize that including a low pass filter in any debounce method is unnecessary in all but extremely noisy environments. That said, practically any debounce method that samples switches at some decent debounce interval (8-32 msecs) works well and allows the use of relatively simple parallel switch state logic for multiple switches. The following method debounces both "press" and "release" switch states.

Code:
/*
 *  K8LH parallel switch logic (and "new press" filter)
 *
 *  swnew  ___----____----_____  invert active lo switches
 *  swold  ____----____----____  switch state latch
 *  swnew  ___-___-___-___-____  changes, press or release
 *  swnew  ___-_______-________  filter out 'release' bits
 *  flags  ____--------________  toggle flag bits for main
 */
    swnew = ~portb;           // sample active lo switches
    swnew ^= swold;           // changes, press or release
    swold ^= swnew;           // update switch state latch
    swnew &= swold;           // filter out 'release' bits
    flags ^= swnew;           // toggle flag bits for main
Code:
;
;  K8LH parallel switch logic (and "new press" filter)
;
;  swnew  ___----____----_______  invert active lo switches
;  swold  ____----____----______  switch state latch
;  swnew  ___-___-___-___-______  changes, press or release
;  swnew  ___-_______-__________  filter out 'release' bits
;  flags  ____--------__________  toggle flag bits for main
;
        comf    PORTB,W         ; sample active lo switches
        xorwf   swold,W         ; changes, press or release
        xorwf   swold,F         ; update switch state latch
        andwf   swold,W         ; filter out 'release' bits
        xorwf   flags,F         ; toggle flag bits for main
 
Last edited:

John P

Joined Oct 14, 2008
2,025
Well, there you have it. We don't need to read a switch 10000 times a second just because we can! In fact if we opt for very rapid reading, it just gives us problems that we need to solve.
 

ErnieM

Joined Apr 24, 2011
8,377
I've never found reason to make a debounce routine any more complicated then seeing if a button (or group of buttons) have the same pattern over two subsequent readings. The separation between readings should be greater then the longest bounce, but short enough to keep the functionality lively. Nothing is more frustrating then pressing a button (however briefly) and having nothing happen. I have found 25 mS works well.

The details of how to debounce buttons also depends on the overall architecture of the program. My projects tend to have 5 buttons and my preference is to do debouncing in an ISR and "publish" the key states in a global variable.

Why 5 buttons? I arrange them in a pattern to suggest UP/DOWN/LEFT/RIGHT with an ENTER in the center. I find this convenient when working with an LCD display to walk a menu chain where either a sub menu or parameter is displayed, selected and modified. With the button state determined in an ISR the main code needs just test the current state. While buttons may be spread over several input ports it is more convenient to lump them into the same port, it is just less processing as one does not have to rearrange them into one variable.

I make the ISR triggered off a timer with a 1 ms period, which gives a useful time tick. At 25 loops the buttons are tested. If the current reading matches the last reading the global key state is updated, and each time the current reading replaces the last reading.

Here is a sample ISR. Note while taken from a working program this is untested code as several other functions have been removed:

Code:
//global
unsigned char Keys = 0;          // initially celar all buttons
int ticks = 0;                   // time heartbeat
#define BUTTON_MASK 0b01001111   // mask for our 5 buttons
#define KEY_DELAY 25             // gives 25mS delay between readings

#pragma interruptlow Heartbeat
void Heartbeat (void)
{
  // low priority ISR
  // Heartbeat is the Timer2 match handler
  // it is called every 1 mS

  static unsigned char LastKeys = 0xFF;
  unsigned char RawKeys;

  if (PIR1bits.TMR2IF)
  {
    ticks++;                  // update heartbeat
    PIR1bits.TMR2IF = 0;      // cleat ISR flag
    if (KeyDelay++ >= KEY_DELAY)
    {
      KeyDelay = 1;                        // reset after a scan
      RawKeys = DATA_PORT & BUTTON_MASK;   // read current keys & mask out any unused pins
      if (LastKeys != RawKeys)
      {
        // we have a new key pressed, save it to test when it is stable
        LastKeys = RawKeys;
      }
      else    //   (LastKeys == RawKeys) for new button state
      {
        // we have a new stable pattern
        Keys = RawKeys;  // copy new stable pattern into global
      }
    }
  }
}
 

MMcLaren

Joined Feb 14, 2010
861
Hi ErnieM,

Thank you for the example but I'm curious why you believe that checking for two consecutive samples at the same state is necessary, especially when you've chosen a sample interval that you believe to be longer than the maximum switch bounce time?

Code:
      if (LastKeys != RawKeys)
      {
        // we have a new key pressed, save it to test when it is stable
        LastKeys = RawKeys;
      }
      else // (LastKeys == RawKeys) for new button state
      {
        // we have a new stable pattern
        Keys = RawKeys; // copy new stable pattern into global
      }

Let's say you press a switch 24-msecs into the interrupt interval and you sample it 1-msec later in the interrupt routine. If you sample it while it's bouncing you may or may not detect a change in state, however, it will certainly be at a stable state when you sample it again in another 25-msecs. In effect you're able to detect the switch state change either on the first or the second sample or within 1-msec or 26-msecs after you've pressed the switch. Your double check code on the other hand would produce a key press flag either 26-msecs or 51-msecs after you pressed the switch. The code literally samples the switch a second (or third) time when it's at a known stable state. Depending on what you're doing, a 51-msec response time may seem sluggish.

Is there an advantage to the double-check that I may be missing? Or, is this just a simplified low pass (noise) filter?

Also, it seems you're simply passing debounced switch state data to main. How do you differentiate a "new press" state from a "still pressed" state in main? In other words how do you prevent code in main from processing a single switch press multiple times?

Cheerful regards, Mike
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
The double check IS the low pass filter. Personally I find the 25mS time acceptable, I've never been able to fool the computer using standard tactile switches.

I don't do anything beyond checking if there are no buttons or one (or more) buttons down. The used could push more then 1 button down and may get some funny results, but that's what you get for messing with the machine <grin>. They really are not too serious as the processor responds to the first button and hangs till all are released.

In my menu driven system the code is typically waiting for a button to be pressed, and occasionally waiting for one to be released. This is done with just a pair of while loops:

Code:
  while(!Keys);     // wait for key to be pressed
  // now we have a press, decode it
  switch (Keys)
  Case Button_A
    {do something}
    break;
  Case Button_B
    {do something else}
    break;
  while(Keys);   // wait for key to be released
That's the basics. Again, my code is too interleaved to show, as all the menus are drawn in a function along with button presses, and even the while loop is done in a function that also calls other tasks (it keeps the USB interface alive for one).
 
Top