From 507f6e19373ed7a08be83c18632200ff290b2189 Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Thu, 27 Aug 2026 14:27:26 +1200 Subject: [PATCH 1/2] Add support for button.pressed_for(ms) --- README.md | 28 ++++++++++++++- examples/hold_button/hold_button.ino | 52 ++++++++++++++++++++++++++++ keywords.txt | 2 ++ src/Button.cpp | 22 ++++++++++++ src/Button.h | 2 ++ 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 examples/hold_button/hold_button.ino diff --git a/README.md b/README.md index 1738873..0e52b12 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Features ** when a button is pressed ** when a button is released ** or when a button changes (i.e. pressing or releasing) +** when a button has been held down for a length of time Requirements ------------ @@ -35,7 +36,7 @@ Installation ------------ Download the ZIP archive (https://github.com/madleech/Button/zipball/master), then open the Arduino IDE and choose Sketch > Include Library > Add .ZIP Library... and select your downloaded file. -You should now see in File > Examples > Button entires for the basic\_usage example. +You should now see in File > Examples > Button entires for the basic\_usage and hold\_button examples. Code Examples ------------- @@ -73,6 +74,25 @@ void loop() { } ``` +And here is a snippet from the 'hold\_button' example, which toggles the built-in LED whenever the button is held down for a second. The full example also reports how long the button has been held for using `held_time()`: + +```C++ +void loop() { + // Toggle the LED each time the button is held for a full second. + // pressed_for() is level-triggered, so we latch it to act once per hold. + if (button.pressed_for(1000)) { + if (!hold_handled) { + led_on = !led_on; + digitalWrite(LED, led_on ? HIGH : LOW); + Serial.println(led_on ? "LED on" : "LED off"); + hold_handled = true; + } + } else { + hold_handled = false; // re-arm once released + } +} +``` + Documentation ------------- **Button(int pin, int debounce_ms = 100)** @@ -87,6 +107,12 @@ Returns true when _and only when_ the button is pressed. Until the button is rel **bool released()** Like `pressed()`, but round the other way. So if you hold down a button, and then release it... that is when it fires. +**bool pressed_for(uint32_t duration_ms)** +Returns true while the button has been held down continuously for at least `duration_ms` milliseconds. Unlike `pressed()`, this is _level-triggered_: it keeps returning true on every call for as long as you keep the button held past the threshold. If you want to act only once per hold (e.g. toggle something), latch the result — see the `hold_button` example. Great for "press and hold for one second to..." style interactions. + +**uint32_t held_time()** +Returns how many milliseconds the button has been continuously pressed for, or `0` if it isn't currently pressed. Handy if you want to react to the length of a hold rather than a fixed threshold. + **bool toggled()** Returns true whenever the button is pressed or released, i.e., its position is toggled. To find out what the position actually is, you can use the `read()` function. diff --git a/examples/hold_button/hold_button.ino b/examples/hold_button/hold_button.ino new file mode 100644 index 0000000..0e2feaa --- /dev/null +++ b/examples/hold_button/hold_button.ino @@ -0,0 +1,52 @@ +#include + +Button button(2); // Connect your button between pin 2 and GND + +const uint8_t LED = LED_BUILTIN; + +bool led_on = false; +bool hold_handled = false; // latch so a single hold toggles the LED only once +uint32_t last_report = 0; + +void setup() +{ + button.begin(); + pinMode(LED, OUTPUT); + + while (!Serial) + { + }; // for Leos + Serial.begin(9600); +} + +void loop() +{ + // Toggle the LED each time the button is held for a full second. + // pressed_for() is level-triggered: it stays true on every loop while the + // button remains held past the threshold. We latch it here so we act on the + // rising edge only, and re-arm once the button is released. + if (button.pressed_for(1000)) + { + if (!hold_handled) + { + led_on = !led_on; + digitalWrite(LED, led_on ? HIGH : LOW); + Serial.println(led_on ? "LED on" : "LED off"); + hold_handled = true; + } + } + else + { + hold_handled = false; + } + + // While the button is held, report how long every 500ms. + uint32_t held = button.held_time(); + if (held > 0 && (uint32_t)(millis() - last_report) >= 500) + { + last_report = millis(); + Serial.print("Held for "); + Serial.print(held); + Serial.println(" ms"); + } +} diff --git a/keywords.txt b/keywords.txt index 5ba7851..7c8ae82 100644 --- a/keywords.txt +++ b/keywords.txt @@ -17,6 +17,8 @@ read KEYWORD2 toggled KEYWORD2 pressed KEYWORD2 released KEYWORD2 +pressed_for KEYWORD2 +held_time KEYWORD2 has_changed KEYWORD2 ####################################### diff --git a/src/Button.cpp b/src/Button.cpp index 8688b14..3f94ec7 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -73,3 +73,25 @@ bool Button::released() { return (read() == RELEASED && has_changed()); } + +// how long (ms) the button has been continuously in its current pressed state, +// or 0 if it is not currently pressed. _ignore_start holds the time of the last +// accepted transition, so while pressed it doubles as the press timestamp. +// Elapsed time uses unsigned (modular) subtraction, so it is correct across the +// ~49-day millis() rollover. +uint32_t Button::held_time() +{ + if (read() != PRESSED) + { + return 0; + } + return (uint32_t)(millis() - _ignore_start); +} + +// is the button currently pressed and has it been held for at least duration_ms. +// Level triggered: stays true for every call while the button remains held past +// the threshold, so callers that want a one-shot should latch the edge. +bool Button::pressed_for(uint32_t duration_ms) +{ + return (read() == PRESSED && held_time() >= duration_ms); +} diff --git a/src/Button.h b/src/Button.h index a6b56c2..8587eac 100644 --- a/src/Button.h +++ b/src/Button.h @@ -17,6 +17,8 @@ class Button bool toggled(); bool pressed(); bool released(); + bool pressed_for(uint32_t duration_ms); + uint32_t held_time(); bool has_changed(); const static bool PRESSED = LOW; From 00480576937ff801cf81e641c0d02635d44a9e3a Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Thu, 27 Aug 2026 14:34:36 +1200 Subject: [PATCH 2/2] Tweak variable ordering to save memory --- src/Button.cpp | 2 +- src/Button.h | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Button.cpp b/src/Button.cpp index 3f94ec7..9aaca9f 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -9,7 +9,7 @@ #include Button::Button(uint8_t pin, uint16_t debounce_ms) - : _pin(pin), _delay(debounce_ms), _state(HIGH), _ignore_start(0), _has_changed(false) + : _ignore_start(0), _delay(debounce_ms), _pin(pin), _state(HIGH), _has_changed(false) { } diff --git a/src/Button.h b/src/Button.h index 8587eac..b4c8199 100644 --- a/src/Button.h +++ b/src/Button.h @@ -25,10 +25,13 @@ class Button const static bool RELEASED = HIGH; private: - uint8_t _pin; + // ordered largest-first so the struct packs without alignment padding on + // 32-bit targets (12 bytes instead of 16; no effect on 8-bit AVR, which is + // unaligned and always 9 bytes) + uint32_t _ignore_start; uint16_t _delay; + uint8_t _pin; bool _state; - uint32_t _ignore_start; bool _has_changed; };