EButton is short for "Event Button". I wrote this library in need of a reliable, compact driver for controlling a single key in
my Arduino projects, with debouncing being properly handled, and fine grained, well defined events. Besides that, I wanted a
library that I would be able to easily strip of unneeded features, making its memory footprint as small as possible.
- Include the library. (doh!)
- Instantiate the
EButtonobject, specfying a pin (connected to one lead of the button, while its other lead is connected to GND. - Write handler method(s).
- Attaching handlers.
- Keep executing the object's
tick()method in a loop. It does all the magic - reads button state changes and triggers appropriate events when detected.
#include "Arduino.h"
// 1. Include the library
#include "EButton.h"
// 2. Instantiate the object and attach it to PIN 2
EButton button(2);
// 3. Handler method for a single-click
void singleClick(EButton &btn) {
Serial.println("We have a single-click!");
}
void setup() {
Serial.begin(115200);
// 4. Attach the handler
button.attachSingleClick(singleClick);
Serial.println("\nClick or double-click!");
}
void loop() {
// 5. Tick the object in a loop
button.tick();
}The class defines two major event types - a Click and a Long-Press:
- Click - an event when the button is released (except when coming from a Long-Press state);
- Long-Press - when the button is kept pressed at least for a time specified by parameter called
longPressTime;
NOTE: A Click event is not triggered when a button is getting pressed, but rather when it gets released. This is done on purpose, in order to be able to tell apart a Click from a Long-Press event. That is possible only after a long-press timeout, or when the key is released before the timeout. In either case - it can not be at the moment of the key getting pressed.
It is important to define how long the class will be counting clicks, to tell apart if there were two discrete clicks, or one double-click (or even a 5-click if you're fast enough).
The parameter called clickTime is a value in milliseconds, defining how long will the driver wait since the last click, before
wrapping up the count. It does not mean that all the clicks have to be performed in that period, but it does specify the maxium
allowed distance between two adjacent clicks.
There are 3 of 8 handler slots available for defining custom methods that act on a given number of clicks:
singleClickMethod- Called if there was a single click;doubleClickMethod- Called if there was a double click;doneClickingMethod- Called when clicks counting is done. UsegetClicks()to get the clicks count.
Events are listed in their order of appearence:
TRANSITION- triggered each time the button state changes from pressed to released, or back;EACH_CLICK- triggered each time the key is released, unless it was inLONG_PRESSEDstate;DONE_CLICKING- triggered after all the clicks have been counted (usegetClicks()to get the clicks count);SINGLE_CLICK- triggered when there was exactly one click;DOUBLE_CLICK- triggered when there were exactly two clicks;LONG_PRESS_STARTtriggered once, at the beginning of a long press (after aTRANSITIONto pressed);DURING_LONG_PRESS- triggered on each next tick() while inLONG_PRESSEDstate (not in the same cycle withLONG_PRESS_START);LONG_PRESS_END- triggered once, at the end of a long press (after aTRANSITIONto released).
Generally, in most of cases it will be enough to handle a SINGLE_CLICK, but that is up to you.
NOTE: It is important to stress the difference between
EACH_CLICKandDONE_CLICKING: the first one is called each time the key is released (unless it was a long-press), whileDONE_CLICKINGis called once, at the end of clicks counting.
Events DONE_CLICKING, SINGLE_CLICK, and DOUBLE_CLICK will be triggered only after the last click.
Therefore, if you perform a click, immediatelly followed by a long press, the DONE_CLICKING and SINGLE_CLICK events will not be
triggered, and that is by design! Instead, the following sequence of events will be triggered:
TRANSITION(on the first key-down)TRANSITION(on fthe irst key-release)EACH_CLICK(after the TRANSITION of the first-key release)TRANSITION(on the second key-down)LONG_PRESS_START(1 second after the key was pressed and held pressed)DURING_LONG_PRESS(on each next tick until the key is released)LONG_PRESS_END(when the key is finally released)
Furhermore, after the long-Press has ended, the button's state will be reset. This will result in any further clicks that might be following the long press, being interpreted as a new series of clicks, separate from the previous sequence.
For each event there is a slot where you can slip your own methods that is triggered when a corresponding event is detected.
A handler method can be any method returning void and accepting one EButton& parameter.
void doneClicking(EButton &btn) {
Serial.print("Counted clicks: ");
Serial.println(btn.getClicks());
}All handler methods are optional, and initially set to NULL.
NOTE: Trigger methods should be short and fast. Instead of running a complex operation in a handler method, rather use it to set a flag indicating that a specific operation has to be performed later on in the code. This especially applies to attempting recursive calls to the
tick()method from within a handler method - which should be avoided, because it will most likely end in tears!
If the memory becomes an issue in your project, you can easily decrease the driver's footprint by disabling support for unneeded events. That way you can make significant savings in your memory-critical projects.
To disable a feature, just add its corresponding #define entry before importing the driver's header file for the very first time,
and don't forget to clean the project after making such a change:
#define EBUTTON_SUPPORT_TRANSITION_DISABLED
#define EBUTTON_SUPPORT_EACH_CLICK_DISABLED
#define EBUTTON_SUPPORT_DONE_CLICKING_DISABLED
#define EBUTTON_SUPPORT_SINGLE_AND_DOUBLE_CLICKS_DISABLED
#define EBUTTON_SUPPORT_LONG_PRESS_START_DISABLED
#define EBUTTON_SUPPORT_LONG_PRESS_DURING_DISABLED
#define EBUTTON_SUPPORT_LONG_PRESS_END_DISABLEDNOTE: If you disable
EBUTTON_SUPPORT_SINGLE_AND_DOUBLE_CLICKS, then you can use theDONE_CLICKINGevent to process single, double, and any other number of clicks. Just usegetClicks()to get the final clicks count. You can then also disable all other features that you don't need.Another way of getting a small footprint in simple cases where you just need to detect each click, regardless their count, is to disable all features except the
EBUTTON_SUPPORT_EACH_CLICK.
CAVEAT: In some assemblers you might get unwanted results and crashes when using a
DISABLEDdefine, due to the badly implemented include/compiling order. If you experience unexplicable crashes and strange behavior, try removing all theDISABLEDdefines and recompile the code.
Due to imperfections of electrical contacts, in most buttons and switches, the state does not just go from one state to another and stays there. Instead, there is always a certain period (depending on the actual switch, between a few and several dozens of milliseconds), in which the contact oscillates between a high and a low state.
Therefore, the easiest way to get a reliable reading, is to wait after the first detected change of state for a certain period of time
(debounceTime), before you get the second reading. Debouncing alway occurs at a change of state, and should not normally occur in
a stable state.
The driver uses a default debounce value defined with the EBUTTON_DEFAULT_DEBOUNCE which is set to a safe value of 50 ms, but you
can always change that value using the setDebounceTime method. Its parameter is a byte, since a debounce value should never go above
255.
This example just listens for single and double clicks.
#include "Arduino.h"
#include "EButton.h"
EButton button(2);
void singleClick(EButton &btn) {
Serial.println("We have a click!");
}
void doubleClick(EButton &btn) {
Serial.println("We have a double click!");
}
void setup() {
Serial.begin(115200);
button.attachSingleClick(singleClick);
button.attachDoubleClick(doubleClick);
Serial.println("\nClick or double-click!");
}
void loop() {
button.tick();
}This example will give you a good picture about the order in which the events are processed.
#include "Arduino.h"
#include "EButton.h"
EButton button(2);
// ------- Printing event details --------
void print(EButton &btn) {
Serial.print(F(" [pressed="));
Serial.print(btn.isButtonPressed());
Serial.print(F(", clicks="));
Serial.print(btn.getClicks());
Serial.print(F(", startTime="));
Serial.print(btn.getStartTime());
Serial.print(F(", prevTransitionTime="));
Serial.print(btn.getPrevTransitionTime());
Serial.println(F("]"));
}
// ------- Handler methods --------
void transitionHandler(EButton &btn) {
Serial.print(F("TRANSITION"));
print(btn);
}
void eachClickHandler(EButton &btn) {
Serial.print(F("EACH_CLICK"));
print(btn);
}
void singleClickHandler(EButton &btn) {
Serial.print(F("SINGLE_CLICK"));
print(btn);
}
void doubleClickHandler(EButton &btn) {
Serial.print(F("DOUBLE_CLICK"));
print(btn);
}
void doneClickingHandler(EButton &btn) {
Serial.print(F("DONE_CLICKING"));
print(btn);
}
void pressStartHandler(EButton &btn) {
Serial.print(F("PRESS_START"));
print(btn);
}
unsigned long t;
void duringPressHandler(EButton &btn) {
if ((unsigned long) (millis() - t) > 1000) {
// Print once a second
Serial.print(F("DURING_PRESS"));
print(btn);
t = millis();
}
}
void pressEndHandler(EButton &btn) {
Serial.print(F("PRESS_END"));
print(btn);
}
// ------- Setting up the driver and registering listeners --------
void setup() {
Serial.begin(115200);
Serial.println(F("\nEButton Demo"));
button.setDebounceTime(EBUTTON_DEFAULT_DEBOUNCE); // not required if using default
button.setClickTime(EBUTTON_DEFAULT_CLICK); // not required if using default
button.setLongPressTime(EBUTTON_DEFAULT_LONG_PRESS); // not required if using default
button.attachTransition(transitionHandler);
button.attachEachClick(eachClickHandler);
button.attachDoneClicking(doneClickingHandler);
button.attachSingleClick(singleClickHandler);
button.attachDoubleClick(doubleClickHandler);
button.attachLongPressStart(pressStartHandler);
button.attachDuringLongPress(duringPressHandler);
button.attachLongPressEnd(pressEndHandler);
}
void loop() {
// Ticking the driver in a loop
button.tick();
}This simple "game" just counts how fast you can click in a sequence.
#include "Arduino.h"
#include "EButton.h"
EButton button(2);
void countClicks(EButton &btn) {
Serial.print("\nYou've managed to click ");
Serial.print(btn.getClicks());
Serial.println(" time(s)!");
}
void setup() {
Serial.begin(115200);
button.attachDoneClicking(countClicks);
Serial.println("\nClick as fast as you can!");
}
void loop() {
button.tick();
}typedef void (*EButtonEventHandler)(EButton&);- references to handler methods.
-
EButton(byte pin, bool pressedLow = true)- Constructor specifying attached pin and used logic.pressedLowistrue, if using a pull-down switch. -
voidsetDebounceTime(byte time)- Setting debounce time in milliseconds. Default isEBUTTON_DEFAULT_DEBOUNCE. -
voidsetClickTime(byte time)- Setting delay after the button was released, when clicks counting ends. In other words, this is a delay before triggeringsingleClick,doubleClick, ordoneClicking event. Default isEBUTTON_DEFAULT_CLICK. -
voidsetLongPressTime(byte time)- Setting minimum time that the button has to be pressed in order to start LONG_PRESSED state. Default isEBUTTON_DEFAULT_LONG_PRESS. -
voidattachTransition(EButtonEventHandler methods)- Attaches a method that is triggered on each transition (state change). -
voidattachEachClick(EButtonEventHandler methods)- Attaches a method that is triggered each time the key goes up (gets released), while not in LONG_PRESSED state. -
voidattachDoneClicking(EButtonEventHandler methods)- Attaches a method that is triggered after all the clicks have been counted. -
voidattachSingleClick(EButtonEventHandler methods)- Attaches a method that is triggered when there was exactly one click. -
voidattachDoubleClick(EButtonEventHandler methods)- Attaches a method that is triggered when there were exactly two clicks. -
voidattachLongPressStart(EButtonEventHandler methods)- Attaches a method that is triggered once, at the beginning of a long press. -
voidattachDuringLongPress(EButtonEventHandler methods)- Attaches a method that is triggered on eachtick()during a long press. -
voidattachLongPressEnd(EButtonEventHandler methods)- Attaches a method that is triggered once, at the end of a long press. -
voidreset()- Reset state. -
voidtick()- Update/tick the button. This method has to be called in a loop. -
bytegetPin()- Returns attached pin number. -
bytegetClicks()- Returns number of performed clicks. -
boolisButtonPressed()- Test if the button was pressed the last time it was sampled. -
boolisLongPressed()- Test it the button is in long-pressed state. -
unsigned longgetStartTime()- Returns the time of the first button press. -
unsigned longgetPrevTransitionTime()- Time of a previous transition. Returns startTime for the first transition.
booloperator==(EButton &other)- Tests if the two have the same address.
1.0.0 (2017-02-18): Original release1.1.0 (2017-02-23): Discrete enabling/disabling START, DURING and END support for LONG_PRESS1.2.0 (2019-07-26): Changed way of disabling features, to allow specific per-project settings, without having to change the EButton.h file1.2.1 (2022-02-01): Added missing #defines to the comments in EButton.h and the keywords.txt files1.3.0 (2023-02-13): Added optional button IDs for shared callback functions.