Software debouncing

Just want to hang out with other Makers and chat about stuff? This is the place to do it.
Post Reply
Brissieboy
Posts: 173
Joined: Fri Sep 20, 2013 7:25 am

Software debouncing

Post by Brissieboy » Mon Sep 09, 2019 6:16 am

Just thought I might throw this up here just in case it might help someone.
This is an example of software debouncing using vertical counters as described here.
I have adapted it to suit my own needs and again here to demonstrate a possible way it could be used in the Arduino environment, but the core functionality of the vertical counters is retained.
It is quite efficient and very effective.
I have added copious comments to help understand what is going on.
It is provided 'as is' and I make no claims or guaranties on its performance, applicability, or code quality.
I am using a timed interrupt (using timer1) to provide 'live' results for several button inputs but
it could potentially be used within a loop() if it repeats often and regularly:

Code: Select all

/*
   This is a simple sketch to demonstrate one way to achieve software debouncing
   using the 'vertical counter' strategy very well explained here:
   http://www.compuphase.com/electronics/debouncing.htm
   The sketch performs the read and debounce task within a timed interrupt
   using TimerOne. The button variables are then available within the loop()
   in (almost) real time. Note that there will be a delay of 40mSec between
   button action and the resulting change to the button variable.
   The button actions will be shown on the serial monitor. If you connect
   an LED to digital pin8 it will reflect the state of button3.
   Assumptions in this example:
   - inputs on digital pin 2, 3, & 7
   - all inputs are normally HIGH, LOW on button press
   - inputs monitored are Digital pin3, Digital pin4 and Digital pin7
   - serial monitor set to 115200
   - diode connected anode to +5V, cathode to digital pin8 via a 220 ohm resistor.
*/

#include <TimerOne.h>

volatile bool button1, button2, button3;
// these variables are the button states, 0 when released, 1 when pressed
// declaring them volatile ensures their value is up-to-date
// 'bool' because they can only have 2 states, true or false

void setup()
{
  Serial.begin(115200);               // opens serial port at 9600 bps
  Timer1.initialize( 10000 );       // starts interrupts at 10000uS = 10mSec
  Timer1.attachInterrupt(debounce); // this causes the debounce() routine
  // to be called at each interrupt (every 10mSec) which then reads and debounces the button inputs
  pinMode(2, INPUT_PULLUP); // digital pin 3 is input with internal pullup enabled
  pinMode(3, INPUT_PULLUP); // and digital pin 4
  pinMode(7, INPUT_PULLUP); // and digital pin 7
  pinMode(8, OUTPUT);       // to drive LED if connected
  digitalWrite(8, HIGH);    // ensure LED is off
  Serial.println(F("Software debounce test"));
}

void loop()
{
  if (button1) // if the button is pressed
  {
    Serial.println(F("Button 1 pressed"));
    while (button1); // just wait for button release
    // if you don't wait then this will be repeated each time the loop is executed
    Serial.println(F("Button 1 released"));
  }
  if (button2)
  {
    Serial.println(F("Button 2 pressed"));
    while (button2);
    Serial.println(F("Button 2 released"));
  }
  if (button3)
  {
    Serial.println(F("Button 3 pressed"));
    digitalWrite(8,LOW); // turns LED on
    while (button3);
    Serial.println(F("Button 3 released"));
    digitalWrite(8,HIGH); // turns LED off
  }
}

