1029 lines
40 KiB
Markdown
1029 lines
40 KiB
Markdown
# AceButton
|
|
|
|
An adjustable, compact, event-driven button library for Arduino platforms.
|
|
|
|
Version: 1.3.3 (2019-03-10)
|
|
|
|
[![AUniter Jenkins Badge](https://us-central1-xparks2018.cloudfunctions.net/badge?project=AceButton)](https://github.com/bxparks/AUniter)
|
|
|
|
## Summary
|
|
|
|
This library provides classes which accept inputs from a mechanical button
|
|
connected to a digital input pin on the Arduino. The library should be able to
|
|
handle momentary buttons, maintained buttons, and switches, but it was designed
|
|
primarily for momentary buttons.
|
|
|
|
The library is called the ACE Button Library (or AceButton Library) because:
|
|
|
|
* many configurations of the button are **adjustable**, either at compile-time
|
|
or run-time
|
|
* the library is optimized to create **compact** objects which take up
|
|
a minimal amount of static memory
|
|
* the library detects changes in the button state and sends **events** to
|
|
a user-defined `EventHandler` callback function
|
|
|
|
Most of the features of the library can be accessed through 2 classes and
|
|
1 callback function:
|
|
|
|
* `AceButton` (class)
|
|
* `ButtonConfig` (class)
|
|
* `EventHandler` (typedef)
|
|
|
|
The `AceButton` class contains the logic for debouncing and determining if a
|
|
particular event has occurred. The `ButtonConfig` class holds various timing
|
|
parameters, the event handler, code for reading the button, and code for
|
|
getting the internal clock. The `EventHandler` is a user-defined callback
|
|
function with a specific signature which is registered with the `ButtonConfig`
|
|
object. When the library detects interesting events, the callback function is
|
|
called by the library, allowing the client code to handle the event.
|
|
|
|
The supported events are:
|
|
|
|
* `AceButton::kEventPressed`
|
|
* `AceButton::kEventReleased`
|
|
* `AceButton::kEventClicked`
|
|
* `AceButton::kEventDoubleClicked`
|
|
* `AceButton::kEventLongPressed`
|
|
* `AceButton::kEventRepeatPressed`
|
|
|
|
(TripleClicked is not supported but can be easily added to the library if
|
|
requested.)
|
|
|
|
### Features
|
|
|
|
Here are the high-level features of the AceButton library:
|
|
|
|
* debounces the mechanical contact
|
|
* supports both pull-up and pull-down wiring
|
|
* event-driven through a user-defined `EventHandler` callback funcition
|
|
* supports 6 event types:
|
|
* Pressed
|
|
* Released
|
|
* Clicked
|
|
* DoubleClicked
|
|
* LongPressed
|
|
* RepeatPressed
|
|
* can distinguish between Clicked and DoubleClicked
|
|
* adjustable configurations at runtime or compile-time
|
|
* timing parameters
|
|
* `digitalRead()` button read function can be overridden
|
|
* `millis()` clock function can be overridden
|
|
* small memory footprint
|
|
* each `AceButton` consumes 14 bytes
|
|
* each `ButtonConfig` consumes 20 bytes
|
|
* one System `ButtonConfig` instance created automatically by the library
|
|
* thoroughly unit tested using [AUnit](https://github.com/bxparks/AUnit)
|
|
* properly handles reboots while the button is pressed
|
|
* properly handles orphaned clicks, to prevent spurious double-clicks
|
|
* only 13-15 microseconds (on 16MHz ATmega328P) per polling call to `AceButton::check()`
|
|
* can be instrumented to extract profiling numbers
|
|
* tested on Arduino AVR (UNO, Nano, etc), Teensy ARM (LC
|
|
and 3.2), ESP8266 and ESP32 platforms
|
|
|
|
Compared to other Arduino button libraries, I think the unique or exceptional
|
|
features of the AceButton library are:
|
|
|
|
* many supported event types (e.g. LongPressed and RepeatPressed)
|
|
* able to distinguish between Clicked and DoubleClicked
|
|
* small memory usage
|
|
* thorough unit testing
|
|
* proper handling of orphaned clicks
|
|
* proper handling of a reboot while button is pressed
|
|
|
|
### Non-goals
|
|
|
|
An Arduino UNO or Nano has 16 times more flash memory (32KB) than static memory
|
|
(2KB), so the library is optimized to minimize the static memory usage. The
|
|
AceButton library is not optimized to create a small program size (i.e. flash
|
|
memory), or for small CPU cycles (i.e. high execution speed). I assumed that if
|
|
you are seriously optimizing for program size or CPU cycles, you will probably
|
|
want to write everything yourself from scratch.
|
|
|
|
That said, the [examples/AutoBenchmark](examples/AutoBenchmark) program
|
|
shows that `AceButton::check()` takes between 13-15 microseconds on a 16MHz
|
|
ATmega328P chip on average. Hopefully that is fast enough for the vast
|
|
majority of people.
|
|
|
|
### HelloButton
|
|
|
|
Here is a simple program (see `examples/HelloButton.ino`) which controls
|
|
the builtin LED on the Arduino board using a momentary button connected
|
|
to PIN 2.
|
|
|
|
```C++
|
|
#include <AceButton.h>
|
|
using namespace ace_button;
|
|
|
|
const int BUTTON_PIN = 2;
|
|
const int LED_ON = HIGH;
|
|
const int LED_OFF = LOW;
|
|
|
|
AceButton button(BUTTON_PIN);
|
|
|
|
void handleEvent(AceButton*, uint8_t, uint8_t);
|
|
|
|
void setup() {
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
button.setEventHandler(handleEvent);
|
|
}
|
|
|
|
void loop() {
|
|
button.check();
|
|
}
|
|
|
|
void handleEvent(AceButton* /* button */, uint8_t eventType,
|
|
uint8_t /* buttonState */) {
|
|
switch (eventType) {
|
|
case AceButton::kEventPressed:
|
|
digitalWrite(LED_BUILTIN, LED_ON);
|
|
break;
|
|
case AceButton::kEventReleased:
|
|
digitalWrite(LED_BUILTIN, LED_OFF);
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
(The `button` and `buttonState` parameters are commented out to avoid an `unused
|
|
parameter` warning from the compiler. We can't remove the parameters completely
|
|
because the method signature is defined by the `EventHandler` typedef.)
|
|
|
|
## Installation
|
|
|
|
The latest stable release is available in the Arduino IDE Library Manager.
|
|
Search for "AceButton". Click install.
|
|
|
|
The development version can be installed by cloning the
|
|
[GitHub repository](https://github.com/bxparks/AceButton), checking out the
|
|
`develop` branch, then manually copying over the contents to the `./libraries`
|
|
directory used by the Arduino IDE. (The result is a directory named
|
|
`./libraries/AceButton`.) The `master` branch contains the stable release.
|
|
|
|
### Source Code
|
|
|
|
The source files are organized as follows:
|
|
* `src/AceButton.h` - main header file
|
|
* `src/ace_button/` - all implementation files
|
|
* `src/ace_button/testing/` - internal testing files
|
|
* `tests/` - unit tests which require [AUnit](https://github.com/bxparks/AUnit)
|
|
* `examples/` - example sketches
|
|
|
|
### Docs
|
|
|
|
Besides this README.md file, the [docs/](docs/) directory contains the
|
|
[Doxygen docs published on GitHub Pages](https://bxparks.github.io/AceButton/html/).
|
|
It can help you navigate an unfamiliar code base.
|
|
|
|
### Examples
|
|
|
|
The following example sketches are provided:
|
|
|
|
* [HelloButton.ino](examples/HelloButton)
|
|
* minimal program that reads a switch and control the built-in LED
|
|
* [SingleButton.ino](examples/SingleButton)
|
|
* controls a single button wired with a pull-up resistor
|
|
* prints out a status line for every supported event
|
|
* [SingleButtonPullDown.ino](examples/SingleButtonPullDown)
|
|
* same as SingleButton.ino but with an external pull-down resistor
|
|
* [Stopwatch.ino](examples/Stopwatch)
|
|
* measures the speed of `AceButton:check()` with a start/stop/reset button
|
|
* uses `kFeatureLongPress`
|
|
* [TunerButtons.ino](examples/TunerButtons)
|
|
* implements 5 radio buttons (tune-up, tune-down, and 3 presets)
|
|
* shows multiple `ButtonConfig` instances
|
|
* shows multiple `EventHandler`s
|
|
* shows an example of how to use `getId()`
|
|
* uses `kFeatureLongPress`, `kFeatureRepeatPress`,
|
|
`kFeatureSuppressAfterLongPress`, and `kFeatureSuppressAfterRepeatPress`
|
|
* [ClickVersusDoubleClickUsingReleased.ino](examples/ClickVersusDoubleClickUsingReleased)
|
|
* a way to distinguish between a `kEventClicked` from a
|
|
`kEventDoubleClicked` using a `kEventReleased` instead
|
|
* [ClickVersusDoubleClickUsingSuppression.ino](examples/ClickVersusDoubleClickUsingSuppression)
|
|
* another way to dstinguish between a `kEventClicked` from a
|
|
`kEventDoubleClicked` using the `kFeatureSuppressClickBeforeDoubleClick`
|
|
flag at the cost of increasing the response time of the `kEventClicked`
|
|
event
|
|
* [ClickVersusDoubleClickUsingBoth.ino](examples/ClickVersusDoubleClickUsingBoth)
|
|
* an example that combines both the "UsingPressed" and "UsingSuppression"
|
|
techniques
|
|
* [CapacitiveButton](examples/CapacitiveButton)
|
|
* reads a capacitive button using the
|
|
[CapacitiveSensor](https://github.com/PaulStoffregen/CapacitiveSensor)
|
|
library
|
|
* [AutoBenchmark.ino](examples/AutoBenchmark)
|
|
* generates the timing stats (min/average/max) for the `AceButton::check()`
|
|
method for various types of events (idle, press/release, click,
|
|
double-click, and long-press)
|
|
|
|
## Usage
|
|
|
|
There are 2 classes and one typedef that a user will normally interact with:
|
|
|
|
* `AceButton` (class)
|
|
* `ButtonConfig` (class)
|
|
* `EventHandler` (typedef)
|
|
|
|
We explain how to use these below.
|
|
|
|
### Include Header and Use Namespace
|
|
|
|
Only a single header file `AceButton.h` is required to use this library.
|
|
To prevent name clashes with other libraries that the calling code may use, all
|
|
classes are defined in the `ace_button` namespace. To use the code without
|
|
prepending the `ace_button::` prefix, use the `using` directive:
|
|
|
|
```C++
|
|
#include <AceButton.h>
|
|
using namespace ace_button;
|
|
```
|
|
|
|
If you are dependent on just `AceButton`, the following might be sufficient:
|
|
|
|
```C++
|
|
#include <AceButton.h>
|
|
using ace_button::AceButton;
|
|
```
|
|
|
|
### Pin Wiring and Initialization
|
|
|
|
An Arduino microcontroller pin can be in an `OUTPUT` mode, an `INPUT` mode, or
|
|
an `INPUT_PULLUP` mode. This mode is controlled by the `pinMode()` method.
|
|
|
|
By default upon boot, the pin is set to the `INPUT` mode. However, this `INPUT`
|
|
mode puts the pin into a high impedance state, which means that if there is no
|
|
wire connected to the pin, the voltage on the pin is indeterminant. When the
|
|
input pin is read (using `digitalRead()`), the boolean value will be a random
|
|
value. If you are using the pin in `INPUT` mode, you *must* connect an external
|
|
pull-up resistor (connected to Vcc) or pull-down resistor (connected to ground)
|
|
so that the voltage level of the pin is defined when there is nothing connected
|
|
to the pin (i.e. when the button is not pressed).
|
|
|
|
The `INPUT_PULLUP` mode is a special `INPUT` mode which tells the
|
|
microcontroller to connect an internal pull-up resistor to the pin. It is
|
|
activated by calling `pintMode(pin, INPUT_PULLUP)` on the given `pin`. This mode
|
|
is very convenient because it eliminates the external resistor, making the
|
|
wiring simpler.
|
|
|
|
The AceButton library itself does *not* call the `pinMode()` function. The
|
|
calling application is responsible for calling `pinMode()`. Normally, this
|
|
happens in the global `setup()` method but the call can happen somewhere else if
|
|
the application requires it. The reason for decoupling the hardware
|
|
configuration from the AceButton library is mostly because the library does not
|
|
actually care about the specific hardware wiring of the button. It does not care
|
|
whether an external resistor is used, or the internal resistor is used. It only
|
|
cares about whether the resistor is a pull-up or a pull-down.
|
|
|
|
See https://www.arduino.cc/en/Tutorial/DigitalPins for additional information
|
|
about the I/O pins on an Arduino.
|
|
|
|
### AceButton Class
|
|
|
|
Each physical button will be handled by an instance of `AceButton`. At a
|
|
minimum, the instance needs to be told the pin number of the button. This can
|
|
be done through the constructor:
|
|
|
|
```C++
|
|
const uint8_t BUTTON_PIN = 2;
|
|
|
|
AceButton button(BUTTON_PIN);
|
|
|
|
void setup() {
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
...
|
|
}
|
|
```
|
|
|
|
Or we can use the `init()` method in the `setup()`:
|
|
|
|
```C++
|
|
AceButton button;
|
|
|
|
void setup() {
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
button.init(BUTTON_PIN);
|
|
...
|
|
}
|
|
```
|
|
|
|
Both the constructor and the `init()` function take 3 optional parameters:
|
|
```C++
|
|
AceButton(uint8_t pin = 0, uint8_t defaultReleasedState = HIGH, uint8_t id = 0);
|
|
|
|
void init(uint8_t pin = 0, uint8_t defaultReleasedState = HIGH, uint8_t id = 0);
|
|
```
|
|
|
|
* `pin`: the I/O pin number assigned to the button
|
|
* `defaultReleasedState`: the logical value of the button when it is in its
|
|
default "released" state (`HIGH` using a pull-up resistor,
|
|
`LOW` for a pull-down pull-down resistor)
|
|
* `id`: an optional, user-defined identifier for the the button,
|
|
for example, an index into an array with additional information
|
|
|
|
The `pin` must be defined either through the constructor or the `init()` method.
|
|
But the other two parameters may be optional in many cases.
|
|
|
|
Finally, the `AceButton::check()` method should be called from the `loop()`
|
|
method periodically. Roughly speaking, this should be about 4 times faster than
|
|
the value of `getDebounceDelay()` so that the various event detection logic can
|
|
work properly. (If the debounce delay is 20 ms, `AceButton::check()` should be
|
|
called every 5 ms or faster.)
|
|
|
|
```C++
|
|
void loop() {
|
|
...
|
|
button.check();
|
|
...
|
|
}
|
|
```
|
|
|
|
### ButtonConfig Class
|
|
|
|
The core concept of the AceButton library is the separation of the
|
|
button (`AceButton`) from its configuration (`ButtonConfig`).
|
|
|
|
* The `AceButton` class has the logic for debouncing and detecting the various
|
|
events (Pressed, Released, etc), and the various bookkeeping variables
|
|
needed to implement the logic. These variables are associated with the
|
|
specific instance of that `AceButton`.
|
|
* The `ButtonConfig` class has the various timing parameters which control
|
|
how much time is needed to detect certain events. This class also has the
|
|
ability to override the default methods for reading the pin (`readButton()`)
|
|
and the clock (`getClock()`). This ability allows unit tests to be written.
|
|
|
|
The `ButtonConfig` can be created and assigned to one or more `AceButton`
|
|
instances using dependency injection through the `AceButton(ButtonConfig*)`
|
|
constructor. If this constructor is used, then the `AceButton::init()` method
|
|
must be used to set the pin number of the button. For example:
|
|
|
|
```C++
|
|
const uint8_t PIN1 = 2;
|
|
const uint8_t PIN2 = 4;
|
|
|
|
ButtonConfig buttonConfig;
|
|
AceButton button1(&buttonConfig);
|
|
AceButton button2(&buttonConfig);
|
|
|
|
void setup() {
|
|
pinMode(PIN1, INPUT_PULLUP);
|
|
button1.init(PIN1);
|
|
|
|
pinMode(PIN2, INPUT_PULLUP);
|
|
button2.init(PIN2);
|
|
...
|
|
}
|
|
```
|
|
|
|
Another way to inject the `ButtonConfig` dependency is to use the
|
|
`AceButton::setButtonConfig()` method but it is recommended that you use the
|
|
constructor instead because the dependency is easier to follow.
|
|
|
|
#### System ButtonConfig
|
|
|
|
A single instance of `ButtonConfig` called the "System ButtonConfig" is
|
|
automatically created by the library at startup. By default, all instances of
|
|
`AceButton` are automatically assigned to this singleton instance. We explain in
|
|
the _Single Button Simplifications_ section below how this simplifies the code
|
|
needed to handle a single button.
|
|
|
|
#### Configuring the EventHandler
|
|
|
|
The `ButtonConfig` class provides a number of methods which are mostly
|
|
used internally by the `AceButton` class. The one method which is expected
|
|
to be used by the calling client code is `setEventHandler()` which
|
|
assigns the user-defined `EventHandler` callback function to the `ButtonConfig`
|
|
instance. This is explained in more detail below in the
|
|
_EventHandler Callback_ section.
|
|
|
|
#### Timing Parameters
|
|
|
|
Here are the methods to retrieve the timing parameters:
|
|
|
|
* `uint16_t getDebounceDelay();` (default: 20 ms)
|
|
* `uint16_t getClickDelay();` (default: 200 ms)
|
|
* `uint16_t getDoubleClickDelay();` (default: 400 ms)
|
|
* `uint16_t getLongPressDelay();` (default: 1000 ms)
|
|
* `uint16_t getRepeatPressDelay();` (default: 1000 ms)
|
|
* `uint16_t getRepeatPressInterval();` (default: 200 ms)
|
|
|
|
The default values of each timing parameter can be changed at run-time using
|
|
the following methods:
|
|
|
|
* `void setDebounceDelay(uint16_t debounceDelay);`
|
|
* `void setClickDelay(uint16_t clickDelay);`
|
|
* `void setDoubleClickDelay(uint16_t doubleClickDelay);`
|
|
* `void setLongPressDelay(uint16_t longPressDelay);`
|
|
* `void setRepeatPressDelay(uint16_t repeatPressDelay);`
|
|
* `void setRepeatPressInterval(uint16_t repeatPressInterval);`
|
|
|
|
#### Hardware Dependencies
|
|
|
|
The `ButtonConfig` class has 2 methods which provide hooks to its external
|
|
hardware dependencies:
|
|
|
|
* `virtual unsigned long getClock();`
|
|
* `virtual int readButton(uint8_t pin);`
|
|
|
|
By default these are mapped to the underlying Arduino system functions respectively:
|
|
|
|
* `millis()`
|
|
* `digitalRead()`
|
|
|
|
Unit tests are possible because these methods are `virtual` and the hardware
|
|
dependencies can be swapped out with fake ones.
|
|
|
|
#### Multiple ButtonConfig Instances
|
|
|
|
We have assumed that there is a 1-to-many relationship between a `ButtonConfig`
|
|
and the `AceButton`. In other words, multiple buttons will normally be
|
|
associated with a single configuration. Each `AceButton` has a pointer to an
|
|
instance of `ButtonConfig`. So the cost of separating the `ButtonConfig` from
|
|
`AceButton` is 2 bytes in each instance of `AceButton`. Note that this is
|
|
equivalent to adding virtual methods to `AceButton` (which would add 2 bytes),
|
|
so in terms of static RAM size, this is a wash.
|
|
|
|
The library is designed to handle multiple buttons, and it assumes that the
|
|
buttons are normally grouped together into a handful of types. For example,
|
|
consider the buttons of a car radio. It has several types of buttons:
|
|
|
|
* the tuner buttons (2, up and down)
|
|
* the preset buttons (6)
|
|
* the AM/FM band button (1)
|
|
|
|
In this example, there are 9 buttons, but only 3 instances of `ButtonConfig`
|
|
would be needed.
|
|
|
|
### EventHandler
|
|
|
|
The event handler is a callback function that gets called when the `AceButton`
|
|
class determines that an interesting event happened on the button. The
|
|
advantage of this mechanism is that all the complicated logic of determining
|
|
the various events happens inside the `AceButton` class, and the user will
|
|
normally not need to worry about the details.
|
|
|
|
#### EventHandler Signature
|
|
|
|
The event handler has the following signature:
|
|
|
|
```C++
|
|
typedef void (*EventHandler)(AceButton* button, uint8_t eventType,
|
|
uint8_t buttonState);
|
|
```
|
|
|
|
The event handler is registered with the `ButtonConfig` object, not with the
|
|
`AceButton` object, although the convenience method
|
|
`AceButton::setEventHandler()` is provided as a pass-through to the underlying
|
|
`ButtonConfig` (see the _Single Button Simplifications_ section below):
|
|
|
|
```C++
|
|
ButtonConfig buttonConfig;
|
|
|
|
void handleEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
|
|
...
|
|
}
|
|
|
|
void setup() {
|
|
...
|
|
buttonConfig.setEventHandler(handleEvent);
|
|
...
|
|
}
|
|
```
|
|
|
|
The motivation for this design is to save static memory. If multiple buttons
|
|
are associated with a single `ButtonConfig`, then it is not necessary for every
|
|
button of that type to hold the same pointer to the `EventHandler` function. It
|
|
is only necessary to save that information once, in the `ButtonConfig` object.
|
|
|
|
**Pro Tip**: Comment out the unused parameter(s) in the `handleEvent()` method
|
|
to avoid the `unused parameter` compiler warning:
|
|
```C++
|
|
void handleEvent(AceButton* /* button */, uint8_t eventType,
|
|
uint8_t /* buttonState */) {
|
|
...
|
|
}
|
|
```
|
|
The Arduino sketch compiler can get confused with the parameters commented out,
|
|
so you may need to add a forward declaration for the `handleEvent()` method
|
|
before the `setup()` method:
|
|
```C++
|
|
void handleEvent(AceButton*, uint8_t, uint8_t);
|
|
```
|
|
|
|
#### EventHandler Parameters
|
|
|
|
The `EventHandler` function receives 3 parameters from the `AceButton`:
|
|
|
|
* `aceButton`
|
|
* pointer to the `AceButton` instance that generated this event
|
|
* can be used to retrieve the `getPin()` or the `getId()`
|
|
* `eventType`
|
|
* the type of this event given by the various `AceButton::kEventXxx`
|
|
constants
|
|
* `buttonState`
|
|
* the `HIGH` or `LOW` button state that generated this event
|
|
|
|
The `aceButton` pointer should be used only to extract information about the
|
|
button that triggered the event. It should **not** be used to modify the
|
|
button's internal variables in any way within the eventHandler. The logic in
|
|
`AceButton::check()` assumes that those internal variable are held constant,
|
|
and if they are changed by the eventHandler, unpredictable results may occur.
|
|
(I was tempted to make the `aceButton` a pointer to a `const AceButton`
|
|
but this cause too many viral changes to the code which seemed to increase
|
|
the complexity without too much benefit.)
|
|
|
|
If you are using only a single button, then you should need to check
|
|
only the `eventType`.
|
|
|
|
It is not expected that `buttonState` will be needed very often. It should be
|
|
sufficient to examine just the `eventType` to determine the action that needs
|
|
to be performed. Part of the difficulty with this parameter is that it has the
|
|
value of `LOW` or `HIGH`, but the physical interpretation of those values depends
|
|
on whether the button was wired with a pull-up or pull-down resistor. The helper
|
|
function `AceButton::isReleased(uint8_t buttonState)` is provided to make this
|
|
determination if you need it.
|
|
|
|
#### One EventHandler
|
|
|
|
Only a single `EventHandler` per `ButtonConfig` is supported. An alternative
|
|
would have been to register a separate event handler for each of the 6
|
|
`kEventXxx` events. But each callback function requires 2 bytes of memory, and
|
|
it was assumed that in most cases, the calling client code would be interested
|
|
in only a few of these event types, so it seemed wasteful to allocate 12 bytes
|
|
when most of these would be unused. If the client code really wanted separate
|
|
event handlers, it can be easily emulated by invoking them through the main
|
|
event handler:
|
|
|
|
```C++
|
|
void handleEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
|
|
switch (eventType) {
|
|
case AceButton:kEventPressed:
|
|
handleEventPressed(button, eventType, buttonState);
|
|
break;
|
|
case AceButton::kEventReleased:
|
|
handleEventReleased(button, eventType, buttonState);
|
|
break;
|
|
...
|
|
}
|
|
}
|
|
```
|
|
|
|
#### EventHandler Tips
|
|
|
|
The Arduino runtime environment is single-threaded, so the `EventHandler` is
|
|
called in the middle of the `AceButton::check()` method, in the same thread as
|
|
the `check()` method. It is therefore important to write the `EventHandler`
|
|
code to run somewhat quickly, so that the delay doesn't negatively impact the
|
|
logic of the `AceButton::check()` algorithm. Since `AceButton::check()` should
|
|
run approximately every 5 ms, the user-provided `EventHandler` should run
|
|
somewhat faster than 5 ms. Given a choice, it is probably better to use the
|
|
`EventHandler` to set some flags or variables and return quickly, then do
|
|
additional processing from the `loop()` method.
|
|
|
|
Speaking of threads, the API of the AceButton Library was designed to work in a
|
|
multi-threaded environment, if that situation were to occur in the Arduino
|
|
world.
|
|
|
|
### Event Types
|
|
|
|
The supported events are defined by a list of constants in `AceButton`:
|
|
|
|
* `AceButton::kEventPressed` (always enabled)
|
|
* `AceButton::kEventReleased` (conditionally enabled)
|
|
* `AceButton::kEventClicked` (default: disabled)
|
|
* `AceButton::kEventDoubleClicked` (default: disabled)
|
|
* `AceButton::kEventLongPressed` (default: disabled)
|
|
* `AceButton::kEventRepeatPressed` (default: disabled)
|
|
|
|
These values are sent to the `EventHandler` in the `eventType` parameter.
|
|
|
|
Two of the events are enabled by default, four are disabled by default but can
|
|
be enabled by using a Feature flag described below.
|
|
|
|
### ButtonConfig Feature Flags
|
|
|
|
There are 9 flags defined in `ButtonConfig` which can
|
|
control the behavior of `AceButton` event handling:
|
|
|
|
* `ButtonConfig::kFeatureClick`
|
|
* `ButtonConfig::kFeatureDoubleClick`
|
|
* `ButtonConfig::kFeatureLongPress`
|
|
* `ButtonConfig::kFeatureRepeatPress`
|
|
* `ButtonConfig::kFeatureSuppressAfterClick`
|
|
* `ButtonConfig::kFeatureSuppressAfterDoubleClick`
|
|
* `ButtonConfig::kFeatureSuppressAfterLongPress`
|
|
* `ButtonConfig::kFeatureSuppressAfterRepeatPress`
|
|
* `ButtonConfig::kFeatureSuppressClickBeforeDoubleClick`
|
|
* `ButtonConfig::kFeatureSuppressAll`
|
|
|
|
These constants are used to set or clear the given flag:
|
|
|
|
```C++
|
|
ButtonConfig* config = button.getButtonConfig();
|
|
|
|
config->setFeature(ButtonConfig::kFeatureLongPress);
|
|
|
|
config->clearFeature(ButtonConfig::kFeatureLongPress);
|
|
|
|
if (config->isFeature(ButtonConfig::kFeatureLongPress)) {
|
|
...
|
|
}
|
|
```
|
|
|
|
The meaning of these flags are described below.
|
|
|
|
#### Event Activation
|
|
|
|
Of the 6 event types, 4 are disabled by default:
|
|
|
|
* `AceButton::kEventClicked`
|
|
* `AceButton::kEventDoubleClicked`
|
|
* `AceButton::kEventLongPressed`
|
|
* `AceButton::kEventRepeatPressed`
|
|
|
|
To receive these events, call `ButtonConfig::setFeature()` with the following
|
|
flags respectively:
|
|
|
|
* `ButtonConfig::kFeatureClick`
|
|
* `ButtonConfig::kFeatureDoubleClick`
|
|
* `ButtonConfig::kFeatureLongPress`
|
|
* `ButtonConfig::kFeatureRepeatPress`
|
|
|
|
To disable these events, call `ButtonConfig::clearFeature()` with one of these
|
|
flags.
|
|
|
|
Enabling `kFeatureDoubleClick` automatically enables `kFeatureClick`, because we
|
|
need to have a Clicked event before a DoubleClicked event can be detected.
|
|
|
|
It seems unlikely that both `LongPress` and `RepeatPress` events would be
|
|
useful at the same time, but both event types can be activated if you need it.
|
|
|
|
#### Event Suppression
|
|
|
|
Event types can be considered to be built up in layers, starting with the
|
|
lowest level primitive events: Pressed and Released. Higher level events are
|
|
built on top of the lower level events through various timing delays. When a
|
|
higher level event is detected, it is sometimes useful to suppress the lower
|
|
level event that was used to detect the higher level event.
|
|
|
|
For example, a Clicked event requires a Pressed event followed by a Released
|
|
event within a `ButtonConfig::getClickDelay()` milliseconds (200 ms by
|
|
default). The Pressed event is always generated. If a Clicked event is
|
|
detected, we could choose to generate both a Released event and a Clicked
|
|
event, and this is the default behavior.
|
|
|
|
However, many times, it is useful to suppress the Released event if the Clicked
|
|
event is detected. The `ButtonConfig` can be configured to suppress these lower
|
|
level events. Call the `setFeature(feature)` method passing the various
|
|
`kFeatureSuppressXxx` constants:
|
|
|
|
* `ButtonConfig::kFeatureSuppressAfterClick`
|
|
* suppresses the Released event after a Clicked event is detected
|
|
* also suppresses the Released event from the *first* Clicked of a
|
|
DoubleClicked, since `kFeatureDoubleClick` automatically enables
|
|
`kFeatureClick`
|
|
* `ButtonConfig::kFeatureSuppressAfterDoubleClick`
|
|
* suppresses the Released event and the *second* Clicked event if a
|
|
DoubleClicked event is detected
|
|
* `ButtonConfig::kFeatureSuppressAfterLongPress`
|
|
* suppresses the Released event if a LongPressed event is detected
|
|
* `ButtonConfig::kFeatureSuppressAfterRepeatPress`
|
|
* suppresses the Released event after the last RepeatPressed event
|
|
* `ButtonConfig::kFeatureSuppressAll`
|
|
* a convenience parameter that is the equivalent of suppressing all of the
|
|
previous events
|
|
* `ButtonConfig::kFeatureSuppressClickBeforeDoubleClick`
|
|
* The *first* Clicked event is postponed by `getDoubleClickDelay()`
|
|
millis until the code can determine if a DoubleClick has occurred. If so,
|
|
then the postponed Clicked message to the `EventHandler` is suppressed.
|
|
* See the section ___Distinguishing Between a Clicked and DoubleClicked___
|
|
for more info.
|
|
|
|
By default, no suppression is performed.
|
|
|
|
As an example, to suppress the `Released` event after a `LongPressed` event
|
|
(this is actually often the case), you would do this:
|
|
|
|
```C++
|
|
ButtonConfig* config = button.getButtonConfig();
|
|
config->setFeature(ButtonConfig::kFeatureSuppressAfterLongPress);
|
|
```
|
|
|
|
The special convenient constant `kFeatureSuppressAll` is equivalent of using all
|
|
suppression constants:
|
|
|
|
```C++
|
|
ButtonConfig* config = button.getButtonConfig();
|
|
config->setFeature(ButtonConfig::kFeatureSuppressAll);
|
|
```
|
|
|
|
All suppressions can be cleared by using:
|
|
```C++
|
|
ButtonConfig* config = button.getButtonConfig();
|
|
config->clearFeature(ButtonConfig::kFeatureSuppressAll);
|
|
```
|
|
|
|
Note, however, that the `isFeature(ButtonConfig::kFeatureSuppressAll)` currently
|
|
means "isAnyFeature() implemented?" not "areAllFeatures() implemented?" We don't
|
|
expect `isFeature()` to be used often (or at all) for `kFeatureSuppressAll`.
|
|
|
|
### Distinguishing Between a Clicked and DoubleClicked
|
|
|
|
On a project using only a small number of buttons (due to physical limits or the
|
|
limited availability of pins), it may be desirable to distinguish between a
|
|
single Clicked event and a DoubleClicked event from a single button. This is a
|
|
challenging problem to solve because fundamentally, a DoubleClicked event *must
|
|
always* generate a Clicked event, because a Clicked event must happen before it
|
|
can become a DoubleClicked event.
|
|
|
|
Notice that on a desktop computer (running Windows, MacOS or Linux), a
|
|
double-click on a mouse always generates both a Clicked and a DoubleClicked. The
|
|
first Click selects the given desktop object (e.g. an icon or a window), and
|
|
the DoubleClick performs some action on the selected object (e.g. open the
|
|
icon, or resize the window).
|
|
|
|
The AceButton Library provides 3 solutions which may work for some projects:
|
|
|
|
**Method 1:** The `kFeatureSuppressClickBeforeDoubleClick` flag causes the first
|
|
Clicked event to be detected, but the posting of the event message (i.e. the
|
|
call to the `EventHandler`) is postponed until the state of the DoubleClicked
|
|
can be determined. If the DoubleClicked happens, then the first Clicked event
|
|
message is suppressed. If DoubleClicked does not occur, the long delayed
|
|
Clicked message is sent via the `EventHandler`.
|
|
|
|
There are two noticeable disadvantages of this method. First, the response time
|
|
of all Clicked events is delayed by about 600 ms (`kClickDelay +
|
|
kDoubleClickDelay`) whether or not the DoubleClicked event happens. Second, the
|
|
user may not be able to accurately produce a Clicked event (due to the physical
|
|
characteristics of the button, or the user's dexterity).
|
|
|
|
It may also be worth noting that only the Clicked event is postponed.
|
|
The accompanying Released event of the Clicked event is not postponed. So a
|
|
single click action (without a DoubleClick) produces the following sequence of
|
|
events to the EventHandler:
|
|
|
|
1. `kEventPressed` - at time 0ms
|
|
1. `kEventReleased` - at time 200ms
|
|
1. `kEventClicked` - at time 600ms (200ms + 400ms)
|
|
|
|
The `ButtonConfig` configuration looks like this:
|
|
```C++
|
|
ButtonConfig* buttonConfig = button.getButtonConfig();
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
|
|
buttonConfig->setFeature(
|
|
ButtonConfig::kFeatureSuppressClickBeforeDoubleClick);
|
|
```
|
|
|
|
See the example code at
|
|
`examples/ClickVersusDoubleClickUsingSuppression/`.
|
|
|
|
**Method 2:** A viable alternative is to use the Released event instead of the
|
|
Clicked event to distinguish it from the DoubleClicked. For this method to work,
|
|
we need to suppress the Released event after both Clicked and DoubleClicked.
|
|
|
|
The advantage of using this method is that there is no response time lag in the
|
|
handling of the Released event. To the user, there is almost no difference
|
|
between triggering on the Released event, versus triggering on the Clicked
|
|
event.
|
|
|
|
The disadvantage of this method is that the Clicked event must be be ignored
|
|
(because of the spurious Clicked event generated by the DoubleClicked). If the
|
|
user accidentally presses and releases the button to quickly, it generates a
|
|
Clicked event, which will cause the program to do nothing.
|
|
|
|
The `ButtonConfig` configuration looks like this:
|
|
```C++
|
|
ButtonConfig* buttonConfig = button.getButtonConfig();
|
|
buttonConfig->setEventHandler(handleEvent);
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
|
|
```
|
|
|
|
See the example code at
|
|
`examples/ClickVersusDoubleClickUsingReleased/`.
|
|
|
|
**Method 3:** We could actually combine both Methods 1 and 2 so that either
|
|
Released or a delayed Click is considered to be a "Click". This may be the best
|
|
of both worlds.
|
|
|
|
The `ButtonConfig` configuration looks like this:
|
|
```C++
|
|
ButtonConfig* buttonConfig = button.getButtonConfig();
|
|
buttonConfig->setEventHandler(handleEvent);
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
|
|
buttonConfig->setFeature(
|
|
ButtonConfig::kFeatureSuppressClickBeforeDoubleClick);
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
|
|
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
|
|
```
|
|
|
|
See the example code at
|
|
`examples/ClickVersusDoubleClickUsingBoth/`.
|
|
|
|
### Single Button Simplifications
|
|
|
|
Although the AceButton library is designed to shine for multiple buttons, you
|
|
may want to use it to handle just one button. The library provides some features
|
|
to make this simple case easy.
|
|
|
|
1. The library automatically creates one instance of `ButtonConfig`
|
|
called a "System ButtonConfig". This System ButtonConfig can be retrieved
|
|
using the class static method `ButtonConfig::getSystemButtonConfig()`.
|
|
1. Every instance of `AceButton` is assigned an instance of the System
|
|
ButtonConfig by default (which can be overridden manually).
|
|
1. A convenience method allows the `EventHandler` for the System
|
|
ButtonConfig to be set easily through `AceButton` itself, instead of having
|
|
to get the System ButtonConfig first, then set the event handler. In other
|
|
words, `button.setEventHandler(handleEvent)` is a synonym for
|
|
`button.getButtonConfig()->setEventHandler(handleEvent)`.
|
|
|
|
These simplifying features allow a single button to be configured and used like
|
|
this:
|
|
|
|
```C++
|
|
AceButton button(BUTTON_PIN);
|
|
|
|
void setup() {
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
button.setEventHandler(handleEvent);
|
|
...
|
|
}
|
|
|
|
void loop() {
|
|
button.check();
|
|
}
|
|
|
|
void handleEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
|
|
...
|
|
}
|
|
```
|
|
|
|
To configure the System ButtonConfig, you may need to add something like
|
|
this to the `setup()` section:
|
|
|
|
```C++
|
|
button.getButtonConfig()->setFeature(ButtonConfig::kFeatureLongPress);
|
|
```
|
|
|
|
### Multiple Buttons
|
|
|
|
When transitioning from a single button to multiple buttons, it's important to
|
|
remember what's happening underneath the convenience methods. The single
|
|
`AceButton` button is assigned to the System ButtonConfig that was created
|
|
automatically. When an `EventHandler` is assigned to the button, it is actually
|
|
assigned to the System ButtonConfig. All subsequent instances of `AceButton`
|
|
will also be associated with this event handler, unless another `ButtonConfig`
|
|
is explicitly assigned.
|
|
|
|
See the example sketch `TunerButtons.ino` to see how to use multiple
|
|
`ButtonConfig` instances with multiple `AceButton` instances.
|
|
|
|
### Events After Reboot
|
|
|
|
A number of edge cases occur when the microcontroller is rebooted:
|
|
|
|
* if the button is held down, should the Pressed event be triggered?
|
|
* if the button is in its natural Released state, should the Released event
|
|
happen?
|
|
* if the button is Pressed down, and `ButtonConfig` is configured to
|
|
support RepeatPress events, should the `kEventRepeatPressed` events
|
|
be triggered initially?
|
|
|
|
I think most users would expect that in all these cases, the answer is no, the
|
|
microcontroller should not trigger an event until the button undergoes a
|
|
human-initiated change in state. The AceButton library implements this logic.
|
|
(It might be useful to make this configurable using a `ButtonConfig` feature
|
|
flag but that is not implemented.)
|
|
|
|
On the other hand, it is sometimes useful to perform some special action if a
|
|
button is pressed while the device is rebooted. To support this use-case, call
|
|
the `AceButton::isPressedRaw()` in the global `setup()` method (after the
|
|
button is configured). It will directly call the `digitalRead()` method
|
|
associated with the button pin and return `true` if the button is in the
|
|
Pressed state.
|
|
|
|
### Orphaned Clicks
|
|
|
|
When a Clicked event is generated, the `AceButton` class looks for a
|
|
second Clicked event within a certain time delay (default 400 ms) to
|
|
determine if the second Clicked event is actually a DoubleClicked event.
|
|
|
|
All internal timestamps in `AceButton` are stored as `uint16_t`
|
|
(i.e. an unsigned integer of 16 bits) in millisecond units. A 16-bit
|
|
unsigned counter rolls over after 65536 iterations. Therefore, if the second
|
|
Clicked event happens between (65.636 seconds, 66.036 seconds) after the first
|
|
Clicked event, a naive-logic would erroneously consider the (long-delayed)
|
|
second click as a double-click.
|
|
|
|
The `AceButton` contains code that prevents this from happening.
|
|
|
|
Note that even if the `AceButton` class uses an `unsigned long` type (a 32-bit
|
|
integer on the Arduino), the overflow problem would still occur after `2^32`
|
|
milliseconds (i.e. 49.7 days). To be strictly correct, the `AceButton` class
|
|
would still need logic to take care of orphaned Clicked events.
|
|
|
|
## Resource Consumption
|
|
|
|
Here are the sizes of the various classes on the 8-bit AVR microcontrollers
|
|
(Arduino Uno, Nano, etc):
|
|
|
|
* sizeof(AceButton): 14
|
|
* sizeof(ButtonConfig): 20
|
|
|
|
(An early version of `AceButton`, with only half of the functionality, consumed
|
|
40 bytes. It got down to 11 bytes before additional functionality increased it
|
|
to 14.)
|
|
|
|
**Program size:**
|
|
|
|
[LibrarySizeBenchmark](examples/LibrarySizeBenchmark/) was used to determine
|
|
the size of the library. For a single button, the library consumed:
|
|
* flash memory: 1100-1330 bytes
|
|
* static memory: 14-28 bytes
|
|
|
|
depending on the target board. See the README.md in the above link for more
|
|
details.
|
|
|
|
**CPU cycles:**
|
|
|
|
The profiling numbers for `AceButton::check()` can be found in
|
|
[examples/AutoBenchmark](examples/AutoBenchmark).
|
|
|
|
In summary, the average numbers for various boards are:
|
|
* Arduino Nano: 13-15 microsesconds
|
|
* Teensy 3.2: 3 microseconds
|
|
* ESP8266: 8-9 microseconds
|
|
* ESP32: 2-3 microseconds
|
|
|
|
## System Requirements
|
|
|
|
This library was developed and tested using:
|
|
* [Arduino IDE 1.8.5 - 1.8.7](https://www.arduino.cc/en/Main/Software)
|
|
* [Teensyduino 1.41](https://www.pjrc.com/teensy/td_download.html)
|
|
* [ESP8266 Arduino Core 2.4.1 - 2.4.2](https://arduino-esp8266.readthedocs.io/en/2.4.2/)
|
|
* [arduino-esp32](https://github.com/espressif/arduino-esp32)
|
|
|
|
I used MacOS 10.13.3 and Ubuntu Linux 17.10 for most of my development.
|
|
|
|
The library has been verified to work on the following hardware:
|
|
|
|
* Arduino Nano clone (16 MHz ATmega328P)
|
|
* Arduino UNO R3 clone (16 MHz ATmega328P)
|
|
* Arduino Pro Micro clone (16 MHz ATmega32U4)
|
|
* Teensy LC (48 MHz ARM Cortex-M0+)
|
|
* Teensy 3.2 (72 MHz ARM Cortex-M4)
|
|
* NodeMCU 1.0 clone (ESP-12E module, 80MHz ESP8266)
|
|
* ESP32 Dev Module (ESP-WROOM-32 module, 240MHz dual core Tensilica LX6)
|
|
|
|
## Background Motivation
|
|
|
|
There are numerous "button" libraries out there for the Arduino. Why write
|
|
another one? I wanted to add a button to an addressable strip LED controller,
|
|
which was being refreshed at 120 Hz. I had a number of requirements:
|
|
|
|
* the button needed to support a LongPress event, in addition to the simple
|
|
Press and Release events
|
|
* the button code must not interfere with the LED refresh code which was
|
|
updating the LEDs at 120 Hz
|
|
* well-tested, I didn't want to be hunting down random and obscure bugs
|
|
|
|
Since the LED refresh code needed to run while the button code was waiting for
|
|
a "LongPress" delay, it seemed that the cleanest API for a button library
|
|
would use an event handler callback mechanism. This reduced the number of
|
|
candidate libraries to a handful. Of these, only a few of them supported a
|
|
LongPress event. I did not find the remaining ones flexible enough for my
|
|
button needs in the future. Finally, I knew that it was tricky to write correct
|
|
code for debouncing and detecting various events (e.g. DoubleClick, LongPress,
|
|
RepeatPress). I looked for a library that contained unit tests, and I found
|
|
none.
|
|
|
|
I decided to write my own and use the opportunity to learn how to create and
|
|
publish an Arduino library.
|
|
|
|
## Changelog
|
|
|
|
See [CHANGELOG.md](CHANGELOG.md).
|
|
|
|
## License
|
|
|
|
* Versions 1.0 to 1.0.6: [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
|
* Versions 1.1 and above: [MIT License](https://opensource.org/licenses/MIT)
|
|
|
|
I changed to the MIT License starting with version 1.1 because the MIT License
|
|
is so simple to understand. I could not be sure that I understood what the
|
|
Apache License 2.0 meant.
|
|
|
|
## Feedback and Support
|
|
|
|
If you have any questions, comments, bug reports, or feature requests, please
|
|
file a GitHub ticket or send me an email. I'd love to hear about how this
|
|
software and its documentation can be improved. Instead of forking the
|
|
repository to modify or add a feature for your own projects, let me have a
|
|
chance to incorporate the change into the main repository so that your external
|
|
dependencies are simpler and so that others can benefit. I can't promise that I
|
|
will incorporate everything, but I will give your ideas serious consideration.
|
|
|
|
## Author
|
|
|
|
Created by Brian T. Park (brian@xparks.net).
|