Debounce improvements / balance

Go To Last Post
35 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm working on a project where I need to debounce 6 input buttons independently of each other.  I need to keep a debounced volatile inputstate and also feed a ringbuffer of key presses and key releases.  The buttons are split across two ports PINA/PINB and putting the 6 of them in a loop in the ISR means that far less optimization takes place and the ISR takes 400-500 cycles, but unwinding it to 6 repeating sections where each button has its own reduces the time in the ISR greatly down to 100uS if activity occurred and 50-60 if it doesn't.  I've found that putting the code that feeds the ringbuffer in its own function saves flash space without significantly affecting speed.  I've tried it as a regular function, static inline function, or even a function within the ISR function - all perform identically.  Any other ideas for improving this code?  I also found that loading the inputstate into a local variable once at the beginning of the 6 sections and setting it once at the end was an improvement as well.

 

static inline void addkey(uint8_t key)
{
  if (inputcount<KEYBOARD_BUFFER_SIZE)
    {
      inputbuffer[inputin]=key;
      inputin++;
      if (inputin==KEYBOARD_BUFFER_SIZE)
        inputin=0;
      inputcount++;
    }
}

ISR(TIM0_COMPA_vect)
  {
    uint8_t c1;

    c1=inputstate;

    //this section repeats 6 times for each button
    if (PINB & _BV(PB_BUTTON0_PIN))
      {
        if (!(c1 & _BV(PB_BUTTON0_PIN)))
          {
            dbcount[0]++;
            if (dbcount[0]>=20)
              {
                c1 |= _BV(PB_BUTTON0_PIN);
                addkey(0);
              }
          }
        else dbcount[0]=0;
      }
    else
      {
        if (c1 & _BV(PB_BUTTON0_PIN))
          {
            dbcount[0]++;
            if (dbcount[0]>=20)
              {
                c1 &= ~_BV(PB_BUTTON0_PIN);
                addkey(8);
              }
          }
        else dbcount[0]=0;
      }

    inputstate=c1;

  }

 

Last Edited: Sat. Sep 26, 2020 - 01:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can just keep the count in the isr. Only when you want to read an input do you test the count. Or simply don’t do the debounce in the isr. Have it set a flag that your main code can poll.

Last Edited: Sat. Sep 26, 2020 - 01:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

if you want fast I think you could use bitwise operations on two consecutive PINA and PINB readings.

Make a table of columns for "prev reading" and "current reading" and "stable" and "pos" columns.

 

volatile uint8_t prevA;			/* previous position */
volatile uint8_t posA;			/* position ON/OFF */
volatile uint8_t stableA;		/* posA is stable */

ISR(TIMER) {
  uint8_t currA;
  uint8_t changedA;

  currA = PINA;				/* current PIN position */
  stableA = ~(prevA ^ currA);		/* two measurements same */
  posA = some bitwise logic expression;
  prevA = currA;
  
  /* repeat for PINB */
}