//*****************************************************************************************
// Reads, debounces and processes the push-button inputs
// debounce is from http://www.compuphase.com/electronics/debouncing.htm
// which has a full explanation of how this works - modified here
// Dtoggle and Dstate bit mapping:
// bit 0, 1 not used
// bit 2 = PB2 = D2 = button1
// bit 3 = PB3 = D3 = button2
// bit 4, 5, 6 not used
// bit 7 = PB7 = D7 = button3
// All inputs are active LOW - normally read high, with low on button pressed.
//*****************************************************************************************
void debounce()
{
  static byte Dstate = B10001100;  // initialised to all inputs OFF. This is to ensure
  //                                   valid values on startup (first run through)
  static byte Dtoggle = B00000000; // same here
  static byte cnt0, cnt1; // 2 bit vertical counter
  byte delta;
  delta = (PIND & B10001100) ^ Dstate; // reads all of port D then masks all but D2, D3, & D7
  // then XORs this with the previously calculated Dstate
  // delta now contains a '1' for each input that has changed state on this read
  cnt1 = (cnt1 ^ cnt0) & delta; // updates the 2 bit vertical counters bit 1
  cnt0 = ~cnt0 & delta; // and bit 0 (just toggles it if needed)
  Dtoggle = delta & ~(cnt0 | cnt1);
  // contain a 1 bit for every bit that is changed after 4 stable reads
  if (Dtoggle) // only process button outputs if something has changed
  {
    Dstate ^= Dtoggle; // now contain the debounced input states after 4 stable reads
    button1 = !(Dstate & B00000100); // button1 pushed (bit is LOW) so set button1 true
    button2 = !(Dstate & B00001000); // same for button2 button
    button3 = !(Dstate & B10000000); // and button3
  }
}

/*
  NOTES:
  Up to 8 inputs can be handled by simply logically combining them into the 8 bits of 'delta'
  using bit manipulation as required.
  As shown above, the button variables reflect the current state of the buttons. If your sketch uses
  delay() or has time consuming tasks, you might miss key strokes unless you prepare for it.
  Alternatively, you can have the buttons set and latched, to be cleared once you have actioned
  them in your sketch by changing these lines:
  if (Dtoggle) // only process button outputs if something has changed
  {
    Dstate ^= Dtoggle; // now contain the debounced input states after 4 stable reads
    button1 = !(Dstate & B00000100); // button1 has changed so set button1 accordingly
    button2 = !(Dstate & B00001000); // same for button2 button
    button3 = !(Dstate & B10000000); // and button3
  }
  to:
  if (Dtoggle) // only process button outputs if something has changed
  {
    Dstate ^= Dtoggle; // now contain the debounced input states after 4 stable reads
    if (!(Dstate & B00000100)) button1 = true; // button1 pushed (bit is LOW) so set button1 true
    if (!(Dstate & B00001000)) button2 = true; // same for button2 button
    if (!(Dstate & B10000000)) button3 = true; // and button3
  }
  In this case you MUST explicitly clear the button variable in your sketch.
*/
This next example modifies it a little to add an auto-repeat function on one of the buttons:

Code: Select all

/*
   This is a simple sketch to demonstrate one way to achieve software debouncing
   using the 'vertical counter' strategy very well explained here:
   http://www.compuphase.com/electronics/debouncing.htm
   The sketch performs the read and debounce task within a timed interrupt
   using TimerOne. The button variables are then available within the loop()
   in (almost) real time. Note that there will be a delay of about 40mSec between
   button action and the resulting change to the button variable.
   The button actions will be shown on the serial monitor. If you connect
   an LED to digital pin8 it will reflect the state of button3.
   button3 has an auto-repeat function in this example.
   Assumptions in this example:
   - inputs on digital pin 2, 3, & 7
   - all inputs are normally HIGH, LOW on button press
   - inputs monitored are Digital pin3, Digital pin4 and Digital pin7
   - serial monitor set to 115200
   - diode connected anode to +5V, cathode to digital pin8 via a 220 ohm resistor.
*/

#include <TimerOne.h>

volatile bool button1, button2, button3;
// these variables are the button states, 0 when released, 1 when pressed
// declaring them volatile ensures their value is up-to-date
// 'bool' because they can only have 2 states, true or false
const int AUTO_DELAY = 50;
// press & hold time for start of auto-repeat for up & down buttons
// in interrupt counts (50 = 500mSec)
const int AUTO_RATE = 3;
// and the repeat interval - actually HALF the interval - also in interrupt
// counts (3 = 30mSec) so repeat rate is about 17 per second

