Leading edge debouncing

I've often thought of debouncing like debouncing an interrupt trigger - #3 by mikalhart
than most of the examples of debouncing I've seen in Arduino code.

In looking at the builtin Arduino Debounce and several of the debounce libraries, the debounced signal triggers after debouncing delay as a minimum pulse width. While the interrupt debouncing form triggers and acts on the first edge and ignores repeated interrupts for a period of time.

Here's a Wokwi with the two different methods together:

I like the responsiveness of starting a timer at the leading edge and safety of ignoring subsequent changes, versus the tradeoff of slower performance and missed signals with longer debouncing intervals. The leading edge form of debouncing reminds me of a latched relay-timer in the Ladder Relay Logic of Programmable Logic Controllers.

Here's Yet Another Alternative Debounce:

// DebounceLeading.in -- Toggle a pin with timer debounce

// This version detects faster than
// https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce
// by triggering on the leading edge, and acting only if it's
// been a while since the last press.
// demo at https://wokwi.com/arduino/projects/324857875800261204
// DaveX 2022-02-28 CC BY-SA

uint8_t buttonPin = 2;
unsigned long buttonLast = 0;
//unsigned long buttonFirst = 0;
int buttonCount;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  unsigned loopMillis = millis();
  if (digitalRead(buttonPin) == LOW) { // process the button
    if ( loopMillis - buttonLast > 50 ) { // long enough
      // Some oneshot button code:
      buttonCount++;
      //buttonFirst = loopMillis;
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
      Serial.print(loopMillis);
      Serial.print("ms leading Edge: ");
      Serial.println(buttonCount);
    }
    // reset the timer whenever the button is pressed:
    buttonLast = loopMillis;
  } // end of button
}

Of the multitude of debouncing libraries available, can you recommend ones that trigger at the leading edge?

I see a problem with your definition of "leading edge". Your code ONLY looks for a "low", where another persons' leading edge may be "high".

1 Like

@Paul_KD7HB
Then, what are about the following traditional definitions?
risingEdge
Figure-1:

risingEdgeDpin2
Figure-2:

I like to debounce on both edges:

const byte ButtonPin = 2;
const unsigned long DebounceTime = 30;

boolean ButtonWasPressed;  // Defaults to 'false'
unsigned long ButtonStateChangeTime = 0; // Debounce timer

void setup()
{
  pinMode (ButtonPin, INPUT_PULLUP);  // Button between Pin and Ground
}

void loop()
{
  unsigned long currentTime = millis();

  boolean buttonIsPressed = digitalRead(ButtonPin) == LOW;  // Active LOW

  // Check for button state change and do debounce
  if (buttonIsPressed != ButtonWasPressed &&
      currentTime - ButtonStateChangeTime > DebounceTime)
  {
    // Button state has changed
    ButtonStateChangeTime = currentTime;
    ButtonWasPressed = buttonIsPressed;

    if (ButtonWasPressed)
    {
      // Button was just pressed
    }
    else
    {
      // Button was just released
    }
  }
}

With multiple buttons it is OK to use a single timer for all of the debouncing:

const byte ButtonCount = 3;
const byte ButtonPins[ButtonCount] = {2, 3, 4};
const unsigned long DebounceTime = 30;

boolean ButtonWasPressed[ButtonCount];  // Defaults to 'false'
unsigned long ButtonStateChangeTime = 0; // Debounce timer common to all buttons

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

  for (byte i = 0; i < ButtonCount; i++)
  {
    pinMode (ButtonPins[i], INPUT_PULLUP);  // Button between Pin and Ground
  }
}

void loop()
{
  checkButtons();
}

void checkButtons()
{
  unsigned long currentTime = millis();

  // Update the buttons
  for (byte i = 0; i < ButtonCount; i++)
  {
    boolean buttonIsPressed = digitalRead(ButtonPins[i]) == LOW;  // Active LOW

    // Check for button state change and do debounce
    if (buttonIsPressed != ButtonWasPressed[i] &&
        currentTime - ButtonStateChangeTime > DebounceTime)
    {
      // Button state has changed
      ButtonStateChangeTime = currentTime;
      ButtonWasPressed[i] = buttonIsPressed;
      if (ButtonWasPressed[i])
      {
        // Button i was just pressed
      }
      else
      {
        // Button i was just released
      }
    }
  }
}
1 Like

