Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC Switch Management Techniques

Status
Not open for further replies.

Mike - K8LH

Well-Known Member
Dear fellow Designers/Hobbyists,

Has anyone else struggled to write code to debounce and manage multiple push button switches in the PIC environment? If so, and you've come up with a clever or innovative technique, would you consider sharing it with us here on the Forum, please? We'd love to study it. Thanks.

In that spirit I'd like to share a technique I've developed to process up to 8 switches in parallel in my applications. In a nutshell, it's 27 lines of ISR code that provides the following features/capabilities;

<> process up to 8 switches in parallel
<> eight independent 24.0-msec "debounce" vertical counters
<> debounced switch press audible feedback (beep or click)
<> momentary switch operation (test then clear a SWITCH bit)
<> toggle switch emulation (push to toggle a SWITCH bit from off-to-on or from on-to-off) perfect for lighted switches

I discovered early on that if I set a switch pressed "memory" or "latch" bit to the last state of the switch that I could determine a couple different conditions. Specifically, if the live switch bit is on and the switch memory bit is off I'm detecting a new switch "press", and if the live switch bit is off and the switch memory bit is on I'm detecting a new switch "release". Translating this into ISR code to detect new switch presses for up to 8 switches in parallel, I came up with this (the SWITCH variable contains pressed switch flag bits for MAIN);

Code:
;
;  toggle SWITCH variable switch pressed flags for MAIN
;
        movf    SWLIVE,W        ; live switch bits for 8 switches
        xorwf   SWPEND,W        ; xor switch pressed latch
        xorwf   SWPEND,f        ; update switch pressed latch
        andwf   SWLIVE,W        ; get new switch pressed bits
        xorwf   SWITCH,f        ; toggle SWITCH flag bits for MAIN
;
I thought the code was pretty cool considering the size (grin). The MAIN program simply tests a SWITCH flag bit and clears it for "momentary" switches and simply tests a SWITCH flag bit for push button switches emulating "toggle" switches (push the switch to toggle the SWITCH flag bit from on-to-off or from off-to-on). I use "toggle" switch emulation for lighted push button switches.

Next I added code to send audible feedback for "new" switch presses;

Code:
;
;  send short beep for "new" switch presses
;
        movf    SWLIVE,W        ; live switch bits for 8 switches
        xorwf   SWPEND,W        ; xor switch pressed latch
        xorwf   SWPEND,f        ; update switch pressed latch
        andwf   SWLIVE,W        ; any new switch pressed bits?
        btfss   STATUS,Z        ; no, skip, else
        bsf     BEEPER,5        ; send 32-msec short beep
        xorwf   SWITCH,f        ; toggle SWITCH flag bits for MAIN
;
;  beep (500-Hz tone with 1.0-msec interrupts)
;
        movf    BEEPER,W        ; is beep on?
        bz      ISR_Next        ; no, branch, else
        btg     Speaker         ; toggle speaker on PORTA,3
        decf    BEEPER,f        ; decrement beep msec counter
;
ISR_Next
Not bad for a handful of instructions but there's something missing. These 8 switches aren't debounced. How the heck can you provide independent debounce timers for 8 switches? I was sure it could be done but I suspected that it wouldn't be very pretty (grin). That's when I discovered vertical counters. They were difficult for me to grasp at first and I won't go into details here but refer you instead to Scott Dattalo's examples. Here's the final fully debounced example ISR code;

Code:
;******************************************************************
;
;  Mike McLaren's "debounce" vertical counter ISR code based
;  on concepts and examples from Jonny Doin and Scott Dattalo
;
;  <> process up to eight switches in parallel
;  <> eight independent 24.0-msec "debounce" vertical counters
;  <> debounced switch press audible feedback (beep or click)
;  <> momentary switch operation (test then clear a SWITCH bit)
;  <> toggle switch emulation (push to toggle a SWITCH bit from
;     off-to-on or from on-to-off) perfect for lighted switches
;
;  27 words/27 cycles (14-bit core)
;
;
;  setup the SWKEYS variable with "live" switch press data from
;  your switch input pins or your scanned switch matrix before
;  falling into this routine.  A '1' bit represents a 'pressed'
;  switch and a '0' bit represents a 'released' switch.
;
;  execute this code each 1.0-msec interrupt cycle
;
;
;  clear vertical counters for bouncing and steady-state switches
;
ISR_Sw
        movf    SWKEYS,W        ; live switch press data          |B0
        xorwf   SLATCH,W        ; debounced pressed switch latch  |B0
        andwf   VCBIT0,f        ;                                 |B0
        andwf   VCBIT1,f        ;                                 |B0
