AceButton  1.4
An adjustable, compact, event-driven button library for Arduino.
AceButton.cpp
1 /*
2 MIT License
3 
4 Copyright (c) 2018 Brian T. Park
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 
25 #include "TimingStats.h"
26 #include "AceButton.h"
27 
28 namespace ace_button {
29 
30 // Macros to perform compile-time assertions. See
31 // https://www.embedded.com/electronics-blogs/programming-pointers/4025549/Catching-errors-early-with-compile-time-assertions
32 // and https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
33 #define CONCAT_(x, y) x##y
34 #define CONCAT(x,y) CONCAT_(x,y)
35 #define COMPILE_TIME_ASSERT(cond, msg) \
36  extern char CONCAT(compile_time_assert, __LINE__)[(cond) ? 1 : -1];
37 
38 // Check that the Arduino constants HIGH and LOW are defined to be 1 and 0,
39 // respectively. Otherwise, this library won't work.
40 COMPILE_TIME_ASSERT(HIGH == 1, "HIGH must be 1")
41 COMPILE_TIME_ASSERT(LOW == 0, "LOW must be 0")
42 
43 // On boards using the new PinStatus API, check that kButtonStateUnknown is
44 // different from all other PinStatus enums.
45 #if ARDUINO_API_VERSION >= 10000
46  COMPILE_TIME_ASSERT(\
49  && AceButton::kButtonStateUnknown != CHANGE \
50  && AceButton::kButtonStateUnknown != FALLING \
51  && AceButton::kButtonStateUnknown != RISING, \
52  "kButtonStateUnknown conflicts with PinStatus enum")
53 #endif
54 
55 AceButton::AceButton(uint8_t pin, uint8_t defaultReleasedState, uint8_t id):
56  mButtonConfig(ButtonConfig::getSystemButtonConfig()) {
57  init(pin, defaultReleasedState, id);
58 }
59 
60 AceButton::AceButton(ButtonConfig* buttonConfig, uint8_t pin,
61  uint8_t defaultReleasedState, uint8_t id) {
62  init(buttonConfig, pin, defaultReleasedState, id);
63 }
64 
65 void AceButton::init(uint8_t pin, uint8_t defaultReleasedState, uint8_t id) {
66  mPin = pin;
67  mId = id;
68  mFlags = 0;
69  mLastButtonState = kButtonStateUnknown;
70  mLastDebounceTime = 0;
71  mLastClickTime = 0;
72  setDefaultReleasedState(defaultReleasedState);
73 }
74 
75 void AceButton::init(ButtonConfig* buttonConfig, uint8_t pin,
76  uint8_t defaultReleasedState, uint8_t id) {
77  mButtonConfig = buttonConfig;
78  init(pin, defaultReleasedState, id);
79 }
80 
81 void AceButton::setDefaultReleasedState(uint8_t state) {
82  if (state == HIGH) {
83  mFlags |= kFlagDefaultReleasedState;
84  } else {
85  mFlags &= ~kFlagDefaultReleasedState;
86  }
87 }
88 
90  return (mFlags & kFlagDefaultReleasedState) ? HIGH : LOW;
91 }
92 
93 // NOTE: It would be interesting to rewrite the check() method using a Finite
94 // State Machine.
96  // Get the micros.
97  uint16_t nowMicros = mButtonConfig->getClockMicros();
98 
99  // Retrieve the current time just once and use that in the various checkXxx()
100  // functions below. This provides some robustness of the various timing
101  // algorithms even if any of the event handlers takes more time than the
102  // threshold time limits such as 'debounceDelay' or longPressDelay'.
103  uint16_t now = mButtonConfig->getClock();
104 
105  uint8_t buttonState = mButtonConfig->readButton(mPin);
106 
107  // debounce the button
108  if (checkDebounced(now, buttonState)) {
109  // check if the button was initialized (i.e. UNKNOWN state)
110  if (checkInitialized(buttonState)) {
111  checkEvent(now, buttonState);
112  }
113  }
114 
115  TimingStats* stats = mButtonConfig->getTimingStats();
116  if (stats != nullptr) {
117  uint16_t elapsedMicros = mButtonConfig->getClockMicros() - nowMicros;
118  stats->update(elapsedMicros);
119  }
120 }
121 
122 void AceButton::checkEvent(uint16_t now, uint8_t buttonState) {
123  // We need to remove orphaned clicks even if just Click is enabled. It is not
124  // sufficient to do this for just DoubleClick. That's because it's possible
125  // for a Clicked event to be generated, then 65.536 seconds later, the
126  // ButtonConfig could be changed to enable DoubleClick. (Such real-time change
127  // of ButtonConfig is not recommended, but is sometimes convenient.) If the
128  // orphaned click is not cleared, then the next Click would be errorneously
129  // considered to be a DoubleClick. Therefore, we must clear the orphaned click
130  // even if just the Clicked event is enabled.
131  //
132  // We also need to check of any postponed clicks that got generated when
133  // kFeatureSuppressClickBeforeDoubleClick was enabled.
134  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick) ||
136  checkPostponedClick(now);
137  checkOrphanedClick(now);
138  }
139 
140  if (mButtonConfig->isFeature(ButtonConfig::kFeatureLongPress)) {
141  checkLongPress(now, buttonState);
142  }
143  if (mButtonConfig->isFeature(ButtonConfig::kFeatureRepeatPress)) {
144  checkRepeatPress(now, buttonState);
145  }
146  if (buttonState != getLastButtonState()) {
147  checkChanged(now, buttonState);
148  }
149 }
150 
151 bool AceButton::checkDebounced(uint16_t now, uint8_t buttonState) {
152  if (isDebouncing()) {
153 
154  // NOTE: This is a bit tricky. The elapsedTime will be valid even if the
155  // uint16_t representation of 'now' rolls over so that (now <
156  // mLastDebounceTime). This is true as long as the 'unsigned long'
157  // representation of 'now' is < (65536 + mLastDebounceTime). We need to cast
158  // this expression into an uint16_t before doing the '>=' comparison below
159  // for compatability with processors whose sizeof(int) == 4 instead of 2.
160  // For those processors, the expression (now - mLastDebounceTime >=
161  // getDebounceDelay()) won't work because the terms in the expression get
162  // promoted to an (int).
163  uint16_t elapsedTime = now - mLastDebounceTime;
164 
165  bool isDebouncingTimeOver =
166  (elapsedTime >= mButtonConfig->getDebounceDelay());
167 
168  if (isDebouncingTimeOver) {
169  clearDebouncing();
170  return true;
171  } else {
172  return false;
173  }
174  } else {
175  // Currently not in debouncing phase. Check for a button state change. This
176  // will also detect a transition from kButtonStateUnknown to HIGH or LOW.
177  if (buttonState == getLastButtonState()) {
178  // no change, return immediately
179  return true;
180  }
181 
182  // button has changed so, enter debouncing phase
183  setDebouncing();
184  mLastDebounceTime = now;
185  return false;
186  }
187 }
188 
189 bool AceButton::checkInitialized(uint16_t buttonState) {
190  if (mLastButtonState != kButtonStateUnknown) {
191  return true;
192  }
193 
194  // If transitioning from the initial "unknown" button state, just set the last
195  // valid button state, but don't fire off the event handler. This handles the
196  // case where a momentary switch is pressed down, then the board is rebooted.
197  // When the board comes up, it should not fire off the event handler. This
198  // also handles the case of a 2-position switch set to the "pressed"
199  // position, and the board is rebooted.
200  mLastButtonState = buttonState;
201  return false;
202 }
203 
204 void AceButton::checkLongPress(uint16_t now, uint8_t buttonState) {
205  if (buttonState == getDefaultReleasedState()) {
206  return;
207  }
208 
209  if (isPressed() && !isLongPressed()) {
210  uint16_t elapsedTime = now - mLastPressTime;
211  if (elapsedTime >= mButtonConfig->getLongPressDelay()) {
212  setLongPressed();
213  handleEvent(kEventLongPressed);
214  }
215  }
216 }
217 
218 void AceButton::checkRepeatPress(uint16_t now, uint8_t buttonState) {
219  if (buttonState == getDefaultReleasedState()) {
220  return;
221  }
222 
223  if (isPressed()) {
224  if (isRepeatPressed()) {
225  uint16_t elapsedTime = now - mLastRepeatPressTime;
226  if (elapsedTime >= mButtonConfig->getRepeatPressInterval()) {
227  handleEvent(kEventRepeatPressed);
228  mLastRepeatPressTime = now;
229  }
230  } else {
231  uint16_t elapsedTime = now - mLastPressTime;
232  if (elapsedTime >= mButtonConfig->getRepeatPressDelay()) {
233  setRepeatPressed();
234  // Trigger the RepeatPressed immedidately, instead of waiting until the
235  // first getRepeatPressInterval() has passed.
236  handleEvent(kEventRepeatPressed);
237  mLastRepeatPressTime = now;
238  }
239  }
240  }
241 }
242 
243 void AceButton::checkChanged(uint16_t now, uint8_t buttonState) {
244  mLastButtonState = buttonState;
245  checkPressed(now, buttonState);
246  checkReleased(now, buttonState);
247 }
248 
249 void AceButton::checkPressed(uint16_t now, uint8_t buttonState) {
250  if (buttonState == getDefaultReleasedState()) {
251  return;
252  }
253 
254  // button was pressed
255  mLastPressTime = now;
256  setPressed();
257  handleEvent(kEventPressed);
258 }
259 
260 void AceButton::checkReleased(uint16_t now, uint8_t buttonState) {
261  if (buttonState != getDefaultReleasedState()) {
262  return;
263  }
264 
265  // Check for click (before sending off the Released event).
266  // Make sure that we don't clearPressed() before calling this.
267  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick)
268  || mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
269  checkClicked(now);
270  }
271 
272  // check if Released events are suppressed
273  bool suppress =
274  ((isLongPressed() &&
275  mButtonConfig->
277  (isRepeatPressed() &&
278  mButtonConfig->
280  (isClicked() &&
282  (isDoubleClicked() &&
283  mButtonConfig->
285 
286  // button was released
287  clearPressed();
288  clearDoubleClicked();
289  clearLongPressed();
290  clearRepeatPressed();
291 
292  if (!suppress) {
293  handleEvent(kEventReleased);
294  }
295 }
296 
297 void AceButton::checkClicked(uint16_t now) {
298  if (!isPressed()) {
299  // Not a Click unless the previous state was a Pressed state.
300  // This can happen if the chip was rebooted with the button Pressed. Upon
301  // Release, it shouldn't generated a click, even accidentally due to a
302  // spurious value in mLastPressTime.
303  clearClicked();
304  return;
305  }
306  uint16_t elapsedTime = now - mLastPressTime;
307  if (elapsedTime >= mButtonConfig->getClickDelay()) {
308  clearClicked();
309  return;
310  }
311 
312  // check for double click
313  if (mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
314  checkDoubleClicked(now);
315  }
316 
317  // Suppress a second click (both buttonState change and event message) if
318  // double-click detected, which has the side-effect of preventing 3 clicks
319  // from generating another double-click at the third click.
320  if (isDoubleClicked()) {
321  clearClicked();
322  return;
323  }
324 
325  // we got a single click
326  mLastClickTime = now;
327  setClicked();
328  if (mButtonConfig->isFeature(
330  setClickPostponed();
331  } else {
332  handleEvent(kEventClicked);
333  }
334 }
335 
336 void AceButton::checkDoubleClicked(uint16_t now) {
337  if (!isClicked()) {
338  clearDoubleClicked();
339  return;
340  }
341 
342  uint16_t elapsedTime = now - mLastClickTime;
343  if (elapsedTime >= mButtonConfig->getDoubleClickDelay()) {
344  clearDoubleClicked();
345  // There should be no postponed Click at this point because
346  // checkPostponedClick() should have taken care of it.
347  return;
348  }
349 
350  // If there was a postponed click, suppress it because it could only have been
351  // postponed if kFeatureSuppressClickBeforeDoubleClick was enabled. If we got
352  // to this point, there was a DoubleClick, so we must suppress the first
353  // Click as requested.
354  if (isClickPostponed()) {
355  clearClickPostponed();
356  }
357  setDoubleClicked();
358  handleEvent(kEventDoubleClicked);
359 }
360 
361 void AceButton::checkOrphanedClick(uint16_t now) {
362  // The amount of time which must pass before a click is determined to be
363  // orphaned and reclaimed. If only DoubleClicked is supported, then I think
364  // just getDoubleClickDelay() is correct. No other higher level event uses the
365  // first Clicked event. If TripleClicked becomes supported, I think
366  // orphanedClickDelay will be either (2 * getDoubleClickDelay()) or
367  // (getDoubleClickDelay() + getTripleClickDelay()), depending on whether the
368  // TripleClick has an independent delay time, or reuses the DoubleClick delay
369  // time. But I'm not sure that I've thought through all the details.
370  uint16_t orphanedClickDelay = mButtonConfig->getDoubleClickDelay();
371 
372  uint16_t elapsedTime = now - mLastClickTime;
373  if (isClicked() && (elapsedTime >= orphanedClickDelay)) {
374  clearClicked();
375  }
376 }
377 
378 void AceButton::checkPostponedClick(uint16_t now) {
379  uint16_t postponedClickDelay = mButtonConfig->getDoubleClickDelay();
380  uint16_t elapsedTime = now - mLastClickTime;
381  if (isClickPostponed() && elapsedTime >= postponedClickDelay) {
382  handleEvent(kEventClicked);
383  clearClickPostponed();
384  }
385 }
386 
387 void AceButton::handleEvent(uint8_t eventType) {
388  ButtonConfig::EventHandler eventHandler = mButtonConfig->getEventHandler();
389  if (eventHandler) {
390  eventHandler(this, eventType, getLastButtonState());
391  }
392 }
393 
394 }
uint16_t getRepeatPressInterval()
Milliseconds between two successive RepeatPressed events.
Definition: ButtonConfig.h:203
uint8_t getDefaultReleasedState()
Get the initial released state of the button, HIGH or LOW.
Definition: AceButton.cpp:89
static const FeatureFlagType kFeatureClick
Flag to activate the AceButton::kEventClicked event.
Definition: ButtonConfig.h:100
uint16_t getDoubleClickDelay()
Milliseconds between the first and second click to register as a double-click.
Definition: ButtonConfig.h:181
TimingStats * getTimingStats()
Get the timing stats.
Definition: ButtonConfig.h:304
static const uint8_t kEventRepeatPressed
Button was held down and auto generated multiple presses.
Definition: AceButton.h:84
static const uint8_t kButtonStateUnknown
Button state is unknown.
Definition: AceButton.h:92
uint16_t getLongPressDelay()
Milliseconds for a long press event.
Definition: ButtonConfig.h:186
static const FeatureFlagType kFeatureDoubleClick
Flag to activate the AceButton::kEventDoubleClicked event.
Definition: ButtonConfig.h:107
virtual int readButton(uint8_t pin)
Return the HIGH or LOW state of the button.
Definition: ButtonConfig.h:259
static const FeatureFlagType kFeatureRepeatPress
Flag to activate the AceButton::kEventRepeatPressed event.
Definition: ButtonConfig.h:113
uint16_t getRepeatPressDelay()
Milliseconds that a button needs to be Pressed down before the start of the sequence of RepeatPressed...
Definition: ButtonConfig.h:196
void init(uint8_t pin=0, uint8_t defaultReleasedState=HIGH, uint8_t id=0)
Reset the button to the initial constructed state.
Definition: AceButton.cpp:65
uint8_t getLastButtonState()
Return the button state that was last valid.
Definition: AceButton.h:207
Class that defines the timing parameters and event handler of an AceButton or a group of AceButton in...
Definition: ButtonConfig.h:56
uint16_t getClickDelay()
Milliseconds to wait for a possible click.
Definition: ButtonConfig.h:175
static const uint8_t kEventDoubleClicked
Button was double-clicked.
Definition: AceButton.h:70
static const FeatureFlagType kFeatureLongPress
Flag to activate the AceButton::kEventLongPress event.
Definition: ButtonConfig.h:110
EventHandler getEventHandler()
Return the eventHandler.
Definition: ButtonConfig.h:284
static const uint8_t kEventLongPressed
Button was held down for longer than ButtonConfig::getLongPressDelay()).
Definition: AceButton.h:76
void(* EventHandler)(AceButton *button, uint8_t eventType, uint8_t buttonState)
The event handler signature.
Definition: ButtonConfig.h:160
static const FeatureFlagType kFeatureSuppressClickBeforeDoubleClick
Flag to suppress kEventClicked before a kEventDoubleClicked.
Definition: ButtonConfig.h:137
void check()
Check state of button and trigger event processing.
Definition: AceButton.cpp:95
static const FeatureFlagType kFeatureSuppressAfterLongPress
Flag to suppress kEventReleased after a kEventLongPressed.
Definition: ButtonConfig.h:126
AceButton(uint8_t pin=0, uint8_t defaultReleasedState=HIGH, uint8_t id=0)
Constructor defines parameters of the button that changes from button to button.
Definition: AceButton.cpp:55
static const uint8_t kEventReleased
Button was released.
Definition: AceButton.h:58
uint16_t getDebounceDelay()
Milliseconds to wait for debouncing.
Definition: ButtonConfig.h:172
virtual unsigned long getClockMicros()
Return the microseconds of the internal clock.
Definition: ButtonConfig.h:252
static const FeatureFlagType kFeatureSuppressAfterDoubleClick
Flag to suppress kEventReleased after a kEventDoubleClicked.
Definition: ButtonConfig.h:123
bool isFeature(FeatureFlagType features)
Check if the given features are enabled.
Definition: ButtonConfig.h:267
static const FeatureFlagType kFeatureSuppressAfterClick
Flag to suppress kEventReleased after a kEventClicked.
Definition: ButtonConfig.h:116
static const uint8_t kEventPressed
Button was pressed.
Definition: AceButton.h:55
virtual unsigned long getClock()
Return the milliseconds of the internal clock.
Definition: ButtonConfig.h:246
static const FeatureFlagType kFeatureSuppressAfterRepeatPress
Flag to suppress kEventReleased after a kEventRepeatPressed.
Definition: ButtonConfig.h:129
static const uint8_t kEventClicked
Button was clicked (Pressed and Released within ButtonConfig::getClickDelay()).
Definition: AceButton.h:64