Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------
Expand All @@ -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
-------------
Expand Down Expand Up @@ -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)**
Expand All @@ -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.

Expand Down
52 changes: 52 additions & 0 deletions examples/hold_button/hold_button.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <Button.h>

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");
}
}
2 changes: 2 additions & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ read KEYWORD2
toggled KEYWORD2
pressed KEYWORD2
released KEYWORD2
pressed_for KEYWORD2
held_time KEYWORD2
has_changed KEYWORD2

#######################################
Expand Down
24 changes: 23 additions & 1 deletion src/Button.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <stdint.h>

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)
{
}

Expand Down Expand Up @@ -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);
}
9 changes: 7 additions & 2 deletions src/Button.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ 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;
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;
};

Expand Down
Loading