Another switch debouncer... with a new twist

In an other post, I came to talk about debouncing switches. I wrote that there are so many debouncing libraries available here in the playground, that it would be useless to write your own debouncer in a sketch and that it would be easier to use a library instead.

But I am retired, and I got time on my hand.

I looked carefully at all the debouncing solutions that were offered. My first observation is that .cpp and .h files are poorely commented. Deciphering what the code meant took me the better part of the weekend. My second observation was that some of them would statisticaly be on the unsafe side, some of them were too heavy in system resources.

One that cought my attention (the last one on the list), was offered by Paul Murray. It simulates an RC circuit and a Schmidt Trigger. It is efficient, fast and lightweight. He goes to great lenght in explaining how he does the magic with simple words. Great job here.

Then I spent my monday (remember, I am retired) searching the net for debouncing. All the solutions presented on those sites were implemented in one way or the other by one of the libraries found on the Playground.

Then I fell on this little gem. It was offered by the Ganssle Group. The autor (Jack Ganssle) pulled up his sleeves, gathered many switches and fired up an oscilloscope. The report that he made of his findings can be found here.

When you see the various ways a switch can lie to you, you see bouncing in a fresh and frightening light.

Later in the article, he tackles both hardware and software ways of mastering the beast. That is where I found the algorithm.

It is fast, lightweight and can tackle all, but the nasty "O" switch (read the article). It can also counteract EMI. Not bad.

So I decided to make it a library and make it available here. You will also find a complete tutorial there.

I was tempted to ask for some feedback on my work, but experience has shown me that (after introducing more than a couple libraries), even in the "Exhibition / Gallery" forum, you get little feedbak if any unless "Light Saber" figures in the subject.

Anyway. To all of you that might consider giving my debouncer a try, I wish you to live long and prosper.

@jbellavance
Old age sucks when there are health issues, however, you do have time to invest in things that that are mind expanding.
Thanks for sharing, I look forward looking at your work a bit later.

As far as debouncing, I usually add a .1μF across a switch going to GND and read the switch(es) every 10-50mS looking for a change in state.

.

Have you seen/used Nick Gammon's Switch Manager offering?

/*SwitchManager skeleton 
 
 
 This sketch is to introduce new people to the SwitchManager library written by Nick Gammon
 
 The library handles switch de-bouncing and provides timing and state change information in your sketch.
 The SwitchManager.h file should be placed in your libraries folder, i.e.
 C:\Users\YourName\Documents\Arduino\libraries\SwitchManager\SwitchManager.h
 You can download the library at:
 http://gammon.com.au/Arduino/SwitchManager.zip    Thank you Nick!
 
 In this example we have 2 normally open (N.O.) switches connected to the Arduino - increment and decrement.
 The increment switch will also be used as a "Reset" switch if pressed for more than two seconds.
 The two switches are connected between GND (0 volts) and an Arduino input pin.
 The library enables pull-up resistors for your switch inputs.
 Pushing a switch makes its pin LOW. Releasing a switch makes its pin HIGH.
 
 The SwitchManager library provides 10ms de-bounce for switches. 
 i.e. enum { debounceTime = 10, noSwitch = -1 };
 If you need more time, edit the SwitchManager.h file
 i.e. enum { debounceTime = 50, noSwitch = -1 }; //here it is changed to 50ms
 */

#include <SwitchManager.h>             
//object instantiations
SwitchManager myIncSwitch;
SwitchManager myDecSwitch;

unsigned long currentMillis;
unsigned long heartBeatMillis;
unsigned long heartFlashRate  = 500UL; // time the led will change state       
unsigned long incShortPress   = 500UL; // 1/2 second
unsigned long incLongPress    = 2000UL;// 2 seconds 
unsigned long decShortPress   = 500UL; // 1/2 second

const byte heartBeatLED       = 13;
const byte incSwitch          = 4; //increment switch is on Arduino pin 4
const byte decSwitch          = 5; //decrement switch is on Arduino pin 5

int myCounter;

//======================================================================

