I have configured my pushbuttons to do one of 3 things:
If the holding time is longer than a threshold, immediately return value B, without waiting for release.
If, upon release, the holding time turns out to have been shorter than the threshold, return value A.
By default, if the button wasn't touched, or it was held for less than the debouncing threshold, return 0.
I'm happy with my function for the time being: in my current application, it doesn't matter that I have a blocking delay() and a blocking while loop. However, this is going to be an issue the day I want to add some functionality to my prototype. For a test, I added an LED that blinks at 20 Hz via a non-blocking state machine. Indeed, when I push a button, the blinking stops until I release the button.
I think I can manage to get rid of the delay(), but I'm having a hard time attempting to convert the while part into a non-blocking version that will give me the same behaviour as the one I have currently.
This is my original, working function:
int pollButton (struct Button* button)
{
bool state = digitalRead(button->pin);
button->pressDetected = false;
if (button->state != state)
{
delay(DEBOUNCE);
button->state = state;
button->timestamp = millis();
while (digitalRead(button->pin) == PRESSED)
{
button->pressDetected = true;
if (millis() - button->timestamp > LONG_PRESS)
{
button->pressDetected = false;
return 0 - button->weight;
}
}
}
if (button->pressDetected)
return button->weight;
return 0;
} // end pollButton()
And this is my failed attempt at a non-blocking version (the buttons are completely unresponsive when I use it):
int pollButtonNB (struct Button* button)
/* Non-blocking version */
{
bool state = digitalRead(button->pin);
button->pressDetected = false;
if (button->state != state)
{
button->timestamp = millis();
if (millis() - button->timestamp > DEBOUNCE &&
digitalRead(button->pin == PRESSED))
{
button->pressDetected = true;
button->state = PRESSED;
if (millis() - button->timestamp > LONG_PRESS)
{
button->timestamp = millis();
button->pressDetected = false;
return 0 - button->weight;
}
}
if (button->pressDetected && state == RELEASED)
{
button->pressDetected = false;
return button->weight;
}
}
return 0;
} //end pollButtonNB()
And this is how I invoke my function within the loop():
for (size_t i = 0; i < NUMBER_OF_BUTTONS; ++i)
{
int x = pollButtonNB(aButtons[i]);
//int x = pollButton(aButtons[i]);
if (x)
{
gCounter += x;
if (gCounter < 0) gCounter = 0;
gRefreshDisplay = true;
}
}
I think this will do what you want without blocking. You should be able to encapsulate this in an object and pass in callback functions for short-press and long-press.
const byte ButtonPin = 2;
const unsigned long DebounceTime = 30;
const unsigned long ButtonLongPressTime = 2000;
boolean ButtonWasPressed; // Defaults to 'false'
boolean ButtonWasLongPressed; // Defaults to 'false'
unsigned long ButtonStateChangeTime = 0; // Debounce/Long Press 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
ButtonWasLongPressed = false;
}
else
{
// Button was just released
if (!ButtonWasLongPressed)
{
// ACT ON SHORT PRESS
}
}
// Check to see if we are in a long-press
if (ButtonWasPressed && ! ButtonWasLongPressed &&
currentTime - ButtonStateChangeTime > ButtonLongPressTime)
{
ButtonWasLongPressed = true;
// ACT ON LONG PRESS
}
}
}
Thank you so much, your code does what I needed. I just had to adapt it to my naming scheme and add a member to my Button struct. I wasn't able to follow its logic at 100%, but at least, it made some sense!
Here is how my function and my new struct turned out to be, in case anyone is following this thread.
struct Button
{
const uint8_t pin:7;
const uint8_t weight:7;
bool pressDetected:1;
bool longPressDetected:1; /*this is new*/
bool state:1;
unsigned long timestamp;
};
int pollButtonNB (struct Button* button)
/* Non-blocking version */
{
unsigned long currentTime = millis();
bool buttonPressed = digitalRead(button->pin) == PRESSED;
if (buttonPressed != button->pressDetected &&
currentTime - button->timestamp > DEBOUNCE)
{
button->timestamp = currentTime;
button->pressDetected = buttonPressed;
if (button->pressDetected)
// Button was just pressed
button->longPressDetected = false;
else if (! button->longPressDetected)
// Button was just released
return button->weight;
}
if (button->pressDetected && ! button->longPressDetected &&
currentTime - button->timestamp > LONG_PRESS)
{
button->longPressDetected = true;
return 0 - button->weight;
}
return 0;
} //end pollButtonNB()
Now that you have a 'struct' you are very close to having a 'class'. The only difference is that the first entries in a 'struct' are public and the first entries in a 'class' are private. Start by moving the functions into the struct:
struct Button
{
Button(int pin, int weight) : pin(pin), weight(weight) {}
void begin() {pinMode(pin, INPUT_PULLUP);}
const uint8_t pin: 7;
const uint8_t weight: 7;
bool pressDetected: 1;
bool longPressDetected: 1; /*this is new*/
bool state: 1;
unsigned long timestamp;
int poll();
const int PRESSED = LOW;
const unsigned DEBOUNCE = 20;
const unsigned LONG_PRESS = 2000;
} WeightButton(2, 12); // Declare an instance of Button
// Now that 'poll' is a member of the Button struct, you
// don't have to pass a pointer to the struct.
int Button::poll() /* Non-blocking version */
{
unsigned long currentTime = millis();
bool buttonPressed = digitalRead(pin) == PRESSED;
if (buttonPressed != pressDetected &&
currentTime - timestamp > DEBOUNCE)
{
timestamp = currentTime;
pressDetected = buttonPressed;
if (pressDetected)
{
// Button was just pressed
longPressDetected = false;
}
else
{
// Button was just released
if (! longPressDetected) // Short press?
return weight;
}
if (pressDetected && ! longPressDetected &&
currentTime - timestamp > LONG_PRESS)
{
longPressDetected = true;
return 0 - weight;
}
return 0;
}
void setup()
{
WeightButton.begin();
}
void loop()
{
int wb_val = WeightButton.poll();
}
Now all you need to do to make it a 'class' is change: