Another switch debouncer... with a new twist

"I am old enough to have seen a wanabe keyboard manufacturer close it's doors because of it's unresponsive keyboards"

Me too.

In the old days a lot of problems were because front end designs were hardware.
Hardware picked up every pulse from nano seconds and up.
Switches were quite good when they were new but soon became bouncy as they aged do to dirt and changing spring tensions.
They could fix this by using retriggerable multivibrators.

Today, fast acting hardware front ends has been replaced by sampling switches with software.
A good algorithm now fixes switch problems.

The good old days can stay right where they are :slight_smile: .
.

retriggerable multivibrators

Never heard

74LS123 for example.

See this PDF:

.

That is a nice piece of hardware.

I used to use these quite often in circuit designs back in the 70s.

FYI
I did some analysis of your library with the following results:

I modified your library so timing marks were outputted on pin 11 (orange trace), lines 76-77, see the code below.

/*
 *EdgeDebounce.cpp 
 *Created by: Jacques bellavance, July 7 2017
 *Released into the public domain
 *GNU GENERAL PUBLIC LICENSE V3
 *
 *This library is designed to debounce switches.
 *It was inspired by this article: http://www.ganssle.com/debouncing.htm
 *It is designed to be lightweight
 *PSEUDOCODE
 * 1) Repeat
 * 1)   Read the switch 16 times
 * 2) Until all reads are identical
 * 3) Return the switch's status
 *    
 *The number of times the switch is repetitively read can be set between 1 and 32
 *Switches can use either an external pulldown resistor or the internal pullup resistor
 *pinMode() is set by the constructor
*/ 

#include <Arduino.h>
#include "EdgeDebounce.h"

//Constructor===============================
//pin: the pin connected to the switch
//------------------------------------------
EdgeDebounce::EdgeDebounce(byte pin, byte mode) {
  MYpin = pin;
  if (mode == PULLUP) {
    pinMode(pin, INPUT_PULLUP);
    MYmode = PULLUP;
  }
  else {
    pinMode(pin, INPUT);
    MYmode = PULLDOWN;
  }
}//Debounce---------------------------------


//setSensitivity==================================================================
//Sets the number of times a switch is read repeatedly by the debouncer routine
//It defaults to 16 times. Allowable values are 1..32
//--------------------------------------------------------------------------------
void EdgeDebounce::setSensitivity(byte w) {
 if (w >= 1 && w <= 32) {
 MYsensitivity = w;
 debounceDontCare = 0xffffffff;
 for (byte i = 0; i < w; i++) debounceDontCare = debounceDontCare << 1 | 0;
 }
}//setSensitivity--------------------------------------------------------------------

 //getSensitivity==================================================================
 //Returns the current sensitivity of Debounce
 //--------------------------------------------------------------------------------
byte EdgeDebounce::getSensitivity() {
  return MYsensitivity;
}//getSensitivity--------------------------------------------------------------------

//pressed=========================================================================================================
//Debounces the switch connected to "MYpin"
//The switch is read 16 times (~60us) to look for 16 consecutive HIGH or LOW
//If unsuccessfull, it means that a change is occuring at that same moment
//and that either a rising or falling edge of the signal is actualy occuring.
//The pin is reread repetetively 16 times until the edge is confirmed.
//If the switch has been declared PULLDOWN: Returns 0 if open or 1 if closed
//If the switch has been declared PULLUP: Returns 1 if open or 0 if closed
//---------------------------------------------------------------------------------------------------------------
int EdgeDebounce::pressed() {
  unsigned long pinStatus;
  do {
    pinStatus = 0xffffffff;
    for (byte i = 1; i <= MYsensitivity; i++) 
    {
    //         111100
    //         321098
    PINB = 0b00001000; //toggle pin 11
    PINB = 0b00001000; //toggle pin 11
    pinStatus = (pinStatus << 1) | digitalRead(MYpin);
    }
    
  } while ((pinStatus != debounceDontCare) && (pinStatus != 0xffffffff));
  return byte(pinStatus & 0x00000001);
}//pressed--------------------------------------------------------------------------------------------------------