void setup()
{
  Serial.begin(9600);

  //gives a visual indication if the sketch is blocking
  pinMode(heartBeatLED, OUTPUT);  

  myIncSwitch.begin (incSwitch, handleSwitchPresses); 
  myDecSwitch.begin (decSwitch, handleSwitchPresses);
  //the handleSwitchPresses() function is called when a switch changes state

} //                   E N D  O F  s e t u p ( )

//======================================================================

void loop()
{
  //leave this line of code at the top of loop()
  currentMillis = millis();

  //***************************
  //some code to see if the sketch is blocking
  if (CheckTime(heartBeatMillis, heartFlashRate, true))
  {
    //toggle the heartBeatLED
    digitalWrite(heartBeatLED,!digitalRead(heartBeatLED));
  }

  //***************************
  //check to see what's happening with the switches
  //"Do not use delay()s" in your sketch as it will make switch changes unresponsive 
  //Use BlinkWithoutDelay (BWD) techniques instead.
  myIncSwitch.check ();  
  myDecSwitch.check (); 

  //***************************
  //put other non-blocking stuff here


} //                      E N D  O F  l o o p ( )


//======================================================================
//                          F U N C T I O N S
//======================================================================


//                        C h e c k T i m e ( ) 
//**********************************************************************
//Delay time expired function
//parameters:
//lastMillis = time we started
//wait = delay in ms
//restart = do we start again  

boolean CheckTime(unsigned long  & lastMillis, unsigned long wait, boolean restart) 
{
  //has time expired for this task?
  if (currentMillis - lastMillis >= wait) 
  {
    //should this start again? 
    if(restart)
    {
      //yes, get ready for the next iteration
      lastMillis = millis();  
    }
    return true;
  }
  return false;

} //                 E N D   o f   C h e c k T i m e ( )


//                h a n d l e S w i t c h P r e s s e s ( )
//**********************************************************************

void handleSwitchPresses(const byte newState, const unsigned long interval, const byte whichPin)
{
  //  You get here "ONLY" if there has been a change in a switches state.

  //When a switch has changed state, SwitchManager passes this function 3 arguments:
  //"newState" this will be HIGH or LOW. This is the state the switch is in now.
  //"interval" the number of milliseconds the switch stayed in the previous state
  //"whichPin" is the switch pin that we are examining  

  switch (whichPin)
  {
    //***************************
    //are we dealing with this switch?
  case incSwitch: 

    //has this switch gone from LOW to HIGH (gone from pressed to not pressed)
    //this happens with normally open switches wired as mentioned at the top of this sketch
    if (newState == HIGH)
    {
      //The incSwitch was just released
      //was this a short press followed by a switch release
      if(interval <= incShortPress) 
      {
        Serial.print("My counter value is = ");
        myCounter++;
        if(myCounter > 1000)
        {
          //limit the counter to a maximum of 1000
          myCounter = 1000; 
        }
        Serial.println(myCounter);
      }

      //was this a long press followed by a switch release
      else if(interval >= incLongPress) 
        //we could also have an upper limit
        //if incLongMillis was 2000UL; we could then have a window between 2-3 seconds
        //else if(interval >= incLongMillis && interval <= incLongMillis + 1000UL) 
      {
        //this could be used to change states in a StateMachine
        //in this example however, we will just reset myCounter
        myCounter = 0;
        Serial.print("My counter value is = ");
        Serial.println(myCounter);
      }

    }

    //if the switch is a normally closed (N.C.) and opens on a press this section would be used
    //the switch must have gone from HIGH to LOW 
    else 
    {
      Serial.println("The incSwitch was just pushed");
    } 

    break; //End of case incSwitch

    //*************************** 
    //are we dealing with this switch?
  case decSwitch: 

    //has this switch gone from LOW to HIGH (gone from pressed to not pressed)
    //this happens with normally open switches wired as mentioned at the top of this sketch
    if (newState == HIGH)
    {
      //The decSwitch was just released
      //was this a short press followed by a switch release
      if(interval <= decShortPress) 
      {
        Serial.print("My counter value is = ");
        myCounter--;
        if(myCounter < 0) 
        {
          //don't go below zero
          myCounter = 0;
        }
        Serial.println(myCounter);
      }

    }

    //if the switch is a normally closed (N.C.) and opens on a press this section would be used
    //the switch must have gone from HIGH to LOW
    else 
    {
      Serial.println("The decSwitch switch was just pushed");
    } 

    break; //End of case decSwitch

    //*************************** 
    //Put default stuff here
    //default:
    //break; //END of default

  } //End switch (whichPin)

} //      E n d   o f   h a n d l e S w i t c h P r e s s e s ( )