;
;  cyclic vertical counter algorithm.  vcmask is seeded with an
;  active switch mask and bn' is the previous state of bn before
;  the bit increment operation.
;
;  a switch is debounced or filtered after sampling it at the
;  same level through four vertical counts spanning 24.0-msecs
;
;    b0 ^= (vcmask)
;    b1 ^= (vcmask & b0')
;    b2 ^= (vcmask & b0' & b1')
;
;  Mike McLaren's "cumulative AND" vertical counter method
;
;    b0 ^= vcmask : vcmask &= bo'
;    b1 ^= vcmask : vcmask &= b1'
;    b2 ^= vcmask
;
        andwf   COLPOS,W        ; 8-msec 'column' prescaler       |B0
        movwf   VCMASK          ; seed "cumulative AND" mask      |B0
        xorwf   VCBIT0,f        ; b0 ^= vcmask                    |B0
        xorwf   VCBIT0,W        ; restore b0' in W                |B0
        andwf   VCMASK,W        ; vcmask &= b0'                   |B0
        movwf   VCMASK          ;                                 |B0
        xorwf   VCBIT1,f        ; b1 ^= vcmask                    |B0
        xorwf   VCBIT1,W        ; restore b1' in W                |B0
        andwf   VCMASK,W        ; vcmask &= b1'                   |B0
;
;  each '1' bit in the SLATCH variable represents a "debounced"
;  24.0-msec switch press (bit cleared when switch is released)
;
        xorwf   SLATCH,f        ; update debounced state latch    |B0
;
;  send a 32-msec short beep for new debounced switch presses
;
        andwf   SWKEYS,W        ; new debounced switch press?     |B0
        btfss   STATUS,Z        ; no, skip, else                  |B0
        bsf     BEEPER,5        ; send 32-msec short beep         |B0
;
;  toggle SWITCH flag bits for processing by the MAIN program
;
;  test SWITCH bits for emulated "toggle" switches.  test then
;  clear SWITCH bits for "momentary" switches.
;
        xorwf   SWITCH,f        ; toggle SWITCH bits for MAIN     |B0
;
;  advance ring counter prescaler (initialized value = 00000001)
;  for the next interrupt cycle
;
        rlf     COLPOS,W        ; advance column ring counter     |B0
        rlf     COLPOS,f        ;                                 |B0
;
;  beep (500-Hz tone with 1.0-msec interrupts)
;
        movf    BEEPER,W        ; beep timer set?                 |B0
        bz      ISR_Next        ; no, branch, else                |B0
        movf    GPIO,W          ; read port                       |B0
        xorlw   1<<Speaker      ;                                 |B0
        movwf   GPIO            ; toggle piezo speaker pin        |B0
        decf    BEEPER,f        ; decrement timer                 |B0
;
ISR_Next
This small "debounce" vertical counter ISR code has turned out to be so handy that I use it for switch management even on the simplest projects that might only have one or two switches. And managing lighted push button switches emulating "toggle" switches is a breeze (push on, push off).

Those of you interested in vertical counters should check out Scott Dattalo's examples and a "vertical counter" thread on the MC Forum.

Have fun. Kind regards, Mike
 
Last edited:
The answer should be obvious- you need an independent debounce counter for each pin.
So you either say:
Code:
if(pinDebounceFlags!=0x00){
    unsigned char i;
    for(i=0;i<8;i++){
        if(pinDebounceCounter[i]>0){
            --pinDebounceCounter[i];
            if(pinDebounceCounter[i]==0){
                 pinDebounceFlags&=~(0b1<<i);
            }
        }
    }
}
or you can go with a timer solution, which is preferred since the debounce period remains constant even when the processor has to do other tasks.
 
Just brought the code in the first post up-to-date.

I also have example vertical counter ISR code now with debounce and repeat capability, if anyone's interested. It's relatively small and tight at 48 words and provides a 512-msec repeat rate for designated repeat keys. It's kind of nice to designate a pair of repeat switches on-the-fly and simply push and hold them to increment or decrement displayed values instead of repeatedly pushing them (then I un-designate them as repeat switches when done).

Regards, Mike
 
That's some really great code Mike nice work thanks for the post.
 
The switch management technique I'm using on my latest project involves scanning them. Even though there are only 4 switches total and it uses up one more pin on the PIC than it would if you simply had them on 4 input lines. The beauty of this setup is that I only need one pull up resistor on the "common" pin, then use four output lines that I set low, then high sequentially as the common pin is polled each time the lines are set low.
 
Last edited:
Hi Jon,

That's a good hardware technique and it costs you even less pins if you can mux' the switches with the column driver lines on a 7-segment LED display or with the data lines on an LCD display;

Regards, Mike

74hc595-4-bit-backpack-png.51948
 

Attachments

  • 74HC595 8-bit Interface.png
    74HC595 8-bit Interface.png
    17.1 KB · Views: 306
  • MacMux 4-Digit 7-Seg.png
    MacMux 4-Digit 7-Seg.png
    37.5 KB · Views: 1,835
  • 74HC595 4-bit Backpack.png
    74HC595 4-bit Backpack.png
    25.8 KB · Views: 1,502
Last edited:
I debounce all 8 inputs simultaneously, using one counter variable.

The procedure requires all the 8 input pins to be stable (unchanged) for 15 loops, and if so, the pin states are copied to a variable; inputs_good.

This variable always has the most recent debounced values of all the pins. Something like this;

Code:
  // debouncing 8 input pins simultaneously in main loop
  Delay_mS(10);           // main delay, loop repeates roughly every 10mS
  inputs_new = PORTB;     // read 8 input pins
  if(inputs_new != inputs_last) debounce = 0;   // reset debounce if pin has changed
  else debounce++;   // else inputs are stable so add a count
  inputs_last = inputs_new;  // record for testing next loop
  if(debounce >= 15)
  {
    debounce = 0;
    inputs_good = inputs_new;	 // inputs_good is the debounced inputs
  }
 
Last edited:
Hi Roman,

Do you use additional code (switch state logic) to detect a "new press" or "new release" state change?

You're using a 150-msec debounce delay but many people can perceive a delay as small as 50-msecs as a sluggish response.

With your single 150-msec timer, what happens if you press or release different switches continually at intervals less than 150-msecs? It looks like you'll never get an "inputs_good" variable update because you reset the 150-msec timer with each change in state. This really seems like a single routine that will debounce up to eight switches one switch at a time rather than "debounce 8 input pins simultaneously" as you claim, unless my analysis is incorrect (?).
 
Last edited:
A few years ago Mike (Pommie) suggested that if you don't have a noise issue you really don't need to sample switches multiple times across the debounce interval and I agree. Sampling switches at some reasonable debounce interval (typically 8 to 32 msecs) works well because a bouncing switch will read as stable during the span of one or two sample intervals. That said, you can debounce and manage up to 8 switches simultaneously using very simple and very efficient parallel switch state logic like the following example which filters out all but the "new press" state change;

Code:
  /*
   *  swnew   ___---___---___---___    sample active lo switches
   *  swold   ____---___---___---__    switch state latch
   *  delta   ___-__-__-__-__-__-__    changes, press or release
   *  swnew   ___-_____-_____-_____    filter out 'release' bits
   */
   while(1)                         // main loop
   { delay_ms(20);                  // 20-msec debounce interval
     swnew = ~portb;                // sample active lo switches
     swnew ^= swold;                // changes, press or release
     swold ^= swnew;                // update switch state latch
     swnew &= swold;                // filter out 'release' bits

     if(swnew.0)                    // if sw(rb0) "new press"
     {                              //
     }                              //
     if(swnew.1)                    // if sw(rb1) "new press"
     {                              //
     }                              //
   }
If noise is an issue, that's when you need to sample multiple times across the debounce interval in order to filter out a (higher frequency) noise pulse. But if you need to handle multiple switches simultaneously then you really need to use independent debounce timers, one for each switch input, in order to avoid the problem I mentioned in an earlier post.

Cheerful regards, Mike
 
Last edited:
Hi Roman,

Do you use additional code (switch state logic) to detect a "new press" or "new release" state change?

Generally not. For data entry (keypad) it can be useful but in a lot of cases the processor just needs to know what state an input is, and be confident that it is a real state, not a noise signal.

You're using a 150-msec debounce delay but many people can perceive a delay as small as 50-msecs as a sluggish response.

Sure, those figures were typed in off the top of my head. Again you are focused primarily on keypad use but I debounce all inputs on all applications, so a 150mS-300mS delay can often be an advantage as it provides very high rejection of pulse noise. The system I posted has been used in commercial applications including automotive. If you need a faster response it's pretty obvious the loop time and or number of debounce counts can be reduced to suit. :)

With your single 150-msec timer, what happens if you press or release different switches continually at intervals less than 150-msecs? It looks like you'll never get an "inputs_good" variable update because you reset the 150-msec timer with each change in state. This really seems like a single routine that will debounce up to eight switches one switch at a time rather than "debounce 8 input pins simultaneously" as you claim, unless my analysis is incorrect (?).

Again it depends on application. If you have a lot of fast activity going on with the inputs then another system would be better suited! It is also easy enough to change the constants to speed the system up, depending of course on the input activity vs potential noise issues. 150mS is fine for normal button presses.

You did ask about other systems, I hope you didn't think I was posting this to try to say it was better than your system? I have a lot of respect for your work which is always excellent, I just wanted to show it can be possible to debounce all the inputs together as a "set" which is very simple. I was not implying this is the best system to be used in every possible application. It's just an "other" system. ;)
 
Hi Roman,

Thank you for a very nice example. I apologize if it seems I was unfairly criticizing it. I thought you may have been exaggerating when you claimed it will debounce 8 input pins simultaneously. I can see now that it will indeed do just that but because you're using a single debounce timer for all eight inputs, the debounce response time may not be consistent depending on the timing of the input changes. This is probably a non-issue because you'll probably never press more than one switch at a time and if you did you probably wouldn't notice a difference in the response time.

Cheerful regards, Mike
 
Last edited:
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top