//closed==============================================
//Returns true if the switch is closed (or ON)
//-----------------------------------------------------
bool EdgeDebounce::closed() {
  if (MYmode == PULLUP) 
  {

  return !pressed();
  }
  else
  {
  return pressed();
  }
}//closed---------------------------------------------

Your Basic_Pullup sketch was modified so timing marks were outputted on pin 10 (red trace), lines 32-33, see the code below.

/*
   Basic-Pullup.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 8

//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()
{
  pinMode(11, OUTPUT); //Orange trace, created in library
  pinMode(10, OUTPUT); //Red trace, created in this sketch
  pinMode(13, OUTPUT);
  //The Library has declared pinMode(BUTTON_PIN, INPUT_PULLUP) for you
}

void loop()
{
  if (button.closed())
  {
    //         111100
    //         321098
    PINB = 0b00000100; //toggle pin 10
    PINB = 0b00000100; //toggle pin 10
    digitalWrite(13, HIGH);  //The .closed method returns true if there is contact
  }
  else
  {
    digitalWrite(13, LOW);
  }
}

Here is the input signal that was fed to input pin 8:
2017-07-22_11-06-24.jpg

This is a magnified view showing all 3 signals:
EDIT The image below is wrong, see post #39 for the correct image.

You can see (for the most part) it takes 16 samples between each write to the LED.
I cannot explain why it appears there are 22 pulses before the first double LED writes, I would have guessed 16.

Why, when the input is HIGH, there is one LED write and why, when the input is LOW, there are two LED writes?
This is a bit confusing.
I will have to look a bit deeper to explain this.

The above images demonstrate one technique on how to analyze what is happening in hardware when code is executed.

.

In fact, there are 48 of them (3 x 16). This is because the signal is dirty.

  • There was a first pass of 16 reads that were not identical
  • There was a second pass of 16 reads that were not identical
  • There was a third pass where all reads were identical, so it returned closed().

As for the 2 signals instead of one, I don't understand.

I would like to see what happens if you initialise the button like this
EdgeDebounce button(BUTTON_PIN, PULLDOWN);
instead of
EdgeDebounce button(BUTTON_PIN, PULLUP);

In fact, there are 48 of them (3 x 16).

Edit
I started at the last noise pulse, but 3X16 makes sense.

As for the 2 signals instead of one, I don't understand.

As here:

I would like to see what happens if you initialise the button like this
EdgeDebounce button(BUTTON_PIN, PULLDOWN);
instead of
EdgeDebounce button(BUTTON_PIN, PULLUP);

I will try.

Using this Sketch:

/*
   Basic-Pulldowwn.ino
   By Jacques Bellavance, July 7 2017
   Shows how to use the Debounce Library
   This sketch is for switches that uses an external pulldown resistor
   Switch pin A : Vcc
   Switch pin B : Arduino's pin 2
   Switch pin B : 10Kohm resistor tied to Ground
*/

#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 PULLDOWN mode
EdgeDebounce button(BUTTON_PIN, PULLDOWN);

void setup()
{
  pinMode(11, OUTPUT); //Orange trace, created in library
  pinMode(10, OUTPUT); //Red trace, created in this sketch
  pinMode(13, OUTPUT);
  //The Library has declared pinMode(BUTTON_PIN, INPUT) for you
}

//Debug LED on pin 13 will light up when the button is pressed
void loop()
{
  if (button.closed())
  {
    //         111100
    //         321098
    PINB = 0b00000100; //toggle pin 10
    PINB = 0b00000100; //toggle pin 10
    digitalWrite(13, HIGH);  //The .closed method returns true if there is a contact
  }
  else
  {
    digitalWrite(13, LOW);
  }
}

Switch pin A : Vcc
Switch pin B : Arduino's pin 2
Switch pin B : 10Kohm resistor tied to Ground

Full pulse

Zoomed in at the front edge

.