//======================================================================
//                      E N D  O F  C O D E
//======================================================================

.

Jacques

Good work.
You could include a function to return the time the switch was in the previous state.

Some comments and my preferences.
You left the pinMode() line in setup out in your examples:

/*
 * Basic.ino
 * By Jacques Bellavance, July 7 2017
 * Shows how to use the Debounce Library
 * This sketch is for switches that uses the internal pullup resistor
 * Switch pin A : Ground
 * Switch pin B : Arduino's pin 2
*/

#include <EdgeDebounce.h>

#define BUTTON_PIN 2

//Create an instance of Debounce and name it button
//button is tied to pin BUTTON_PIN and is in PULLUP mode
EdgeDebounce button(BUTTON_PIN, PULLUP);  

void setup() 
{  <------<<<< I prefer keeping this on a separate line
  
     pinMode(13, OUTPUT);  <------<<<< You forgot to add this line in all your examples

  //Nothing to do here. The Library has declared pinMode(BUTTON_PIN, INPUT_PILLUP) for you
}

void loop() 
{
  if (button.closed()) 
  { <-----<<<<< I prefer using { } all the time

     digitalWrite(13, HIGH);  //The .closed method returns true if there is contact
  }

  else
  {
     digitalWrite(13, LOW);
  }

I measured a call to button.closed(); at ~90μS using the below code, after all I am retired :wink:

PINB = 0b00010000; //Toggle pin 12, takes 62.5nS
PINB = 0b00010000;
button.closed(); //Took ~90μS
PINB = 0b00010000; //Toggle pin 12, takes 62.5nS
PINB = 0b00010000;
.

Hi Larry,

Thanks for taking the time to look at my Library.

Adding a cap to the switch is half of an RC debouncer, but it sure can help a lot to stabilize the signal.

Reading the switch at some interval is a great way not to loose cpu time as you can do a lot in those 10 - 50mS, just as you did in your HeartBeatLed example.

larryd:
J
You could include a function to return the time the switch was in the previous state.

I am not sure of what you mean here. Is it something like : "Your switch was open for the last 15 seconds", and "Your switch was closed for the last 0.51 seconds"?

If so, yes, of course that could be added. As I wrote, what I like most about the EdgeDebounce Class, is that it is fast and lightweight. Writing a Class that would inherit from EdgeDebounce with this and other utilities is certainly feasable. If you want to have this info, I certainly would give it a shot. (Never built a Class that inherited from another one before, but that could be fun.)

larryd:
You left the pinMode() line in setup out in your examples:

/*
  • Basic.ino
  • By Jacques Bellavance, July 7 2017
  • Shows how to use the Debounce Library
  • This sketch is for switches that uses the internal pullup resistor
  • Switch pin A : Ground
  • Switch pin B : Arduino's pin 2
    */

#include <EdgeDebounce.h>

#define BUTTON_PIN 2

//Create an instance of Debounce and name it button
//button is tied to pin BUTTON_PIN and is in PULLUP mode
EdgeDebounce button(BUTTON_PIN, PULLUP);

void setup()
{  <------<<<< I prefer keeping this on a separate line
 
    pinMode(13, OUTPUT);  <------<<<< You forgot to add this line in all your examples

//Nothing to do here. The Library has declared pinMode(BUTTON_PIN, INPUT_PILLUP) for you
}

You are right. I was so eager to mention that BUTTON_PIN's mode was automaticaly set that I forgot to do it with pin 13.

Stand by, I'll correct that right now. Ok I'm back. Thanks for seeing this.

Nice of you for taking the time to chrono button.close(). What I did was:

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned long clock = millis();
for (unsigned long  i = 0 ; i < 1000000 ; i++) int j = digitalRead(3);
Serial.println(millis()-clock);
}

