Debouncing multiple contact inputs efficiently

I have around 20 independent contact inputs to an HCS12 that all need to be debounced. It's a straightforward task, but doing it in the way that I've always done it, and that AFAIAA is more or less standard, results in a fair chunk of code that could cause problems with other aspects of the application which need fairly high speed. Are there any tricks for debouncing multiple inputs in an efficient way?

Reply to
Bruce Varley
Loading thread data ...

There's a reasonably good survey of the various techniques over at

formatting link

--
Rich Webb     Norfolk, VA
Reply to
Rich Webb

Unless every $0.001 (yes tenth of a cent) counts.

The main trick I use on busy systems is do it in hardware especially for that sort of quantity with something like the MAX6818 whuch debounces 8 switches all in hardware, ESD protection and best of all an interupt for ANY output changes. Smaller versions exist for 2 or 4 inputs, without interupt pin.

Leave software to be have time for other code and interrupts.

--
Paul Carpenter          | paul@pcserviceselectronics.co.uk
    PC Services
 Timing Diagram Font
  GNU H8 - compiler & Renesas H8/H8S/H8 Tiny
 For those web sites you hate
Reply to
Paul

I guess there's no perfect method - if you poll each input separately and use a software timer for every input, it works great but uses more cpu time than you can afford (probably).

If you can easily read all inputs into one 16 or 32 bit word (one bit per input), you could poll the inputs in a timer interrupt at 1000 ... 100 Hz and store the N most recent words. If a given bit is zero in all N words (use bitwise OR), the input is low. If a given bit is one in all words, (use bitwise AND), the input is high. Depending on the nature of the inputs, you may need 5 words ot even less to get a valid status.

This method may use less than one asm instruction per input, so I think it's quite efficient.

-jm

Reply to
Jukka Marin

Use 1 bit per switch to indicate current state, and a short counter that keeps track of bounce period. Total 1 byte per switch.

At a suitable frequency, e.g. 100 Hz, perform the following code:

for each switch s if counter[s] = 0 if new_position(s) old_position[s] then generate event counter[s] = N else counter[s] = counter[s] - 1

Code size is independent of number of switches, you just use some more RAM for the state. You could put 2 switches in a byte, at the cost of more complex code.

Reply to
Arlet Ottens

Just as a note to others, a superseted HC11 instruction set device.

Yes. You can debounce 8 at a time with this processor. I don't know the exact part you are using or where to get the datatsheet. But perhaps I can assume you can read "20 "independent contact inputs" with three 8-bit port reads? If so, you should be able to completely debounce all 20 with perhaps 35 instruction executions every timed poll event. In assembly. I'm not talking c, here.

I wrote about this a long time ago in this group, perhaps

2001. You can google it up, I'd guess. But since I have a snapshot I will post part of my comments from back then and you can decide if it is something you care about. I used this on an AVR (AT90S2313) for 8 switches (one port) and it looks like this:

.equ SWPORTPINS = PINB .def SwRawCurr = r4 .def SwRawPrev = r5 .def SwState = r6 .def SwDebCurr = r7 .def SwDebPrev = r8

; Debounce the input switches.

mov SwRawPrev, SwRawCurr in SwRawCurr, SWPORTPINS mov Timer0Tmp1, SwRawCurr eor Timer0Tmp1, SwRawPrev mov Timer0Tmp0, Timer0Tmp1 or Timer0Tmp1, SwState mov SwState, Timer0Tmp0 mov Timer0Tmp0, Timer0Tmp1 com Timer0Tmp0 and Timer0Tmp1, SwDebCurr and Timer0Tmp0, SwRawCurr or Timer0Tmp1, Timer0Tmp0 mov SwDebPrev, SwDebCurr mov SwDebCurr, Timer0Tmp1

That does everything required for all 8. This was placed inside a timer event. The byte variable SwDebCurr was what the main application used for the debounced bit values for each switch. The rest is temporary or internal state.

Here is the basic idea behind the above code taken from a post in 2001 that I did:

In any case, have fun.

Jon

Reply to
Jon Kirwan

You can use a relatively infrequent timer interrupt to poll the inputs, and allow that routine to be interrupted or have a fair amount of jitter as to when it starts without losing any reliability. There is generally no reason to add any hardware, IMHO, particularly not with a micro such as the HCS12 that has prioritized interrupts and allows nested interrupts.