Here is an example using switch monitoring every 10ms with 2 sequential reads before acknowledging a valid state change.
In comparison your library allows switch detection at less than 300uS, with the attached sketch needing ~20ms.

//***************************************************************************
//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.
//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.
//***************************************************************************

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

byte lastButton         = HIGH;
byte sampleCounter      = 0;
byte currentButton;

unsigned long currentMillis;
unsigned long lastMillis;

const unsigned long switchDelay    = 10UL;  //read switch(es) every 10ms

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

  pinMode (myLED, OUTPUT);
  pinMode (10, OUTPUT);
  pinMode (11, OUTPUT);
  
  pinMode (myButton, INPUT_PULLUP);

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

//***************************************************************************
void loop()
{
  currentMillis = millis();
  
  //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.

  //         111100
  //         321098
  PINB = 0b00001000; //toggle pin 11
  PINB = 0b00001000; //toggle pin 11
  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

      //         111100
      //         321098
      PINB = 0b00000100; //toggle pin 10
      PINB = 0b00000100; //toggle pin 10
      digitalWrite(myLED, LOW);
    }

    //the button is HIGH (released)
    else
    {
      PINB = 0b00000100; //toggle pin 10
      PINB = 0b00000100; //toggle pin 10
      digitalWrite(myLED, HIGH);
    }

    //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 ( )

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

Pluse being sampled:
2017-07-22_12-52-53.jpg

All traces:

.

I am glad to be able to see how my baby behaves, and that it does what is is supposed to do.

Thank you for taking the time to do all this.

Would you mind if I used your images in the tutorial? It explains the algorithm much better than words. I would mention that you furnished them of course.

Jacques

jbellavance:
I am glad to be able to see how my baby behaves, and that it does what is is supposed to do.

Thank you for taking the time to do all this.

Would you mind if I used your images in the tutorial? It explains the algorithm much better than words. I would mention that you furnished them of course.

Jacques

Not a problem.
I had things set up to look at the 10ms with 2 sequential reads example and then decided to look at your examples.

Use the images as needed.

FYI
BTW, I modified Nick Gammon's SwitchManager library to only pass signals > ~20ms, here are the changes.

I am calling it SwitchManagerWithFilter.h version 1.01

// Class for managing switch presses
// Author: Nick Gammon
// Date: 18 December 2013
// Modified: 12 February 2015 to pass pin number to function

// Noise filter Added REV.  1.01
// 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.

/*
  Example:

  #include <SwitchManager.h>

  SwitchManager mySwitch;  // declare

  // newState will be LOW or HIGH (the is the state the switch is now in)  
  // interval will be how many mS between the opposite state and this one  
  // whichPin will be which pin caused this change (so you can share the function amongst multiple switches)

  void handleSwitchPress (const byte newState, const unsigned long interval, const byte whichPin)
   {

   }

  void setup ()
   {
   mySwitch.begin (2, handleSwitchPress);
   }

  void loop ()
   {
   mySwitch.check ();  // check for presses
   }

*/

#include <Arduino.h>


class SwitchManager
{
    enum { debounceTime = 10, noSwitch = -1 };
    typedef void (*handlerFunction) (const byte newState,
                                     const unsigned long interval,
                                     const byte whichSwitch);

    int pinNumber_;
    handlerFunction f_;
    byte oldSwitchState_;
    unsigned long switchPressTime_;  // when the switch last changed state
    unsigned long lastLowTime_;
    unsigned long lastHighTime_;
    byte sampleCounter_;             // 1.01

  public:

    // constructor
    SwitchManager ()
    {
      pinNumber_ = noSwitch;
      f_ = NULL;
      oldSwitchState_  = HIGH;
      switchPressTime_ = 0;
      lastLowTime_     = 0;
      lastHighTime_    = 0;
      sampleCounter_   = 0;          // 1.01
    }

    void begin (const int pinNumber, handlerFunction f)
    {
      pinNumber_ = pinNumber;
      f_ = f;
      if (pinNumber_ != noSwitch)
        pinMode (pinNumber_, INPUT_PULLUP);
    }  // end of begin

