More than noob button debounce example: test & critique?

Press button (pin 7 to GND) and led13 blinks until button release.

Note: code updated to show total bounce+debounce time in micros and a loop() speed counter.

// buttonsketch 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Feb 24/18 by GFS

/*
  Button Debounce Example, free by GoForSmoke

This code shows use of struct, union, bit fields and 16 bit timers.
It can work with many buttons, uses structs to hold button data.
Button data structs can be arrayed even for matrix arrangements.

-- for this example connect a button between pin 7 and GND
--- or stick a jumper in pin 7 and while holding the board steady
--- tap the free end onto the grounded USB port box to press.

Yes 16 bit timers, and they time micros where millis skips digits.
My debounce time is 2000 micros after the last change detected.
 */



#include <avr/io.h>
#include "Arduino.h"

// these defines are used to read and write button status bits
// button status tells the last 2 button reads and whether or not it is bouncing (read again later)
// all in one small number, 0 to 7. Your sketch may only care if the value is 2 = press event.
#define CURRENT_1 1
#define PREVIOUS_2 2
#define HISTORY_3 3
#define BOUNCE_4 4
#define pre_BOUNCE_8 8

struct statusBits // this is a custom variable with named bit fields
{
  byte current : 1;  // 1 bit can only be 0 or 1
  byte previous : 1;
  byte bounce : 1;
  byte pre_bounce : 1;
  byte : 4; // these 4 bits are unnamed, could be used if named to count 0 to 15
};

union buttonRead  // the same data can be read different ways as different names
{
  struct statusBits bits;
  byte value;
};

buttonRead buttonState; // used to read button status as a single state value

struct buttonData   // 6 bytes
{
  byte pin; // these static variables are private to this function
  statusBits state; // current, previous, bounce, pre_bounce
  unsigned int start; // 16 bit time
};

buttonData demoButton = { 7, CURRENT_1, 0 };


const unsigned int debounceMicros = 2000; // button response takes at least 1 milli after the last bounce

statusBits buttonTask( buttonData *button )
{
  // -------------- start of button handler: debounces as needed, sets button->state
  button->state.previous = 0;
  button->state.previous = button->state.current;
  button->state.current = digitalRead( button->pin );

  if (( button->state.current ) != ( button->state.previous )) // if different,
    // state change detected
  {
    if ( button->state.bounce == 0 )
    {
      button->state.bounce = 1;
      button->state.pre_bounce = button->state.previous;
    }
    button->start = micros();    // restarts the bounce clock
  }
  else if ( button->state.bounce > 0 ) // then wait to clear debounce bit
  {
    unsigned int now = micros(); // temp stack variable 
    if ( now - button->start >= debounceMicros ) // then stable button state achieved
    {
      button->state.previous = button->state.pre_bounce;
      button->state.bounce = button->state.pre_bounce = 0;
    }
  }
  // -------------- end of button handler: debounces as needed, sets button->state

  return button->state;
}

byte ledState, ledPin = 13;
unsigned int startBlink, waitBlink;