void setup()
{
  Serial.begin(115200);             // opens serial port at 9600 bps
  Timer1.initialize( 10000 );       // starts interrupts at 10000uS = 10mSec
  Timer1.attachInterrupt(debounce); // this causes the debounce() routine
  // to be called at each interrupt (every 10mSec) which then reads and debounces the button inputs
  pinMode(2, INPUT_PULLUP); // digital pin 3 is input with internal pullup enabled
  pinMode(3, INPUT_PULLUP); // and digital pin 4
  pinMode(7, INPUT_PULLUP); // and digital pin 7
  pinMode(8, OUTPUT);       // to drive LED if connected
  digitalWrite(8, HIGH);    // ensure LED is off
  Serial.println(F("Software debounce test"));
}

void loop()
{
  if (button1) // if the button is pressed
  {
    Serial.println(F("Button 1 pressed"));
    while (button1); // just wait for button release
    // if you don't wait then this will be repeated each time the loop is executed
    Serial.println(F("Button 1 released"));
  }
  if (button2)
  {
    Serial.println(F("Button 2 pressed"));
    while (button2);
    Serial.println(F("Button 2 released"));
  }
  if (button3)
  {
    Serial.println(F("Button 3 pressed"));
    digitalWrite(8, LOW); // turns LED on
    while (button3);
    Serial.println(F("Button 3 released"));
    digitalWrite(8, HIGH); // turns LED off
  }
}

//*****************************************************************************************
// Reads, debounces and processes the push-button inputs
// debounce is from http://www.compuphase.com/electronics/debouncing.htm
// which has a full explanation of how this works - modified here
// Dtoggle and Dstate bit mapping:
// bit 0, 1 not used
// bit 2 = PB2 = D2 = button1
// bit 3 = PB3 = D3 = button2
// bit 4, 5, 6 not used
// bit 7 = PB7 = D7 = button3 (with auto repeat)
// All inputs are active LOW - normally read high, with low on button pressed.
// button3 has autorepeat at the delay and rate as set by AUTO_DELAY and AUTO_RATE
// Suggested starting values:
// AUTO_DELAY 50 press & hold time for start of auto-repeat for up & down buttons
// in interrupt counts (50 = 500mSec for 10mSec interrupt)
// AUTO_RATE 3 and the repeat interval - actually HALF the interval
// also in interrupt counts (3 = 30mSec) so repeat rate is about 17 per second
//*****************************************************************************************
void debounce()
{
  static byte Dstate = B10001100;  // initialised to all inputs OFF. This is to ensure
  //                                   valid values on startup (first run through)
  static byte Dtoggle = B00000000; // same here
  static byte cnt0, cnt1; // 2 bit vertical counter
  static int but3Ctr; // counter for auto-repeat
  byte delta;
  delta = (PIND & B10001100) ^ Dstate; // reads all of port D then masks all but D2, D3, & D7
  // then XORs this with the previously calculated Dstate
  // delta now contains a '1' for each input that has changed state on this read
  cnt1 = (cnt1 ^ cnt0) & delta; // updates the 2 bit vertical counters bit 1
  cnt0 = ~cnt0 & delta; // and bit 0 (just toggles it if needed)
  Dtoggle = delta & ~(cnt0 | cnt1);
  // contain a 1 bit for every bit that is changed after 4 stable reads
  if (Dtoggle) // only process button outputs if something has changed
  {
    Dstate ^= Dtoggle; // now contain the debounced input states after 4 stable reads
    button1 = !(Dstate & B00000100); // button1 pushed (bit is LOW) so set button1 true
    button2 = !(Dstate & B00001000); // same for button2 button
    if (button3 = !(Dstate & B10000000)) // and button3
      // button3 has just actived so start the auto-repeat timing
      but3Ctr = 1; // this initiates the auto-repeat
    else but3Ctr = 0;
  }
  if (but3Ctr)
  {
    if (but3Ctr >= AUTO_DELAY) // initial delay has expired, so need to do the auto repeat
    {
      if (but3Ctr >= AUTO_DELAY + AUTO_RATE) // need to change state yet?
      {
        if (button3 == 0) button3 = 1; else button3 = 0; // toggle the button /on/off
        but3Ctr = AUTO_DELAY; // and restart the counter
      }
    }
    but3Ctr++;    
  }
}

Post Reply