I got 3.210 seconds on average to digitalRead() 1,000,000 times. It translates to roughly to 51μs. In the french Tutorial, I mentioned around 60μs. in the english tutorial, I did'nt mention anything. Now that I have a real Chrono, I'll correct the french version and upgrade the english version with this new data.

Have a nice day.

The heartBeat code takes μS to run, not much time there.
It is there really to confirm (with the eye) there are no large timing gaps.
I'll measure it and get back.

"you can do a lot in those 10 - 50mS"
I think you might be misunderstanding, you don't take 10-50 mS to read the switch, you look for a change in state every 10-50 mS.
I'll attach an example later. (breakfast time now)
I'll measure this time too.

Measuring the 'call' with a scope gives exact timing (as marked by the PINB code) but what's a few μS amongst friends. :wink:

I urge you to try the Switch Manager library in some of your sketches.
I do like the feature where switch interval can give a switch multiple uses.
Example, a switch push for < 500mS does one thing, example increment a value, then a push > 2 seconds sets that value to zero.

.

Hi Larry,

larryd:
"you can do a lot in those 10 - 50mS"
I think you might be misunderstanding, you don't take 10-50 mS to read the switch, you look for a change in state every 10-50 mS.
I'll attach an example later. (breakfast time now)
I'll measure this time too.

I undersood the "you look for a change in state every 10-50 mS" part. That's why I wrote : "Reading the switch at some interval is a great way NOT to loose cpu time."

Up until relatively recently, I used a Debounce Library that did this :

Set counter to 0
pinStatus = read the switch once
while (counter < target)
{
  newStatus = read the switch
  if (newStatus == pinStatus)
  {
    counter++
  }
}

That did allow me to handle short and long presses, repeat after xxxms, and return only upon release.

I also have a Library that handles analog switches. It only averages several repeated analogRead() to act as a debounce. You can find it here

I certainly will give a look to SwitchManager and get you back on it.

Jacques

jbellavance:
I certainly will give a look to SwitchManager and get you back on it.

Jacques

With the attached code, the time it takes to call checkSwitches() is ~5uS.
i.e. every 50mS we take 5uS to check one switch.

The time it takes to toggle the LED is ~11uS every 1/4 second.

const byte heartBeatLED = 13;
const byte myButton     = 8;
const byte myLED        = 12;

byte lastButton = HIGH;
byte currentButton;

unsigned int counter;

unsigned long currentMillis;
unsigned long heartBeatMillis;
unsigned long lastMillis;

const unsigned long heartBeatDelay = 250UL;

//***************************************************************************
void setup()
{
  Serial.begin(9600);

  pinMode (heartBeatLED, OUTPUT);
  pinMode (myLED, OUTPUT);
  pinMode (11, OUTPUT);

  pinMode (myButton, INPUT_PULLUP);

} //END of      s e t u p ( )

//***************************************************************************
void loop()
{
  currentMillis = millis(); //for milli second timing

  //***************************
  //HeartBeat LED, should toggle every heartBeatDelay milliseconds if code is nonblocking
  if (currentMillis - heartBeatMillis >= heartBeatDelay)
  {
    //reset timing
    heartBeatMillis = heartBeatMillis + heartBeatDelay;
    //Toggle heartBeatLED
    digitalWrite(heartBeatLED, !digitalRead(heartBeatLED));
  }


  //***************************
  //checkSwitches every 50ms
  if (currentMillis - lastMillis >= 50)
  {
    //reset timing
    lastMillis = lastMillis + 50;

    PINB = 0b00001000;
    PINB = 0b00001000;    
    checkSwitches();
    PINB = 0b00001000;
    PINB = 0b00001000;
  }

  //***************************
  //Other nonblocking code
  //***************************


} //END of       l o o p  ( )


//***************************************************************************
void checkSwitches()
{
  //***************************
  currentButton = digitalRead(myButton);

  if (lastButton != currentButton)
  {
    //was the button released?
    if (currentButton == HIGH) //HIGH = released
    {
      //      counter++;
      //      Serial.println(counter);
      //      digitalWrite(myLED, !digitalRead(myLED));
    }

    lastButton = currentButton;

  }
  //***************************

} //END of   c h e c k S w i t c h e s ( )