void main() {
  ...
  if ((stableA & _BV(BUTTON0_PIN))) {
    if ((posA & _BV(BUTTON0_PIN)) != (last_posA & _BV(BUTTON0_PIN))) {
      /* button changed */
    }
    last_posA = posA;
  }
  ...
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Easy. 16-bit vertical counters.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's a looped attack that may/may-not be quicker. The pre-computed {keyMask} could help

 

#define NR_KEYS 6
ISR(TIM0_COMPA_vect)
{
    static uint8_t inputstate;
    static int8_t dbcount[NR_KEYS]; // Yes a Signed Integer

    uint8_t c1 = inputstate;
    uint8_t currectScan = 0;

    if (PINB & _BV(PB_BUTTON0_PIN))
        currectScan |= _BV(0);
    if (PINB & _BV(PB_BUTTON1_PIN))
        currectScan |= _BV(1);

    // OR ... If you have ALL keys contiguous on PORTB
    //        Consider adding dummies to make it so.
    currectScan = PINB;

    for (int8_t key = 0, keyMask = _BV(0); key < NR_KEYS; key++, keyMask <<= 1) {
        if (currectScan & keyMask) {
            if (++dbcount[key] >= 20) {
                dbcount[key] = 20;
                if ((c1 & keyMask) == 0) {
                    c1 |= keyMask;
                    addkey(key);
                }
            }
        }
        else {
            if (--dbcount[key] <= 0) {
                dbcount[key] = 0;
                if ((c1 & keyMask) != 0) {
                    c1 &= ~keyMask;
                    addkey(key + 8);
                }
            }
        }
    }

    inputstate = c1;
}

 

Last Edited: Sat. Sep 26, 2020 - 03:23 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:
time in the ISR greatly down to 100uS

I believe that using the Danni Debounce could improve this some:

  • Read the two ports and merge into a single byte variable
  • Do Danni Debounce on that variable

 

 

David

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

also feed a ringbuffer of key presses and key releases.

Why do you need a ringbuffer....are you not able to process any verified (pressed or released) button before the next press? The key values should guide your state machine & hopefully its fast-acting.   Valid button presses should com in at a rather low rate, since they have slooow  verification times.   State machine works even if you are looking for multi-press combos.

 

would this be simpler/faster?

ISR(TIM0_COMPA_vect)
  {
    uint8_t c1;

    c1=inputstate;

    //this section repeats 6 times for each button
    if (PINB & _BV(PB_BUTTON0_PIN)
      {
       if (dbcount[0] < 30)
         {
           dbcount[0]++;
         }
       else
          {
           c1 |= _BV(PB_BUTTON0_PIN);
          }
     }
    else
      {
	if (dbcount[0] > 0)
          {
           dbcount[0]--;
          }
	else
	  {
	    c1 &= ~_BV(PB_BUTTON0_PIN);
	  }
       }

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Sat. Sep 26, 2020 - 03:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Vertical counters look quite interesting.  Why 16 bit ones?

 

https://www.compuphase.com/elect...

 

4 states is close enough, but how could it be extended to 8 states by adding a cnt2?

 

What would the cnt2 = line look like?  Not just cnt2 = cnt2 ^ cnt1; I am guessing because it might involve other the value of cnt0 too, right?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This reminds me of how they add in logic - it seems like there was some sort of method they could use to determine if bit 7 was going to toggle without looking at the values of bits 6-0, but maybe I'm thinking correctly about it or how it works.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:

Vertical counters look quite interesting.  Why 16 bit ones?

 

I think you could use a set of 8-bit variables for each port.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have used (derived from Peter Dannegger's debouncing routine):

void processKeysDebounce(uint8_t keys) {
    uint8_t previous_state = key_state;     // Active keys will be "1" in key_state, inactive keys will be "0"

    #if (ACTIVE_STATES == ACTIVE_BOTH)      // 3
        uint8_t i  = key_state ^ (keys ^ normalState);    // Use this if some keys are active high and others active low
    #elif (ACTIVE_STATES == ACTIVE_HIGH)    // 1
        uint8_t i = key_state ^ keys;    // Which inputs are different from previous state? (when all keys active high)
    #elif (ACTIVE_STATES == ACTIVE_LOW)     // 0
        uint8_t i = key_state ^ ~keys;    // Which inputs are different from previous state? (when all keys active low)
    #endif

    // For bits where i is 1 (same state as previous) decrement vertical counter
    // For bits where i is 0 (different state from previous) reset all bits of vertical counter (ctN set to 1)
    v_ctr0 = ~( v_ctr0 & i );        // Decrement vertical counter for bits set in i
    v_ctr1 = v_ctr0 ^ (v_ctr1 & i);

#if NUM_DEBOUNCE_STATES == 8
    v_ctr2 = (v_ctr2 & i) ^ (v_ctr1 & v_ctr0);
    i &= v_ctr0 & v_ctr1 & v_ctr2;    // Detect counter roll over (all 1's)
#else   // Use 4 debounce states
    i &= v_ctr0 & v_ctr1;            // Detect counter roll over (all 1's)
#endif

    // When all bits are 1, counter has underflowed... thus N samples are identical
    key_state ^= i;                            // Toggle steady state of those pins that rolled over

#ifdef MULTIPLE_DEBOUNCE
    // Use "|=" if multiple debounce cycles may be called between checking key_changed
    key_changed |= key_state  ^ previous_state;    // Indicate which bits have changed since previous state
#else
    // Use "=" if every debounce cycle is followed by a check of key_changed
    key_changed = key_state  ^ previous_state;    // Indicate which bits have changed since previous state (all 0s = none)
#endif    
}

(No Warranties)

David

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:

Vertical counters look quite interesting.  Why 16 bit ones?

 

Style, nothing more. Once the compiler gets hold of it then the result is usually the same as two distinct 8-bit operations.

 

 

alank2 wrote:

What would the cnt2 = line look like?  Not just cnt2 = cnt2 ^ cnt1; I am guessing because it might involve other the value of cnt0 too, right?

 

Truth table time.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:

What would the cnt2 = line look like?  Not just cnt2 = cnt2 ^ cnt1; I am guessing because it might involve other the value of cnt0 too, right?

incrementing 3 bit counter

0 0 0

0 0 1

0 1 0

0 1 1

1 0 0

1 0 1

1 1 0

1 1 1

0 0 0

 

bit2 toggles when both bit0 and bit1 are 1, else it stays the same.

On that basis, my first attempt would be

cnt2 = cnt2 ^ (cnt1 & cnt0)

But I haven't worked it all through.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks MrKendo - that cn2 = cn2 ^ (cnt1 & cnt0) was what I was wondering.

 

I find the vertical counters interesting, but I would still have to decode them into keys somewhere else, outside of the ISR.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

FWIW, I often use a 3 stage debounce.  It's fast and simple.  This is for 8 buttons, but extending to 16 or 32 is just a matter of changing the variable sizes.

 

// u8 is uint8_t
// call every debounce period, e.g. every 10 ms

u8 get_debounced_buttons(void)
{
    static u8 last1 = 0;
    static u8 last2 = 0;
    u8 raw, buttons;

    raw = ~PINB;    // or wherever your buttons are located
    buttons = raw & last1 & last2;
    last2 = last1;
    last1 = raw;
    return buttons;
}

This just gives the debounced buttons.  A tiny bit of extra code will give button push and release edges as needed.

Last Edited: Sun. Sep 27, 2020 - 03:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alank2 wrote:

I find the vertical counters interesting, but I would still have to decode them into keys somewhere else, outside of the ISR.

Yes, but then instead of a 100 us ISR you have a 5  us ISR (assuming ~ 10 MHz clk).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why bother with an ISR?  Just put a lower limit on how often PINA and PINB are read:

// Fetches decounced key states.
// Requires an 8-bit timer that runs slow enough.
// How often getkeys needs to be called to avoid
// wraparound issues is left as an exercies for the reader.
// Whether one needs to avoid wraparound issues is application-specific.
uint8_t getkeys(void)
{
    static uint8_t retval, expiration=0x80;
    unsigned time=TIMERwhatever;   // chosen to avoid signed arithmetic
    unsigned timeafterexpiration= time-expiration;
    if(==(timeafterexpiration & 0x80)) {
        // time has expired
        retval=FUNCwhatever(PINA, PINB);
        expiration=time+DEBOUNCEinterval;
    }
    return retval;
}  // getkeys

should work.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
Just put a lower limit on how often PINA and PINB are read

Indeed. I was wondering why the original code counted  up to 20, when with a 10ms scan rate I've gotten away with a variant of Vertical Counter that triggers at a count of only 2.

 

A Vertical Counter that triggers at 20 (5-bits) isn't immediately straight-forward.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The 20 was me trying to do something different than normal button entry - it has moved to 5 or 25ms debouncing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do you actually need to debounce?  Typical pushbuttons settle in about 5ms, so why not just use a polling interval of >= 15ms?

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Debounce should do more than just deal with the bouncing of the switch - there are other disturbances that should be filtered out. EMI and ESD can also find their way into the input and these can be a bit more tricky to filter out from a simple delay. 

For those of us that have put designs through the European CE tests will understand the problem. The average hobbyist doesn't need to comply with these requirements, so why bother with extra filtering? The reasons these tests were conceived still exist.

 

Just about everyone has a cell phone - these generate bursts of rf that can easily find their way into an unprotected input. 

How many posts do we see from people using interrupts for button or switch inputs complaining about random inputs?

 

If you want to build a robust device, it would make sense to follow industry norms. The old computing maxim - garbage in,garbage out applies. Any external input should be filtered and rejected if it is not what is expected. Be that a switch input, analog input, serial data etc. If the input is a switch, then that has physical limitations on how fast the state is expected to change. Debouncing/filtering should ensure these expectations are met.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 Typical pushbuttons settle in about 5ms, so why not just use a polling interval of >= 15ms?

The bounce can easily be much longer than 5ms, I'd say that 5ms is often exceeded.  And, what do you mean by polling?  Surely  if you poll, say, every 200ms, that is not good enough---what if they press the button just 1ms before you poll?

So you must mean something at least comparing two or more successive polls?  

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you're confident that the only way the reading can change is if the button is pressed or released (no weird gliches or noise), a single reading at an interval > bounce time is reasonable.

Say button can bounce for up to 10ms.

Take a reading once every 50ms.

If button was pressed just before a reading, you might read as pressed or unpressed as it's still bouncing.

If you catch it reading as pressed, fine, it's now pressed.

If you catch it as unpressed, fine, you will catch it as pressed on the next reading (assuming user holds it down long enough, not generally a problem).

Either way, it will have stopped bouncing by next read.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'd love to see some data as to button detection reliability vs. # of reads in the button debounce (with the polling interval longer than the worst case button bounce time)  .I doubt such data exists because every piece of hardware is different, but I still wish there was a data-based number of percentage of false reads with a single poll vs. a double poll (2 consecutive button detects), a triple poll (this is what I tend to use), etc.  My guess is that a double poll is enough for 99.9% of applications, and a triple poll 99.99999%.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

I am submitting the state machine code that I came up with years ago.  This state machine will debounce both the key press and key release.  I typically insert it into a timer isr that samples the buttons every 5-10 mS.  It has never failed me and I've debounced oodles of switches/buttons over the years.  Maybe someone will find it useful.

Take care - Jim

 

 //***********************************************
 //
 //  debounce state machine
 //  * execute in a 5 or 10 mSec timer interrupt
 //  * pb_deb is global variable
 //  * For Reference:
 //	8 push buttons on Port A
 //	(config as input w/internal pull-ups)
 //
 //***********************************************
void debounce(void)
{
     static uint8_t pb_last;
            uint8_t pb_now;

    //read the current button state (PORTA used as reference in this example)
    pb_now = ~PINA;

    //process the debounce filtering algorithm
    pb_deb = ( pb_last & pb_now)|
	     ( pb_last & pb_deb)|
	     (~pb_last & pb_now & pb_deb);

    //save the button state for the next sample period
    pb_last = pb_now;
}

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The code in #26, when you work through it, is effectively a count of 2 ie. needs 2 consecutive readings opposite to the current state to change state.

An alternative would be the vertical count method but with just cnt0, that would count up to 2 as well.

Probably not much between them.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

a single reading at an interval > bounce time 

Your statement might be reworded for the unwary, a single read is not enough--in fact, you describe multiple reads. 

Maybe just: readings (pollings) at intervals > bounce time 

 

Note that actual debouncing ensures the switch was not bumped or glitched....it has to be held for a certain amount of time

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Mon. Sep 28, 2020 - 05:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One thing that is always important to me on any project is to get the user interface right - I've dealt with so many things that had buggy/iffy inputs that sometimes register a press and sometimes don't that it just frustrates me when I run into something like that.  To really do this well I think an interrupt is required because its timing is consistent and known, unlike some main loop code that checks at odd intervals.  This is another reason I use a ringbuffer for the keys - if the ISR just sets a flag saying this key was pressed, what if it was pressed twice for example?  A ring buffer would reflect the two presses.  You can do a lot more with this approach such as tracking presses vs. releases, or allowing combination options where buttons can be held and you generate a repeating key, or if you process an action on the release, then you can have b1, b2, or b1+b2 that does something else.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:

a single reading at an interval > bounce time 

Your statement might be reworded for the unwary, a single read is not enough--in fact, you describe multiple reads. 

Maybe just: readings (pollings) at intervals > bounce time 

Well yes, clearly it requires reading the input periodically.

The point is, with this most basic method, the state is determined solely from the current reading, rather than from a sequence of previous readings.

That is what is meant by 'a single reading'.

Clearly there is a risk if there is a 'glitch' at just the point you make the reading you could misinterpret that as a button press (or release).

That's why i said

MrKendo wrote:
If you're confident that the only way the reading can change is if the button is pressed or released (no weird gliches or noise),

 

Whether it counts as debouncing or not, depends on definition, OK it's probably better described as 'avoiding bounce issues by simply not reading the input fast enough to notice'.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

Debounce should do more than just deal with the bouncing of the switch - there are other disturbances that should be filtered out. EMI and ESD can also find their way into the input and these can be a bit more tricky to filter out from a simple delay. 

For those of us that have put designs through the European CE tests will understand the problem. The average hobbyist doesn't need to comply with these requirements, so why bother with extra filtering? The reasons these tests were conceived still exist.

 

The NES used a simple latched shift register for the controllers, polled at 60Hz (probably 50Hz in Europe), without any debouncing.

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ralphd wrote:
The NES used a simple latched shift register for the controllers, polled at 60Hz (probably 50Hz in Europe), without any debouncing.

A shift register means saving past states.  Were those states used in deriving button pushes e.g. through an AND gate?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Using a video game system as a benchmark? Maybe the debouncing was done in the main unit?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, you generally want to protect against a noise glitch as being called a press or release  (debouncing works in both directions). Same thing with an accidental tap (such as due to vibration).

You'll be glad you did.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

With enough noise, any sampling method is meaningless.

To filter out high-frequency noise, an RC filter would seem useful:

100 nF * 10 Kohm = 1ms = 10 nF * 100 Kohm.

Buffers with Schmitt triggers might also help.

If one has the pins, one might make the switches double throw.

A logical state does not change if the corresponding pair of inputs are not complementary.

 

If one is really going to try to "debounce" noise entirely through software,

one needs to be guided by some sort of actual or assumed bounds on what it can do.

One also needs to be guided by the desired response time and the damage done by getting it wrong.

The x identical samples taken y ms apart criterion implicitly assumes some bounds.

Iluvatar is the better part of Valar.