void blinkLed()
{
  if ( waitBlink > 0 )
  {
    unsigned int now = millis(); // temp variable now = low 16 bits of millis
    if ( now - startBlink >= waitBlink )
    {
      ledState = !ledState;
      digitalWrite( ledPin, ledState );
      startBlink += waitBlink;
    }
  }
  else if ( ledState > 0 )
  {
    ledState = 0;
    digitalWrite( ledPin, LOW );
  }
}

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "This code shows use of struct, union, bit fields and 16 bit timers." ));
  Serial.println( F( "It can work with many buttons, uses structs to hold button data." ));
  Serial.println( F( "Button data structs can be arrayed even for matrix arrangements." ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( demoButton.pin, INPUT_PULLUP );

  pinMode( ledPin, OUTPUT );
};


void loop()
{
  blinkLed();
  
  buttonState.bits = buttonTask( &demoButton ); // read bit fields into union 

  // this is why the buttonRead union was made, to read the bits as one state value
  switch ( buttonState.value )
  {
    case CURRENT_1 :
      Serial.print( F( "up    " ));
      Serial.println( millis());
      Serial.println();
      waitBlink = 0;
      break;
    case PREVIOUS_2 :
      Serial.print( F( "down  " ));
      Serial.println( millis());
      waitBlink = 200;
      break;
  }
  // end of do something with button

  // put any new tasks here or up top or between the handler and do something tasks
}

I think that the bit-field names do make it easier to read what the button handler function does.

The big leap may be in going from the four status bits to a single state value to switch to case code for.

Yes, I did this all using bit logic commands before but this way might be within reach of more forum members.

Handling the button with a function means no library to mess with, some members don't like libraries, and it makes the code more accessible. I made the button data as a struct so that multiple buttons can easily be added. The best way is to make a button struct array and process only one button per pass through loop() so as not to block any other tasks in the code now or likely added in the future, just don't block and the sketch can have a future!

I'd put in a loop() counter but it would break up the down-up pairing on Serial Monitor.

First, please test even if only with a jumper in pin 7 touched to the USB port box to ground it, hold the board still though!
Serial Monitor should be set up for 115200 baud and open. up and down times are in millis, see how fast you can tap the button... I got down to 31ms between up and down and I'm an old coot.

I get solid results with 2000 micros of pin reads since the last change as debounce time --- no 2ms wide bounces.
What's your mileage please?

This example has a blink task as well as a button task. As now it blinks only when the button is held down.

I will add a user command task if there's interest. How does 40 commands with average 5 letters, stored in EEPROM sound?

GoForSmoke:
I get solid results with 2000 micros of pin reads since the last change as debounce time --- no 2ms wide bounces.

My age-old debounce routine is a bit shifter and I usually sample at around 10mS, a minimum debounce in or out taking 40mS. The main code only 'sees' the button when debounce is complete. So, I'd up that 2mS to 10mS as a default.

For faster response (usually non-buttons), a 'post-activation-debounce' is handy.

Have a look at Jack Ganssle: A Guide to Debouncing - he has some nice data and 'scope traces on a bunch of switches.

Yours,
TonyWilk

Did you try this one? It takes an Arduino and a jumper.

I debounced a jumper, total time unknown but I can find out with the next rev.

Yes I saw the debounce guide back in 2012 and Nick Gammon's blog around then too.

If a button wobbles and breaks contact, throw the POS away!

I read contacts closing and opening and debounce them. There's nothing says it has to be a human working the switch. I run lots of buttons interleaved with the rest of the sketch tasks, getting > 60 reads a millisecond still covers each button well. I am only looking for no change for 2ms.

Consider that the jumper makes no bounces over 2ms wide. It can bounce 1000's of times for 20 solid ms but all of them are shorter than 2ms. I'm going to add a total usec from start to debounced trace to the sketch.

I had started a sketch that would report a change before debouncing but it would be vulnerable to falsing. It'd be very easy with this sketch: while the debounce bit is set, the current state is ASSUMED to be the opposite of the pre_bounce state. That's done in the sketch, no change to the button handler is needed.

If I let the board wobble when the pin is on the (grounded) USB port, the output is shite but when I'm fair about it, some contacts are incredibly short and the worst happen on breaks where I see > 7000 micros total (2000 as stability test) as the worst, bit of scraping there. I use 32-bit micros for this, it don't roll over for over 70 minutes, no usable button bounces for 70 minutes.

Try it. Put a jumper in pin 7 and another in GND and cross the free ends. Primitive switch.

I could make the sketch faster using port manipulation instead of 5x slower Arduino read and write.

** Updated sketch.**

// buttonsketch 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Feb 24/18 by GFS
// now updated to include total bounce+debounce micros and loop count reporting.

/*
  Button Debounce Example, free by GoForSmoke

This code shows use of struct, union, bit fields and 16 bit timers.
It can work with many buttons, uses structs to hold button data.
Button data structs can be arrayed even for matrix arrangements.

-- for this example connect a button between pin 7 and GND
--- or stick a jumper in pin 7 and while holding the board steady
--- tap the free end onto the grounded USB port box to press.

Yes 16 bit timers, and they time micros where millis skips digits.
My debounce time is 2000 micros after the last change detected.
 */



#include <avr/io.h>
#include "Arduino.h"

// button status tells the last 2 button reads and whether or not it is bouncing (read again later)
// all in one small number, 0 to 7. Your sketch may only care if the value is 2 = press event.

struct statusBits // this is a custom variable with named bit fields
{
  byte current : 1;  // 1 bit can only be 0 or 1
  byte previous : 1;
  byte bounce : 1;
  byte pre_bounce : 1;
  byte : 4; // these 4 bits are unnamed, could be used if named to count 0 to 15
};

union buttonRead  // the same data can be read different ways as different names
{
  struct statusBits bits;
  byte value;
};

buttonRead buttonState; // used to read button status in void loop()

struct buttonData   // 4 + 8 bytes
{
  byte pin; // these static variables are private to this function
  statusBits state; // current, previous, bounce, pre_bounce
  unsigned int start; // 16 bit micros
  unsigned long changeDetectedUs; // 32 bit micros for timing total bounce event
  unsigned long debouncedUs; // 32 bit micros for timing total bounce event
};

buttonData demoButton = { 7, 1 };


const unsigned int debounceMicros = 2000; // button response takes at least 1 milli after the last bounce

statusBits buttonTask( buttonData *button ) // run any button with buttonData
{
  // -------------- start of button handler: debounces as needed, sets button->state
  button->state.previous = 0;
  button->state.previous = button->state.current;
  button->state.current = digitalRead( button->pin );

  if (( button->state.current ) != ( button->state.previous )) // if different,
    // state change detected
  {
    if ( button->state.bounce == 0 )
    {
      button->changeDetectedUs = micros();
      button->state.bounce = 1;
      button->state.pre_bounce = button->state.previous;
    }
    button->start = micros();    // restarts the bounce clock
  }
  else if ( button->state.bounce > 0 ) // then wait to clear debounce bit
  {
    unsigned int now = micros(); // temp stack variable 
    if ( now - button->start >= debounceMicros ) // then stable button state achieved
    {
      button->debouncedUs = micros();
      button->state.previous = button->state.pre_bounce;
      button->state.bounce = button->state.pre_bounce = 0;
    }
  }
  // -------------- end of button handler: debounces as needed, sets button->state

  return button->state;
}

byte ledState, ledPin = 13;
unsigned int startBlink, waitBlink;

void blinkLed()
{
  if ( waitBlink > 0 )
  {
    unsigned int now = millis(); // temp variable now = low 16 bits of millis
    if ( now - startBlink >= waitBlink )
    {
      ledState = !ledState;
      digitalWrite( ledPin, ledState );
      startBlink += waitBlink;
    }
  }
  else if ( ledState > 0 )
  {
    ledState = 0;
    digitalWrite( ledPin, LOW );
  }
}

unsigned long loopCount, lastCount, startCount;


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "This code shows use of struct, union, bit fields and 16 bit timers." ));
  Serial.println( F( "It can work with many buttons, uses structs to hold button data." ));
  Serial.println( F( "Button data structs can be arrayed even for matrix arrangements." ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( demoButton.pin, INPUT_PULLUP );

  pinMode( ledPin, OUTPUT );
};


void loop()
{
  blinkLed();
  
  buttonState.bits = buttonTask( &demoButton ); // read bit fields into union 

  // this is why the buttonRead union was made, to read the bits as one state value
  switch ( buttonState.value )
  {
    case 1 : // pin is HIGH, was LOW
      Serial.print( F( "up    " ));
      Serial.print( millis());
      Serial.print( F( "  bounce us  " ));
      Serial.println( demoButton.debouncedUs - demoButton.changeDetectedUs );
      Serial.print( F( "    void loop() counter:  " ));
      Serial.println( lastCount );
      Serial.println();
      waitBlink = 0;
      break;
    case 2 : // pin is LOW, was HIGH
      Serial.print( F( "down  " ));
      Serial.print( millis());
      Serial.print( F( "  bounce us  " ));
      Serial.println( demoButton.debouncedUs - demoButton.changeDetectedUs );
      waitBlink = 200;
      break;
  }
  // end of do something with button

  loopCount++;
  if ( micros() - startCount >= 1000000 ) // 1 second
  {
    lastCount = loopCount;
    loopCount = 0;
    startCount += 1000000;
  }

  // put any new tasks here or up top or between the handler and do something tasks
}

I didn’t look too closely, but in ‘Smokes code, I noticed the the handler is slightly different on the up vs down phases of the input state change.
Is the denounce applied equally in both directions?

There are cases where N/C vs N/O inputs are relevant.
There are cases which use the short release as the ‘select’ state, and wait for a ‘long’ press after the pressed state is established.

I don't see where the handler uses any different code based on a HIGH to LOW transition than a LOW to HIGH.

Can you point that out to me in the code?

I -did- report the button down and button up transitions in the loop() part of the sketch but that's not the buttonTask function.

Most Arduino buttons are sprung normally open. My jumper says "normally whaaa?" My code says "who cares?".
Just because NO and NC can make a difference doesn't mean it will be bad or that unicorns will fly on use.

I'd like to see people actually try the sketch out and see if my results are typical or not. From the data I get it appears that many times my jumper quits bouncing in 8 micros or less while at other times it can bounce for over 5000 micros though more usually less than 2000 --- and then this debounce watches for 2000 more.

I have to be very messy to exceed 8ms total switch state change detection time. Typical is less than half that.

Contact button code is a lesson but IMO cap sense is better since those buttons don't wear out. I need a cap touch sense mouse btw.

There are cases which use the short release as the 'select' state, and wait for a 'long' press after the pressed state is established.

That sounds like feature-engineering, not something that just happens to code. As now the handler does collect time that the main sketch can use to determine hold-down time, that would be the app, not the button handler function.

Don’t get me wrong. I like what you’re doing.
When glancing through buttonTask(), I felt it may it be asymmetric when handling ‘non typical’ buttons.
As you mentioned - most buttons are wired N/O to 0V.
Just keeping my eyes open for other cases before they surface (Devil’s Advocate!)

I could guess that contact breaks will bounce longer than contact closures but the data from the sketch says that's not always so on the scale that this sketch is watching, 4 usec granular.

Debounce is for dry contact switches. N/O and N/C just refer to the "up" position state. But I guess you could wire a switch asymmetrically?

How do you like the use of bit-fields as opposed to using bitwise logic to manipulate a byte as far as code readability?
I converted the bitwise logic code line for line and I don't think the bit-fields code will compile bigger or slower or maybe even different. I was only moving a bit per line anyway.

When I offered the library before, I got asked for do-it-in-a-sketch. I hope this is easier to read than what I had and this is an improvement on that as well.

The example is a do many things example too. Button, led, and loop counter all have running, interactive tasks.

Hmmmm, maybe button hold down time could affect the led blink interval? Something better, a count the blinks mode switch?

I hate to sound negative when someone has put a lot of well-intentioned effort into something but, truthfully, I have no idea what the code in the Original Post is intended to demonstrate. At the very least I think a more extensive introduction is required.

It all seems to me overly complicated whatever its aim may be.

Debouncing one or several buttons just needs a brief (50 millisecs?) interval between successive reads. A single interval (using millis() ) will cover any number of buttons.

If the intention is to detect the change of state of a button (something which seems to be regularly confused with debouncing) then it is not at all obvious that is what it is doing. And the title is all wrong.

At the same time if the primary purpose is to illustrate the use of structs and unions it also seems too difficult to follow for that - and the title is all wrong for that also.

Whatever is the intention, if this is intended as a stepping stone from the very basics to an intermediate level of knowledge I reckon an explanation of the style is required - for example the use of -> and of .

I am all for demos of debouncing, of button state changes, and of the use of structs and unions but I don't think it helps an inexperienced programmer to try to cover them all in the same tutorial. Things that are very easy when you know them are like the north face of the Eiger when you don't.

In an effort not to be entirely negative I do agree 100% with the idea of using functions (rather than a library) in demo code so that it is all easily accessible to the student.

If this is all irrelevant because I have misunderstood the intended market then I apologize.

...R

This is for "more than noobs" who have the gumption and guts to figure out what they're looking at.

It addresses the very well known topic of button debounce while showing techniques that can help "beginners more than noob" get experience.

It shows non-blocking, separated interactive tasks programming.

It debounces my jumper in 3ms or less on average.

I can see that I need to add a comment that it is not for beginners or those who would remain beginners.