//***************************************************************************

Here is the timing for checkSwitches:
2017-07-19_11-32-07.jpg

I think you will agree the heartBeat toggling and the switch reading timings are negligible.

.

Hi,

Yes, both processes are very fast.

I just finished reading Nick Gammond's page on switches. It is really interesting, and I see that there are quite a few situations where this approach could be profitable.

What it basicaly does is to ignore the first state change (if calls to read the switch are further apart than the debounce time), making us sure that the next read is outside the turbulence zone.

I have to appologise about "the half of an RC debouncer" remark earlier. I didn't realise that the internal pull-up resistor was completing the Resistor-Capacitor duo, making it a complete debouncer.

Jacques

All in all this stuff is interesting. :slight_smile:

.

jbellavance:
. . .
making us sure that the next read is outside the turbulence zone.
. . .

Jacques

You don't have to worry about the 'turbulence zone' if you read the switch every 10-50mS.

.

Just added some examples:

  • Polling.ino: Test how fast you are compared to your Arduino
  • Reading.ino: When you just want to know if the switch's state changed
  • WasPressed.ino: If the switch was pressed, wait for it to be released before taking action
  • Toggle.ino: Transform you momentary button into an on/off swich
  • ToggleV2.ino : Same as toggle.ino, but take action only when the state of the switch has changed
  • ShortAndLong.ino: react differently if the switch has been pressed for a short time or for a long time
  • Repeat.ino: If you press the key long enough, it will repeat the character at a rapid rate.

If you have other ideas, Ill be glad to include them.

Jacques

You have been busy!

I some times like to include a switch filter in projects.
Maybe add something similar or have the option of enabling a filter.

Here is an example that ignores noise on a pin if it is less than ~20ms.
Edit:
More accurately:
With an input sample rate of 10ms, input signals > 20ms are guaranteed to be captured.
**Signals 10-20ms might be captured with signals < 10ms guaranteed not to be captured. **

//***************************************************************************
//Simple demonstration showing one way to handle switch scanning and
//how to filter noise on a pin input.
//In this case, a switch input that is less than 20ms is ignored.
//***************************************************************************
 
const byte heartBeatLED = 13;
const byte myLED        = 12;
const byte myButton     = 8;
 
byte lastButton         = HIGH;
byte sampleCounter      = 0;
byte currentButton;
 
unsigned int counter;
 
unsigned long currentMillis;
unsigned long heartBeatMillis;
unsigned long lastMillis;
 
const unsigned long heartBeatDelay = 250UL; //toggle LED every 1/4 second
const unsigned long switchDelay    = 10UL;  //read switch(es) every 10ms
 
//***************************************************************************
void setup()
{
  Serial.begin(9600);
 
  pinMode (heartBeatLED, OUTPUT);
  pinMode (myLED, OUTPUT);
 
  pinMode (myButton, INPUT_PULLUP);
 
} //END of                        s e t u p ( )
 
//***************************************************************************
void loop()
{
  currentMillis = millis();
 
  //***************************
  //The ‘Heart Beat LED’, should toggle every heartBeatDelay milliseconds if code is nonblocking
  if (currentMillis - heartBeatMillis >= heartBeatDelay)
  {
    //reset timing
    heartBeatMillis = heartBeatMillis + heartBeatDelay;
   
    //Toggle heartBeatLED
    digitalWrite(heartBeatLED, !digitalRead(heartBeatLED));
  }
 
    //go check the switches
    checkSwitches();
 
  //***************************
  // Other nonblocking code
  //***************************
 
 
} //END of                      l o o p  ( )
 
 
//***************************************************************************
void checkSwitches()
{
  //Time to check the switches? (10ms)
  if (currentMillis - lastMillis < switchDelay)
  {
    //it's not time
    return;
  }
    //reset timing for next iteration
    lastMillis = lastMillis + switchDelay;
 
  //***************************
  //We must read 2 'sequential' samples before we accept a valid button change.
  //Hence, it takes 2 X 10ms = 20ms to detect a valid button change.
  //This filters out any circuit noise less than 20ms.
  //More accurately:
  //With an input sample rate of 10ms, input signals > 20ms are guaranteed to be captured. 
  //Signals 10-20ms might be captured with signals < 10ms guaranteed not to be captured.   
 
  currentButton = digitalRead(myButton);
 
  //Has there been a button state change?
  if (lastButton == currentButton)
  {
    //no change, reset the sample counter
    sampleCounter = 0;
    
    return;
  }

    //there was a button change
    sampleCounter++; //used in filtering circuit noise
 
    //Is this the 2nd time in two sequential reads
    if (sampleCounter >= 2)
    {
      //Is the button pushed?
      if (currentButton == LOW) //Button pushed makes the pin LOW
      {
        //do something
        counter++;
        Serial.println(counter);
        digitalWrite(myLED, !digitalRead(myLED));
      }
 
      //the button is HIGH (released)
      else
      {
        //do button HIGH stuff
      }
 
      //update the lastButton state for the next read
      lastButton = currentButton;
      //finished with this button change, get ready for the next 2 samples
      sampleCounter = 0;
    }
 
  //***************************
 
} //END of             c h e c k S w i t c h e s ( )
 
//***************************************************************************

Hi Larry,

I am not sure I understand what you refer to as noise.

Is it bouncing or Electromagnetic Interferences?

Jacques

There are times, especially with long wires, when induced noise from other wiring can cause noise spikes on a pin.
These can cause false button change detection.

Yes, I understand. We are talking EMI.

Actually, the EdgeDebounce Class will reject such signals and return only when the signal is clean again.

The algorithm goes like this :

Step 1: Read the switch 16 times
Step 2: Repeat step 1 until all 16 reads are identical.

It has been used in noisy (electricaly speaking) industrial environments.

I invite you to read what the Ganssle group says here in the "An alternative" section.

It is this algorithm (modified to make it more flexible) that I use.

They say it can effectively remove both bounces and EMI.

Jacques

Are we talking about μs, I am thinking milli seconds.

Another way to deal with this is to monitor a normally closed switch going to GND.
When the switch is pressed, the pin then goes to a HIGH through a resistor to +5v.

N.C. to GND is very effective in preventing false button pushes.

However, many times we have to used N.O. switches etc.

.

I don't see how reading a switch twice at milliseconds intervals could be more reliable at detecting noise than reading a switch 16 times at μs intervals, and refusing a faulty signal until it is stable.

Jacques

I am not saying 'better' just another way.

Edge detection is not important when you are writing code for slowly operated switches etc.
Edge detection is important when writing code for things like sequence event recorders etc.

When it comes to our Arduino apps, we are interested if a switch has been pushed only if it has been held for a defined period of time.
I suggest 20ms or more, but not < 20ms.

I am not sure if I am remembering correctly, but if reading an input 16 times takes 90μs, any noise spike of > 90μs could get a through.

Let's say you read an input and there is no change in state.
Then if you read the input at 10ms and detect a state change, then read the switch at the next 10ms interval and there is no change, you can assume there was a noise spike that caused the change. Note the last state variable does not get updated.

Now let's say you read an input and there was no change in state.
Then if you read the input at 10ms and detect a state change, then read the switch at the next 10ms interval and the change is still there, you can assume this is a valid switch change. Then and only then do you update the last state variable which readies us for the next two sequential reads.

Or, the spike duration was humongous. :wink:

EDIT
More accurately:
With an input sample rate of 10ms, input signals > 20ms are guaranteed to be captured.
**Signals 10-20ms might be captured with signals < 10ms guaranteed not to be captured. **

.

Ok.

Switches go on and off when pressed by human hands. Some switches do not have "positive" responses to a press. (I am old enough to have seen a wanabe keyboard manufacturer close it's doors because of it's unresponsive keyboards).

Lags of such an amplitude are not caused by bounces or EMI. They are caused by lousy switches and/or unsure human hands. (that includes very old and very young hands) :wink:

That said, I can see why refusing a state change lasting less than 20 ms could be offered. (That would bring the Class to a halt as far as responsiveness is concerned).

Jacques