├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── examples ├── Advanced │ └── Advanced.ino ├── Basic │ └── Basic.ino ├── Passthru │ └── Passthru.ino └── TimingConsiderations │ └── TimingConsiderations.ino ├── keywords.txt ├── library.properties └── src ├── ButtonEvents.cpp └── ButtonEvents.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [fasteddy516] 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 fasteddy516 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ButtonEvents 2 | 3 | ###### An Arduino library for catching tap, double-tap and press-and-hold events for buttons 4 | 5 | Written by Edward Wright (fasteddy@thewrightspace.net) 6 | 7 | Available at https://github.com/fasteddy516/ButtonEvents 8 | 9 | _ButtonEvents utilizes (and thus depends on) the Bounce2 library by Thomas O. Fredericks, which 10 | is available at https://github.com/thomasfredericks/Bounce2._ 11 | 12 | 13 | # Description 14 | 15 | ButtonEvents is a library for Arduino that provides methods for detecting **tap**, 16 | **double-tap** and **press-and-hold** events associated with buttons connected 17 | to pins configured as digital inputs. 18 | 19 | 20 | # Installation 21 | 22 | Put the "ButtonEvents" folder in your Arduino "libraries" folder. To identify 23 | this location from the Arduino IDE, open "menubar-> File -> Preferences". 24 | 25 | Select "menubar -> Sketch -> Import Library -> ButtonEvents" to import the library 26 | into your sketch. An "#include ButtonEvents.h" line will appear at the top of 27 | your Sketch. 28 | 29 | **In order to use the ButtonEvents library, the Bounce2 library is required, and must 30 | also be installed. A link to the Bounce2 library is included at the top of this file.** 31 | 32 | 33 | # Details 34 | 35 | ButtonEvents can detect **tap**, **double-tap** and **press-and-hold** events 36 | based on transitions that occur on a pin configured as a digital input - generally 37 | as the result of a button press. 38 | 39 | * A **tap** event is triggered after the button has been *released*, and the 40 | double-tap detection window has elapsed with no further button presses. 41 | 42 | * A **double-tap** event is triggered after the button has been released and is 43 | then *pressed* again before the double-tap detection window has elapsed. 44 | 45 | * A **press-and-hold** event is triggered after the button has been *pressed 46 | and held* down for the hold duration. 47 | 48 | 49 | The raw signal on the input pin is debounced using the Bounce2 library. Unless 50 | otherwise specified, the signal on the pin is assumed to be active-low, meaning 51 | that when the button is pressed is generates a low signal on the pin. 52 | 53 | A newly created ButtonEvent instance assumes the following default settings: 54 | * 35ms debounce for raw input signal on pin 55 | * 150ms double-tap detection window 56 | * 500ms hold duration 57 | * Active-low signal on input pin 58 | 59 | 60 | All of these settings can be adjusted using the methods described later in this 61 | file. 62 | 63 | 64 | # Examples 65 | 66 | A number of code examples are included with this library, and can be accessed 67 | through the Arduino IDE by opening "menubar-> File -> Examples -> ButtonEvents" 68 | and choosing the desired example sketch. 69 | 70 | 71 | # Types 72 | 73 | ButtonEvents uses two enumerated types for code readability: 74 | 75 | `enum ButtonState { idle, pressed, released };` 76 | 77 | `enum ButtonEvent { none, tap, doubleTap, hold };` 78 | 79 | 80 | # Methods 81 | 82 | #### `ButtonEvents()` 83 | * Instantiates a ButtonEvents object. (See *Basic* example sketch.) 84 | 85 | 86 | #### `void attach(int pin)` 87 | * Attaches a ButtonEvents instance to a pin. The pin needs to be configured 88 | using `pinMode()` before ButtonEvents is attached to it. (See *Basic* example sketch) 89 | 90 | 91 | #### `void activeHigh()` 92 | * Sets the input processing mode to active high. (See *Advanced* example sketch) 93 | 94 | 95 | #### `void activeLow()` 96 | * Sets the input processing mode to active low. (See *Advanced* example sketch) 97 | 98 | 99 | #### `void debounceTime(unsigned int debounce_ms)` 100 | * Sets the debounce time for the raw input signal. (See *Advanced* example sketch) 101 | 102 | 103 | #### `void doubleTapTime(unsigned int doubleTap_ms)` 104 | * Sets the double-tap event detection window. (See *Advanced* example sketch) 105 | 106 | 107 | #### `void holdTime(unsigned int hold_ms)` 108 | * Sets the time required to trigger a hold event. (See *Advanced* example sketch) 109 | 110 | 111 | #### `bool update()` 112 | * Like the Bounce2 library, ButtonEvents does not use interrupts. Instead, 113 | you have to "update" the instance in order to detect events, and the updates 114 | need to be done as frequently as possible - presumably within your `loop()`. 115 | The `update()` method updates the instance and returns true (1) if a button 116 | event occurred, **or** if the pin state changed. False (0) if no event or 117 | state change occurred. `update()` should only be called once per loop(). 118 | (See *Basic* example sketch) 119 | 120 | 121 | #### `void reset()` 122 | * Resets the saved button state to idle. (See *TimingConsiderations* example sketch) 123 | 124 | 125 | #### `void retime()` 126 | * Sets the button event timestamp to the current value of `millis()`. (See *TimingConsiderations* example sketch) 127 | 128 | 129 | #### `ButtonEvent event()` 130 | * Returns the button event detected during the last `update()` call. (See *Advanced* example sketch) 131 | 132 | 133 | #### `bool tapped()` 134 | * Returns true if the 'tap' event was detected. (See *Basic* example sketch) 135 | 136 | 137 | #### `bool doubleTapped()` 138 | * Returns true if the 'doubleTap' event was detected. (See *Basic* example sketch) 139 | 140 | 141 | #### `bool held()` 142 | * Returns true if the 'held' event was detected. (See *Basic* example sketch) 143 | 144 | # Bounce2 Passthru Methods 145 | 146 | If access to underlying Bounce2 library functionality is required, the following 147 | passthru methods can be used. Please refer to the Bounce2 documentation for 148 | information regarding these methods: 149 | 150 | #### `void interval(unsigned int interval_ms)` 151 | 152 | 153 | #### `bool read()` 154 | 155 | 156 | #### `bool fell()` 157 | 158 | 159 | #### `bool rose()` 160 | -------------------------------------------------------------------------------- /examples/Advanced/Advanced.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ButtonEvents - An Arduino library for catching tap, double-tap and press-and-hold events for buttons. 3 | 4 | Written by Edward Wright (fasteddy@thewrightspace.net) 5 | Available at https://github.com/fasteddy516/ButtonEvents 6 | 7 | Utilizes the Bounce2 library by Thomas O. Fredericks 8 | Available at https://github.com/thomasfredericks/Bounce2 9 | 10 | Example Sketch - Advanced Usage: 11 | This sketch demonstrates the use of some of the additional methods provided in this library. As in 12 | the 'Basic' example, it will monitor a button connected to pin 7 and send strings to the serial monitor 13 | indicating when events are triggered. 14 | */ 15 | 16 | #include // we have to include the library in order to use it 17 | 18 | const byte buttonPin = 7; // our button will be connected to pin 7 19 | 20 | ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button 21 | 22 | 23 | // this is where we run one-time setup code 24 | void setup() { 25 | 26 | // configure the button pin as a digital input with internal pull-up resistor enabled 27 | pinMode(buttonPin, INPUT_PULLUP); 28 | 29 | // attach our ButtonEvents instance to the button pin 30 | myButton.attach(buttonPin); 31 | 32 | // If your button is connected such that pressing it generates a high signal on the pin, you need to 33 | // specify that it is "active high" 34 | myButton.activeHigh(); 35 | 36 | // If your button is connected such that pressing it generates a low signal on the pin, you can specify 37 | // that it is "active low", or don't bother, since this is the default setting anyway. 38 | myButton.activeLow(); 39 | 40 | // By default, the raw signal on the input pin has a 35ms debounce applied to it. You can change the 41 | // debounce time if necessary. 42 | myButton.debounceTime(15); //apply 15ms debounce 43 | 44 | // The double-tap detection window is set to 150ms by default. Decreasing this value will result in 45 | // more responsive single-tap events, but requires really fast tapping to trigger a double-tap event. 46 | // Increasing this value will allow slower taps to still trigger a double-tap event, but will make 47 | // single-tap events more laggy, and can cause taps that were meant to be separate to be treated as 48 | // double-taps. The necessary timing really depends on your use case, but I have found 150ms to be a 49 | // reasonable starting point. If you need to change the double-tap detection window, you can do so 50 | // as follows: 51 | myButton.doubleTapTime(250); // set double-tap detection window to 250ms 52 | 53 | // The hold duration can be increased to require longer holds before an event is triggered, or reduced to 54 | // have hold events trigger more quickly. 55 | myButton.holdTime(2000); // require button to be held for 2000ms before triggering a hold event 56 | 57 | // initialize the arduino serial port and send a welcome message 58 | Serial.begin(9600); 59 | Serial.println("ButtonEvents 'Advanced' example started"); 60 | } 61 | 62 | 63 | // this is the main loop, which will repeat forever 64 | void loop() { 65 | 66 | // The update() method returns true if an event or state change occurred. It serves as a passthru 67 | // to the Bounce2 library update() function as well, so it will stll return true if a press/release 68 | // is detected but has not triggered a tap/double-tap/hold event 69 | if (myButton.update() == true) { 70 | 71 | // The event() method returns tap, doubleTap, hold or none depending on which event was detected 72 | // the last time the update() method was called. The following code accomplishes the same thing 73 | // we did in the 'Basic' example, but I personally prefer this arrangement. 74 | switch(myButton.event()) { 75 | 76 | // things to do if the button was tapped (single tap) 77 | case (tap) : { 78 | Serial.println("TAP event detected"); 79 | break; 80 | } 81 | 82 | // things to do if the button was double-tapped 83 | case (doubleTap) : { 84 | Serial.println("DOUBLE-TAP event detected"); 85 | break; 86 | } 87 | 88 | // things to do if the button was held 89 | case (hold) : { 90 | Serial.println("HOLD event detected"); 91 | break; 92 | } 93 | 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/Basic/Basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ButtonEvents - An Arduino library for catching tap, double-tap and press-and-hold events for buttons. 3 | 4 | Written by Edward Wright (fasteddy@thewrightspace.net) 5 | Available at https://github.com/fasteddy516/ButtonEvents 6 | 7 | Utilizes the Bounce2 library by Thomas O. Fredericks 8 | Available at https://github.com/thomasfredericks/Bounce2 9 | 10 | Example Sketch - Basic Usage: 11 | This sketch demonstrates the most simple use of the ButtonEvents library. It will monitor a button 12 | connected to pin 7 and send strings to the serial monitor indicating when one of the supported 13 | button events has occured. The events that can be detected are: 14 | 15 | 1) A 'tap' event is triggered after the button has been released, and the double-tap detection 16 | window has elapsed with no further button presses. 17 | 18 | 2) A 'double-tap' event is triggered after the button has been released and is then pressed again 19 | before the double-tap detection window has elapsed. The default double-tap detection window 20 | duration is 150ms. 21 | 22 | 3) A 'press-and-hold' event is triggered after the button has been pressed and held down for the hold 23 | duration. The default hold duration is set to 500ms. 24 | 25 | The raw signal on the input pin has a 35ms debounce applied to it (using the Bounce2 library) by 26 | default. Unless otherwise specified, the signal on the input is assumed to be active low, meaning 27 | that when the button is pressed is generates a low signal on the pin. 28 | 29 | The double-tap detection window, hold duration, debounce time and input type (active low/high) can 30 | all be adjusted as needed - please see the 'Advanced' example sketch included with this library for 31 | details. For this 'Basic' example, we will stick with the default settings. 32 | */ 33 | 34 | #include // we have to include the library in order to use it 35 | 36 | const byte buttonPin = 7; // our button will be connected to pin 7 37 | 38 | ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button 39 | 40 | 41 | // this is where we run one-time setup code 42 | void setup() { 43 | 44 | // Configure the button pin as a digital input with internal pull-up resistor enabled. This pin 45 | // setup needs to be done before we attach our ButtonEvents instance to the pin. 46 | pinMode(buttonPin, INPUT_PULLUP); 47 | 48 | // attach our ButtonEvents instance to the button pin 49 | myButton.attach(buttonPin); 50 | 51 | // initialize the arduino serial port and send a welcome message 52 | Serial.begin(9600); 53 | Serial.println("ButtonEvents 'basic' example started"); 54 | } 55 | 56 | 57 | // this is the main loop, which will repeat forever 58 | void loop() { 59 | 60 | // the update() method is where all the magic happens - it needs to be called regularly in order 61 | // to function correctly, so it should be part of the main loop. 62 | myButton.update(); 63 | 64 | // things to do if the button was tapped (single tap) 65 | if (myButton.tapped() == true) { 66 | Serial.println("TAP event detected"); 67 | } 68 | 69 | // things to do if the button was double-tapped 70 | if (myButton.doubleTapped() == true) { 71 | Serial.println("DOUBLE-TAP event detected"); 72 | } 73 | 74 | // things to do if the button was held 75 | if (myButton.held() == true) { 76 | Serial.println("HOLD event detected"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/Passthru/Passthru.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ButtonEvents - An Arduino library for catching tap, double-tap and press-and-hold events for buttons. 3 | 4 | Written by Edward Wright (fasteddy@thewrightspace.net) 5 | Available at https://github.com/fasteddy516/ButtonEvents 6 | 7 | Utilizes the Bounce2 library by Thomas O. Fredericks 8 | Available at https://github.com/thomasfredericks/Bounce2 9 | 10 | Example Sketch - Bounce2 Passthru: 11 | This sketch demonstrates the ButtonEvents passthru methods that provide access to the underlying 12 | Bounce2 functionality if needed. It will monitor a button connected to pin 7 and send strings 13 | to the serial monitor indicating when Bounce2 events are triggered. 14 | */ 15 | 16 | #include // we have to include the library in order to use it 17 | 18 | const byte buttonPin = 7; // our button will be connected to pin 7 19 | 20 | ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button 21 | 22 | 23 | // this is where we run one-time setup code 24 | void setup() { 25 | 26 | // configure the button pin as a digital input with internal pull-up resistor enabled 27 | pinMode(buttonPin, INPUT_PULLUP); 28 | 29 | // attach our ButtonEvents instance to the button pin 30 | myButton.attach(buttonPin); 31 | 32 | // set the debounce time to 50ms 33 | myButton.interval(50); 34 | 35 | // initialize the arduino serial port and send a welcome message 36 | Serial.begin(9600); 37 | Serial.println("ButtonEvents 'Passthru' example started"); 38 | } 39 | 40 | 41 | // this is the main loop, which will repeat forever 42 | void loop() { 43 | 44 | // Update the ButtonEvents (and underlying Bounce2) instance. Remember that this update() method 45 | // returns true if ButtonEvents *or* Bounce2 detects an event, so - if you are using the return 46 | // value in your code - the results will be slightly different than when using the Bounce2 47 | // update() method by itself. 48 | myButton.update(); 49 | 50 | // things to do if the debounced signal from the button pin went high 51 | if (myButton.rose() == true) { 52 | Serial.println("Signal on button pin went HIGH"); 53 | Serial.print("myButton.read() returns a value of "); 54 | Serial.println(myButton.read()); 55 | Serial.println("---"); 56 | } 57 | 58 | // things to do if the debounced signal from the button pin went low 59 | if (myButton.fell() == true) { 60 | Serial.println("Signal on button pin went LOW"); 61 | Serial.print("myButton.read() returns a value of "); 62 | Serial.println(myButton.read()); 63 | Serial.println("---"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/TimingConsiderations/TimingConsiderations.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ButtonEvents - An Arduino library for catching tap, double-tap and press-and-hold events for buttons. 3 | 4 | Written by Edward Wright (fasteddy@thewrightspace.net) 5 | Available at https://github.com/fasteddy516/ButtonEvents 6 | 7 | Utilizes the Bounce2 library by Thomas O. Fredericks 8 | Available at https://github.com/thomasfredericks/Bounce2 9 | 10 | Example Sketch - Timing Considerations: 11 | This sketch demonstrates how to avoid/mitigate some issues that can occur when the possibility of 12 | large delays between update() method calls exists in your code. It uses the same button on pin 7 13 | that we have utilized in the other examples, along with another input on pin 8 that we will pretend 14 | is connected to an active low sensor device. Note that this example isn't really meant to be executed 15 | and observed on an Arduino, it is meant to be read through. 16 | */ 17 | 18 | #include // we have to include the library in order to use it 19 | 20 | const byte buttonPin = 7; // our button will be connected to pin 7 21 | const byte sensorPin = 8; // our pretend sensor will be "connected" to pin 8 22 | 23 | ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button 24 | 25 | 26 | // this is where we run one-time setup code 27 | void setup() { 28 | 29 | // configure our button and sensor input pins 30 | pinMode(buttonPin, INPUT_PULLUP); 31 | pinMode(sensorPin, INPUT_PULLUP); 32 | 33 | // attach our ButtonEvents instance to the button pin 34 | myButton.attach(buttonPin); 35 | 36 | // initialize the arduino serial port and send a welcome message 37 | Serial.begin(9600); 38 | Serial.println("ButtonEvents 'Timing Considerations' example started"); 39 | } 40 | 41 | 42 | // Because the ButtonEvents library uses comparisons based on millis() rather than interrupts, it 43 | // is critical that the update() method is called frequently in order to process events properly. 44 | // There may be instances in your code where execution is significantly delayed between calls to 45 | // the update() method, causing button events to trigger improperly/unexpectedly. Consider the 46 | // following main loop: 47 | void loop() { 48 | 49 | // This is a tightly-packed version of our standard event detection printout logic. The update() 50 | // method is called as part of the if() statement every time the loop executes. 51 | if (myButton.update()) { 52 | switch(myButton.event()) { 53 | case (tap) : { Serial.print("TAP"); break; } 54 | case (doubleTap) : { Serial.print("DOUBLE-TAP"); break; } 55 | case (hold) : { Serial.print("HOLD"); break; } 56 | } 57 | Serial.println(" event detected"); 58 | } 59 | 60 | // Imagine that our sensor connected to pin 8 requires that we delay the main loop for 2 full 61 | // seconds whenever it pulls the input pin low: 62 | if (digitalRead(sensorPin) == LOW) { 63 | delay(2000); // delay for 2 seconds (2000ms) when our pretend sensor tells us to 64 | } 65 | 66 | // Let's pretend that our button is tapped at the same time that our sensor pulls the signal on 67 | // 'sensorPin' low. The initial button press was detected and logged in the update() method 68 | // before the 2 second delay, and those 2 seconds will have elapsed by the time we get through 69 | // the main loop and get around to calling the update() method again. This will result in a 70 | // 'hold' event being triggered even though the button was actually only tapped. The ButtonEvents 71 | // library includes two methods to help avoid this type of incorrect mis-detection. 72 | 73 | // The first method is reset(), which resets the last known state of the button (as stored by the 74 | // update() method to 'idle'. 75 | if (digitalRead(sensorPin) == LOW) { 76 | delay(2000); // delay for 2 seconds (2000ms) when our pretend sensor tells us to 77 | myButton.reset(); // reset the saved button state to 'idle' to prevent event mis-detection 78 | } 79 | 80 | // Using the reset() method prevents the 'hold' event from incorrectly triggering the next time 81 | // the update() method is called. It actually prevents *any* event from triggering the next 82 | // time update() is called, including what would have been a tap event. Use the reset() method 83 | // when you want button events that get interrupted by delays to be completely ignored. 84 | 85 | // The second method is retime(), which restarts the timing logic used by the update() method: 86 | if (digitalRead(sensorPin) == LOW) { 87 | delay(2000); // delay for 2 seconds (2000ms) when our pretend sensor tells us to 88 | myButton.retime(); // restart button event timing logic 89 | } 90 | 91 | // Using the reset() method will allow a tap that occurred before the delay to be triggered the 92 | // next time the update() method is called, and allows hold events to trigger after the delay, 93 | // assuming that the button was pressed before the delay, and is held for the full hold duration 94 | // after the delay. Double-taps that are interrupted by delays will never be triggered. 95 | } 96 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For ButtonEvents 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | ButtonEvents KEYWORD1 10 | ButtonState KEYWORD1 11 | ButtonEvent KEYWORD1 12 | 13 | ####################################### 14 | # Methods and Functions (KEYWORD2) 15 | ####################################### 16 | 17 | attach KEYWORD2 18 | activeHigh KEYWORD2 19 | activeLow KEYWORD2 20 | debounceTime KEYWORD2 21 | doubleTapTime KEYWORD2 22 | holdTime KEYWORD2 23 | interval KEYWORD2 24 | update KEYWORD2 25 | reset KEYWORD2 26 | retime KEYWORD2 27 | event KEYWORD2 28 | tapped KEYWORD2 29 | doubleTapped KEYWORD2 30 | held KEYWORD2 31 | read KEYWORD2 32 | rose KEYWORD2 33 | fell KEYWORD2 34 | 35 | ####################################### 36 | # Instances (KEYWORD2) 37 | ####################################### 38 | 39 | ####################################### 40 | # Constants (LITERAL1) 41 | ####################################### 42 | 43 | idle LITERAL1 44 | pressed LITERAL1 45 | released LITERAL1 46 | none LITERAL1 47 | tap LITERAL1 48 | doubleTap LITERAL1 49 | hold LITERAL1 50 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ButtonEvents Library 2 | version=1.0.1 3 | author=Edward Wright 4 | maintainer=Edward Wright 5 | sentence=Arduino library for handling of tap, double-tap and press-and-hold button events. 6 | paragraph=This library also makes use of the Bounce2 library by Thomas O Fredericks, available at the time this was written at https://github.com/thomasfredericks/Bounce2 7 | category=Signal Input/Output 8 | url=https://github.com/fasteddy516/ButtonEvents 9 | architectures=avr 10 | -------------------------------------------------------------------------------- /src/ButtonEvents.cpp: -------------------------------------------------------------------------------- 1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * 2 | Written by Edward Wright (fasteddy@thewrightspace.net) 3 | Utilizes the Bounce2 library (https://github.com/thomasfredericks/Bounce2) by Thomas O Fredericks (tof@t-o-f.info) 4 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 5 | 6 | #include "Arduino.h" 7 | #include "ButtonEvents.h" 8 | 9 | // default (and only) constructor 10 | ButtonEvents::ButtonEvents() { 11 | debouncedButton.interval(DEFAULT_DEBOUNCE_MS); 12 | doubleTapTime_ms = DEFAULT_DOUBLETAP_MS; 13 | holdTime_ms = DEFAULT_HOLD_MS; 14 | isActiveLow = DEFAULT_ACTIVE_LOW; 15 | eventTime_ms = 0; // initialize button timestamps and states... 16 | buttonState = idle; 17 | buttonEvent = none; 18 | } 19 | 20 | // passthru to Bounce2 attach() method 21 | void ButtonEvents::attach(int pin) { 22 | debouncedButton.attach(pin); 23 | } 24 | 25 | // passthru to Bounce2 attach() overload 26 | void ButtonEvents::attach(int pin, int mode) { 27 | debouncedButton.attach(pin, mode); 28 | } 29 | 30 | // set button mode to active high 31 | void ButtonEvents::activeHigh() { 32 | isActiveLow = false; 33 | } 34 | 35 | // set button mode to active low 36 | void ButtonEvents::activeLow() { 37 | isActiveLow = true; 38 | } 39 | 40 | // alias/passthru to Bounce2 interval() method 41 | void ButtonEvents::debounceTime(unsigned int debounce_ms) { 42 | debouncedButton.interval(debounce_ms); 43 | } 44 | 45 | // method to set the 'doubleTap' event detection window 46 | void ButtonEvents::doubleTapTime(unsigned int doubleTap_ms) { 47 | doubleTapTime_ms = doubleTap_ms; 48 | } 49 | 50 | // method to set the amount of time that must elapse to trigger a 'hold' event 51 | void ButtonEvents::holdTime(unsigned int hold_ms) { 52 | holdTime_ms = hold_ms; 53 | } 54 | 55 | // passthru to Bounce2 interval() method 56 | void ButtonEvents::interval(unsigned int interval_ms) { 57 | debouncedButton.interval(interval_ms); 58 | } 59 | 60 | // returns true if button was pressed (accounts for active high/low) 61 | bool ButtonEvents::buttonPressed() { 62 | if (isActiveLow && debouncedButton.fell()) return true; 63 | else if (!isActiveLow && debouncedButton.rose()) return true; 64 | return false; 65 | } 66 | 67 | // returns true if button was released (accounts for active high/low) 68 | bool ButtonEvents::buttonReleased() { 69 | if (isActiveLow && debouncedButton.rose()) return true; 70 | else if (!isActiveLow && debouncedButton.fell()) return true; 71 | return false; 72 | } 73 | 74 | // calls the Bounce2 update() method, then runs button event detection logic 75 | bool ButtonEvents::update() { 76 | bool passthruState = debouncedButton.update(); // update debounced button state 77 | 78 | if (buttonPressed()) { 79 | // if the button was previously idle, store the press time and update the button state 80 | if (buttonState == idle) { 81 | eventTime_ms = millis(); 82 | buttonState = pressed; 83 | } 84 | 85 | // if the button was in a released state (waiting for a double tap), update the button 86 | // state and indicate that a double tap event occurred 87 | else if (buttonState == released) { 88 | buttonState = idle; 89 | buttonEvent = doubleTap; 90 | return true; 91 | } 92 | } 93 | 94 | else if (buttonReleased()) { 95 | // if the button was in a pressed state, store the release time and update the button state 96 | if (buttonState == pressed) { 97 | eventTime_ms = millis(); 98 | buttonState = released; 99 | } 100 | } 101 | 102 | // if the button is currently in a pressed state... 103 | if (buttonState == pressed) { 104 | // if the specified hold time has been reached or passed, update the button state and 105 | // indicate that a hold event occurred 106 | if ((millis() - eventTime_ms) >= holdTime_ms) { 107 | buttonState = idle; 108 | buttonEvent = hold; 109 | return true; 110 | } 111 | } 112 | 113 | // if the button is currently in a released state... 114 | else if (buttonState == released) { 115 | // if the specified double tap time has been reached or passed, update the button state 116 | // and indicate that a (single) tap event occurred 117 | if ((millis() - eventTime_ms) >= doubleTapTime_ms) { 118 | buttonState = idle; 119 | buttonEvent = tap; 120 | return true; 121 | } 122 | } 123 | 124 | // if we get to this point, indicate that no button event occurred in this cycle 125 | buttonEvent = none; 126 | return passthruState; 127 | } 128 | 129 | // resets the saved button state to idle 130 | void ButtonEvents::reset() { 131 | buttonState = idle; 132 | buttonEvent = none; 133 | } 134 | 135 | // sets the button event timestamp to the current value of millis() 136 | void ButtonEvents::retime() { 137 | eventTime_ms = millis(); 138 | 139 | // prevent double-tap from triggering after a call to retime() - only taps and holds 140 | // can be effectivley retimed after a delay 141 | if (buttonState == released) { 142 | eventTime_ms += doubleTapTime_ms; 143 | } 144 | } 145 | 146 | // returns the last triggered event 147 | ButtonEvent ButtonEvents::event() { 148 | return buttonEvent; 149 | } 150 | 151 | // returns true if button was tapped 152 | bool ButtonEvents::tapped() { 153 | return (buttonEvent == tap); 154 | } 155 | 156 | // returns true if button was double-tapped 157 | bool ButtonEvents::doubleTapped() { 158 | return (buttonEvent == doubleTap); 159 | } 160 | 161 | // returns true if button was held 162 | bool ButtonEvents::held() { 163 | return (buttonEvent == hold); 164 | } 165 | 166 | // passthru to Bounce2 read() method 167 | bool ButtonEvents::read() { 168 | return debouncedButton.read(); 169 | } 170 | 171 | // passthru to Bounce2 fell() method 172 | bool ButtonEvents::fell() { 173 | return debouncedButton.fell(); 174 | } 175 | 176 | // passthru to Bounce2 rose() method 177 | bool ButtonEvents::rose() { 178 | return debouncedButton.rose(); 179 | } 180 | -------------------------------------------------------------------------------- /src/ButtonEvents.h: -------------------------------------------------------------------------------- 1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * 2 | Written by Edward Wright (fasteddy@thewrightspace.net) 3 | Utilizes the Bounce2 library (https://github.com/thomasfredericks/Bounce2) by Thomas O Fredericks (tof@t-o-f.info) 4 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 5 | 6 | #ifndef ButtonEvents_h 7 | #define ButtonEvents_h 8 | 9 | #include // use Thomas Fredericks' button debounce library 10 | 11 | // these are the default settings used in the constructor - all of them can be changed after instantiation using the corresponding methods 12 | #define DEFAULT_DEBOUNCE_MS 35 13 | #define DEFAULT_DOUBLETAP_MS 150 14 | #define DEFAULT_HOLD_MS 500 15 | #define DEFAULT_ACTIVE_LOW true 16 | 17 | // enumerations to keep things readable 18 | enum ButtonState { idle, pressed, released }; 19 | enum ButtonEvent { none, tap, doubleTap, hold }; 20 | 21 | class ButtonEvents { 22 | public: 23 | // public methods... 24 | ButtonEvents(); // default constructor - follows the argument-free Bounce2 convention. Use defaults, or set explicitly later 25 | void attach(int pin); // passthru to Bounce2 attach() method 26 | void attach(int pin, int mode); // passthru to Bounce2 attach() overload 27 | void activeHigh(); // set button mode to active high 28 | void activeLow(); // set button mode to active low 29 | void debounceTime(unsigned int debounce_ms); // alias/passthru to Bounce2 interval() method 30 | void doubleTapTime(unsigned int doubleTap_ms); // method for setting the doubleTap event detection window 31 | void holdTime(unsigned int hold_ms); // method for setting the time required to trigger a hold event 32 | void interval(unsigned int interval_ms); // passthru to Bounce2 interval() method 33 | bool update(); // calls the Bounce2 update() method, then runs button event detection logic 34 | void reset(); // resets the saved button state to idle 35 | void retime(); // sets the button event timestamp to the current value of millis() 36 | ButtonEvent event(); // returns the button event detected during update() call 37 | bool tapped(); // returns true if the 'tap' event was detected 38 | bool doubleTapped(); // returns true if the 'doubleTap' event was detected 39 | bool held(); // returns true if the 'held' event was detected 40 | bool read(); // passthru to Bounce2 read() method; 41 | bool fell(); // passthru to Bounce2 fell() method; 42 | bool rose(); // passthru to Bounce2 rose() method; 43 | 44 | private: 45 | // private instance variables... 46 | unsigned long eventTime_ms; // remember when the button was pressed/released 47 | unsigned int doubleTapTime_ms; // how long to wait for a double tap after the initial button release 48 | unsigned int holdTime_ms; // how long the button must be held to generate a hold event 49 | ButtonState buttonState; // current button state 50 | ButtonEvent buttonEvent; // detected button event 51 | bool isActiveLow; 52 | Bounce debouncedButton; // button debounced using Thomas Fredericks' Bounce2 library 53 | 54 | // private methods... 55 | bool buttonPressed(); // returns true if the button was pressed (accounts for active high/low) 56 | bool buttonReleased(); // returns true if the button was released (accounts for active high/low) 57 | }; 58 | 59 | #endif 60 | 61 | --------------------------------------------------------------------------------