diff --git a/.github/workflows/LibraryBuild.yml b/.github/workflows/LibraryBuild.yml index 674fcb7..d1d3000 100644 --- a/.github/workflows/LibraryBuild.yml +++ b/.github/workflows/LibraryBuild.yml @@ -6,7 +6,15 @@ # This is the name of the workflow, visible on GitHub UI. name: LibraryBuild -on: [push, pull_request] # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request + +on: + push: # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request + paths: + - '**.ino' + - '**.cpp' + - '**.h' + - '**LibraryBuildWithAction.yml' + pull_request: jobs: build: diff --git a/README.md b/README.md index cb4ca4f..043ba73 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -# EasyButton -### [Version 2.0.0](https://github.com/ArminJo/EasyButtonAtInt01/releases) +# [EasyButton](https://github.com/ArminJo/EasyButtonAtInt01) +Available as Arduino library "EasyButtonAtInt01" + +### [Version 2.1.0](https://github.com/ArminJo/EasyButtonAtInt01/releases) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Installation instructions](https://www.ardu-badge.com/badge/EasyButtonAtInt01.svg?)](https://www.ardu-badge.com/EasyButtonAtInt01) @@ -8,22 +10,24 @@ [![Hit Counter](https://hitcounter.pythonanywhere.com/count/tag.svg?url=https%3A%2F%2Fgithub.com%2FArminJo%2FEasyButtonAtInt01)](https://github.com/brentvollebregt/hit-counter) Arduino library for handling push buttons just connected between ground and INT0 and / or INT1 pin.
- -No external pullup, **no polling needed**. - -The library is totally **based on interrupt** and **debouncing is implemented in a not blocking way**. -Debouncing is merely done by ignoring a button change within the debouncing time. So **button state is instantly available** without debouncing delay! - +- No external pullup, **no polling needed**. +- The library is totally **based on interrupt** and **debouncing is implemented in a not blocking way**. +Debouncing is merely done by ignoring a button change within the debouncing time (default 50 ms). +So **button state is instantly available** without debouncing delay! - Each button press toggles a state variable, so **no external logic for implementing a toggle button is needed**. -- Support for **double press detection** is included. See Callback example. -- Support for **long press detection**, which needs some polling or blocking, is included. See EasyButtonExample example. - -INT0 and INT1 are connected to: -- Pin 2 / 3 on most Arduinos (ATmega328*). -- PB6 / PA3 on ATtiny167. To use one of PA0 to PA7 instead of PA3 just (re)define INT1_PIN before including *EasyButtonAtInt01.cpp.h*. E.g. `#define INT1_PIN 7`. See EasyButtonExample.cpp. -- Only PB2 / INT0 at on ATtinyX5. +- Support for **double press detection** is included. See EasyButtonExample and Callback example. +- Support for **long press detection**, is included. See EasyButtonExample example. +- Support to **measure maximum bouncing period of a button**. See EasyButtonExample example. +## Table of available pins for the 2 buttons +| CPU | Button 0 | Button 1 using INT1 | Button 1 using PCINT, if INT1_PIN is defined !=3 | +|-|-|-|-| +| ATmega328* | D2 | D3 | Pin 0 to 2, 4 to 7 | +| ATtiny5x | PB2 | | PB0 - PB5 | +| ATtiny167 | PB6 | PA3 | PA0 to PA2, PA4 to PA7 | +To use the PCINT buttons instead of the default one, just define INT1_PIN **before** including *EasyButtonAtInt01.cpp.h*.
+E.g. `#define INT1_PIN 7`. See [EasyButtonExample.cpp](examples/EasyButtonExample/EasyButtonExample.ino#L46). ## Usage To use a single button, it needs only: @@ -36,32 +40,36 @@ EasyButton Button0AtPin2; void setup() {} void loop() { ... - 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 ... } ``` To use 2 buttons, it needs only: + ``` #define USE_BUTTON_0 // Enable code for button at INT0 (pin2) #define USE_BUTTON_1 // Enable code for button at INT1 (pin3) #include "EasyButtonAtInt01.cpp.h" EasyButton Button0AtPin2(true); // true -> Button is connected to INT0 (pin2) -EasyButton Button0AtPin3(false); // false -> Button is not connected to INT0 => connected to INT1 (pin3) +EasyButton Button1AtPin3(false); // false -> Button is not connected to INT0 => connected to INT1 (pin3) void setup() {} void loop() { ... digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState); - delay(50); - digitalWrite(LED_BUILTIN, Button0AtPin3.ButtonToggleState); - delay(50); + delay(100); + digitalWrite(LED_BUILTIN, Button1AtPin3.ButtonToggleState); + delay(200); ... } ``` ## Usage of callback function The callback function is is called on every button press with ButtonToggleState as parameter.
-Before callback function is called, interrupts are enabled! This allows the timer interrupt for millis() to work! +**The value at the first call (after first press) is true**.
+The callback function runs in an interrupt service context, which means it should be as short as possible. +But before callback function is called, interrupts are enabled. +This allows the timer interrupt for millis() to work and therfore **delay() and millis() can be used in the callback function**. ``` #define USE_BUTTON_0 // Enable code for button at INT0 (pin2) @@ -77,10 +85,49 @@ void setup() {} void loop() {} ``` -## Handling multiple definition error -If you get the error "multiple definition of `__vector_1'" (or `__vector_2') because another library uses the attachInterrupt() function, +## Long press detection +Check it cyclical in your loop. Do not forget, that you will get a callback (if enabled) at the start of the long press. +The blocking call only blocks if button is pressed, otherwise it returns immediately. + +``` +void loop() { +... + // default long press period is 400 ms + if (Button1AtPin3.checkForLongPressBlocking()) { + doSomething(); + } +... +} +``` + +## Double press detection +Call checkForDoublePress() only from callback function. It will not work as expected called outside the callback function. + +``` +#define USE_BUTTON_0 // Enable code for button at INT0 (pin2) +#include "EasyButtonAtInt01.cpp.h" + +void printButtonToggleState(bool aButtonToggleState); +EasyButton Button0AtPin2(&printButtonToggleState); + +// initial value is false, so first call is with true +void printButtonToggleState(bool aButtonToggleState) { + digitalWrite(LED_BUILTIN, aButtonToggleState); + // This function works reliable only if called in callback function + if (Button0AtPin2.checkForDoublePress()) { + Serial.println(F("Button 0 double press (< 400 ms) detected")); + } +} + +void setup() {} +void loop() {} +``` + +## Handling the `multiple definition` error +If you get the error `multiple definition of __vector_1` (or `__vector_2`) because another library uses the attachInterrupt() function, then comment out the line `#define USE_ATTACH_INTERRUPT` in *EasyButtonAtInt01.h* or define global symbol with `-DUSE_ATTACH_INTERRUPT` which is not yet possible in Arduino IDE:-(.
+ ## Consider to use [Sloeber](http://eclipse.baeyens.it/stable.php?OS=Windows) as IDE
If you are using Sloeber as your IDE, you can easily define global symbols at *Properties/Arduino/CompileOptions*.
@@ -90,16 +137,22 @@ EasyButton(bool aIsButtonAtINT0); // Constructor EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState)); // Constructor void init(); // used by constructors +#define EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS 400 +#define EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS 400 + bool readButtonState(); bool updateButtonState(); 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); bool checkForForButtonNotPressedTime(uint16_t aTimeoutMillis); ``` # Revision History +### Version 2.1.0 +- Avoid 1 ms delay for `checkForLongPressBlocking()` if button is not pressed. +- Only one true result per press for `checkForLongPressBlocking()`. ### Version 2.0.0 - Ported to ATtinyX5 and ATiny167. @@ -114,7 +167,7 @@ bool checkForForButtonNotPressedTime(uint16_t aTimeoutMillis); - initial version for ATmega328. # CI -Since Travis CI is unreliable and slow, the library examples are now tested with GitHub Actions for the following boards: +The library examples are tested with GitHub Actions for the following boards: - arduino:avr:uno - arduino:avr:leonardo diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino index 58b3076..ac386a4 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/Callback.ino @@ -33,7 +33,7 @@ void showButtonToggleState(bool aButtonToggleState); // The callback function EasyButton Button0AtPin2(&showButtonToggleState); // Only 1. button (USE_BUTTON_0) enabled -> button is connected to INT0 (pin2) -#define VERSION_EXAMPLE "2.0" +#define VERSION_EXAMPLE "2.1" #if defined(ARDUINO_AVR_DIGISPARK) #define LED_BUILTIN PB1 @@ -69,7 +69,7 @@ void showButtonToggleState(bool aButtonToggleState) { /* * Double press (< 200 ms) detection by calling checkForForDoublePress() once at button press time. */ - if (Button0AtPin2.checkForDoublePress(200)) { - Serial.println(F("Double press (< 200 ms) detected")); + if (Button0AtPin2.checkForDoublePress(300)) { + Serial.println(F("Double press (< 300 ms) detected")); } } diff --git a/examples/DebounceTest/DebounceTest.ino b/examples/DebounceTest/DebounceTest.ino index 95ef348..b0958a6 100644 --- a/examples/DebounceTest/DebounceTest.ino +++ b/examples/DebounceTest/DebounceTest.ino @@ -28,7 +28,9 @@ //#define USE_ATTACH_INTERRUPT // enable it if you get the error " multiple definition of `__vector_1'" (or `__vector_2') //#define MEASURE_INTERRUPT_TIMING +//#define DO_NOT_REQUIRE_LONG_AND_DOUBLE_PRESS +#define ANALYZE_MAX_BOUNCING_PERIOD #define BUTTON_DEBOUNCING_MILLIS 2 #define USE_BUTTON_0 // Enable code for 1. button at INT0 diff --git a/examples/EasyButtonExample/ATtinySerialOut.cpp b/examples/EasyButtonExample/ATtinySerialOut.cpp index 522638a..4cdd41f 100644 --- a/examples/EasyButtonExample/ATtinySerialOut.cpp +++ b/examples/EasyButtonExample/ATtinySerialOut.cpp @@ -233,27 +233,27 @@ void writeByte(int8_t aByte) { writeStringSkipLeadingSpaces(tStringBuffer); } -void writeInt(int aInteger) { +void writeInt(int16_t aInteger) { char tStringBuffer[7]; itoa(aInteger, tStringBuffer, 10); writeStringSkipLeadingSpaces(tStringBuffer); } -void writeUnsignedInt(unsigned int aInteger) { +void writeUnsignedInt(uint16_t aInteger) { char tStringBuffer[6]; - itoa(aInteger, tStringBuffer, 10); + utoa(aInteger, tStringBuffer, 10); writeStringSkipLeadingSpaces(tStringBuffer); } -void writeLong(long aLong) { +void writeLong(int32_t aLong) { char tStringBuffer[12]; ltoa(aLong, tStringBuffer, 10); writeStringSkipLeadingSpaces(tStringBuffer); } -void writeUnsignedLong(unsigned long aLong) { +void writeUnsignedLong(uint32_t aLong) { char tStringBuffer[11]; - ltoa(aLong, tStringBuffer, 10); + ultoa(aLong, tStringBuffer, 10); writeStringSkipLeadingSpaces(tStringBuffer); } @@ -373,27 +373,27 @@ void TinySerialOut::print(uint8_t aByte, uint8_t aBase) { } } -void TinySerialOut::print(int aInteger, uint8_t aBase) { +void TinySerialOut::print(int16_t aInteger, uint8_t aBase) { char tStringBuffer[7]; itoa(aInteger, tStringBuffer, aBase); writeStringSkipLeadingSpaces(tStringBuffer); } -void TinySerialOut::print(unsigned int aInteger, uint8_t aBase) { +void TinySerialOut::print(uint16_t aInteger, uint8_t aBase) { char tStringBuffer[6]; - itoa(aInteger, tStringBuffer, aBase); + utoa(aInteger, tStringBuffer, aBase); writeStringSkipLeadingSpaces(tStringBuffer); } -void TinySerialOut::print(long aLong, uint8_t aBase) { +void TinySerialOut::print(int32_t aLong, uint8_t aBase) { char tStringBuffer[12]; ltoa(aLong, tStringBuffer, aBase); writeStringSkipLeadingSpaces(tStringBuffer); } -void TinySerialOut::print(unsigned long aLong, uint8_t aBase) { +void TinySerialOut::print(uint32_t aLong, uint8_t aBase) { char tStringBuffer[11]; - ltoa(aLong, tStringBuffer, aBase); + ultoa(aLong, tStringBuffer, aBase); writeStringSkipLeadingSpaces(tStringBuffer); } @@ -423,22 +423,22 @@ void TinySerialOut::println(uint8_t aByte, uint8_t aBase) { println(); } -void TinySerialOut::println(int aInteger, uint8_t aBase) { +void TinySerialOut::println(int16_t aInteger, uint8_t aBase) { print(aInteger, aBase); println(); } -void TinySerialOut::println(unsigned int aInteger, uint8_t aBase) { +void TinySerialOut::println(uint16_t aInteger, uint8_t aBase) { print(aInteger, aBase); println(); } -void TinySerialOut::println(long aLong, uint8_t aBase) { +void TinySerialOut::println(int32_t aLong, uint8_t aBase) { print(aLong, aBase); println(); } -void TinySerialOut::println(unsigned long aLong, uint8_t aBase) { +void TinySerialOut::println(uint32_t aLong, uint8_t aBase) { print(aLong, aBase); println(); } diff --git a/examples/EasyButtonExample/ATtinySerialOut.h b/examples/EasyButtonExample/ATtinySerialOut.h index df7d0cd..f8f3755 100644 --- a/examples/EasyButtonExample/ATtinySerialOut.h +++ b/examples/EasyButtonExample/ATtinySerialOut.h @@ -151,10 +151,10 @@ void writeByte(int8_t aByte); void writeUnsignedByte(uint8_t aByte); void writeUnsignedByteHex(uint8_t aByte); void writeUnsignedByteHexWithPrefix(uint8_t aByte); -void writeInt(int aInteger); -void writeUnsignedInt(unsigned int aInteger); -void writeLong(long aLong); -void writeUnsignedLong(unsigned long aLong); +void writeInt(int16_t aInteger); +void writeUnsignedInt(uint16_t aInteger); +void writeLong(int32_t aLong); +void writeUnsignedLong(uint32_t aLong); void writeFloat(double aFloat); void writeFloat(double aFloat, uint8_t aDigits); @@ -185,20 +185,20 @@ class TinySerialOut void print(const char* aStringPtr); void print(char aChar); void print(uint8_t aByte, uint8_t aBase = 10); - void print(int aInteger, uint8_t aBase = 10); - void print(unsigned int aInteger, uint8_t aBase = 10); - void print(long aLong, uint8_t aBase = 10); - void print(unsigned long aLong, uint8_t aBase = 10); + void print(int16_t aInteger, uint8_t aBase = 10); + void print(uint16_t aInteger, uint8_t aBase = 10); + void print(int32_t aLong, uint8_t aBase = 10); + void print(uint32_t aLong, uint8_t aBase = 10); void print(double aFloat, uint8_t aDigits = 2); void println(const char* aStringPtr); void println(const __FlashStringHelper * aStringPtr); void println(char aChar); void println(uint8_t aByte, uint8_t aBase = 10); - void println(int aInteger, uint8_t aBase = 10); - void println(unsigned int aInteger, uint8_t aBase = 10); - void println(long aLong, uint8_t aBase = 10); - void println(unsigned long aLong, uint8_t aBase = 10); + void println(int16_t aInteger, uint8_t aBase = 10); + void println(uint16_t aInteger, uint8_t aBase = 10); + void println(int32_t aLong, uint8_t aBase = 10); + void println(uint32_t aLong, uint8_t aBase = 10); void println(double aFloat, uint8_t aDigits = 2); void println(void); diff --git a/examples/EasyButtonExample/ATtinyUtils.cpp b/examples/EasyButtonExample/ATtinyUtils.cpp deleted file mode 100644 index 4868069..0000000 --- a/examples/EasyButtonExample/ATtinyUtils.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * ATtinyUtils.cpp - * - * Copyright (C) 2016-2020 Armin Joachimsmeyer - * Email: armin.joachimsmeyer@gmail.com - * - * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. - * - * ArduinoUtils is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) - -//#define DEBUG -#ifdef DEBUG -// Should be first include to avoid unwanted use of Serial object defined in HardwareSerial -#include "ATtinySerialOut.h" -#endif - -#include "ATtinyUtils.h" - -#include // required for boot_signature_byte_get() -#include // required for clock_prescale_set() -#include // required for isBODSFlagExistent() -#include "digitalWriteFast.h" - -// since we have not included Arduino.h -#define INPUT 0x0 -#define OUTPUT 0x1 -#define INPUT_PULLUP 0x2 - -/* - * Use port pin number (PB0-PB5) not case or other pin number - */ -inline void toggleFastPortB(uint8_t aOutputPinNumber) { - PINB = (1 << aOutputPinNumber); -} - -// for constant values we can also use: digitalWriteFast() -inline void digitalWriteFastPortB(uint8_t aOutputPinNumber, bool aValue) { - (aValue ? PORTB |= (1 << aOutputPinNumber) : PORTB &= ~(1 << aOutputPinNumber)); -} - -// for constant values we can also use: digitalReadFast() -inline bool digitalReadFastPortB(uint8_t aInputPinNumber) { - return (PINB & (1 << aInputPinNumber)); -} - -// not for INPUT_PULLUP - can be done by setting to input and adding digitalWriteFastPortB(aOutputPinNumber,1); -inline void pinModeFastPortB(uint8_t aOutputPinNumber, uint8_t aMode) { - (aMode ? DDRB |= (1 << aOutputPinNumber) /* OUTPUT */: DDRB &= ~(1 << aOutputPinNumber)); -} - -#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) - -/* - * initialize outputs and use PWM Mode - * if aUseOutputB == false output frequency at Pin6/5 - PB1/PB0 - OCR1A/!OCR1A - * else at Pin3/2 - PB4/PB3 - OCR1B/!OCR1B - */ -void toneWithTimer1PWM(uint16_t aFrequency, bool aUseOutputB) { - uint8_t tPrescaler = 0x01; - uint16_t tOCR = F_CPU / aFrequency; - while (tOCR > 0x100 && tPrescaler < 0x0F) { - tPrescaler++; - tOCR >>= 1; - } - - OCR1C = tOCR - 1; // The frequency of the PWM will be Timer Clock 1 frequency divided by (OCR1C value + 1). - - if (aUseOutputB) { - OCR1B = tOCR / 2; // 50% PWM - pinModeFast(PB3, OUTPUT); - pinModeFast(PB4, OUTPUT); - GTCCR = (1 << PWM1B) | (1 << COM1B0); // PWM Mode with OCR1B (PB4) + !OCR1B (PB3) outputs enabled - TCCR1 = tPrescaler; - } else { - OCR1A = tOCR / 2; // 50% PWM - pinModeFast(PB1, OUTPUT); - pinModeFast(PB0, OUTPUT); - GTCCR = 0; - TCCR1 = (1 << PWM1A) | (1 << COM1A0) | tPrescaler; // PWM Mode with OCR1A (PB1) + !OCR1A (PB0) outputs enabled - } -} - -void periodicInterruptWithTimer1(uint16_t aFrequency){ - uint8_t tPrescaler = 0x01; - uint16_t tOCR = F_CPU / aFrequency; - while (tOCR > 0x100 && tPrescaler < 0x0F) { - tPrescaler++; - tOCR >>= 1; - } - OCR1C = tOCR - 1; // The frequency of the interrupt will be Timer Clock 1 frequency divided by (OCR1C value + 1). - - TCCR1 |= (1 << CTC1) | tPrescaler; // clear timer on compare match - TIMSK |= (1 << OCIE1A); // enable compare match interrupt -} -#endif - -uint8_t getBODLevelFuses() { - return boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS) & (~FUSE_BODLEVEL2 | ~FUSE_BODLEVEL1 | ~FUSE_BODLEVEL0 ); -} - -bool isBODLevelBelow2_7Volt() { - return (getBODLevelFuses() >= 6); -} - -/* - * @return true if BODS flag is existent - should be true for ATtiny85 revision C and later - */ -bool isBODSFlagExistent() { - sleep_bod_disable() - ; - /* - * Check if flag can be set - this works only for ATtini85 revision C, which is quite unpopular (2019) on Chinese boards. - */ - return MCUCR & _BV(BODS); -} - -/* - * Code to change Digispark Bootloader clock settings to get the right CPU frequency - * and to reset Digispark OCCAL tweak. - * Call it if you want to use the standard ATtiny library, BUT do not call it, if you need Digispark USB functions available for 16 MHz. - */ -void changeDigisparkClock() { - uint8_t tLowFuse = boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS); -#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) - if ((tLowFuse & (~FUSE_CKSEL3 | ~FUSE_CKSEL2 | ~FUSE_CKSEL1 | ~FUSE_CKSEL0 )) == 0x01) { // cannot use ~FUSE_CKSEL0 on right side :-( -#elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) - if ((tLowFuse & (~FUSE_CKSEL3 | ~FUSE_CKSEL2 | ~FUSE_CKSEL1 )) == 0x0E) { // cannot use ~FUSE_CKSEL1 on right side :-( -#endif - /* - * Here we have High Frequency PLL Clock ( 16 MHz) - */ -#if (F_CPU == 1000000) - // Divide 16 MHz Pll clock by 16 for Digispark Boards to get the requested 1 MHz - clock_prescale_set(clock_div_16); -// CLKPR = (1 << CLKPCE); // unlock function -// CLKPR = (1 << CLKPS2); // 0x04 -> %16 -#endif -#if (F_CPU == 8000000) - // Divide 16 MHz Pll clock by 2 for Digispark Boards to get the requested 8 MHz - clock_prescale_set(clock_div_2); -// CLKPR = (1 << CLKPCE); // unlock function -// CLKPR = (1 << CLKPS0); // 0x01 -> %2 -#endif - } - - /* - * Code to reset Digispark OCCAL tweak - */ -#define SIGRD 5 // required for boot_signature_byte_get() - uint8_t tStoredOSCCAL = boot_signature_byte_get(1); - if (OSCCAL != tStoredOSCCAL) { -#ifdef DEBUG - uint8_t tOSCCAL = OSCCAL; - writeString(F("Changed OSCCAL from ")); - writeUnsignedByteHex(tOSCCAL); - writeString(F(" to ")); - writeUnsignedByteHex(tStoredOSCCAL); - writeCRLF(); -#endif - // retrieve the factory-stored oscillator calibration bytes to revert the Digispark OSCCAL tweak - OSCCAL = tStoredOSCCAL; - } -} - -#endif // defined (__AVR_ATtiny85__) diff --git a/examples/EasyButtonExample/ATtinyUtils.h b/examples/EasyButtonExample/ATtinyUtils.h deleted file mode 100644 index f44e2ce..0000000 --- a/examples/EasyButtonExample/ATtinyUtils.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * ATtinyUtils.h - * - * Copyright (C) 2018-2020 Armin Joachimsmeyer - * Email: armin.joachimsmeyer@gmail.com - * - * This file is part of ArduinoUtils https://github.com/ArminJo/ArduinoUtils. - * - * Arduino-Utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -// -// ATMEL ATTINY85 -// -// +-\/-+ -// PCINT5/!RESET/ADC0/dW (5) PB5 1| |8 Vcc -// PCINT3/XTAL1/CLKI/!OC1B/ADC3 (3) PB3 2| |7 PB2 (2) SCK/USCK/SCL/ADC1/T0/INT0/PCINT2 -// PCINT4/XTAL2/CLKO/OC1B/ADC2 (4) PB4 3| |6 PB1 (1) MISO/DO/AIN1/OC0B/OC1A/PCINT1 / TX Debug output -// GND 4| |5 PB0 (0) MOSI/DI/SDA/AIN0/OC0A/!OC1A/AREF/PCINT0 -// +----+ - -// ATMEL ATTINY167 -// Pin numbers are for Digispark core -// Pin numbers in parenthesis are for ATTinyCore -// -// +-\/-+ -// RX 6 (0) PA0 1| |20 PB0 (D8) 0 OC1AU TONE Timer 1 Channel A -// TX 7 (1) PA1 2| |19 PB1 (9) 1 OC1BU Internal LED -// 8 (2) PA2 3| |18 PB2 (10) 2 OC1AV Timer 1 Channel B -// INT1 9 (3) PA3 4| |17 PB3 (11) 4 OC1BV connected with 51 Ohm to D- and 3.3 volt Zener. -// AVCC 5| |16 GND -// AGND 6| |15 VCC -// 10 (4) PA4 7| |14 PB4 (12) XTAL1 -// 11 (5) PA5 8| |13 PB5 (13) XTAL2 -// 12 (6) PA6 9| |12 PB6 (14) 3 INT0 connected with 68 Ohm to D+ (and disconnected 3.3 volt Zener). Is terminated with ~20 kOhm if USB attached :-( -// 5 (7) PA7 10| |11 PB7 (15) RESET -// +----+ -// - -#ifndef ATTINYUTILS_H_ -#define ATTINYUTILS_H_ - -#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) - -#include -#include - -#if defined(ARDUINO_AVR_DIGISPARK) -# ifndef LED_BUILTIN -#define LED_BUILTIN PB1 -# endif - -#elif defined(ARDUINO_AVR_DIGISPARKPRO) -#undef LED_BUILTIN // In case we use another core e.g. in Eclipse -#define LED_BUILTIN 1 // On a Digispark Pro we have PB1 / D1 (Digispark library) or D9 (ATtinyCore lib) / on DigisparkBoard labeled as pin 1 -#endif - -#if (F_CPU == 1000000) -#define TIMER0_CLOCK_DIVIDER_FOR_64_MICROS (_BV(CS01) | _BV(CS00)) - -#define TIMER1_CLOCK_DIVIDER_FOR_8_MICROS _BV(CS12) -#define TIMER1_CLOCK_DIVIDER_FOR_4_MICROS (_BV(CS11) | _BV(CS10)) -#define TIMER1_CLOCK_DIVIDER_FOR_2_MICROS _BV(CS11) -#define TIMER1_CLOCK_DIVIDER_FOR_1_MICRO _BV(CS10) -#endif - -#if (F_CPU == 8000000) -#define TIMER0_CLOCK_DIVIDER_FOR_128_MICROS (_BV(CS02) | _BV(CS00)) - -#define TIMER1_CLOCK_DIVIDER_FOR_8_MICROS (_BV(CS12) | _BV(CS11)| _BV(CS10)) -#define TIMER1_CLOCK_DIVIDER_FOR_4_MICROS (_BV(CS12) | _BV(CS11)) -#define TIMER1_CLOCK_DIVIDER_FOR_2_MICROS (_BV(CS12) | _BV(CS10)) -#define TIMER1_CLOCK_DIVIDER_FOR_1_MICRO _BV(CS12) -#endif - -/* - * Formula is only valid for constant values - * Loading of constant value adds 2 extra cycles (check .lss file for exact timing) - * - * Only multiple of 4 cycles are possible. Last loop is only 3 cycles. - * 1 -> 3(+2) cycles - * 2 -> 7(+2) cycles - * 3 -> 11(+2) cycles - * 4 -> 15(+2) cycles - * 5 -> 19(+2) cycles - * 6 -> 23(+2) cycles - */ -inline void delay4CyclesInlineExact(uint16_t a4Microseconds) { - // the following loop takes 4 cycles (4 microseconds at 1 MHz) per iteration - __asm__ __volatile__ ( - "1: sbiw %0,1" "\n\t" // 2 cycles - "brne .-4" : "=w" (a4Microseconds) : "0" (a4Microseconds)// 2 cycles - ); -} -#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) -void toneWithTimer1PWM(uint16_t aFrequency, bool aUseOutputB = false); -#endif - -uint8_t getBODLevelFuses(); -bool isBODLevelBelow2_7Volt(); -bool isBODSFlagExistent(); -void changeDigisparkClock(); - -#endif // defined (__AVR_ATtiny85__) -#endif /* ATTINYUTILS_H_ */ - -#pragma once diff --git a/examples/EasyButtonExample/EasyButtonExample.ino b/examples/EasyButtonExample/EasyButtonExample.ino index e8d3efc..3433389 100644 --- a/examples/EasyButtonExample/EasyButtonExample.ino +++ b/examples/EasyButtonExample/EasyButtonExample.ino @@ -1,7 +1,8 @@ /* * EasyButtonExample.cpp * - * Example for one or two buttons using PA7/PCINT0 for button 1 on an ATtiny167 and PB3//PCINT for button 1 on an ATtiny*5 + * Example for one or two buttons + * Button1 using PCINT0 - PA7 on an ATtiny167 and * * Copyright (C) 2018 Armin Joachimsmeyer * armin.joachimsmeyer@gmail.com @@ -26,40 +27,46 @@ #include //#define USE_ATTACH_INTERRUPT // enable it if you get the error " multiple definition of `__vector_1'" (or `__vector_2') -//#define BUTTON_DEBOUNCING_MILLIS 80 +//#define BUTTON_DEBOUNCING_MILLIS 80 // With this you can adapt to the characteristic of your button. Default is 50. +#define ANALYZE_MAX_BOUNCING_PERIOD -#define USE_BUTTON_0 // Enable code for 1. button at INT0 -//#define USE_BUTTON_1 // Enable code for 2. button at INT1 or PCINT[0:7] +#define USE_BUTTON_0 // Enable code for button 0 at INT0. +#define USE_BUTTON_1 // Enable code for button 1 at INT1 or PCINT[0:7] + +// definitions for ATtinies #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) #include "ATtinySerialOut.h" -#include "ATtinyUtils.h" // for changeDigisparkClock() -#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) && TX_PIN == PB2 + +# if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) && TX_PIN == PB2 #error "Please change TX_PIN in ATtinySerialOut.h from PB2 to e.g. PB0 for use with this example" -#endif +# endif + # if defined(ARDUINO_AVR_DIGISPARK) #define LED_BUILTIN PB1 +#define INT1_PIN 1 // use PB2 for button 1 + # elif defined(ARDUINO_AVR_DIGISPARKPRO) #undef LED_BUILTIN // In case we use another core e.g. in Eclipse #define LED_BUILTIN 9 // On a Digispark Pro we have PB1 / D9 / PCB pin 1 + #define INT1_PIN 7 // use PCINT7/PA7 instead of INT1/PA3 for button 1 on ATtiny167 -#define INTENTIONALLY_USE_PCI0_FOR_BUTTON1 // yes we know that we use the PCINT[0:7] for button 1. It is no typo. +//#define INTENTIONALLY_USE_PCI0_FOR_BUTTON1 // yes we know that we use the PCINT[0:7] for button 1. It is no typo. + # else #undef LED_BUILTIN #define LED_BUILTIN PB1 // define pin of built in LED for your ATtiny -# endif -#endif +# endif // ATTiny type +#endif // ATTinies #include "EasyButtonAtInt01.cpp.h" -EasyButton Button1AtPin3(true); // true -> button is connected to INT0 -#ifdef USE_BUTTON_1 -bool sButtonLongPressDetected = false; -// The callback function for button 2 +// The callback function for button 1 void printButtonToggleState(bool aButtonToggleState); -EasyButton Button2AtPA7(false, &printButtonToggleState); // false -> button is not connected to INT0 but connected to INT1 or PCINT[0:7] -#endif -#define VERSION_EXAMPLE "2.0" +EasyButton Button0AtPin2(true, &printButtonToggleState); // true -> button is connected to INT0 +EasyButton Button1AtPin3((bool)false); // false -> button is not connected to INT0 but connected to INT1 or PCINT[0:7]. (bool) to avoid overloaded warning for digispark compiler. + +#define VERSION_EXAMPLE "2.1" long sOldDeltaMillis; @@ -67,10 +74,6 @@ void setup() { // initialize the digital pin as an output. pinMode(LED_BUILTIN, OUTPUT); -#if defined(ARDUINO_AVR_DIGISPARK) || defined(ARDUINO_AVR_DIGISPARKPRO) - changeDigisparkClock(); -#endif - Serial.begin(115200); #if defined(__AVR_ATmega32U4__) while (!Serial) @@ -84,7 +87,8 @@ void setup() { void loop() { /* - * Button 1 + * Button 1 - check manually. + * But it is easier to just use a callback function as we do for button 0 */ Button1AtPin3.updateButtonState(); // this is ONLY required if we expect a regular button press which is shorter than BUTTON_DEBOUNCING_MILLIS! if (Button1AtPin3.ButtonStateHasJustChanged) { @@ -95,7 +99,7 @@ void loop() { /* * Print new status */ - Serial.print(F("Button1 IsActive=")); + Serial.print(F("Button 1 IsActive=")); Serial.print(Button1AtPin3.ButtonStateIsActive); Serial.print(F(" ToggleState=")); Serial.print(Button1AtPin3.ButtonToggleState); @@ -104,39 +108,51 @@ void loop() { Serial.print(Button1AtPin3.ButtonPressDurationMillis); Serial.print(F(" ms")); } + +#if defined(ANALYZE_MAX_BOUNCING_PERIOD) + /* + * Print max bouncing period for the button + */ if (Button1AtPin3.MaxBouncingPeriodMillisHasJustChanged) { Button1AtPin3.MaxBouncingPeriodMillisHasJustChanged = false; Serial.print(F(" MaxBouncingPeriod=")); Serial.print(Button1AtPin3.MaxBouncingPeriodMillis); } +#endif Serial.println(); } -#ifdef USE_BUTTON_1 /* - * Button 2 uses a callback function - * - * Long press (> 500 ms) detection by polling function checkForLongPress() + * Long press (> 400 ms) detection function for button 0 can be called in loop */ - if (!sButtonLongPressDetected && Button2AtPA7.checkForLongPress(500) == EASY_BUTTON_LONG_PRESS_DETECTED) { - sButtonLongPressDetected = true; - Serial.println(F("Button2 long press (> 500 ms) detected")); + if (Button0AtPin2.checkForLongPressBlocking()) { + Serial.println(F("Button 0 long press (> 400 ms) detected")); } -#endif } -#ifdef USE_BUTTON_1 void printButtonToggleState(bool aButtonToggleState) { digitalWrite(LED_BUILTIN, aButtonToggleState); - Serial.print("Button2 ToggleState="); + Serial.print("Button 0 ToggleState="); Serial.print(aButtonToggleState); - sButtonLongPressDetected = false; - if (Button2AtPA7.MaxBouncingPeriodMillisHasJustChanged) { - Button2AtPA7.MaxBouncingPeriodMillisHasJustChanged = false; + +#if defined(ANALYZE_MAX_BOUNCING_PERIOD) + /* + * Print max bouncing period for the button + */ + if (Button0AtPin2.MaxBouncingPeriodMillisHasJustChanged) { + Button0AtPin2.MaxBouncingPeriodMillisHasJustChanged = false; Serial.print(F(" MaxBouncingPeriod=")); - Serial.print(Button2AtPA7.MaxBouncingPeriodMillis); + Serial.print(Button0AtPin2.MaxBouncingPeriodMillis); } +#endif Serial.println(); + + /* + * This function works reliable only if called in callback function + */ + if (Button0AtPin2.checkForDoublePress()) { + Serial.println(F("Button 0 double press (< 400 ms) detected")); + } } -#endif + diff --git a/examples/OneButton/OneButton.ino b/examples/OneButton/OneButton.ino index a300327..b79efa7 100644 --- a/examples/OneButton/OneButton.ino +++ b/examples/OneButton/OneButton.ino @@ -27,7 +27,9 @@ //#define USE_ATTACH_INTERRUPT // enable it if you get the error " multiple definition of `__vector_1'" (or `__vector_2') -#define USE_BUTTON_0 // Enable code for 1. button at INT0 +#define USE_BUTTON_0 // Enable code for 1. button at INT0 / D2 +#define DO_NOT_REQUIRE_LONG_AND_DOUBLE_PRESS + #include "EasyButtonAtInt01.cpp.h" #if defined(ARDUINO_AVR_DIGISPARK) @@ -57,6 +59,10 @@ void setup() { void loop() { delay(10); + /* + * Manually check button state change + * Using a callback function is sometimes easier, see EasyButtonExample and Callback example. + */ if (Button0AtPin2.ButtonStateHasJustChanged) { // reset flag in order to do call digitalWrite() only once per button press Button0AtPin2.ButtonStateHasJustChanged = false; diff --git a/library.properties b/library.properties index ca0a46a..f8d4988 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=EasyButtonAtInt01 -version=2.0.0 +version=2.1.0 author=Armin Joachimsmeyer maintainer=Armin Joachimsmeyer 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 */