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?
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
}
}
}
}
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)
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?
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
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.
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.
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…
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.
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.
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.
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.