Skip to content

Commit

Permalink
Merge pull request #5 from bxparks/develop
Browse files Browse the repository at this point in the history
Support ESP8266
  • Loading branch information
bxparks authored Mar 7, 2018
2 parents 983176f + 2b12f2b commit ec163e8
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 81 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

* 1.0.4 (2018-03-07)
* Support ESP8266.
* Split `loop()` in `Stopwatch.ino` into inner and outer loops, to
allow `loop()` to return periodically.
* Perform manual testing, since ArduinoUnit does not work on ESP8266.
* Optimize `check()` so that `checkOrphanedClick()` is called only when
needed.
* README.md: add benchmark numbers for ESP8266, fix typos.
* Fix various compiler warnings about unused variables.
* 1.0.3 (2018-02-13)
* Make library work on Teensy LC and 3.2.
* Fix `elapsedTime` expression that breaks on 32-bit processors
Expand Down
74 changes: 45 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ will refer to all of these as "buttons" from now on.

The library is called the ACE Button Library (or AceButton Library) because:

* many configurations of the button is **adjustable**, either at compile-time or
run-time
* the library has been optimized to create **compact** objects which take up
* 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
Expand Down Expand Up @@ -69,7 +69,7 @@ Here are the high-level features of the AceButton library:
[ArduinoUnit](https://github.com/mmurdoch/arduinounit)
* properly handles reboots while the button is pressed
* properly handles orphaned clicks, to prevent spurious double-clicks
* only 14-15 microseconds per polling call to `AceButton::check()`
* only 12-14 microseconds (on 16MHz ATmega328P) per polling call to `AceButton::check()`

Compared to other Arduino button libraries, I think the unique or exceptional
features of the AceButton library are:
Expand All @@ -90,7 +90,7 @@ want to write everything yourself from scratch.

That said, the __Stopwatch.ino__ example sketch shows that the call to
`AceButton::check()` (which should be called at least every 10-20 milliseconds
from `loop()`) takes only 14-15 microseconds on a 16MHz ATmega328P chip in the
from `loop()`) takes only 12-14 microseconds on a 16MHz ATmega328P chip in the
idle case. Hopefully that is fast enough for the vast majority of people.

