sentence=Small and easy to use Arduino library for using push buttons at INT0 and / or INT1 pin using interrupts.
Just connect buttons between ground and pin 2 or 3 of your Arduino - that's it
No call of begin() or update() function needed, no polling function to call. No blocking debouncing.
-paragraph=INT0 and INT1 are connected to Pin 2 / 3 on most Arduinos (ATmega328), to PB6 / PA3 on ATtiny167 and on ATtinyX5 we have only INT0 at PB2.
In you main program define an EasyButton and use ButtonStateIsActive or ButtonToggleState to determine your action.
Or use a callback function which will be called once on every button press.
Usage:#define USE_BUTTON_0
#include "EasyButtonAtInt01.cpp.h"
EasyButton Button0AtPin2;
void setup() {}
void loop() {
...
digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState);
...
}
Functions for long and double press detection are included.
+paragraph=INT0 and INT1 are connected to Pin 2 / 3 on most Arduinos (ATmega328), to PB6 / PA3 on ATtiny167 and on ATtinyX5 we have only INT0 at PB2.
In you main program define an EasyButton and use ButtonStateIsActive or ButtonToggleState to determine your action.
Or use a callback function which will be called once on every button press.
Usage:#define USE_BUTTON_0
#include "EasyButtonAtInt01.cpp.h"
EasyButton Button0AtPin2;
void setup() {}
void loop() {
...
digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState);
...
}
Functions for long and double press detection are included.
New: Revisited long and double press functions.
category=Signal Input/Output
url=https://github.com/ArminJo/EasyButtonAtInt01
architectures=avr
diff --git a/src/EasyButtonAtInt01.cpp.h b/src/EasyButtonAtInt01.cpp.h
index 41515cc..0d4a281 100644
--- a/src/EasyButtonAtInt01.cpp.h
+++ b/src/EasyButtonAtInt01.cpp.h
@@ -39,13 +39,13 @@
/*
* Usage:
- * #define USE_BUTTON_0 // Enable code for button at INT0 (pin2 on 328P)
- * #define USE_BUTTON_1 // Enable code for button at INT1 (PCINT0 for ATtinyX5)
+ * #define USE_BUTTON_0 // Enable code for button at INT0 (pin2 on 328P, PB6 on ATtiny167, PB2 on ATtinyX5)
+ * #define USE_BUTTON_1 // Enable code for button at INT1 (pin3 on 328P, PA3 on ATtiny167, PCINT0 / PCx for ATtinyX5)
* #include "EasyButtonAtInt01.h"
* EasyButton Button0AtPin2(true); // true -> Button is connected to INT0
* EasyButton Button0AtPin3(false, &Button3CallbackHandler); // false -> button is not connected to INT0 => connected to INT1
* ...
- * digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState); // initial value is false
+ * digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState); // The value at the first call after first press is true
* ...
*
*/
@@ -129,7 +129,11 @@ void EasyButton::init(bool aIsButtonAtINT0) {
#if defined(LED_FEEDBACK_TEST)
pinModeFast(BUTTON_TEST_FEEDBACK_LED_PIN, OUTPUT);
#endif
+
#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1)
+ /*
+ * Only button 0 requested
+ */
INT0_DDR_PORT &= ~(_BV(INT0_BIT)); // pinModeFast(2, INPUT_PULLUP);
INT0_OUT_PORT |= _BV(INT0_BIT);
sPointerToButton0ForISR = this;
@@ -142,6 +146,9 @@ void EasyButton::init(bool aIsButtonAtINT0) {
# endif //USE_ATTACH_INTERRUPT
#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0)
+ /*
+ * Only button 1 requested
+ */
INT1_DDR_PORT &= ~(_BV(INT1_BIT));
INT1_OUT_PORT |= _BV(INT1_BIT);
sPointerToButton1ForISR = this;
@@ -172,37 +179,45 @@ void EasyButton::init(bool aIsButtonAtINT0) {
# endif // ! defined(ISC10)
#elif defined(USE_BUTTON_0) && defined(USE_BUTTON_1)
- if (isButtonAtINT0) {
- /*
- * Button 0
- */
- INT0_DDR_PORT &= ~(_BV(INT0_BIT)); // pinModeFast(2, INPUT_PULLUP);
- INT0_OUT_PORT |= _BV(INT0_BIT);
- sPointerToButton0ForISR = this;
+ /*
+ * Both buttons 0 + 1 requested
+ */
+ if (isButtonAtINT0) {
+ /*
+ * Button 0
+ */
+ INT0_DDR_PORT &= ~(_BV(INT0_BIT)); // pinModeFast(2, INPUT_PULLUP);
+ INT0_OUT_PORT |= _BV(INT0_BIT);
+ sPointerToButton0ForISR = this;
# if defined(USE_ATTACH_INTERRUPT)
- attachInterrupt(digitalPinToInterrupt(INT0_PIN), &handleINT0Interrupt, CHANGE);
+ attachInterrupt(digitalPinToInterrupt(INT0_PIN), &handleINT0Interrupt, CHANGE);
# else
- EICRA |= (1 << ISC00); // interrupt on any logical change
- EIFR |= 1 << INTF0; // clear interrupt bit
- EIMSK |= 1 << INT0; // enable interrupt on next change
+ EICRA |= (1 << ISC00); // interrupt on any logical change
+ EIFR |= 1 << INTF0; // clear interrupt bit
+ EIMSK |= 1 << INT0; // enable interrupt on next change
# endif //USE_ATTACH_INTERRUPT
- } else {
- /*
- * Button 1
- * Allow PinChangeInterrupt
- */
- INT1_DDR_PORT &= ~(_BV(INT1_BIT)); // pinModeFast(INT1_BIT, INPUT_PULLUP);
- INT1_OUT_PORT |= _BV(INT1_BIT);
- sPointerToButton1ForISR = this;
+ } else {
+ /*
+ * Button 1
+ * Allow PinChangeInterrupt
+ */
+ INT1_DDR_PORT &= ~(_BV(INT1_BIT)); // pinModeFast(INT1_BIT, INPUT_PULLUP);
+ INT1_OUT_PORT |= _BV(INT1_BIT);
+ sPointerToButton1ForISR = this;
# if (! defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3)
# if defined(PCICR)
- PCICR |= 1 << PCIE0; // Enable pin change interrupt for port PA0 to PA7
- PCMSK0 = digitalPinToBitMask(INT1_PIN);
+ /*
+ * ATtiny167 + 87. Enable pin change interrupt for port PA0 to PA7
+ */
+ PCICR |= 1 << PCIE0;
+ PCMSK0 = digitalPinToBitMask(INT1_PIN);
# else
- // ATtinyX5 no ISC10 flag existent
- GIMSK |= 1 << PCIE; //PCINT enable, we have only one
- PCMSK = digitalPinToBitMask(INT1_PIN);
+ /*
+ *ATtinyX5. Enable pin change interrupt for port PB0 to PB5
+ */
+ GIMSK |= 1 << PCIE; // PCINT enable, we have only one
+ PCMSK = digitalPinToBitMask(INT1_PIN);
# endif
# elif (INT1_PIN != 3)
/*
@@ -219,7 +234,7 @@ void EasyButton::init(bool aIsButtonAtINT0) {
EIMSK |= 1 << INT1; // enable interrupt on next change
# endif //USE_ATTACH_INTERRUPT
# endif // ! defined(ISC10)
- }
+ }
#endif
ButtonStateIsActive = false; // negative logic for ButtonStateIsActive! true means button pin is LOW
ButtonToggleState = false;
@@ -246,12 +261,11 @@ bool EasyButton::readButtonState() {
* Returns stored state if in debouncing period otherwise current state of button
*/
bool EasyButton::readDebouncedButtonState() {
- bool tCurrentButtonStateIsActive = readButtonState();
// Check for bouncing period
if (millis() - ButtonLastChangeMillis <= BUTTON_DEBOUNCING_MILLIS) {
return ButtonStateIsActive;
}
- return tCurrentButtonStateIsActive;
+ return readButtonState();
}
/*
@@ -293,58 +307,66 @@ bool EasyButton::updateButtonState() {
uint16_t EasyButton::updateButtonPressDuration() {
if (readDebouncedButtonState()) {
// Button still active -> update ButtonPressDurationMillis
- noInterrupts();
- // really needed, since otherwise we may get wrong results if interrupted by button ISR
- unsigned long tButtonLastChangeMillis = ButtonLastChangeMillis;
- interrupts();
- ButtonPressDurationMillis = millis() - tButtonLastChangeMillis;
+ ButtonPressDurationMillis = millis() - ButtonLastChangeMillis;
}
return ButtonPressDurationMillis;
}
/*
- * Used for long button press recognition.
+ * Used for long button press recognition, while button is still pressed!
* returns EASY_BUTTON_LONG_PRESS_DETECTED, EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE and EASY_BUTTON_LONG_PRESS_ABORT
*/
uint8_t EasyButton::checkForLongPress(uint16_t aLongPressThresholdMillis) {
+ uint8_t tRetvale = EASY_BUTTON_LONG_PRESS_ABORT;
if (readDebouncedButtonState()) {
- // Button still active -> update ButtonPressDurationMillis
+ // Button still active -> update current ButtonPressDurationMillis
+ // noInterrupts() is required, since otherwise we may get wrong results if interrupted during load of long value by button ISR
noInterrupts();
- // really needed, since otherwise we may get wrong results if interrupted by button ISR
- unsigned long tButtonLastChangeMillis = ButtonLastChangeMillis;
+ ButtonPressDurationMillis = millis() - ButtonLastChangeMillis;
interrupts();
- ButtonPressDurationMillis = millis() - tButtonLastChangeMillis;
- if (ButtonPressDurationMillis >= aLongPressThresholdMillis) {
- // long press detected
- return EASY_BUTTON_LONG_PRESS_DETECTED;
- }
- return EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE; // you may try again
+ tRetvale = EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE; // if not detected, you may try again
+ }
+ if (ButtonPressDurationMillis >= aLongPressThresholdMillis) {
+ // long press detected
+ return EASY_BUTTON_LONG_PRESS_DETECTED;
}
- return EASY_BUTTON_LONG_PRESS_ABORT;
+
+ return tRetvale;
}
/*
- * Checks blocking for long press of button
- * @return true if long press was detected
+ * Checks for long press of button
+ * Blocks until long press threshold is reached or button was released.
+ * @return true if long press was detected - only once for each long press
*/
bool EasyButton::checkForLongPressBlocking(uint16_t aLongPressThresholdMillis) {
- uint8_t tLongPressCheckResult;
- do {
- tLongPressCheckResult = checkForLongPress(aLongPressThresholdMillis);
- delay(1);
- } while (tLongPressCheckResult == EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE);
- return (tLongPressCheckResult == EASY_BUTTON_LONG_PRESS_DETECTED);
+ if (!ButtonLongPressJustDetected) {
+ /*
+ * wait as long as button is pressed shorter than threshold millis.
+ */
+ while (checkForLongPress(aLongPressThresholdMillis) == EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE) {
+ delay(1);
+ }
+ /*
+ * Here button was not presses or time was greater than threshold.
+ * ButtonPressDurationMillis was updated by call to checkForLongPress before
+ */
+ if (ButtonPressDurationMillis >= aLongPressThresholdMillis) {
+ ButtonLongPressJustDetected = true;
+ return true;
+ }
+ }
+ return false;
}
/*
* Double press detection by computing difference between current (active) timestamp ButtonLastChangeMillis
* and last release timestamp ButtonReleaseMillis.
+ * !!!Works only reliable if called in callback function!!!
* @return true if double press detected.
*/
bool EasyButton::checkForDoublePress(uint16_t aDoublePressDelayMillis) {
- noInterrupts();
unsigned long tReleaseToPressTimeMillis = ButtonLastChangeMillis - ButtonReleaseMillis;
- interrupts();
return (tReleaseToPressTimeMillis <= aDoublePressDelayMillis);
}
@@ -354,6 +376,7 @@ bool EasyButton::checkForDoublePress(uint16_t aDoublePressDelayMillis) {
* @return true if timeout reached: false if last button release was before aTimeoutMillis
*/
bool EasyButton::checkForForButtonNotPressedTime(uint16_t aTimeoutMillis) {
+ // noInterrupts() is required, since otherwise we may get wrong results if interrupted during load of long value by button ISR
noInterrupts();
unsigned long tButtonReleaseMillis = ButtonReleaseMillis;
interrupts();
@@ -374,16 +397,16 @@ void EasyButton::handleINT01Interrupts() {
* This is faster than readButtonState();
*/
#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1)
- tCurrentButtonStateIsActive = INT0_IN_PORT & _BV(INT0_BIT); // = digitalReadFast(2);
- #elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0)
+ tCurrentButtonStateIsActive = INT0_IN_PORT & _BV(INT0_BIT); // = digitalReadFast(2);
+#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0)
tCurrentButtonStateIsActive = INT1_IN_PORT & _BV(INT1_BIT); // = digitalReadFast(3);
#elif defined(USE_BUTTON_0) && defined(USE_BUTTON_1)
- if (isButtonAtINT0) {
- tCurrentButtonStateIsActive = INT0_IN_PORT & _BV(INT0_BIT); // = digitalReadFast(2);
- } else {
- tCurrentButtonStateIsActive = INT1_IN_PORT & _BV(INT1_BIT); // = digitalReadFast(3);
- }
- #endif
+ if (isButtonAtINT0) {
+ tCurrentButtonStateIsActive = INT0_IN_PORT & _BV(INT0_BIT); // = digitalReadFast(2);
+ } else {
+ tCurrentButtonStateIsActive = INT1_IN_PORT & _BV(INT1_BIT); // = digitalReadFast(3);
+ }
+#endif
tCurrentButtonStateIsActive = !tCurrentButtonStateIsActive; // negative logic for tCurrentButtonStateIsActive! true means button pin is LOW
#ifdef TRACE
Serial.print(tCurrentButtonStateIsActive);
@@ -473,11 +496,12 @@ void EasyButton::handleINT01Interrupts() {
ButtonStateIsActive = tCurrentButtonStateIsActive;
ButtonStateHasJustChanged = true;
if (tCurrentButtonStateIsActive) {
+ ButtonLongPressJustDetected = false; // reset lock flag for long button press detection
/*
* Action on button press, no action on release
*/
#ifdef LED_FEEDBACK_TEST
- digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, HIGH);
+ digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, HIGH);
#endif
ButtonToggleState = !ButtonToggleState;
if (ButtonPressCallback != NULL) {
@@ -511,7 +535,7 @@ void EasyButton::handleINT01Interrupts() {
ButtonPressDurationMillis = tDeltaMillis;
ButtonReleaseMillis = tMillis;
#ifdef LED_FEEDBACK_TEST
- digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, LOW);
+ digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, LOW);
#endif
}
}
diff --git a/src/EasyButtonAtInt01.h b/src/EasyButtonAtInt01.h
index c5f95cf..26a000f 100644
--- a/src/EasyButtonAtInt01.h
+++ b/src/EasyButtonAtInt01.h
@@ -32,6 +32,10 @@
*/
/*
+ * Version 2.1.0 - 5/2020
+ * - Avoid 1 ms delay for checkForLongPressBlocking() if button is not pressed.
+ * - Only one true result per press for checkForLongPressBlocking().
+ *
* Version 2.0.0 - 1/2020
* - Ported to ATtinyX5 and ATiny167.
* - Support also PinChangeInterrupt for button 1 on Pin PA0 to PA7 for ATtiniy87/167.
@@ -57,7 +61,12 @@
* ...
*
*/
-
+/*
+ * Define USE_ATTACH_INTERRUPT to force use of the arduino function attachInterrupt().
+ * Needed if you get the error " multiple definition of `__vector_1'" (or `__vector_2'), because another library uses the attachInterrupt() function.
+ * For one button it needs additional 160 bytes FLASH, for 2 buttons it needs additional 88 bytes.
+ */
+//#define USE_ATTACH_INTERRUPT
/*
* You can define your own value if you have buttons which are worse or better than the one I have.
* Since debouncing is not done with blocking wait, reducing this value makes not much sense, except you expect regular short button presses,
@@ -67,23 +76,14 @@
* Test your own new value with the DebounceTest example
*
* Analyze the current button debounce value with defining ANALYZE_MAX_BOUNCING_PERIOD and looking at MaxBouncingPeriodMillis.
+ * Defining ANALYZE_MAX_BOUNCING_PERIOD computes the maximum bouncing period.
+ * this is the time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS
*/
+//#define ANALYZE_MAX_BOUNCING_PERIOD
#ifndef BUTTON_DEBOUNCING_MILLIS
#define BUTTON_DEBOUNCING_MILLIS 50 // 35 millis measured for my button :-).
#endif
-/*
- * Define USE_ATTACH_INTERRUPT to force use of the arduino function attachInterrupt().
- * Needed if you get the error " multiple definition of `__vector_1'" (or `__vector_2'), because another library uses the attachInterrupt() function.
- * For one button it needs additional 160 bytes FLASH, for 2 buttons it needs additional 88 bytes.
- */
-//#define USE_ATTACH_INTERRUPT
-//
-/*
- * Defining ANALYZE_MAX_BOUNCING_PERIOD computes the maximum bouncing period.
- * this is the time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS
- */
-#define ANALYZE_MAX_BOUNCING_PERIOD
/*
* Return values for checkForLongPress()
*/
@@ -91,6 +91,9 @@
#define EASY_BUTTON_LONG_PRESS_ABORT 1 // button was released, no long press detection possible
#define EASY_BUTTON_LONG_PRESS_DETECTED 2
+#define EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS 400
+#define EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS 400
+
/*
* Activate LED_BUILTIN as long as button is pressed
*/
@@ -204,7 +207,6 @@
#define INT0_BIT INT0_PIN
#endif
-
class EasyButton {
public:
@@ -228,36 +230,40 @@ class EasyButton {
* Updates the ButtonPressDurationMillis by polling, since this cannot be done by interrupt.
*/
uint16_t updateButtonPressDuration();
- uint8_t checkForLongPress(uint16_t aLongPressThresholdMillis);
- bool checkForLongPressBlocking(uint16_t aLongPressThresholdMillis);
- bool checkForDoublePress(uint16_t aDoublePressDelayMillis);
+ uint8_t checkForLongPress(uint16_t aLongPressThresholdMillis = EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS);
+ bool checkForLongPressBlocking(uint16_t aLongPressThresholdMillis = EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS);
+ bool checkForDoublePress(uint16_t aDoublePressDelayMillis = EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS); // !!!Works only reliable if called in callback function!!!
bool checkForForButtonNotPressedTime(uint16_t aTimeoutMillis);
+
void handleINT01Interrupts();
+ bool LastChangeWasBouncingToInactive; // Internal state, reflects actual reading with spikes and bouncing. Negative logic: true / active means button pin is LOW
+ volatile bool ButtonStateIsActive; // Negative logic: true / active means button pin is LOW. If last press duration < BUTTON_DEBOUNCING_MILLIS it holds wrong value (true instead of false) :-(
+ volatile bool ButtonToggleState; // Toggle is on press, not on release - initial value is false
- bool LastChangeWasBouncingToInactive; // Internal state, reflects actual reading with spikes and bouncing. Negative logic: true / active means button pin is LOW
- volatile bool ButtonStateIsActive; // negative logic: true / active means button pin is LOW. If last press duration < BUTTON_DEBOUNCING_MILLIS it holds wrong value (true instead of false) :-(
- volatile bool ButtonToggleState; // Toggle is on press, not on release - initial value is false
/*
* Flag to enable action only once. Only set to true by library. Can be checked and set to false my main program to enable only one action per button press
*/
volatile bool ButtonStateHasJustChanged;
+
/*
* Duration of active state. Is is set at button release. Can in theory not be less than BUTTON_DEBOUNCING_MILLIS.
* By definition, shorter presses are recognized as bouncing.
* To cover this case you can call updateButtonState() from an outside loop which updates the button state in this case.
*/
volatile uint16_t ButtonPressDurationMillis;
- volatile unsigned long ButtonLastChangeMillis; // for debouncing and long press detection
+ volatile unsigned long ButtonLastChangeMillis; // For debouncing
+
volatile unsigned long ButtonReleaseMillis; // for double press recognition
+ volatile bool ButtonLongPressJustDetected; // Lock flag for long button press detection
-#ifdef ANALYZE_MAX_BOUNCING_PERIOD
+#if defined(ANALYZE_MAX_BOUNCING_PERIOD)
volatile unsigned int MaxBouncingPeriodMillis = 0; // Maximum bouncing period. Time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS
volatile bool MaxBouncingPeriodMillisHasJustChanged;
#endif
volatile bool isButtonAtINT0;
- void (*ButtonPressCallback)(bool aButtonToggleState) = NULL; // if not null, is called on every button press with ButtonToggleState as parameter
+ void (*ButtonPressCallback)(bool aButtonToggleState) = NULL; // If not null, is called on every button press with ButtonToggleState as parameter
#if defined(USE_BUTTON_0)
static EasyButton * sPointerToButton0ForISR;
@@ -268,11 +274,9 @@ class EasyButton {
};
// end of class definition
-
void handleINT0Interrupt();
void handleINT1Interrupt();
-
/*
* This functions are weak and can be replaced by your own code
*/