Reply to
Spehro Pefhany

In the extreme, you can just poll the switches slowly enough that any switch bounce will settle out before the next time the processor gets around to reading the switches. This tends to get the delay long enough that users will notice, but if that's not a problem it's certainly an easy solution to apply.

--
www.wescottdesign.com
Reply to
Tim Wescott

IME the problem with slow polling is not response time but missing keypresses - they can be much briefer than you might imagine. I've not had any issues at 40Hz or so, and at the other end ~100Hz is still more than long enough to counter bounce.

At ~20Hz I've found that keypresses occasionally go missing which I attributed to them occuring between polling intervals. Depending on the application, larger switches with greater actuation force will slow the user down, and possibly allow the device to keep up.

--
Andrew Smallshaw
andrews@sdf.lonestar.org
Reply to
Andrew Smallshaw

I wrote a simple keyscanner state machine some years ago in C for a port's worth of switches, that has an upper and lower half, with a tick timer driving the lower half and handshaking flags to the upper half. The lower half is called every

10-20 mS from the tick timer and when one or more keys are pressed, the code waits for the next tick time, looks at the port again and if the same, handshakes the value to the upper half. It's the upper half's job to decide if the value is valid (ie: single key down) or invalid. It's quite easy to organise stuff like autorepeat as well...

Regards,

Chris

Reply to
ChrisQ

be

fair

If all inputs are key switches, I'd combine them to produce one number of 20 bit. If that number remains constant for, say 50 ms, you're safe to read the switch states. Only one timer, one word.

Meindert

Reply to
Meindert Sprang

I have always debounced in parallel for as many bits as needed. Computers have convenient AND, OR, and XOR instructions that operate individually on the bits of a variable. Depending upon your architecture these can be as many as 8, 16, 32 or even 64 bits at a time. The technique uses a concept called vertical calculation where each bit position across multiple variables represents a slice of the computation result. For debouncing setup a push down array of variables that act as a filter. The length of the array times the sample timer interrupt period relates to the time constant of the filter. Use the periodic timer interrupt to sample the inputs and trickle them through the filter array. Any bit columns that stay at the same value through the whole array are considered debounced and stable. I keep the results also in a vector variable that can then be sampled by the mainline code using a bit mask to check for conditions as "is input low", "is input high", "has input just changed to low" and "has input just changed to high". My logic keeps the transition detect bits sticky in the state variables until either scanned by the mainline code or the input has changed state back the opposite transition after making it through the filter.

I typically have the timer interrupt sample inputs at a 10msec rate and use an array depth of maybe 4 or 5 to regulate the debounce detection period. While not the complete solution here is a sample of the type of C code that samples the inputs.

/* setup the number of input poll samples to filter for stable state */ #define SWITCH_POLL_COUNT 5

/* array of bit flags for switches filter */ unsigned char xdata swt_filter[SWITCH_POLL_COUNT]; unsigned char xdata swt_status; /* current filtered input state */ unsigned char xdata swt_previous; /* previous filtered input state */ unsigned char xdata swt_true; /* saved input true transition */ unsigned char xdata swt_false; /* saved input false transition */

/*

** ** routine to initialize the swt scan port logic to the inactive ** state. ** */