### HelloButton
Expand Down Expand Up @@ -591,22 +591,29 @@ 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` constant:
`kFeatureSuppressXxx` constants:

* `ButtonConfig::kFeatureSuppressAfterClick`
* suppresses the Released event after a Clicked event is detected
* 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 (the first Clicked event cannot be
suppressed, because we don't yet know if another click will be generated by
the button)
* suppresses the Released event and the *second* Clicked event if a
DoubleClicked event is detected
* the *first* Clicked event cannot be suppressed because the code does not
wait for a possible DoubleClicked before triggering the first Clicked
event
* (an optional Feature flag could be added if suppression of the
*first* Clicked is needed, at the expense of waiting extra time for this
first Clicked event to trigger)
* `ButtonConfig::kFeatureSuppressAfterLongPress`
* suppresses the Released event if a LongPressed event is detected
* suppresses the Released event if a LongPressed event is detected
* `ButtonConfig::kFeatureSuppressAfterRepeatPress`
* suppresses the Released event after the last RepeatPressed event
* 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
* a convenience parameter that is the equivalent of suppressing all of the
previous events

By default, no suppression is performed.

Expand Down Expand Up @@ -757,30 +764,39 @@ Here are the profiling numbers for `AceButton::check()` using the
`Stopwatch.ino` example program:

* Arduino Nano (16 MHz ATmega328P)
* 14-15 microseconds
* Arduino UNO R3 (16 MHz ATmega328P)
* 14-15 microseconds
* 11.4 - 14.4 microseconds
* Teensy LC (48 MHz ARM Cortex-M0+)
* 10-11 microseconds
* 4.3 - 6.4 microseconds
* Teensy 3.2 (72 MHz ARM Cortex-M4)
* 5-6 microseconds
* 1.7 - 3.5 microseconds
* NodeMCU 1.0 clone (ESP-12E module, 80MHz ESP8266)
* 6.0 - 7.1 microseconds

The small numbers are with all events (except Pressed and Released) disabled.
The larger numbers are with all events enabled.

## System Requirements

This library was developed using
[Arduino IDE 1.8.5](https://www.arduino.cc/en/Main/Software)
running on MacOS 10.13.3 and Ubuntu Linux 17.04, and
[Teensyduino 1.41](https://www.pjrc.com/teensy/td_download.html).
This library was developed and tested using:
* [Arduino IDE 1.8.5](https://www.arduino.cc/en/Main/Software)
* [Teensyduino 1.41](https://www.pjrc.com/teensy/td_download.html)
* [ESP8266 Arduino Core 2.4.0](https://arduino-esp8266.readthedocs.io/en/2.4.0-rc2/)

I used MacOS 10.13.3 and Ubuntu Linux 17.04 for most of my development.

The library has been verified to work on the following hardware:

* Arduino Nano (16 MHz ATmega328P)
* Arduino UNO R3 (16 MH ATmega328P)
* Arduino Nano clone (16 MHz ATmega328P)
* Arduino UNO R3 clone (16 MHz ATmega328P)
* Teensy LC (48 MHz ARM Cortex-M0+)
* Teensy 3.2 (72 MHz ARM Corect-M4)
* Teensy 3.2 (72 MHz ARM Cortex-M4)
* NodeMCU 1.0 clone (ESP-12E module, 80MHz ESP8266)

The unit tests require [ArduinoUnit](https://github.com/mmurdoch/arduinounit)
to be installed.
The unit tests require
[ArduinoUnit 2.2](https://github.com/mmurdoch/arduinounit)
to be installed. Unfortunately, ArduinoUnit
[lacks support for ESP8266](https://github.com/mmurdoch/arduinounit/issues/68)
so only manual testing was possible on the ESP8266 platform.

## Background Motivation

Expand Down
78 changes: 65 additions & 13 deletions examples/Stopwatch/Stopwatch.ino
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
* - press: stop the stopwatch, printing out the result
* - long press: reset the stopwatch, allowing press to start the process again
*
* It appears that AceButton::check() takes about 16 microseconds.
* Each 'long press' alternates between enableAllEvents() and disableAllEvents()
* so that we can compare the timing of check() when all events are off (except
* Pressed and Released) and when all events are on.
*/

#include <AceButton.h>
Expand All @@ -30,7 +32,8 @@ AdjustableButtonConfig adjustableButtonConfig;
AceButton button(BUTTON_PIN);

// counters to determine the duration of a single call to AceButton::check()
unsigned long loopCounter = 0;
uint16_t innerLoopCounter = 0;
uint16_t outerLoopCounter = 0;
unsigned long startMillis = 0;
unsigned long stopMillis = 0;

Expand All @@ -42,6 +45,8 @@ const uint8_t STOPWATCH_STOPPED = 2;
// implements a finite state machine (FSM)
uint8_t stopwatchState = STOPWATCH_INIT;

bool allEventsEnabled = false;

void setup() {
Serial.begin(9600);

Expand All @@ -63,14 +68,26 @@ void setup() {
}

void loop() {
// Should be called every 20ms or faster for the default debouncing time
// of ~50ms.
button.check();

// increment loop counter
if (stopwatchState == STOPWATCH_STARTED) {
loopCounter++;
}
// We split the loop into an inner loop and an outer loop. The inner loop
// allows us to measure the speed of button.check() without the overhead of
// the outer loop. However, we must allow the outer loop() method to return
// periodically to allow the microcontroller to its own stuff. This is
// especially true on an ESP8266 board, where a Watch Dog Timer will
// soft-reset the board if loop() doesn't return every few seconds.
do {
// button.check() Should be called every 20ms or faster for the default
// debouncing time of ~50ms.
button.check();

// increment loop counter
if (stopwatchState == STOPWATCH_STARTED) {
innerLoopCounter++;
}
} while (innerLoopCounter);

// Each time the innerLoopCounter rolls over (65536), increment the outer loop
// counter, and return from loop(), to prevent WDT errors on ESP8266.
outerLoopCounter++;
}

// The event handler for the button.
Expand All @@ -79,15 +96,30 @@ void handleEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
switch (eventType) {
case AceButton::kEventPressed:
if (stopwatchState == STOPWATCH_INIT) {

// enable or disable higher level events, to get different performance
// numbers
if (allEventsEnabled) {
enableAllEvents();
} else {
disableAllEvents();
}

Serial.println(F("handleEvent(): stopwatch started"));
startMillis = now;
loopCounter = 0;
innerLoopCounter = 0;
outerLoopCounter = 0;
stopwatchState = STOPWATCH_STARTED;
Serial.println(F("handleEvent(): stopwatch started"));
} else if (stopwatchState == STOPWATCH_STARTED) {
stopMillis = now;
stopwatchState = STOPWATCH_STOPPED;
unsigned long duration = stopMillis - startMillis;
double microsPerLoop = (double) duration * 1000 / loopCounter;
uint32_t loopCounter = ((uint32_t) outerLoopCounter << 16) +
innerLoopCounter;
float microsPerLoop = duration * 1000.0f / loopCounter;

// reenable all events after stopping
enableAllEvents();

Serial.println(F("handleEvent(): stopwatch stopped"));
Serial.print(F("handleEvent(): duration (ms): "));
Expand All @@ -96,13 +128,33 @@ void handleEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
Serial.print(loopCounter);
Serial.print(F("; micros/loop: "));
Serial.println(microsPerLoop);

// Setting 0 allows the loop() function to return periodically.
innerLoopCounter = 0;
}
break;
case AceButton::kEventLongPressed:
if (stopwatchState == STOPWATCH_STOPPED) {
stopwatchState = STOPWATCH_INIT;
Serial.println(F("handleEvent(): stopwatch reset"));
allEventsEnabled = !allEventsEnabled;
}
break;
}
}

void enableAllEvents() {
Serial.println(F("enabling high level events"));
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureClick);
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureDoubleClick);
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureLongPress);
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureRepeatPress);
}

void disableAllEvents() {
Serial.println(F("disabling high level events"));
adjustableButtonConfig.clearFeature(ButtonConfig::kFeatureClick);
adjustableButtonConfig.clearFeature(ButtonConfig::kFeatureDoubleClick);
adjustableButtonConfig.clearFeature(ButtonConfig::kFeatureLongPress);
adjustableButtonConfig.clearFeature(ButtonConfig::kFeatureRepeatPress);
}
6 changes: 3 additions & 3 deletions examples/TunerButtons/TunerButtons.ino
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ void setup() {

#if ENABLE_SERIAL == 1
while (! Serial); // Wait until Serial is ready - Leonardo
Serial.println(F("stopwatch ready"));
Serial.println(F("tuner buttons ready"));
#endif

printStation(currentStation);
Expand Down Expand Up @@ -157,7 +157,7 @@ void handleTuneEvent(AceButton* button, uint8_t eventType,
}

void retrievePreset(uint8_t id) {
if (id >= 0 || id <= NUM_PRESETS) {
if (id <= NUM_PRESETS) {
currentStation = stations[id];
#if ENABLE_SERIAL == 1
Serial.print(F("memory "));
Expand All @@ -169,7 +169,7 @@ void retrievePreset(uint8_t id) {
}

void setPreset(uint8_t id) {
if (id >= 0 || id <= NUM_PRESETS) {
if (id <= NUM_PRESETS) {
stations[id] = currentStation;

#if ENABLE_SERIAL == 1
Expand Down
Loading

0 comments on commit ec163e8

Please sign in to comment.