Hi all! This is my first post… I have been reading for a while now and learning lots, but I have come across a problem that has me well and truly stumped. I hope someone can help…
I am trying to wire up a button to my Arduino which when I will press. Upon release of the button, the program should work out how long the button was pressed for, and then execute different functions based on this time.
The major problem I have is bouncing, so I have implemented some debounce code, which works well, but only on the button press. I seem to be falling down with the “how long was it pressed for?” part. Every approach I have taken seems to not work. Here is the code I have so far to show what I’m trying to do… I really hope someone can see the issue as I’m pulling my hair out! The code works in that the first function runs and the menu page is moved, but if I press and hold the second function to clear the page does not, and then the menu skips when it shouldn’t. Any ideas??
Thanks!
int buttonState;
int lastButtonState = LOW;
int ButtonPressTime;
int menu;
unsigned long lastDebounceTime = 0;
unsigned long DebounceDelay = 200;
int PressHoldShort = 2000;
void setup() {
pinMode(buttonPin, INPUT);
}
void loop() {
int reading = digitalRead(MenuPin);
if (reading != lastButtonState) {
lastDebounceTime = millis(); // reset the debouncing timer
menuincrement = 1;
}
if (reading == LOW && (millis() - lastDebounceTime) > DebounceDelay && (millis() - lastDebounceTime) < PressHoldShort && menuincrement == 1) { //compare current debounce time against value set
menu++;
menuincrement = 0;
ResetState = reading;
}
if (reading == LOW && (millis() - lastDebounceTime) > PressHoldShort && menuincrement == 1) { //compare current debounce time against value set
ResetState = reading;
clearstats();
}
if (menu == 14) menu = 1;
}
You are failing to split the “determine how long the button was pressed” and the decision making based on that.
As long as the button is still pressed you can not determine how long the button will be pressed in total. Aka, you can not preform an action until the button is also released. The exception is is the button is pressed longer than you specified for the longest pressed action.
So split the two. Make code to determine is the button was pressed (aka, i released again), store that time and set a flag. Next you can check what action to do.
And my tip to make debounce easy, grab a library like Bounce2 But even then remember you van only decide it was a short press action after the button is released again.
if (reading == LOW && (millis() - lastDebounceTime) > DebounceDelay && (millis() - lastDebounceTime) < PressHoldShort && menuincrement == 1) //compare current debounce time against value set
Whilst it is tempting to write big, compound tests like this it is much easier to debug if you write it as a series of separate tests because you are able to determine which one(s) return true when expected to, or not.
Personally I would take the test for how long the button had been pressed out of the compound test. Considering that you are dealing with a “short” button press of 2 seconds do you need to debounce the input at all ?
Save the value of millis() when the button becomes pressed and save it again when it becomes released. Subtract one from the other to determine how long the button has been pressed
Thanks both! Very much appreciate the input. I will split the code out and see where I get.
To the point of do I need it debouncing at all... I'm running at 96mhz and it seems to pick up a few debounces I think up to about 120ms. So the problem I face is that I'm struggling to figure out how to flag when the button is definitely pressed, and then when it is definitely unpressed, given that it will probably flicker on both events.
You start from being idle, you receive a press (so pin goes LOW because of the input pull-up), you enter a theoretical bouncing state for a certain time. If the pin goes back HIGH after that time it's a glitch, you can't consider the button was really pressed, but if the pin is still LOW, then you can consider the button to be pressed.
Then same goes upon release
The resulting code for this small state machine (with everything coded so that it's easy to read, no optimization / code factoring) could be:
enum : byte {BUTTON_IDLE, BUTTON_BOUCING_DOWN, BUTTON_PRESSED, BUTTON_BOUNCING_UP} buttonState;
enum buttonAction_t : byte {BUTTON_NOCHANGE, BUTTON_DOWN, BUTTON_GLITCH_DOWN, BUTTON_DOWN_CONFIRMED, BUTTON_UP, BUTTON_GLITCH_UP, BUTTON_UP_CONFIRMED};
const byte buttonPin = 7;
const unsigned long buttonTimeOut = 15; // ms
buttonAction_t testButton()
{
static unsigned long startTime = 0;
buttonAction_t result = BUTTON_NOCHANGE;
byte bState = digitalRead(buttonPin);
switch (buttonState) {
case BUTTON_IDLE:
if (bState == LOW) {
startTime = millis();
buttonState = BUTTON_BOUCING_DOWN;
result = BUTTON_DOWN;
}
break;
case BUTTON_BOUCING_DOWN:
if (millis() - startTime >= buttonTimeOut) {
if (bState == LOW) {
result = BUTTON_DOWN_CONFIRMED;
buttonState = BUTTON_PRESSED;
} else {
result = BUTTON_GLITCH_DOWN;
buttonState = BUTTON_IDLE;
}
}
break;
case BUTTON_PRESSED:
if (bState == HIGH) {
startTime = millis();
buttonState = BUTTON_BOUNCING_UP;
result = BUTTON_UP;
}
break;
case BUTTON_BOUNCING_UP:
if (millis() - startTime >= buttonTimeOut) {
if (bState == LOW) {
result = BUTTON_GLITCH_UP;
buttonState = BUTTON_PRESSED;
} else {
result = BUTTON_UP_CONFIRMED;
buttonState = BUTTON_IDLE;
}
}
break;
}
return result;
}
void setup()
{
Serial.begin(115200);
pinMode(buttonPin, INPUT_PULLUP);
}
void loop()
{
buttonAction_t buttonState = testButton();
switch (buttonState) {
case BUTTON_NOCHANGE: break;
case BUTTON_DOWN: Serial.println(F("BUTTON_DOWN")); break;
case BUTTON_GLITCH_DOWN: Serial.println(F("BUTTON_GLITCH_DOWN")); break;
case BUTTON_DOWN_CONFIRMED: Serial.println(F("BUTTON_DOWN_CONFIRMED")); break;
case BUTTON_UP: Serial.println(F("BUTTON_UP")); break;
case BUTTON_GLITCH_UP: Serial.println(F("BUTTON_GLITCH_UP")); break;
case BUTTON_UP_CONFIRMED: Serial.println(F("BUTTON_UP_CONFIRMED")); break;
}
}
UKHeliBob:
Can you please clarify what length of button press periods you are aiming to measure and how many different periods ?
Looking for:
short press (<1 second) for menu change
medium press (1>5 seconds) for partial screen reset
long press (>5 seconds) for full screen reset reset
The function called will then depend which menu page (1-13) the medium and long presses were recorded on. If the medium or long press are recorded then the page should not change.
Like I said, split the debounce (and maybe even the duration calculation) from the decision making. Especially, like you said, that can include other parameters as well (like menu item).
Ok thank you very much for your help! I am definitely getting somewhere, but am getting some weird results…
I have setup Bounce2 like this:
pinMode( MenuPin, INPUT );
// After setting up the button, setup the Bounce instance :
menubutton.attach(MenuPin);
menubutton.interval(80); // De-bounce time of x milliseconds
Then the main bit is below. I have added some screen display to show me what the button press duration is and I’m getting some odd results. Firstly, the screen shows up a number before I have released the button. Secondly, it seems to show the duration from the previous button press, so I’m one step out of phase… How is that happening??
I did leave out the “buttonPressedDuration >= 5000” part with a reason Because the first part of the if, else if statement already checked for that. So at that point ‘buttonPressedDuration’ can never be < 5000
About a third of the way down the page is a heading called "Switch Bounce".
He tests for a press 3 times in succession and if all 3 readings are the same then assumes that it is a press and not bounce. I adopted his approach and get rock solid consistency for what I am doing.
If you just do 3 readings directly in sequence it will not be much of a debounce. It's all about how to determine the time passed between the same readings. There are multiple ways which can give you good result with pro's and cons. Good article about it on HackADay. But if you don't have specific requirement except if to work and be simple, most libraries will do. And in my opinion, especially in the "be simple" department Bounce2 is great.
The delays were only temporary so I can read the screen - will delete all of that in the final code
LastMenuChange I put in as a test - without it, when pressing the button it seemed to often travel through more than 1 page - sometimes 2 or 3. So I raised the debounce interval to over 100 but the button became a bit slow to use then. So I experimented with a debounce of around 50 and a further argument to not move the page if it had already moved within say 250ms. Seemed to make it a little more stable.
I tried putting in the >= 5000 part because I was getting some odd results. If I pressed the button momentarily it would sometimes trigger the mid press case or the long press even with the durations set way high (40000).
No, that's pull down. Still don't know why that crappy tutorial is up there. So that would explain some or the weirdness. fell() and rose() point to the electrical state of a pin, not the button state. A happy coincidence is that if corresponds to the button state as well if a normally open push button is wired with a pull up. Which is very common.
If your circuit is still on a breadboard I would suggest to:
Remove the resistor
Place the button only between a pin and GND
call pinMode(pin, INPUT_PULLUP) in setup for that pin (or use button.attach(pin, INPUT_PULLUP) if using Bounce2).
450nick:
The delays were only temporary so I can read the screen - will delete all of that in the final code
Now would be a good time They will mess with the rest. And atm you only update after a button press (or at least that's what the snippets show and that's the trouble with snippets over a MCVE). So no need to randomly wait.
450nick:
LastMenuChange I put in as a test - without it, when pressing the button it seemed to often travel through more than 1 page - sometimes 2 or 3.
If the debounce is correct and you only cycle trough pages when a button became pressed/released (and not IF a button is pressed/or released) that should not happen. So remove it and fix the real problem
But I think for now, try to make a MCVE to get your head around everything. Drop the real menu's and just try to increment a "page" variable the way you like and trigger other prints with long and medium presses. Once you get the hang of it, it will be easy to implement in your project.
YOU HERO! The button was causing all of the problems... It works like a dream now, super pleased! I've been trying to figure that out for about 3 days. Thank you thank you thank you!