/*
State change detection (edge detection)
Often, you don't need to know the state of a digital input all the time, but
you just need to know when the input changes from one state to another.
For example, you want to know when a button goes from OFF to ON. This is called
state change detection, or edge detection.
This example shows how to detect when a button or button changes from off to on
and on to off.
The circuit:
- pushbutton attached to pin 2 from +5V
- 10 kilohm resistor attached to pin 2 from ground
- LED attached from pin 13 to ground through 220 ohm resistor (or use the
built-in LED on most Arduino boards)
created 27 Sep 2005
modified 30 Aug 2011
by Tom Igoe
This example code is in the public domain.
https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection/
*/
// this constant won't change:
const int buttonPin = 2; // the pin that the pushbutton is attached to
const int ledPin = 13; // the pin that the LED is attached to
// Variables will change:
int buttonPushCounter = 0; // counter for the number of button presses
int buttonState = 0; // current state of the button
int lastButtonState = 0; // previous state of the button
void setup() {
// initialize the button pin as a input:
pinMode(buttonPin, INPUT);
// initialize the LED as an output:
pinMode(ledPin, OUTPUT);
// initialize serial communication:
Serial.begin(9600);
}
void loop() {
// read the pushbutton input pin:
buttonState = digitalRead(buttonPin);
// compare the buttonState to its previous state
if (buttonState != lastButtonState) {
// if the state has changed, increment the counter
if (buttonState == HIGH) {
// if the current state is HIGH then the button went from off to on:
buttonPushCounter++;
Serial.println("on");
Serial.print("number of button pushes: ");
Serial.println(buttonPushCounter);
} else {
// if the current state is LOW then the button went from on to off:
Serial.println("off");
}
// Delay a little bit to avoid bouncing
delay(50);
}
// save the current state as the last state, for next time through the loop
lastButtonState = buttonState;
// turns on the LED every four button pushes by checking the modulo of the
// button push counter. the modulo function gives you the remainder of the
// division of two numbers:
if (buttonPushCounter % 4 == 0) {
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
}
Per these posts and these guidelines, if you've got something to say about the official example, its schematics, its explanation, or you have a better way of doing it, please share.
Rigging the State Change Detection example to use the S3/INPUT_PULLUP scheme instead of the S1/ external pulldown scheme would be easier, safer, and cheaper. However, the Active-LOW logic is a little bit more harder to explain than Active-HIGH button logic.
Problem with your Fritzing picture: Won't work if the "TAC" button is turned 90 degrees, pull down (or up) resistor should be connected to the same pin as the wire to input. Beginners should avoid those 4 legged buttons but they're in every "starter" kit.
Hey, it's not my F'zing diagram. It's a direct link to the diagram in the official documentation for the state-change example as linked the File/Examples/02Digital/StateChangeDetection code in everyone's IDE.
And yes, a four-wire, two-terminal device adds ambiguity to the learning process.
So, if the button on the first picture were oriented like your second one the 5V bus would have a 10k load and the input pin would still be floating. That was my point.
At least this code might work. I would have sworn that not only does the offered example edge detector do a few annoying things to keep things simple, it just plain doesn't work.
Musta been another similar state change or button press counting example. I feel sorry for all ppl who stumble into example code that just does not work.
The essential trick behind edge detection is remembering a value from before and comparing it to the currentValue, and the extra bits in the official sketch might distract from that.
If I was going to use this active-HIGH circuit and leave in some extra bits like debouncing and doing something interesting with the LED, I'd highlight those extra bits as separate features:
/*
State change detection (edge detection)
(with debouncing and LED toggling)
https://wokwi.com/projects/391475222059330561
Modified per https://forum.arduino.cc/t/state-change-detection-edge-detection-for-pushbuttons/1231632/10?u=davex
to focus on the change detection,
and to use millis() for debouncing the change detection instead of delay();
Often, you don't need to know the state of a digital input all the time, but
you just need to know when the input changes from one state to another.
For example, you want to know when a button goes from OFF to ON. This is called
state change detection, or edge detection.
This example shows how to:
1) detect when a button or button changes from off to on and on to off.
2) debounce an input with millis()
3) toggle a LED based on its state
The circuit:
- pushbutton attached to pin 2 from +5V
- 10 kilohm resistor attached to pin 2 from ground
- LED attached from pin 13 to ground through 220 ohm resistor (or use the
built-in LED on most Arduino boards)
created 27 Sep 2005
modified 30 Aug 2011
by Tom Igoe
modified 04 Mar 2023 by DveX for https://forum.arduino.cc/t/state-change-detection-edge-detection-for-pushbuttons/1231632/10
This example code is in the public domain.
Based on:
https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection/
*/
// this constant won't change:
const int buttonPin = 2; // the pin that the pushbutton is attached to
const int ledPin = 13; // the pin that the LED is attached to
// Variables will change:
int buttonState = 0; // current state of the button
int lastButtonState = 0; // previous state of the button
uint32_t lastChangeMillis = 0; // previous time of change for debouncing
uint32_t debounceInterval = 50;
void setup() {
// initialize the button pin as a input:
pinMode(buttonPin, INPUT);
// initialize the LED as an output:
pinMode(ledPin, OUTPUT);
// initialize serial communication:
Serial.begin(115200);
}
void loop() {
// read the pushbutton input pin:
buttonState = digitalRead(buttonPin);
uint32_t currentMillis = millis();
// compare the buttonState to its previous state
if (buttonState != lastButtonState ) { // if the state has changed
if (currentMillis - lastChangeMillis >= debounceInterval) { // limit rate using time stamps
lastChangeMillis = currentMillis; // record the time for future debouncing
// save the current button state as the real last state, for next time through the loop
lastButtonState = buttonState;
// act on the detected state change:
if (buttonState == HIGH) {
// if the current state is HIGH then the button went from off to on:
Serial.print("HIGH ");
toggleLed();
} else {
// if the current state is LOW then the button went from on to off:
Serial.print("LOW ");
}
}
}
}
void toggleLed(void){
if (digitalRead(ledPin) == LOW) { // toggle the LED as needed
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
}
if (buttonState != lastButtonState ) { // if the state has changed
if (currentMillis - lastChangeMillis >= debounceInterval) { // limit...
// stuff
}
}
The two conditions are in a logical and test. Since and commutes, there is no reason one can't write
if (currentMillis - lastChangeMillis >= debounceInterval) { // limit...
if (buttonState != lastButtonState ) { // if the state has changed
// same stuff
}
}
Since forever I do the time test first.
I think there may be a good reason to do.
If the first sketch presented is simply state change detectation, a loop-level,"poor man's" debouncing can be placed outside the context of the edge detector, and excused momentarily with some hand waving.
I usually put it at the bottom.
Then, the Ugly Truth can be revealed, and a proper debouncing mechanism can envelop the edge detector, accomplishing the same thing as the crude debounce, only better, as now it no longer blocks.
It's also handy to do the time test first, as it affords the occasional opportunity to out-dent the millis() time limited block.
I do this at the top of throttled loops meant to run at, say, 50 Hz.
They are indeed a logical and, and your reasons make sense. but there could be reasons to do one or the other first. The simplicity of explaining a loop-level delay() or taking the next step into an encompassing if(millis() -last >= interval){...} is good. But swapping the logic can have an impact on the responsiveness the debouncing/rate limiting, switching between edge detection and polling.
If you stretched out the delay/rate-limit interval beyond usual debouncing times, the outer polling test might cause problems. For a ridiculous motivating example, consider building a Skinnerian pigeon feeder button to rate-limit the pigeon self-feedings to no more than 1 feeding per 10 minutes (600000ms). You'd likely want an instantaneous response and to mark the time interval from the actual feeding. The Arduino stock example could do this by increasing the delay to delay(300000) (half because it would take 5 minutes to reset). Or a little more clearly, one could move the delay up to the leading edge of the button press and delay(600000). Polling with either a blocking or non-blocking outer test would likely miss pigeon pecks and starve the pigeons.
I mean to say that the ordering of the clauses could possibly matter, depending on the application.
Active-LOW Wokwi example, with bonus code for falling-edge rate-limited pigeon feeding:
Haha, the only sense I can make of the rest of your post is that this is not what you mean to say.
But I will admit that you have made me think.
Both the version where the delay() is within the edge detecting and the version where there is no delay at all will leave the response near instant and the pigeons happy.
In both they will get fed, but not again for the brain dead delay() period or the millis() lockout period.
The bad guy here is the poor man. A delay() every loop means the pigeons will wait, on average, 1/2 the brain dead period. Worse, if you really mean pigeons, they gonna be unhappy (hungry) because the tiny window of observation such a loop affords means they'd have to be lucky, or peck and hold down the button. Pigeons are smart, but they may not be equipped to press and hold. I bet monkeys would figure out that pressing and holding the button would deliver the reward - it reminds me of trying to get some device with two or three buttons to work in the absence of the instructions… it's virtually impossible to test every "combination" some clever engineer might have come up with.
I don't think you say that
if (A)
if (B)
C();
is different if tests A and B are swapped, at least not unless we worry about the costs of doing A and B and whether any side effects need to be considered.
But this just shows to go you that it is probably necessary for noobs to see this and have it explained and experience the results of not doing it, or doing it wrong before it can be truly considered locked-in knowledge.
Of course some of my knowledge is locked in so well it has become virtually inaccessible.
I have always argued for the no delay near instant response, and I have usually expressed that preference in the context of regular normally open switches operated by humans.
Other signals that need debouncing or deglitching or whatever you wanna call it may need some of the more elaborate or obscure methods.
The side effects do need to be considered, or rather the effects of the placement of the lastMillis=currentMillis update within which set of braces for which condition. The timestamp reset needs to be updated inside the inner set of braces of the two conditions to work properly, not wrapped around the outside:
if (A){
if (B){
C();
// update the time here inside the (A AND B)
lastMillis = currentMillis;
}
//lastMillis = currentMillis; // not here inside only A
}
If you put it outside you get a non-blocking, but unwanted behavior of C() like the brain-dead delay(600000) on every loop. It also needs to be initialized strangely (lastMillis = -interval) to duplicate the delay(600000) behavior that would feed both the t=1s & t=601s button-pecking pigeons.
Transforming the example's delay() to use millis() needs some special care to get the same behavior.
and the two conditions are no longer performing an and statement.
Taking part of the roll of function C() out gives you
if (A) {
if (B)
C();
whatever();
}
The flowchart on the left is the one with the time update where the pigeons want it. The one on the right shows the movement to the X box of what will now make the system inconsistent and sluggish.
Your pseuedocode in that post clearly has the // same stuff comment, but from the "envelop" and "out-dent the millis() time-limited block" language it might not be excruciatingly clear that the timestamp setting needs to be within the inner scope and not outside in the enveloping scope of the time-limiting block.
(I'm sure I agree with you. Is there anything left of this horse?)
LOL, I'm sure you aren't just trying to make it stop.
You are too kind. I could have made it clear, period. Instead of sharing how my own brain works, something I have yet to learn is no a good idea.
No, haha. The takeway is as simple as "start a lockout timer when the thing you want to lock out happens".
Reset the debounce timer when you see an edge.
I recently tore apart a debounce library that looked plausible, but had one or more of these errors. The only context it ever worked correctly in had a loop throttle of some kind…
OK, found it. One error, which is that it acts on the positive edge, not both. So only one edge invokes the lockout period…
So instead of
if (buttonState != lastButtonState ) {
there was
if (!buttonState && lastButtonState ) {
I won't say how long I looked at the function before I even noticed, so closely to the idiomatic pattern did it hew.
Well they have four legs for a good reason, physical stability in the face of frequently applied finger pressure - 2 leg version would likely fatigue and break with a much shorter useful life.
The way to wire these switches without worry is diagonal, corner-to-corner, then you don't have to remember which way they are commoned up internally.
How about if, contrary to the Arduino tutorial, you don't rely on how the button pins are internally connected to rig the pull-down resistor? I think it would be clearer and cleaner to connect the pull-up/pulldown or current limiting resistors directly to the Arduino pins.