void swt_init(void) { unsigned char i;

for(i=0; i> 5; /* read switches from P3.5 -> P3.7 */ swt_tmp = P4 & 0x02; /* get switch at P4.1 */ swt_tmp

Reply to
Michael Karas

the problem with polling is sensitivity against glitches, not response speed.

Unless you need to suppress short glitches, the polling method is good.

Oliver

--
Oliver Betz, Munich
despammed.com is broken, use Reply-To:
Reply to
Oliver Betz

These are "independent" so there are no interrelationships between their states, right? Do all of the contact closures have the same mechanical characteristics (i.e., debouncing the make vs. break condition as well as times involved)? Or, are some, e.g., limit switches on motion mechanisms, others keypads or photodetected events, etc.? (i.e., debounce applies to any "signal transition")

First, decide *how* you want to debounce them. I.e., are you looking to debounce both edges? Just the leading/trailing? When do you want to "signal" the associated event?

Second, decide how *often* you need to "see" the switches. That is driven by the minimum time you expect a switch to stay in any particular state *and* the maximum delay you can tolerate between a physical change in the "switch" input and the program's response to it.

I like to build little state machines to debounce switches. You can do this with a hand-coded algorithm or with a more formal "state machine language". The latter is preferable (to me) because it makes things glaringly obvious (you have to "read" code to understand what it's trying to do)

Build a list of "input descriptors" (e.g., address + mask), a list of associated "state variables" and have a tiny piece of code that grabs an input descriptor, sets the state of the FSM as previously stored (state variable), examine the input, run the state machine, update the state variable

*and* output(s) (depends on Mealy or Moore design) and then move on.

typedef struct { int *sensor; /* where the input is presented to the system */ int mask; /* how to isolate this single bit */

int state; /* current state of this input's FSM */ int event; /* event to signal when input is detected */ int working; /* working storage for that FSM */ } descriptor;

(note the first part of that struct is essentially R/O and could be moved into ROM. "ints" are probably WAY too big for your needs)

descriptor inputs[];

ptr = &inputs[0]; while (NULL != ptr->sensor) {

/* I like shedding the "ptr->" syntax early. Let the compiler clean this up for me later! */ current = ptr->state; sensed = *(ptr->sensor) & ptr->mask; working = ptr->working;

switch (current) { case IDLE: // twiddling thumbs if (sensed) { next = SIGNALED; working = COUNTDOWN; signal_event(event); } break;

case SIGNALED: // already saw (and signaled) edge if (working) // ignore input for debounce time working--; else next = DEBOUNCED; break;

case DEBOUNCED: // wait for switch to be released if (!sensed) next = IDLE; break;

default: /* CAN'T HAPPEN */ }

ptr->state = next; ptr->working = working;

ptr++; }

(early in the morning so apologies for bugs)

This algorithm generates an event (programmer defined) for each input on the leading edge of the input transition. It then ignores that particular input for COUNTDOWN iterations (drive this from a periodic task/interrupt). Initialization left to the student.

Note that the logic used to implement the debounce can vary. When do you want to "re-arm" the switch? Do you want to restart the timer at each bounce *within* the debounce period (i.e., so the input must be "stable" for the entire duration of the timer)? When do you want to recognize the "event" (before or after)? I like signaling events early (i.e., as soon as you see *any* activity) and letting the debounce happen in parallel with the user's interaction with the device (instead of putting these things in series). But, this presupposes you never get any "false positives" (e.g., electrical noise on high impedance inputs) that would erroneously signal the event before the algorithm could decide NOT to do so.

[MAKE SURE YOU UNDERSTAND HOW YOUR "switches" CHANGE OVER TIME! You can tune your debounce algorithm with brand new switches only to discover that, as they *wear*, their bounce characteristics change -- effectively rendering your algorithm a giant, expensive NoOp. I've seen switches work great in the lab and then, when operated at 0C, seen entirely different behavior :< ]

While it looks like a lot of code, it really distills to very little. Most of the overhead is a consequence of designing it to handle bits "anywhere" instead of relying on them being in particular places and/or relationships to each other (I like driving algorithms with tables).

It also allows the time constants to be tweeked easily (i.e., if the frequency at which you invoke the routine changes) as well as tweeking them for particular inputs (e.g., a "membrane switch" -- notoriously crappy -- for one bit and an Hg-wetted relay closure for another).

And, of course, "signal_event()" can be embellished (or replaced) to allow specific actions to be associated with each input/ E.g., you could, instead, store a pointer to a function in the "descriptor" and invoke it in lieu of the signal_event call. (I prefer to just pass the events to the rest of my system instead of distributing parts of the algorithm to different subsystems -- e.g., the "button debouncer")

As to your "speed" issue, you can sample the inputs "somewhere" and *process* them "somewhere else". You can, e.g., invoke *one* iteration (one switch worth) of the algorithm at each "debounce task activation" (i.e., require 20 passes through the algorithm to handle all 20 of your switches). You can move this all to a lower priority task/service -- if the system is in overload, you don't scan switches as reliably, etc. (careful to make sure the switch that you are now no longer scanning isn't, perhaps, the "Emergency Stop", etc.)

HTH

Reply to
Don Y

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.