Ok! Next, clicks and holds. Whenever the button is relesed, that's a click. If a click occurs within the double-click threshold of a previous click, then a click counter should be incremented.
When a button is held for the long and short periods, we want to fire a "hold" message, but we only want to do this once.
And I'll get rid of that "Button held" message .
class Button {
private:
const byte pin;
const char* name;
byte state;
uint32_t debounceMs;
static const uint32_t DEBOUNCE_ms = 50;
static const uint32_t DOUBLECLICK_ms = 500;
static const uint32_t SHORTHOLD_ms = 1000;
static const uint32_t LONGHOLD_ms = 2000;
uint32_t prevClickMs;
uint32_t holdStartMs;
int clickCount = 0;
boolean shortHoldFired;
boolean longHoldFired;
public:
Button(const char *name, const byte pin) : pin(pin), name(name) {}
void setup() {
pinMode(pin, INPUT_PULLUP);
Serial.print("Button ");
Serial.print(name);
Serial.println(" set up.");
state = digitalRead(pin);
debounceMs = millis();
}
void loop() {
if (millis() - debounceMs < DEBOUNCE_ms) return;
byte prevState = state;
state = digitalRead(pin);
if (state != prevState) {
debounceMs = millis();
}
if (state == HIGH && prevState == LOW) {
Serial.print("Button ");
Serial.print(name);
Serial.println(" released.");
// button has just been released. We have a click.
if (millis() - prevClickMs <= DOUBLECLICK_ms) {
clickCount ++;
}
else {
clickCount = 1;
}
prevClickMs = millis();
Serial.print("Button ");
Serial.print(name);
Serial.print(" ");
Serial.print(clickCount);
Serial.println(" click(s)");
}
else if (state == LOW && prevState == HIGH) {
Serial.print("Button ");
Serial.print(name);
Serial.println(" pressed.");
// button pressed. time to start the hold counter
holdStartMs = millis();
shortHoldFired = false;
longHoldFired = false;
}
else if (state == LOW && prevState == LOW) {
// button is being held. Do we have a hold event?
if (!longHoldFired && millis() - holdStartMs >= LONGHOLD_ms) {
Serial.print("Button ");
Serial.print(name);
Serial.print(" long hold on ");
Serial.print(clickCount);
Serial.println(" click(s)");
longHoldFired = true;
}
else if (!shortHoldFired && millis() - holdStartMs >= SHORTHOLD_ms) {
Serial.print("Button ");
Serial.print(name);
Serial.print(" short hold on ");
Serial.print(clickCount);
Serial.println(" click(s)");
shortHoldFired = true;
}
}
}
};
Button button[] = {
Button("House", 2),
Button("Car", 3),
Button("Widget", 4)
};
const int BUTTONS = sizeof(button) / sizeof(*button);
void setup() {
Serial.begin(9600);
while (!Serial) ;
Serial.print("beginning sketch in 5");
for (int i = 4; i >= 0; i--) {
delay(500);
Serial.print(' ');
Serial.print(i);
}
Serial.println(" GO!");
for (int i = 0; i < BUTTONS; i++) button[i].setup();
}
void loop() {
for (int i = 0; i < BUTTONS; i++) button[i].loop();
}
Button Widget pressed.
Button Widget released.
Button Widget 1 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 2 click(s)
Button Widget pressed.
Button Car pressed.
Button Car released.
Button Car 1 click(s)
Button Widget short hold on 2 click(s)
Button Car pressed.
Button Car released.
Button Car 2 click(s)
Button Car pressed.
Button Car released.
Button Car 3 click(s)
Button Car pressed.
Button Widget long hold on 2 click(s)
Button Car short hold on 3 click(s)
Button Widget released.
Button Widget 1 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 2 click(s)
Button Car released.
Button Car 1 click(s)
Now, here we can see a bit of a … well, not an error, but a failure to specify what we want. The release at the end of a hold is treated like a click. Furthermore, it's always treated as a single-click because the hold is longer than the double-click timeout. Let's make a modification to keep it consistent.
class Button {
private:
const byte pin;
const char* name;
byte state;
uint32_t debounceMs;
static const uint32_t DEBOUNCE_ms = 50;
static const uint32_t DOUBLECLICK_ms = 500;
static const uint32_t SHORTHOLD_ms = 1000;
static const uint32_t LONGHOLD_ms = 2000;
uint32_t prevClickMs;
uint32_t holdStartMs;
int clickCount = 0;
boolean shortHoldFired;
boolean longHoldFired;
public:
Button(const char *name, const byte pin) : pin(pin), name(name) {}
void setup() {
pinMode(pin, INPUT_PULLUP);
Serial.print("Button ");
Serial.print(name);
Serial.println(" set up.");
state = digitalRead(pin);
debounceMs = millis();
}
void loop() {
if (millis() - debounceMs < DEBOUNCE_ms) return;
byte prevState = state;
state = digitalRead(pin);
if (state != prevState) {
debounceMs = millis();
}
if (state == HIGH && prevState == LOW) {
Serial.print("Button ");
Serial.print(name);
Serial.println(" released.");
// button has just been released. We have a click.
// unless we have done a hold, in which case we don't trteat this as a click.
if (shortHoldFired || longHoldFired) {
clickCount = 0;
}
else {
if (millis() - prevClickMs <= DOUBLECLICK_ms) {
clickCount ++;
}
else {
clickCount = 1;
}
Serial.print("Button ");
Serial.print(name);
Serial.print(" ");
Serial.print(clickCount);
Serial.println(" click(s)");
}
prevClickMs = millis();
}
else if (state == LOW && prevState == HIGH) {
Serial.print("Button ");
Serial.print(name);
Serial.println(" pressed.");
// button pressed. time to start the hold counter
holdStartMs = millis();
shortHoldFired = false;
longHoldFired = false;
}
else if (state == LOW && prevState == LOW) {
// button is being held. Do we have a hold event?
if (!longHoldFired && millis() - holdStartMs >= LONGHOLD_ms) {
Serial.print("Button ");
Serial.print(name);
Serial.print(" long hold on ");
Serial.print(clickCount);
Serial.println(" click(s)");
longHoldFired = true;
}
else if (!shortHoldFired && millis() - holdStartMs >= SHORTHOLD_ms) {
Serial.print("Button ");
Serial.print(name);
Serial.print(" short hold on ");
Serial.print(clickCount);
Serial.println(" click(s)");
shortHoldFired = true;
}
}
}
};
Button button[] = {
Button("House", 2),
Button("Car", 3),
Button("Widget", 4)
};
const int BUTTONS = sizeof(button) / sizeof(*button);
void setup() {
Serial.begin(9600);
while (!Serial) ;
Serial.print("beginning sketch in 5");
for (int i = 4; i >= 0; i--) {
delay(500);
Serial.print(' ');
Serial.print(i);
}
Serial.println(" GO!");
for (int i = 0; i < BUTTONS; i++) button[i].setup();
}
void loop() {
for (int i = 0; i < BUTTONS; i++) button[i].loop();
}
utton Widget pressed.
Button Widget released.
Button Widget 1 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 2 click(s)
Button House pressed.
Button Widget pressed.
Button Widget released.
Button Widget 3 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 4 click(s)
Button Widget pressed.
Button House released.
Button House 1 click(s)
Button House pressed.
Button House released.
Button House 2 click(s)
Button House pressed.
Button Widget short hold on 4 click(s)
Button House short hold on 2 click(s)
Button Widget long hold on 4 click(s)
Button House long hold on 2 click(s)
Button House released.
Button Widget released.
Much better