That induces a DebounceTime lag after the noise.

Given a noisy signal like (HT Switch - Wikipedia):

What I'm looking for is something that triggers and acts on the leading edge, rather than waiting for a debounceDelay before acting.

Triggering on the leading edge, and then supressing subsequent triggers for a period of time certainly may be susceptible to noise, but on the other hand, a noisy switch can lead to missing signals
For instance, if this pulse was only 5ms long, a debounce scheme that depended on a 3ms debounceDelay would could, because of noise, completely miss the signal. (This reminds me of my frustrating alarm clock, where I really have to grind my thumb into its aged membrane switch)

The code from debouncing an interrupt trigger - #3 by mikalhart triggers quickly and ignores the noise:

void my_interrupt_handler()
{
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 200) 
  {
    ... do your thing
  }
  last_interrupt_time = interrupt_time;
}

Do all of the debounce/button libraries follow the lag-inducing pattern of Debounce.ino?

That what?

a7

I think of the one-shot timer inside the conditional's braces as the leading-edge debouncing code pattern:

if (flagDoTask){ // process the task
    if ( loopMillis - last > interval ) { // long enough
      // Some oneshot code to be excuted once per event:
      ;;
   }
    // reset the timer:
    last = loopMillis;
  } // end of task

But it's not expensive to have a timer each.

If you had ten buttons and jammed them all down at once, it be a while before they all "went down" as they one by one get seen, with a delay between each.

It may never mean anything, only because I've worked with musicians am I so interested in keeping things snappy. Milliseconds matter.

This (with a ridiculous debounce time) shows why one might not be OK with a single timer.

Press and hold the button I've wried to all 3 inputs.

a7

1 Like

Oops-- 'twas meant as a reply to Leading edge debouncing - #4 by johnwasser And double oops -- @johnwasser is right. By tracking both edges, his code triggers on the leading edge of the change.

And order is restored to the Universe.

Was digging around for my stronger reading glasses. :wink:

a7

1 Like

How do you know if the leading edge is a real button press or a noise spike?

The hardware solution to switch bounce is to add a simple capacitor to ground the switch output. That will absorb the bounce on BOTH edges, so you get a clean open and close signals. The exact same thing can be accomplished with a digital low-pass filter with an appropriate time constant. The simplicity of this approach is hard to beat.

3 Likes

Pushbuttons don’t close randomly, or open randomly. Any contact is on the way to full contact, and vice versa.

If you find them doing, get better switches or take other measures to eliminate whatever is causing the spurious opening or closing what isn’t due to being pressed or released.

Someone might mention the small capacitor hardware debouncing that can mean you don’t need software at all, or suggest it to be the belt accompaniment to suspenders. edit unposted: @RayLivingston for example and search just now I was thinking of @groundFungus a reliable proponent of the method. That may rely on the hysteresis present on the inputs, which is not true for all digital inputs but is nicely there apron the Arduino microprocessors.

I guess if the button was “launch all missles” you would want to be a bit more thorough and do the kind of debouncing that waits to see if… but you probably wouldn’t be doing it with just one inexpensive pushbutton, nor on an Arduino.

I do not experience buttons switching on that I did not press. I have never worked back to find it to have been the source of a problem. Perhaps my luck will run out…

a7

1 Like

I added @johnwasser 's scheme and the Bounce2 scheme to the demo at LeadingEdgeDebounce.ino - Wokwi Arduino and ESP32 Simulator

It indeed catches the leading edge:

1764ms leading Edge Debounce: 1
1765ms Wasser Debounce: 1
1816ms Bounce2 Debounce: 1
1817ms Arduino Debounce.ino: 1
2731ms leading Edge Debounce: 2
2732ms Wasser Debounce: 2
2783ms Bounce2 Debounce: 2
2783ms Arduino Debounce.ino: 2
3731ms leading Edge Debounce: 3
3731ms Wasser Debounce: 3
3783ms Bounce2 Debounce: 3
3784ms Arduino Debounce.ino: 3
4547ms leading Edge Debounce: 4
4548ms Wasser Debounce: 4
5030ms Wasser Debounce: 5
5031ms leading Edge Debounce: 5
5564ms leading Edge Debounce: 6
5565ms Wasser Debounce: 6
6014ms leading Edge Debounce: 7
6014ms Wasser Debounce: 7
6067ms Bounce2 Debounce: 4
6067ms Arduino Debounce.ino: 4
6798ms Wasser Debounce: 8
6798ms leading Edge Debounce: 8
6850ms Bounce2 Debounce: 5
6851ms Arduino Debounce.ino: 5
7531ms leading Edge Debounce: 9
7531ms Wasser Debounce: 9
7582ms Bounce2 Debounce: 6
7584ms Arduino Debounce.ino: 6
10653ms leading Edge Debounce: 10
10654ms Wasser Debounce: 10
10704ms Bounce2 Debounce: 7
10705ms Arduino Debounce.ino: 7
11887ms leading Edge Debounce: 11
11887ms Wasser Debounce: 11
11937ms Bounce2 Debounce: 8
11938ms Arduino Debounce.ino: 8
13086ms leading Edge Debounce: 12
13086ms Wasser Debounce: 12
13139ms Bounce2 Debounce: 9
13140ms Arduino Debounce.ino: 9
...

Note that the Arduino Debounce and the Bounce2 scheme report after a delay, while the @JohnWasser and leading edge schemes report on the first observation of the change.

It looks like the Bounce2 library has a pre-include define, #define BOUNCE_LOCK_OUT, that should enable leading edge debouncing: GitHub - thomasfredericks/Bounce2: Debouncing library for Arduino and Wiring

...but I'm not sure it works. I added it to my demo here, but the performance didn't seem to change relative to the other methods.

Maybe it is because Arduino version is a few versions behind the library at 2.60.0, versus 2.70 on Github?

Not a GitHub wizard, only a naive user, but can't it spill about the difference between versions?

Also, you can use zip libraries with wokwi if you have an account I think, so you could test the latest in your demo.

Added: OK I can't see any dev history at the GitHub, perhaps I don't really know how to find it.

But I did click on through to find a general short denounce class that puts the traditional "don't look again too soon" into object form.

Which is interesting but I still like to see the code in these tiny programs we write, so I don't have to research, understand or trust the work of someone else, when possible.

I think this should be something everyone does at least once, no matter they never do again.

a7

To my eye, the code in my IDE's bounce2 library (below) looks the same as the code at Bounce2/Bounce2.cpp at master · thomasfredericks/Bounce2 · GitHub

bool Debouncer::update()
{

    unsetStateFlag(CHANGED_STATE);
#ifdef BOUNCE_LOCK_OUT
    
    // Ignore everything if we are locked out
    if (millis() - previous_millis >= interval_millis) {
        bool currentState = readCurrentState();
        if ( currentState != getStateFlag(DEBOUNCED_STATE) ) {
            previous_millis = millis();
            changeState();
        }
    }
    

#elif
...
}

I wouldn't mind using a library if I liked the way it did things.

I updated the Wokwi simulation to repeatedly and noisily press the button and raised an issue at Does #define BOUNCE_LOCK_OUT work? · Issue #85 · thomasfredericks/Bounce2 · GitHub over the #define BOUNCE_LOCK_OUT not appearing to have an effect.

@DaveX

I made a bounce simulator I use IRL that runs complimentary digital outputs, a pull-up transistor and one for pulling down and also a CMOS switch. So I can usually hook that onto any putative debounced input.

I developed it in the wokwi, but TBH it never occurred to me that I could do some tests all on one Arduino. In the wokwi.

Obvsly this testing requires a half-way decent debouncing algorithm so you get the CPU time to simulate the bouncing.

IRL we can't always get at the software on something being tested. Like to win a bet when ppl don't believe that the microwave oven has a crap debouncer in there somewhere. :expressionless:

I have asked the wokwi boys to implement bouncing for switches beyond does or doesn't, so far it seems it is a low priority. I'd like to see something like bounce for N micro (or milli)seconds.

I read your code to see if Yes! you used an LFSR for real synthetic noise. I thought of gating an LFSR chip with external logic, but the venerable MM5837 doesn't really get going until its Vcc is over 10 volts. Doing it otherwise would mean even more hardware. Software works fine.

Nice work.

a7

1 Like

Thanks. It's hacked together out of recycled pieces. I had a prof who said you don't really know if your system worked unless you simulate it.

I haven't played with it much, but the OCM1C0A Output Compare Modulator on the Mega, etc. could be a cheap way to simulate some test signals.