This needs a combination of patterns.
First, the button pin needs to be debounced. This can be done with the "blink-without-delay"-pattern where the button pin is sampled with a low period (40 ms in the example code below).
Second, a simple state-machine is needed to keep track of the button state but also if the return button action function, doReturnButton(), has already been called.
The button is assumed to be connected from GND to pin4. The button action function, doButton(), is called when the button is pushed (goes LOW).
const int buttonPin = 4;
const unsigned long debounceInterval = 40;
const unsigned long returnInverval = 400;
bool buttonState = true;
bool returnButtonState = false;
unsigned long buttonDownMillis = 0L;
void doButton()
{
Serial.print(buttonDownMillis);
Serial.println(F(":doButton"));
}
void doReturnButton()
{
Serial.print(buttonDownMillis);
Serial.println(F(":doReturnButton"));
}
void setup()
{
Serial.begin(57600);
while (!Serial);
pinMode(buttonPin, INPUT_PULLUP);
}
void loop()
{
unsigned long currentMillis = millis();
static unsigned long previousMillis = 0L;
// Check if it is time to sample the button pin
if (currentMillis - previousMillis >= debounceInterval) {
previousMillis = currentMillis;
// Did the button change
if (digitalRead(buttonPin) != buttonState) {
buttonState = !buttonState;
// Was the button released
if (buttonState) {
returnButtonState = false;
}
// Was the button pushed
else {
buttonDownMillis = currentMillis;
returnButtonState = true;
doButton();
}
}
// Check if button was pushed and time for return action
else if (returnButtonState) {
if (currentMillis - buttonDownMillis >= returnInverval) {
returnButtonState = false;
doReturnButton();
}
}
}
}
What does the code do?
- It samples the button pin with a debounce interval.
- If the button state changes:
a. High to Low it calls the doButton() function and captures a timestamp. It also allows detect of return time interval.
b. Low to High. Return time interval is not allowed any more.
- It checks if the return time interval has elapsed and if so calls the doReturnButton() function. The detect is then not allowed any more.
The doButton() function is called when the button is pushed (transition high to low), only once. The doReturnButton() function is called if the return time limit exceeds and the button is still low. Both are only called once per push (transition high to low).
To avoid the multiple transitions that occur when a button is pushed a debounce time period is used. The can also be done in hardware with a RC-circuit.
With some abstraction it could be rewritten as:
#include <Timemark.h>
Timemark debounce(40);
Timemark buttonHold(400);
const int buttonPin = 4;
bool buttonState;
void doButton()
{
Serial.println(F("doButton"));
}
void doReturnButton()
{
Serial.println(F("doReturnButton"));
}
void setup()
{
Serial.begin(9600);
while (!Serial);
Serial.println(F("started"));
pinMode(buttonPin, INPUT_PULLUP);
buttonState = digitalRead(buttonPin);
debounce.start();
}
void loop()
{
if (debounce.expired()) {
bool currentState = digitalRead(buttonPin);
if (currentState != buttonState) {
buttonState = currentState;
if (buttonState) {
if (buttonHold.expired())
doReturnButton();
else
doButton();
buttonHold.stop();
}
else {
buttonHold.start();
}
}
}
}
This also demonstrates the callback on the release of the button. The Timemark class hides all the time intervals, expire calculations, etc.
Cheers!
millis()
– Greenonline Feb 9 '16 at 4:17