    void check ()
    {
      // we need a valid pin number and a valid function to call
      if (pinNumber_ == noSwitch || f_ == NULL)
        return;

      // Time to check the switch?
      if (millis () - switchPressTime_ < debounceTime) //         1.01
      {
        //it's not time yet
        return;
      }

      switchPressTime_ = millis();

      byte switchState = digitalRead(pinNumber_);

      //Has the switch changed state?
      if (switchState == oldSwitchState_) //                      1.01
      {
        // Reset the counter as no state changed was detected     1.01
        sampleCounter_ = 0; //                                    1.01

        return; //                                                1.01
      }

      // The switch has changed state
      sampleCounter_++; // This is used to filter circuit noise   1.01

      // Must read the switch sequential 2 times before we validate a switch change.            1.01
      // i.e. if debounceTime is 10ms, it will take 20ms to validate a switch change.           1.01
      // With an input sample rate of 10ms, input signals > 20ms are guaranteed to be captured. 1.01
      // Signals 10-20ms might be captured, with signals < 10ms guaranteed not to be captured.  1.01

      // Is this the 2nd time in two sequential reads?  1.01
      if (sampleCounter_ >= 2) //                       1.01
      {
        oldSwitchState_ =  switchState;  // remember for next time

        // Get ready for the next 2 samples             1.01
        sampleCounter_ = 0; //                          1.01

        // see if switch is open or closed

        if (switchState == LOW)
        {
          lastLowTime_ = switchPressTime_;
          f_ (LOW, lastLowTime_ -  lastHighTime_, pinNumber_);
        }

        // switch must be HIGH then
        else
        {
          lastHighTime_ = switchPressTime_;
          f_ (HIGH, lastHighTime_ - lastLowTime_, pinNumber_);
        }

      }

    }  // end of operator ()

};  // class SwitchManager

.

Thanks for allowing me to use your images.

The use of sampleCounter_ is a good idea.

I think I will also try to add this feature (ignore changes if less than 20ms) as an option on EdgeDebounce. It will be an option, because the Library can be used with mechanical encoders. They need responsiveness.

Jacques

EDIT : Images now included in the tutorial

I cleaned this up a bit:

Yes, It is in PULLUP mode again. I still don't see why it fires once when it should, and fires twice when it should not. Your modification to the code to read pins on the oscilloscope seems very logical to me.

I don't see this behaviour when running the sketches with pin 13's LED in PULLUP mode as a monitor.

It is a mistery that remains to be resolved.

I wonder if someone out there can confirm or infirm that digitalWrite(xx, LOW) brings the pin HIGH twice.

Confusing to me too.
digitalWrites HIGH and LOW are the same.

I will look at it later later.
I don't like unexplained things, OCD you know :wink:

.

I have seen OCD so many times on this site... but still don't know what it means. (I am french... realy)

Obsessive Compulsory Disorder
"people feel the need to check things repeatedly, perform certain routines repeatedly"

OK I found the problem, it was with me :frowning:

This is what I said was the code:

if (button.closed())
 {
   //         111100
   //         321098
   PINB = 0b00000100; //toggle pin 10
   PINB = 0b00000100; //toggle pin 10
   digitalWrite(13, HIGH);  //The .closed method returns true if there is contact
 }

This is what I actual had for that image:

 //         111100
 //         321098
 PINB = 0b00000100; //toggle pin 10
 PINB = 0b00000100; //toggle pin 10
 
 if (button.closed())
 {
 //         111100
 //         321098
 PINB = 0b00000100; //toggle pin 10
 PINB = 0b00000100; //toggle pin 10
   digitalWrite(13, HIGH);  //The .closed method returns true if there is contact
 }

Makes perfect sense there would be two pulses when the input was LOW.

Now I can sleep sound. :slight_smile:

.

And so do I.

Thanks a millon, and sleep tight.

Jacques

This would be the proper image:
EDIT this replaces the image in post #24