From 313c819b49385a2f119bd2600d4b3dfa5c8e9601 Mon Sep 17 00:00:00 2001 From: Armin Date: Wed, 15 Sep 2021 10:10:22 +0200 Subject: [PATCH] Renamed EasyButtonAtInt01.cpp.h to EasyButtonAtInt01.hpp. Bumped version to 3.3.0 --- README.md | 21 +- examples/Callback/Callback.ino | 4 +- examples/DebounceTest/DebounceTest.ino | 4 +- examples/EasyButtonExample/ATtinySerialOut.h | 58 +- ...TtinySerialOut.cpp => ATtinySerialOut.hpp} | 59 +- .../EasyButtonExample/EasyButtonExample.ino | 6 +- examples/OneButton/OneButton.ino | 4 +- library.properties | 4 +- src/EasyButtonAtInt01.cpp.h | 659 +---------------- src/EasyButtonAtInt01.h | 10 +- src/EasyButtonAtInt01.hpp | 692 ++++++++++++++++++ src/digitalWriteFast.h | 2 +- 12 files changed, 804 insertions(+), 719 deletions(-) rename examples/EasyButtonExample/{ATtinySerialOut.cpp => ATtinySerialOut.hpp} (94%) create mode 100644 src/EasyButtonAtInt01.hpp diff --git a/README.md b/README.md index f886a61..072807a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ So **button state is instantly available** without debouncing delay! | 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*.
+To use the PCINT buttons instead of the default one, just define INT1_PIN **before** including *EasyButtonAtInt01.hpp*.
E.g. `#define INT1_PIN 7`. See [EasyButtonExample.cpp](examples/EasyButtonExample/EasyButtonExample.ino#L52). ## Usage @@ -36,7 +36,7 @@ To use a single button, it needs only: ``` #define USE_BUTTON_0 // Enable code for button at INT0 (pin2) -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" EasyButton Button0AtPin2; void setup() {} @@ -51,7 +51,7 @@ 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) or PCINT[0:7] -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" EasyButton Button0AtPin2(); // no parameter -> Button is connected to INT0 (pin2) EasyButton Button1AtPin3(BUTTON_AT_INT1_OR_PCINT); // Button is connected to INT1 (pin3) @@ -69,14 +69,13 @@ void loop() { ## Usage of callback functions The button press callback function is is called on every button press with ButtonToggleState as parameter.
**The value at the first call (after first press) is true**.
-The button release callback function is called on every button release with the additional parameter ButtonPressDurationMillis.
-Both callback functions run in an interrupt service context, which means they should be as short as possible. -But before a 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 a callback function**. +The button release callback function is called on every button release with the additional parameter `ButtonPressDurationMillis`.
+Both callback functions run in an interrupt service context, which means they should be as short/fast as possible. +In this library, interrupts are enabled before the callback function is called. This allows the timer interrupt for millis() to work and therfore **delay() and millis() can be used in a callback function**. ``` #define USE_BUTTON_0 // Enable code for button at INT0 (pin2) -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" // Initial value is false, so first call is with true void handleButtonPress(bool aButtonToggleState) { @@ -93,7 +92,7 @@ The easiest way is to check it in the button release handler. Do not forget, tha ``` #define USE_BUTTON_0 // Enable code for button at INT0 (pin2) -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" void handleButtonRelease(bool aButtonToggleState, uint16_t aButtonPressDurationMillis); EasyButton Button0AtPin2(NULL, &handleButtonRelease); // Button is connected to INT0 (pin2) @@ -116,7 +115,7 @@ Call checkForDoublePress() only from button press callback function. It will not ``` #define USE_BUTTON_0 // Enable code for button at INT0 (pin2) -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" void handleButtonPress(bool aButtonToggleState); EasyButton Button0AtPin2(&printButtonToggleState); @@ -189,7 +188,7 @@ bool checkForForButtonNotPressedTime(uint16_t aTimeoutMillis); - Analyzes maximum debouncing period. - Double button press detection support. - Very short button press handling. -- Renamed to EasyButtonAtInt01.cpp.h +- Renamed to EasyButtonAtInt01.hpp ### Version 1.0.0 - initial version for ATmega328. diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino index e00a5aa..b870c7e 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/Callback.ino @@ -28,7 +28,7 @@ //#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 (pin2) -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" void handleButtonPress(bool aButtonToggleState); // The button press callback function void handleButtonRelease(bool aButtonToggleState, uint16_t aButtonPressDurationMillis); @@ -50,7 +50,7 @@ void setup() { Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_USB) || defined(SERIAL_PORT_USBVIRTUAL) || defined(ARDUINO_attiny3217) - delay(4000); // To be able to connect Serial monitor after reset or power up and before first printout + delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ "\r\nUsing library version " VERSION_EASY_BUTTON " from " __DATE__)); diff --git a/examples/DebounceTest/DebounceTest.ino b/examples/DebounceTest/DebounceTest.ino index 5e2489d..27963e9 100644 --- a/examples/DebounceTest/DebounceTest.ino +++ b/examples/DebounceTest/DebounceTest.ino @@ -33,7 +33,7 @@ #define BUTTON_DEBOUNCING_MILLIS 2 #define USE_BUTTON_0 // Enable code for 1. button at INT0 -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" EasyButton Button0AtPin2; // Only 1. button (USE_BUTTON_0) enabled -> button is connected to INT0 @@ -54,7 +54,7 @@ void setup() { Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_USB) || defined(SERIAL_PORT_USBVIRTUAL) || defined(ARDUINO_attiny3217) - delay(4000); // To be able to connect Serial monitor after reset or power up and before first printout + delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ "\r\nUsing library version " VERSION_EASY_BUTTON " from " __DATE__)); diff --git a/examples/EasyButtonExample/ATtinySerialOut.h b/examples/EasyButtonExample/ATtinySerialOut.h index 4260c43..5bb380a 100644 --- a/examples/EasyButtonExample/ATtinySerialOut.h +++ b/examples/EasyButtonExample/ATtinySerialOut.h @@ -1,7 +1,7 @@ /* * ATtinySerialOut.h * - * Copyright (C) 2015-2020 Armin Joachimsmeyer + * Copyright (C) 2015-2021 Armin Joachimsmeyer * Email: armin.joachimsmeyer@gmail.com * * This file is part of TinySerialOut https://github.com/ArminJo/ATtinySerialOut. @@ -33,7 +33,7 @@ // ATMEL ATTINY167 // Pin numbers are for Digispark core -// Pin numbers in Parenthesis are for ATTinyCore +// Pin numbers in parenthesis are for ATTinyCore // // +-\/-+ // RX 6 (0) PA0 1| |20 PB0 (D8) 0 OC1AU TONE Timer 1 Channel A @@ -49,28 +49,52 @@ // +----+ // +// MH-ET LIVE Tiny88 (16.0MHz) board +// Digital Pin numbers in parenthesis are for ATTinyCore library +// USB +// +-\__/-+ +// PA2 15| |14 PB7 +// PA3 16| |13 PB5 SCK +// 17 PA0 A6| |12 PB4 MISO +// 18 PA1 A7| |11 PB3 MOSI +// (D17) 19 PC0 A0| |10 PB2 OC1B/PWM SS +// (D18) 20 PC1 A1| |9 PB1 OC1A/PWM +// (D19) 21 PC2 A2| |8 PB0 +// (D20) 22 PC3 A3| |7 PD7 RX +//SDA (D21) 23 PC4 A4| |6 PD6 TX +//SCL (D22) 24 PC5 A5| |5 PD5 +// (D23) PC1 25| |4 PD4 +//RESET PC6 RST| |3 PD3 INT1 +//LED PD0 0| |5V +//USB+ PD1 1| |GND +//USB- INT0 PD2 2| |VIN +// +------+ + #ifndef ATTINY_SERIAL_OUT_H_ #define ATTINY_SERIAL_OUT_H_ #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) \ || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) \ - || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) + || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) \ + || defined(__AVR_ATtiny88__) #include -#define VERSION_ATTINY_SERIAL_OUT "1.2.1" -#define VERSION_ATTINY_SERIAL_OUT_MAJOR 1 -#define VERSION_ATTINY_SERIAL_OUT_MINOR 2 +#define VERSION_ATTINY_SERIAL_OUT "2.0.0" +#define VERSION_ATTINY_SERIAL_OUT_MAJOR 2 +#define VERSION_ATTINY_SERIAL_OUT_MINOR 0 #if (F_CPU != 1000000) && (F_CPU != 8000000) && (F_CPU != 16000000) #error F_CPU value must be 1000000, 8000000 or 16000000. #endif -#if defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) -# ifndef TX_PIN +#if !defined(TX_PIN) +# if defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) // Digispark PRO board #define TX_PIN PA1 // (package pin 2 / TXD on Tiny167) - can use one of PA0 to PA7 here -# endif -#else // defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) -# ifndef TX_PIN + +# elif defined(__AVR_ATtiny88__) // MH-ET LIVE Tiny88(16.0MHz) board +#define TX_PIN PD6 // (board pin 6) - can use one of PD3 to PD7 here + +# else // Tiny X4 + X5 Digispark board #define TX_PIN PB2 // (package pin 7 on Tiny85) - can use one of PB0 to PB4 (+PB5) here # endif #endif @@ -86,7 +110,7 @@ * @8/16 MHz use 115200 baud instead of 230400 baud. */ //#define TINY_SERIAL_DO_NOT_USE_115200BAUD -#ifndef TINY_SERIAL_DO_NOT_USE_115200BAUD // define this to force using other baud rates +#if !defined(TINY_SERIAL_DO_NOT_USE_115200BAUD) // define this to force using other baud rates #define USE_115200BAUD #endif @@ -128,7 +152,11 @@ void writeFloat(double aFloat, uint8_t aDigits); char nibbleToHex(uint8_t aByte); +#if defined(TINY_SERIAL_INHERIT_FROM_PRINT) +class TinySerialOut: public Print +#else class TinySerialOut +#endif { public: @@ -145,6 +173,7 @@ class TinySerialOut size_t write(uint8_t aByte); operator bool(); // To support "while (!Serial); // wait for serial port to connect. Required for Leonardo only +#if !defined(TINY_SERIAL_INHERIT_FROM_PRINT) void print(const __FlashStringHelper *aStringPtr); void print(const char *aStringPtr); void print(char aChar); @@ -166,13 +195,14 @@ class TinySerialOut void println(double aFloat, uint8_t aDigits = 2); void println(void); +#endif // TINY_SERIAL_INHERIT_FROM_PRINT }; // #if ... to be compatible with ATTinyCores and AttinyDigisparkCores #if (!defined(UBRRH) && !defined(UBRR0H)) /*AttinyDigisparkCore and AttinyDigisparkCore condition*/ \ || USE_SOFTWARE_SERIAL /*AttinyDigisparkCore condition*/\ - || ((defined(UBRRH) || defined(UBRR0H) || defined(UBRR1H) || defined(LINBRRH)) && !USE_SOFTWARE_SERIAL)/*AttinyDigisparkCore condition for HardwareSerial*/ + || ((defined(UBRRH) || defined(UBRR0H) || defined(UBRR1H) || (defined(LINBRRH)) && !USE_SOFTWARE_SERIAL))/*AttinyDigisparkCore condition for HardwareSerial*/ // Switch to SerialOut since Serial is already defined // or activate line 745 in TinyDebugSerial.h included in AttinyDigisparkCores/src/tiny/WProgram.h at line 24 for AttinyDigisparkCores extern TinySerialOut SerialOut; @@ -183,7 +213,9 @@ extern TinySerialOut SerialOut; # endif extern TinySerialOut Serial; #endif +#if !defined(TINY_SERIAL_INHERIT_FROM_PRINT) #define Print TinySerialOut +#endif #endif // defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) diff --git a/examples/EasyButtonExample/ATtinySerialOut.cpp b/examples/EasyButtonExample/ATtinySerialOut.hpp similarity index 94% rename from examples/EasyButtonExample/ATtinySerialOut.cpp rename to examples/EasyButtonExample/ATtinySerialOut.hpp index 0c3fe16..5b73785 100644 --- a/examples/EasyButtonExample/ATtinySerialOut.cpp +++ b/examples/EasyButtonExample/ATtinySerialOut.hpp @@ -1,5 +1,5 @@ /* - * ATtinySerialOut.cpp + * ATtinySerialOut.hpp * * For transmitting debug data over bit bang serial with 115200 baud for 1/8/16 MHz ATtiny clock. * For 1 MHz you can choose also 38400 baud (120 bytes smaller code size). @@ -7,12 +7,13 @@ * 1 Start, 8 Data, 1 Stop, No Parity * * Using PB2 // (Pin7 on Tiny85) as default TX pin to be compatible with digispark board - * To change the output pin, modify the line "#define TX_PIN ..." in TinySerialOut.h or or set it as compiler symbol like "-DTX_PIN PB1". + * To change the output pin, add a line "#define TX_PIN ..." before the line #include "TinySerialOut.hpp" + * or or set it as compiler symbol like "-DTX_PIN PB1". * * Using the Serial.print commands needs 4 bytes extra for each call. * * - * Copyright (C) 2015-2020 Armin Joachimsmeyer + * Copyright (C) 2015-2021 Armin Joachimsmeyer * Email: armin.joachimsmeyer@gmail.com * * This file is part of TinySerialOut https://github.com/ArminJo/ATtinySerialOut. @@ -377,6 +378,7 @@ size_t TinySerialOut::write(uint8_t aByte) { writeBinary(aByte); return 1; } +#if !defined(TINY_SERIAL_INHERIT_FROM_PRINT) void TinySerialOut::print(const char *aStringPtr) { writeString(aStringPtr); @@ -482,6 +484,7 @@ void TinySerialOut::println() { print('\r'); print('\n'); } +#endif // !defined(TINY_SERIAL_INHERIT_FROM_PRINT) /* * The Serial Instance!!! @@ -501,13 +504,19 @@ TinySerialOut Serial; * Basic serial output function *******************************/ -inline void delay4CyclesInlineExact(uint16_t a4Microseconds) { - /* - * The loop takes 4 cycles (4 microseconds at 1 MHz). Last loop is only 3 cycles. Setting of loop counter a4Microseconds needs 2 cycles - * 3 -> 13 cycles (3*4 -1 + 2) = 3*4 + 1 - * 4 -> 17 cycles - * 5 -> 21 cycles - */ +/* + * Formula is only valid for constant values + * Loading of constant value adds 2 extra cycles (check .lss file for exact timing) + * + * The loop takes 4 cycles (4 microseconds at 1 MHz). 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 delay4CyclesExact(uint16_t a4Microseconds) { asm volatile ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne .-4" : "=w" (a4Microseconds) : "0" (a4Microseconds)// 2 cycles @@ -694,22 +703,22 @@ void write1Start8Data1StopNoParity(uint8_t aValue) { "cbi %[txport] , %[txpin]" "\n\t" // 2 PORTB &= ~(1 << TX_PIN); #if (F_CPU == 1000000) && !defined(USE_115200BAUD) // 1 MHz 38400 baud // 0 cycles padding to get additional 4 cycles - //delay4CyclesInlineExact(5); -> 20 cycles + //delay4CyclesExact(5); -> 20 cycles "ldi r30 , 0x05" "\n\t"// 1 #elif ((F_CPU == 8000000) && defined(USE_115200BAUD)) || ((F_CPU == 16000000) && !defined(USE_115200BAUD)) // 8 MHz 115200 baud OR 16 MHz 230400 baud // 3 cycles padding to get additional 7 cycles "nop" "\n\t"// 1 _nop"(); "nop" "\n\t"// 1 _nop"(); "nop" "\n\t"// 1 _nop"(); - //delay4CyclesInlineExact(15); -> 61 cycles + //delay4CyclesExact(15); -> 61 cycles "ldi r30 , 0x0F" "\n\t"// 1 #elif (F_CPU == 8000000) && !defined(USE_115200BAUD) // 8 MHz 230400 baud // 0 cycles padding to get additional 4 cycles - //delay4CyclesInlineExact(7); -> 29 cycles + //delay4CyclesExact(7); -> 29 cycles "ldi r30 , 0x07" "\n\t"// 1 #elif (F_CPU == 16000000) && defined(USE_115200BAUD) // 16 MHz 115200 baud // 0 cycles padding to get additional 4 cycles - //delay4CyclesInlineExact(33); -> 133 cycles + //delay4CyclesExact(33); -> 133 cycles "ldi r30 , 0x21" "\n\t"// 1 #endif "ldi r31 , 0x00" "\n\t" // 1 @@ -739,22 +748,22 @@ void write1Start8Data1StopNoParity(uint8_t aValue) { "nop" "\n\t"// 1 "nop" "\n\t"// 1 "nop" "\n\t"// 1 - // delay4CyclesInlineExact(3); -> 13 cycles + // delay4CyclesExact(3); -> 13 cycles "ldi r30 , 0x03" "\n\t"// 1 #elif ((F_CPU == 8000000) && defined(USE_115200BAUD)) || ((F_CPU == 16000000) && !defined(USE_115200BAUD)) // 8 MHz 115200 baud OR 16 MHz 230400 baud // 3 cycles padding to get additional 11 cycles "nop" "\n\t"// 1 "nop" "\n\t"// 1 "nop" "\n\t"// 1 - // delay4CyclesInlineExact(14); -> 57 cycles + // delay4CyclesExact(14); -> 57 cycles "ldi r30 , 0x0E" "\n\t"// 1 #elif (F_CPU == 8000000) && !defined(USE_115200BAUD) // 8 MHz 230400 baud // 0 cycles padding to get additional 8 cycles - // delay4CyclesInlineExact(6); -> 25 cycles + // delay4CyclesExact(6); -> 25 cycles "ldi r30 , 0x05" "\n\t"// 1 #elif (F_CPU == 16000000) && defined(USE_115200BAUD) // 16 MHz 115200 baud // 0 cycles padding to get additional 8 cycles - //delay4CyclesInlineExact(32); -> 129 cycles + //delay4CyclesExact(32); -> 129 cycles "ldi r30 , 0x20" "\n\t"// 1 #endif "ldi r31 , 0x00" "\n\t" // 1 @@ -775,16 +784,16 @@ void write1Start8Data1StopNoParity(uint8_t aValue) { "sbi %[txport] , %[txpin]" "\n\t"// 2 PORTB |= 1 << TX_PIN; #if (F_CPU == 1000000) && !defined(USE_115200BAUD) // 1 MHz 38400 baud - // delay4CyclesInlineExact(4); -> 17 cycles - gives minimum 25 cycles for stop bit + // delay4CyclesExact(4); -> 17 cycles - gives minimum 25 cycles for stop bit "ldi r30 , 0x04" "\n\t"// 1 #elif ((F_CPU == 8000000) && defined(USE_115200BAUD)) || ((F_CPU == 16000000) && !defined(USE_115200BAUD)) // 8 MHz 115200 baud OR 16 MHz 230400 baud - // delay4CyclesInlineExact(15) -> 61 cycles - gives minimum 69 cycles for stop bit + // delay4CyclesExact(15) -> 61 cycles - gives minimum 69 cycles for stop bit "ldi r30 , 0x0F" "\n\t"// 1 #elif (F_CPU == 8000000) && !defined(USE_115200BAUD) // 8 MHz 230400 baud - // delay4CyclesInlineExact(5) -> 27 cycles - gives minimum 35 cycles for stop bit + // delay4CyclesExact(5) -> 27 cycles - gives minimum 35 cycles for stop bit "ldi r30 , 0x05" "\n\t"// 1 #elif (F_CPU == 16000000) && defined(USE_115200BAUD) // 16 MHz 115200 baud - // delay4CyclesInlineExact(32) -> 129 cycles - gives minimum 137 cycles for stop bit + // delay4CyclesExact(32) -> 129 cycles - gives minimum 137 cycles for stop bit "ldi r30 , 0x20" "\n\t"// 1 #endif "ldi r31 , 0x00" "\n\t" // 1 @@ -821,7 +830,7 @@ void write1Start8Data1StopNoParity_C_Version(uint8_t aValue) { // start bit TX_PORT &= ~(1 << TX_PIN); _NOP(); - delay4CyclesInlineExact(4); + delay4CyclesExact(4); // 8 data bits uint8_t i = 8; @@ -843,7 +852,7 @@ void write1Start8Data1StopNoParity_C_Version(uint8_t aValue) { _NOP(); _NOP(); _NOP(); - delay4CyclesInlineExact(3); + delay4CyclesExact(3); --i; } while (i > 0); @@ -856,7 +865,7 @@ void write1Start8Data1StopNoParity_C_Version(uint8_t aValue) { // Stop bit TX_PORT |= 1 << TX_PIN; // -8 cycles to compensate for fastest repeated call (1 ret + 1 load + 1 call) - delay4CyclesInlineExact(4); // gives minimum 25 cycles for stop bit :-) + delay4CyclesExact(4); // gives minimum 25 cycles for stop bit :-) } #elif defined(ARDUINO_ARCH_APOLLO3) void AttinySerialOutDummyToAvoidBFDAssertions(){ diff --git a/examples/EasyButtonExample/EasyButtonExample.ino b/examples/EasyButtonExample/EasyButtonExample.ino index 80c535d..ac3aa23 100644 --- a/examples/EasyButtonExample/EasyButtonExample.ino +++ b/examples/EasyButtonExample/EasyButtonExample.ino @@ -35,7 +35,7 @@ // definitions for ATtinies #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) -#include "ATtinySerialOut.h" +#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut" # 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 @@ -58,7 +58,7 @@ # endif // ATTiny type #endif // ATTinies -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" // The callback function for button 1 void handleButtonPress(bool aButtonToggleState); @@ -73,7 +73,7 @@ void setup() { Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_USB) || defined(SERIAL_PORT_USBVIRTUAL) || defined(ARDUINO_attiny3217) - delay(4000); // To be able to connect Serial monitor after reset or power up and before first printout + delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ "\r\nUsing library version " VERSION_EASY_BUTTON " from " __DATE__)); diff --git a/examples/OneButton/OneButton.ino b/examples/OneButton/OneButton.ino index 162260f..f29afcc 100644 --- a/examples/OneButton/OneButton.ino +++ b/examples/OneButton/OneButton.ino @@ -29,7 +29,7 @@ #define USE_BUTTON_0 // Enable code for 1. button at INT0 / D2 -#include "EasyButtonAtInt01.cpp.h" +#include "EasyButtonAtInt01.hpp" #if defined(ARDUINO_AVR_DIGISPARK) #define LED_BUILTIN PB1 @@ -47,7 +47,7 @@ void setup() { Serial.begin(115200); #if defined(__AVR_ATmega32U4__) || defined(SERIAL_USB) || defined(SERIAL_PORT_USBVIRTUAL) || defined(ARDUINO_attiny3217) - delay(4000); // To be able to connect Serial monitor after reset or power up and before first printout + delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ "\r\nUsing library version " VERSION_EASY_BUTTON " from " __DATE__)); diff --git a/library.properties b/library.properties index 3d51676..5885059 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=EasyButtonAtInt01 -version=3.2.0 +version=3.3.0 author=Armin Joachimsmeyer maintainer=Armin Joachimsmeyer sentence=Small and easy to use Arduino library for using push buttons at INT0/pin2 and / or any PinChangeInterrupt pin.
Functions for long and double press detection are included.

Just connect buttons between ground and any pin of your Arduino - that's it

No call of begin() or polling function like update() required. No blocking debouncing delay.
-paragraph=
Define an EasyButtonIn in you main program and use ButtonStateIsActive or ButtonToggleState to determine your action.
Or use a callback function which will be called once on every button press or release.

Usage:
#define USE_BUTTON_0
#include "EasyButtonAtInt01.cpp.h"
EasyButton Button0AtPin2;

void setup() {}
void loop() {
...
digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState);
...
}


New: Second button now possible on each ATmega328 pin.
+paragraph=
Define an EasyButtonIn in you main program and use ButtonStateIsActive or ButtonToggleState to determine your action.
Or use a callback function which will be called once on every button press or release.

Usage:
#define USE_BUTTON_0
#include "EasyButtonAtInt01.hpp"
EasyButton Button0AtPin2;

void setup() {}
void loop() {
...
digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState);
...
}


New: Renamed EasyButtonAtInt01.cpp.h to EasyButtonAtInt01.hpp. => You must change: #include "EasyButtonAtInt01.cpp.h" to: #include "EasyButtonAtInt01.hpp"
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 9fb7db8..24ea881 100644 --- a/src/EasyButtonAtInt01.cpp.h +++ b/src/EasyButtonAtInt01.cpp.h @@ -1,5 +1,5 @@ /* - * EasyButtonAtInt01.cpp.h + * EasyButtonAtInt01.hpp * * This file can be directly configured and included in one source. * Include EasyButtonAtInt01.h file if you need the declarations in a second source file. @@ -12,7 +12,7 @@ * * Usage: * #define USE_BUTTON_0 - * #include "EasyButtonAtInt01.cpp.h" + * #include "EasyButtonAtInt01.hpp" * EasyButton Button0AtPin2(true); * * Copyright (C) 2018 Armin Joachimsmeyer @@ -35,658 +35,5 @@ */ #if defined(__AVR__) -#include -#include "EasyButtonAtInt01.h" - -/* - * Usage: - * #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.cpp.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); // The value at the first call after first press is true - * ... - * - */ - -// For external measurement of code timing -//#define MEASURE_EASY_BUTTON_INTERRUPT_TIMING -#if defined(MEASURE_EASY_BUTTON_INTERRUPT_TIMING) || defined(LED_FEEDBACK_TEST) -#include "digitalWriteFast.h" -#endif - -#if defined(USE_BUTTON_0) -EasyButton *EasyButton::sPointerToButton0ForISR; -#endif -#if defined(USE_BUTTON_1) -EasyButton *EasyButton::sPointerToButton1ForISR; -#endif - -// @formatter:off // the eclipse formatter has problems with // comments in undefined code blocks - -/* - * These constructors are deterministic if only one button is enabled - * If two buttons are enabled they can be taken for the 1. button at INT0 - */ -EasyButton::EasyButton() { -#if defined(USE_BUTTON_0) - init(true); // 1. button -#else - init(false); // 2. button -#endif -} -/* - * The same with aButtonPressCallback - */ -EasyButton::EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState)) { - ButtonPressCallback = aButtonPressCallback; -#if defined(USE_BUTTON_0) - init(true); // 1. button -#else - init(false); // 2. button -#endif -} - -#if ! defined(NO_BUTTON_RELEASE_CALLBACK) -EasyButton::EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState), - void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)) { - ButtonPressCallback = aButtonPressCallback; - ButtonReleaseCallback = aButtonReleaseCallback; -# if defined(USE_BUTTON_0) - init(true); // 1. button -# else - init(false); // 2. button -# endif -} -#endif // NO_BUTTON_RELEASE_CALLBACK - -/* - * These constructors use the first (bool) parameter to decide which button to take. - */ -#if defined(USE_BUTTON_0) && defined(USE_BUTTON_1) -EasyButton::EasyButton(bool aIsButtonAtINT0) -#else -// Constructor with unused attribute to avoid warnings -EasyButton::EasyButton(bool aIsButtonAtINT0 __attribute__((unused))) -#endif - { -#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) - init(true); // 1. button -#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) - init(false); // 2. button -#else - init(aIsButtonAtINT0); -#endif -} - -#if defined(USE_BUTTON_0) && defined(USE_BUTTON_1) -EasyButton::EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState)) -#else -// Constructor with unused attribute to avoid warnings -EasyButton::EasyButton(bool aIsButtonAtINT0 __attribute__((unused)), void (*aButtonPressCallback)(bool aButtonToggleState)) -#endif - { - ButtonPressCallback = aButtonPressCallback; -#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) - init(true); // 1. button -#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) - init(false); // 2. button -#else - init(aIsButtonAtINT0); -#endif -} - -#if ! defined(NO_BUTTON_RELEASE_CALLBACK) -# if defined(USE_BUTTON_0) && defined(USE_BUTTON_1) -EasyButton::EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState), - void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)) -# else -// Constructor with unused attribute to avoid warnings -EasyButton::EasyButton(bool aIsButtonAtINT0 __attribute__((unused)), void (*aButtonPressCallback)(bool aButtonToggleState), - void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)) -# endif - { - ButtonPressCallback = aButtonPressCallback; - ButtonReleaseCallback = aButtonReleaseCallback; -# if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) -init(true); // 1. button -# elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) -init(false); // 2. button -# else - init(aIsButtonAtINT0); -# endif -} -#endif // NO_BUTTON_RELEASE_CALLBACK - -/* - * Sets pin mode to INPUT_PULLUP if not defined(BUTTON_IS_ACTIVE_HIGH) and enables INT0 Interrupt on any logical change. - */ -void EasyButton::init(bool aIsButtonAtINT0) { - isButtonAtINT0 = aIsButtonAtINT0; -#if defined(MEASURE_EASY_BUTTON_INTERRUPT_TIMING) - pinModeFast(INTERRUPT_TIMING_OUTPUT_PIN, OUTPUT); -#endif - -#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); -# if ! defined(BUTTON_IS_ACTIVE_HIGH) - INT0_OUT_PORT |= _BV(INT0_BIT); // enable pullup -# endif - sPointerToButton0ForISR = this; -# if defined(USE_ATTACH_INTERRUPT) - 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 -# endif //USE_ATTACH_INTERRUPT - -#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) - /* - * Only button 1 requested - */ - INT1_DDR_PORT &= ~(_BV(INT1_BIT)); -# if ! defined(BUTTON_IS_ACTIVE_HIGH) - INT1_OUT_PORT |= _BV(INT1_BIT); // enable pullup -# endif - 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); -# else - // ATtinyX5 no ISC10 flag existent - GIMSK |= 1 << PCIE;//PCINT enable, we have only one - PCMSK = digitalPinToBitMask(INT1_PIN); -# endif -# elif (INT1_PIN != 3) - /* - * ATmega328 (Uno, Nano ) etc. Enable pin change interrupt for port PD0 to PD7 (Arduino pin 0 to 7) - */ - PCICR |= 1 << PCIE2; - PCMSK2 = digitalPinToBitMask(INT1_PIN); -# else -# if defined(USE_ATTACH_INTERRUPT) - attachInterrupt(digitalPinToInterrupt(INT1_PIN), &handleINT1Interrupt, CHANGE); -# else - EICRA |= (1 << ISC10); // interrupt on any logical change - EIFR |= 1 << INTF1; // clear interrupt bit - EIMSK |= 1 << INT1; // enable interrupt on next change -# endif //USE_ATTACH_INTERRUPT -# endif // ! defined(ISC10) - -#elif defined(USE_BUTTON_0) && defined(USE_BUTTON_1) - /* - * Both buttons 0 + 1 requested - */ - if (isButtonAtINT0) { - /* - * Button 0 - */ - INT0_DDR_PORT &= ~(_BV(INT0_BIT)); // pinModeFast(2, INPUT); -# if ! defined(BUTTON_IS_ACTIVE_HIGH) - INT0_OUT_PORT |= _BV(INT0_BIT); // enable pullup -# endif - sPointerToButton0ForISR = this; -# if defined(USE_ATTACH_INTERRUPT) - 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 -# endif //USE_ATTACH_INTERRUPT - } else { - /* - * Button 1 - * Allow PinChangeInterrupt - */ - INT1_DDR_PORT &= ~(_BV(INT1_BIT)); // pinModeFast(INT1_BIT, INPUT_PULLUP); -# if ! defined(BUTTON_IS_ACTIVE_HIGH) - INT1_OUT_PORT |= _BV(INT1_BIT); // enable pullup -# endif - sPointerToButton1ForISR = this; - -# if (! defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3) -# if defined(PCICR) - /* - * ATtiny167 + 87. Enable pin change interrupt for port PA0 to PA7 - */ - PCICR |= 1 << PCIE0; - PCMSK0 = digitalPinToBitMask(INT1_PIN); -# else - /* - *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 == 4 || INT1_PIN == 5 || INT1_PIN == 6 || INT1_PIN == 7 - //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt for port PD0 to PD7 (Arduino pin 0 to 7) - PCICR |= 1 << PCIE2; - PCMSK2 = digitalPinToBitMask(INT1_PIN); -# elif INT1_PIN == 8 || INT1_PIN == 9 || INT1_PIN == 10 || INT1_PIN == 11 || INT1_PIN == 12 || INT1_PIN == 13 - //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 0 to 5 for port PB0 to PB5 (Arduino pin 8 to 13) - PCICR |= _BV(PCIE0); - PCMSK0 = digitalPinToBitMask(INT1_PIN); -# elif INT1_PIN == A0 || INT1_PIN == A1 || INT1_PIN == A2 || INT1_PIN == A3 || INT1_PIN == A4 || INT1_PIN == A5 - //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 8 to 13 for port PC0 to PC5 (Arduino pin A0 to A5) - PCICR |= _BV(PCIE1); - PCMSK1 = digitalPinToBitMask(INT1_PIN); -# else -# if defined(USE_ATTACH_INTERRUPT) - attachInterrupt(digitalPinToInterrupt(INT1_PIN), &handleINT1Interrupt, CHANGE); -# else - EICRA |= (1 << ISC10); // interrupt on any logical change - EIFR |= 1 << INTF1; // clear interrupt bit - 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; -} - -/* - * Negative logic for readButtonState() true means button pin is LOW, if button is active low (default) - */ -bool EasyButton::readButtonState() { -#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) -# if defined(BUTTON_IS_ACTIVE_HIGH) - return (INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); -# else - return !(INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); -# endif - -#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) -# if defined(BUTTON_IS_ACTIVE_HIGH) - return (INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); -# else - return !(INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); -# endif - -#elif defined(USE_BUTTON_0) && defined(USE_BUTTON_1) -# if defined(BUTTON_IS_ACTIVE_HIGH) - if (isButtonAtINT0) { - return (INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); - } else { - return (INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); - } -# else - if (isButtonAtINT0) { - return !(INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); - } else { - return !(INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); - } -# endif -#endif -} - -// @formatter:on // the eclipse formatter has problems with // comments in undefined code blocks - -/* - * Returns stored state if in debouncing period otherwise current state of button - */ -bool EasyButton::readDebouncedButtonState() { - // Check for bouncing period - if (millis() - ButtonLastChangeMillis <= BUTTON_DEBOUNCING_MILLIS) { - return ButtonStateIsActive; - } - return readButtonState(); -} - -/* - * Update button state if state change was not captured by the ISR - * @return true if state was changed and updated - */ -bool EasyButton::updateButtonState() { - noInterrupts(); - if (readDebouncedButtonState() != ButtonStateIsActive) { -#ifdef TRACE - if (LastBounceWasChangeToInactive) { - Serial.print(F("Updated button state, assume last button press was shorter than debouncing period of ")); - Serial.print(BUTTON_DEBOUNCING_MILLIS); - Serial.print(F(" ms")); -#ifdef ANALYZE_MAX_BOUNCING_PERIOD - Serial.print(F(" MaxBouncingPeriod was=")); - Serial.print(MaxBouncingPeriodMillis); - MaxBouncingPeriodMillis = 0; -#endif - } else { - // It can happen, that we just catch the release of the button here, so no worry! - Serial.print(F("Update button state to ")); - Serial.print(!ButtonStateIsActive); - Serial.print(F(", current state was not yet caught by ISR")); - } - Serial.println(); -#endif - handleINT01Interrupts(); - interrupts(); - return true; - } - interrupts(); - return false; -} - -/* - * Updates the ButtonPressDurationMillis by polling, since this cannot be done by interrupt. - */ -uint16_t EasyButton::updateButtonPressDuration() { - if (readDebouncedButtonState()) { - // Button still active -> update ButtonPressDurationMillis - ButtonPressDurationMillis = millis() - ButtonLastChangeMillis; - } - return ButtonPressDurationMillis; -} - -/* - * Used for long button press recognition, while button is still pressed! - * !!! Consider to use button release callback handler and check the ButtonPressDurationMillis - * 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 current ButtonPressDurationMillis - // noInterrupts() is required, since otherwise we may get wrong results if interrupted during load of long value by button ISR - noInterrupts(); - ButtonPressDurationMillis = millis() - ButtonLastChangeMillis; - interrupts(); - 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 tRetvale; -} - -/* - * Checks for long press of button - * Blocks until long press threshold is reached or button was released. - * !!! Consider to use button release callback handler and check the ButtonPressDurationMillis - * @return true if long press was detected - only once for each long press - */ -bool EasyButton::checkForLongPressBlocking(uint16_t aLongPressThresholdMillis) { - /* - * 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 pressed or time was greater than threshold. - * ButtonPressDurationMillis was updated by call to checkForLongPress before - */ - return (ButtonPressDurationMillis >= aLongPressThresholdMillis); -} - -/* - * Double press detection by computing difference between current (active) timestamp ButtonLastChangeMillis - * and last release timestamp ButtonReleaseMillis. - * !!!Works only reliable if called early in ButtonPress callback function!!! - * @return true if double press detected. - */ -bool EasyButton::checkForDoublePress(uint16_t aDoublePressDelayMillis) { - unsigned long tReleaseToPressTimeMillis = ButtonLastChangeMillis - ButtonReleaseMillis; - return (tReleaseToPressTimeMillis <= aDoublePressDelayMillis); -} - -/* - * Checks if button was not pressed in the last aTimeoutMillis - * Can be used to recognize timeout for user button actions - * @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(); - return (millis() - tButtonReleaseMillis >= aTimeoutMillis); -} - -/* - * 1. Read button pin level and invert logic level since we have negative logic because of using pullups. - * 2. Check for bouncing - state change during debounce period. We need millis() to be enabled to run in the background. - * 3. Check for spikes - interrupts but no level change. - * 4. Process valid button state change. If callback requested, call callback routine, get button pin level again and handle if button was released in the meantime. - */ -void EasyButton::handleINT01Interrupts() { - // Read button value - bool tCurrentButtonStateIsActive; - - /* - * 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 = 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 ! defined(BUTTON_IS_ACTIVE_HIGH) - tCurrentButtonStateIsActive = !tCurrentButtonStateIsActive; // negative logic for tCurrentButtonStateIsActive! true means button pin is LOW -#endif - -#ifdef TRACE - Serial.print(tCurrentButtonStateIsActive); - Serial.print('-'); -#endif - - unsigned long tMillis = millis(); - unsigned int tDeltaMillis = tMillis - ButtonLastChangeMillis; - // Check for bouncing - state change during debounce period - if (tDeltaMillis <= BUTTON_DEBOUNCING_MILLIS) { - /* - * Button is bouncing, signal is ringing - do nothing, ignore and wait for next interrupt - */ -#ifdef ANALYZE_MAX_BOUNCING_PERIOD - if (MaxBouncingPeriodMillis < tDeltaMillis) { - MaxBouncingPeriodMillis = tDeltaMillis; - Serial.print(F("Bouncing, MBP=")); - Serial.println(MaxBouncingPeriodMillis); - // Serial.print(F("ms=")); - // Serial.print(tMillis); - // Serial.print(F(" D=")); - // Serial.println(tDeltaMillis); - } -#endif - if (tCurrentButtonStateIsActive) { - LastBounceWasChangeToInactive = false; - } else { - /* - * Store, that switch goes inactive during debouncing period. - * This may be a bouncing issue (fine) but it can also be a very short button press. - * In this case we do not set the ButtonStateIsActive to false because we are in debouncing period. - * On the next press this will be detected as a spike, if not considered. - */ - LastBounceWasChangeToInactive = true; - } - - } else { - /* - * Here we are after debouncing period - */ - if (tCurrentButtonStateIsActive == ButtonStateIsActive) { - /* - * No valid change detected - current is equals last state - */ - if (tCurrentButtonStateIsActive && LastBounceWasChangeToInactive) { - // We assume we had a very short press before (or a strange spike), which was handled as a bounce. -> must adjust last button state - ButtonStateIsActive = false; -#ifdef TRACE - Serial.println(F("Preceding short press detected, which was handled as bounce")); -#endif - - } else { - /* - * tCurrentButtonStateIsActive == OldButtonStateIsActive. We had an interrupt, but nothing seems to have changed -> spike - * Do nothing, ignore and wait for next interrupt - */ -#ifdef TRACE - Serial.println(F("Spike")); -#endif - } - } - - // do not use else since we may have changed ButtonStateIsActive - if (tCurrentButtonStateIsActive != ButtonStateIsActive) { - /* - * Valid change detected - */ - ButtonLastChangeMillis = tMillis; - LastBounceWasChangeToInactive = false; -#ifdef TRACE - Serial.println(F("Change")); -#endif - ButtonStateIsActive = tCurrentButtonStateIsActive; - ButtonStateHasJustChanged = true; - if (tCurrentButtonStateIsActive) { - /* - * Button pressed - */ -#ifdef LED_FEEDBACK_TEST - digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, HIGH); -#endif - ButtonToggleState = !ButtonToggleState; - if (ButtonPressCallback != NULL) { - /* - * Call callback function. - * interrupts() is required if callback function needs more time to allow millis() to proceed. - * Otherwise we may see bouncing instead of button release followed by spike instead of button press - */ - interrupts(); - ButtonPressCallback(ButtonToggleState); - /* - * Check button again since it may changed back while processing callback function - */ - if (!readButtonState()) { - // button released now, so maintain status -#ifdef TRACE - Serial.println(F("Button release during callback processing detected.")); -#endif - ButtonStateIsActive = false; - tMillis = millis(); - ButtonPressDurationMillis = tMillis - ButtonLastChangeMillis; - ButtonLastChangeMillis = tMillis; - ButtonStateHasJustChanged = true; - ButtonReleaseMillis = tMillis; - } - } - } else { - /* - * Button release - */ - ButtonPressDurationMillis = tDeltaMillis; - ButtonReleaseMillis = tMillis; -#if ! defined(NO_BUTTON_RELEASE_CALLBACK) - if (ButtonReleaseCallback != NULL) { - /* - * Call callback function. - * interrupts() is required if callback function needs more time to allow millis() to proceed. - */ - interrupts(); - ButtonReleaseCallback(ButtonToggleState, ButtonPressDurationMillis); - /* - * Check button again since it may be activated while processing callback function - */ - if (readButtonState()) { - // button activated now, so maintain status -# ifdef TRACE - Serial.println(F("Button active after callback processing detected.")); -# endif - ButtonStateIsActive = true; - ButtonLastChangeMillis = millis(); - ButtonStateHasJustChanged = true; - LastBounceWasChangeToInactive = false; - } - } -#endif -#ifdef LED_FEEDBACK_TEST - digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, LOW); -#endif - } - } - } -} - -// end of class definitions - -/* - * This functions are weak and can be replaced by your own code - */ -#if defined(USE_BUTTON_0) -void __attribute__ ((weak)) handleINT0Interrupt() { - EasyButton::sPointerToButton0ForISR->handleINT01Interrupts(); -} -#endif - -#if defined(USE_BUTTON_1) -void __attribute__ ((weak)) handleINT1Interrupt() { - EasyButton::sPointerToButton1ForISR->handleINT01Interrupts(); -} -#endif - -#if not defined(USE_ATTACH_INTERRUPT) -// ISR for PIN PD2 -// Cannot make the vector itself weak, since the vector table is already filled by weak vectors resulting in ignoring my weak one:-( -//ISR(INT0_vect, __attribute__ ((weak))) { -# if defined(USE_BUTTON_0) -ISR(INT0_vect) { -# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING - digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, HIGH); -# endif - handleINT0Interrupt(); -# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING - digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, LOW); -# endif -} -# endif - -# if defined(USE_BUTTON_1) -# if (! defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3) -// on ATtinyX5 we do not have a INT1_vect but we can use the PCINT0_vect -ISR(PCINT0_vect) -# elif INT1_PIN == 4 || INT1_PIN == 5 || INT1_PIN == 6 || INT1_PIN == 7 -// PCINT for ATmega328 Arduino pins 0 to 7 -ISR(PCINT2_vect) -# elif INT1_PIN == 8 || INT1_PIN == 9 || INT1_PIN == 10 || INT1_PIN == 11 || INT1_PIN == 12 || INT1_PIN == 13 -// PCINT for ATmega328 Arduino pins 8 (PB0) to 13 (PB5) - (PCINT 0 to 5) -ISR(PCINT0_vect) -# elif INT1_PIN == A0 || INT1_PIN == A1 || INT1_PIN == A2 || INT1_PIN == A3 || INT1_PIN == A4 || INT1_PIN == A5 -// PCINT for ATmega328 Arduino pins A1 (PC0) to A5 (PC5) - (PCINT 8 to 13) -ISR(PCINT1_vect) -# else -ISR(INT1_vect) -# endif -{ -# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING - digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, HIGH); -# endif - handleINT1Interrupt(); -# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING - digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, LOW); -# endif -} -# endif -#endif // not defined(USE_ATTACH_INTERRUPT) - +#error ------For version >= 3.3.0 you must change: #include "EasyButtonAtInt01.cpp.h" to: #include "EasyButtonAtInt01.hpp"------ #endif // defined(__AVR__) diff --git a/src/EasyButtonAtInt01.h b/src/EasyButtonAtInt01.h index 4ce6041..7620892 100644 --- a/src/EasyButtonAtInt01.h +++ b/src/EasyButtonAtInt01.h @@ -1,5 +1,5 @@ /* - * EasyButtonAtInt01.cpp.h + * EasyButtonAtInt01.hpp * * Arduino library for handling push buttons connected between ground and INT0 and / or INT1 pin. * 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. @@ -360,6 +360,12 @@ void __attribute__ ((weak)) handleINT1Interrupt(); #endif // defined(__AVR__) /* + * Version 3.3.0 - 1/2021 + * - Renamed EasyButtonAtInt01.cpp.h to EasyButtonAtInt01.hpp. + * + * Version 3.2.0 - 1/2021 + * - Allow button1 on pin 8 to 13 and A0 to A5 for ATmega328. + * * Version 3.1.0 - 6/2020 * - 2 sets of constructors, one for only one button used and one for the second button if two buttons used. * - Map pin numbers for Digispark pro boards, for use with with digispark library. @@ -379,7 +385,7 @@ void __attribute__ ((weak)) handleINT1Interrupt(); * - Support also PinChangeInterrupt for button 1 on Pin PA0 to PA7 for ATtiniy87/167. * - Long press detection support. * - Double press detection support. - * - Renamed to EasyButtonAtInt01.cpp.h + * - Renamed to EasyButtonAtInt01.hpp */ #endif /* EASY_BUTTON_AT_INT01_H_ */ diff --git a/src/EasyButtonAtInt01.hpp b/src/EasyButtonAtInt01.hpp new file mode 100644 index 0000000..ffe741b --- /dev/null +++ b/src/EasyButtonAtInt01.hpp @@ -0,0 +1,692 @@ +/* + * EasyButtonAtInt01.hpp + * + * This file can be directly configured and included in one source. + * Include EasyButtonAtInt01.h file if you need the declarations in a second source file. + * + * Arduino library for handling push buttons connected between ground and INT0 and / or INT1 pin. + * 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. + * The library is totally based on interrupt. + * Debouncing is implemented in a not blocking way! It is merely done by ignoring a button change within the debouncing time. + * So button state is instantly available without debouncing delay! + * + * Usage: + * #define USE_BUTTON_0 + * #include "EasyButtonAtInt01.hpp" + * EasyButton Button0AtPin2(true); + * + * Copyright (C) 2018 Armin Joachimsmeyer + * armin.joachimsmeyer@gmail.com + * + * This file is part of EasyButtonAtInt01 https://github.com/ArminJo/EasyButtonAtInt01. + * + * EasyButtonAtInt01 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__) +#include +#include "EasyButtonAtInt01.h" + +/* + * Usage: + * #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.hpp" + * 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); // The value at the first call after first press is true + * ... + * + */ + +// For external measurement of code timing +//#define MEASURE_EASY_BUTTON_INTERRUPT_TIMING +#if defined(MEASURE_EASY_BUTTON_INTERRUPT_TIMING) || defined(LED_FEEDBACK_TEST) +#include "digitalWriteFast.h" +#endif + +#if defined(USE_BUTTON_0) +EasyButton *EasyButton::sPointerToButton0ForISR; +#endif +#if defined(USE_BUTTON_1) +EasyButton *EasyButton::sPointerToButton1ForISR; +#endif + +// @formatter:off // the eclipse formatter has problems with // comments in undefined code blocks + +/* + * These constructors are deterministic if only one button is enabled + * If two buttons are enabled they can be taken for the 1. button at INT0 + */ +EasyButton::EasyButton() { +#if defined(USE_BUTTON_0) + init(true); // 1. button +#else + init(false); // 2. button +#endif +} +/* + * The same with aButtonPressCallback + */ +EasyButton::EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState)) { + ButtonPressCallback = aButtonPressCallback; +#if defined(USE_BUTTON_0) + init(true); // 1. button +#else + init(false); // 2. button +#endif +} + +#if ! defined(NO_BUTTON_RELEASE_CALLBACK) +EasyButton::EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState), + void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)) { + ButtonPressCallback = aButtonPressCallback; + ButtonReleaseCallback = aButtonReleaseCallback; +# if defined(USE_BUTTON_0) + init(true); // 1. button +# else + init(false); // 2. button +# endif +} +#endif // NO_BUTTON_RELEASE_CALLBACK + +/* + * These constructors use the first (bool) parameter to decide which button to take. + */ +#if defined(USE_BUTTON_0) && defined(USE_BUTTON_1) +EasyButton::EasyButton(bool aIsButtonAtINT0) +#else +// Constructor with unused attribute to avoid warnings +EasyButton::EasyButton(bool aIsButtonAtINT0 __attribute__((unused))) +#endif + { +#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) + init(true); // 1. button +#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) + init(false); // 2. button +#else + init(aIsButtonAtINT0); +#endif +} + +#if defined(USE_BUTTON_0) && defined(USE_BUTTON_1) +EasyButton::EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState)) +#else +// Constructor with unused attribute to avoid warnings +EasyButton::EasyButton(bool aIsButtonAtINT0 __attribute__((unused)), void (*aButtonPressCallback)(bool aButtonToggleState)) +#endif + { + ButtonPressCallback = aButtonPressCallback; +#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) + init(true); // 1. button +#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) + init(false); // 2. button +#else + init(aIsButtonAtINT0); +#endif +} + +#if ! defined(NO_BUTTON_RELEASE_CALLBACK) +# if defined(USE_BUTTON_0) && defined(USE_BUTTON_1) +EasyButton::EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState), + void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)) +# else +// Constructor with unused attribute to avoid warnings +EasyButton::EasyButton(bool aIsButtonAtINT0 __attribute__((unused)), void (*aButtonPressCallback)(bool aButtonToggleState), + void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)) +# endif + { + ButtonPressCallback = aButtonPressCallback; + ButtonReleaseCallback = aButtonReleaseCallback; +# if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) +init(true); // 1. button +# elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) +init(false); // 2. button +# else + init(aIsButtonAtINT0); +# endif +} +#endif // NO_BUTTON_RELEASE_CALLBACK + +/* + * Sets pin mode to INPUT_PULLUP if not defined(BUTTON_IS_ACTIVE_HIGH) and enables INT0 Interrupt on any logical change. + */ +void EasyButton::init(bool aIsButtonAtINT0) { + isButtonAtINT0 = aIsButtonAtINT0; +#if defined(MEASURE_EASY_BUTTON_INTERRUPT_TIMING) + pinModeFast(INTERRUPT_TIMING_OUTPUT_PIN, OUTPUT); +#endif + +#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); +# if ! defined(BUTTON_IS_ACTIVE_HIGH) + INT0_OUT_PORT |= _BV(INT0_BIT); // enable pullup +# endif + sPointerToButton0ForISR = this; +# if defined(USE_ATTACH_INTERRUPT) + 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 +# endif //USE_ATTACH_INTERRUPT + +#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) + /* + * Only button 1 requested + */ + INT1_DDR_PORT &= ~(_BV(INT1_BIT)); +# if ! defined(BUTTON_IS_ACTIVE_HIGH) + INT1_OUT_PORT |= _BV(INT1_BIT); // enable pullup +# endif + 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); +# else + // ATtinyX5 no ISC10 flag existent + GIMSK |= 1 << PCIE;//PCINT enable, we have only one + PCMSK = digitalPinToBitMask(INT1_PIN); +# endif +# elif (INT1_PIN != 3) + /* + * ATmega328 (Uno, Nano ) etc. Enable pin change interrupt for port PD0 to PD7 (Arduino pin 0 to 7) + */ + PCICR |= 1 << PCIE2; + PCMSK2 = digitalPinToBitMask(INT1_PIN); +# else +# if defined(USE_ATTACH_INTERRUPT) + attachInterrupt(digitalPinToInterrupt(INT1_PIN), &handleINT1Interrupt, CHANGE); +# else + EICRA |= (1 << ISC10); // interrupt on any logical change + EIFR |= 1 << INTF1; // clear interrupt bit + EIMSK |= 1 << INT1; // enable interrupt on next change +# endif //USE_ATTACH_INTERRUPT +# endif // ! defined(ISC10) + +#elif defined(USE_BUTTON_0) && defined(USE_BUTTON_1) + /* + * Both buttons 0 + 1 requested + */ + if (isButtonAtINT0) { + /* + * Button 0 + */ + INT0_DDR_PORT &= ~(_BV(INT0_BIT)); // pinModeFast(2, INPUT); +# if ! defined(BUTTON_IS_ACTIVE_HIGH) + INT0_OUT_PORT |= _BV(INT0_BIT); // enable pullup +# endif + sPointerToButton0ForISR = this; +# if defined(USE_ATTACH_INTERRUPT) + 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 +# endif //USE_ATTACH_INTERRUPT + } else { + /* + * Button 1 + * Allow PinChangeInterrupt + */ + INT1_DDR_PORT &= ~(_BV(INT1_BIT)); // pinModeFast(INT1_BIT, INPUT_PULLUP); +# if ! defined(BUTTON_IS_ACTIVE_HIGH) + INT1_OUT_PORT |= _BV(INT1_BIT); // enable pullup +# endif + sPointerToButton1ForISR = this; + +# if (! defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3) +# if defined(PCICR) + /* + * ATtiny167 + 87. Enable pin change interrupt for port PA0 to PA7 + */ + PCICR |= 1 << PCIE0; + PCMSK0 = digitalPinToBitMask(INT1_PIN); +# else + /* + *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 == 4 || INT1_PIN == 5 || INT1_PIN == 6 || INT1_PIN == 7 + //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt for port PD0 to PD7 (Arduino pin 0 to 7) + PCICR |= 1 << PCIE2; + PCMSK2 = digitalPinToBitMask(INT1_PIN); +# elif INT1_PIN == 8 || INT1_PIN == 9 || INT1_PIN == 10 || INT1_PIN == 11 || INT1_PIN == 12 || INT1_PIN == 13 + //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 0 to 5 for port PB0 to PB5 (Arduino pin 8 to 13) + PCICR |= _BV(PCIE0); + PCMSK0 = digitalPinToBitMask(INT1_PIN); +# elif INT1_PIN == A0 || INT1_PIN == A1 || INT1_PIN == A2 || INT1_PIN == A3 || INT1_PIN == A4 || INT1_PIN == A5 + //ATmega328 (Uno, Nano ) etc. Enable pin change interrupt 8 to 13 for port PC0 to PC5 (Arduino pin A0 to A5) + PCICR |= _BV(PCIE1); + PCMSK1 = digitalPinToBitMask(INT1_PIN); +# else +# if defined(USE_ATTACH_INTERRUPT) + attachInterrupt(digitalPinToInterrupt(INT1_PIN), &handleINT1Interrupt, CHANGE); +# else + EICRA |= (1 << ISC10); // interrupt on any logical change + EIFR |= 1 << INTF1; // clear interrupt bit + 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; +} + +/* + * Negative logic for readButtonState() true means button pin is LOW, if button is active low (default) + */ +bool EasyButton::readButtonState() { +#if defined(USE_BUTTON_0) && not defined(USE_BUTTON_1) +# if defined(BUTTON_IS_ACTIVE_HIGH) + return (INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); +# else + return !(INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); +# endif + +#elif defined(USE_BUTTON_1) && not defined(USE_BUTTON_0) +# if defined(BUTTON_IS_ACTIVE_HIGH) + return (INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); +# else + return !(INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); +# endif + +#elif defined(USE_BUTTON_0) && defined(USE_BUTTON_1) +# if defined(BUTTON_IS_ACTIVE_HIGH) + if (isButtonAtINT0) { + return (INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); + } else { + return (INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); + } +# else + if (isButtonAtINT0) { + return !(INT0_IN_PORT & _BV(INT0_BIT)); // = digitalReadFast(2); + } else { + return !(INT1_IN_PORT & _BV(INT1_BIT)); // = digitalReadFast(3); + } +# endif +#endif +} + +// @formatter:on // the eclipse formatter has problems with // comments in undefined code blocks + +/* + * Returns stored state if in debouncing period otherwise current state of button + */ +bool EasyButton::readDebouncedButtonState() { + // Check for bouncing period + if (millis() - ButtonLastChangeMillis <= BUTTON_DEBOUNCING_MILLIS) { + return ButtonStateIsActive; + } + return readButtonState(); +} + +/* + * Update button state if state change was not captured by the ISR + * @return true if state was changed and updated + */ +bool EasyButton::updateButtonState() { + noInterrupts(); + if (readDebouncedButtonState() != ButtonStateIsActive) { +#ifdef TRACE + if (LastBounceWasChangeToInactive) { + Serial.print(F("Updated button state, assume last button press was shorter than debouncing period of ")); + Serial.print(BUTTON_DEBOUNCING_MILLIS); + Serial.print(F(" ms")); +#ifdef ANALYZE_MAX_BOUNCING_PERIOD + Serial.print(F(" MaxBouncingPeriod was=")); + Serial.print(MaxBouncingPeriodMillis); + MaxBouncingPeriodMillis = 0; +#endif + } else { + // It can happen, that we just catch the release of the button here, so no worry! + Serial.print(F("Update button state to ")); + Serial.print(!ButtonStateIsActive); + Serial.print(F(", current state was not yet caught by ISR")); + } + Serial.println(); +#endif + handleINT01Interrupts(); + interrupts(); + return true; + } + interrupts(); + return false; +} + +/* + * Updates the ButtonPressDurationMillis by polling, since this cannot be done by interrupt. + */ +uint16_t EasyButton::updateButtonPressDuration() { + if (readDebouncedButtonState()) { + // Button still active -> update ButtonPressDurationMillis + ButtonPressDurationMillis = millis() - ButtonLastChangeMillis; + } + return ButtonPressDurationMillis; +} + +/* + * Used for long button press recognition, while button is still pressed! + * !!! Consider to use button release callback handler and check the ButtonPressDurationMillis + * 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 current ButtonPressDurationMillis + // noInterrupts() is required, since otherwise we may get wrong results if interrupted during load of long value by button ISR + noInterrupts(); + ButtonPressDurationMillis = millis() - ButtonLastChangeMillis; + interrupts(); + 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 tRetvale; +} + +/* + * Checks for long press of button + * Blocks until long press threshold is reached or button was released. + * !!! Consider to use button release callback handler and check the ButtonPressDurationMillis + * @return true if long press was detected - only once for each long press + */ +bool EasyButton::checkForLongPressBlocking(uint16_t aLongPressThresholdMillis) { + /* + * 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 pressed or time was greater than threshold. + * ButtonPressDurationMillis was updated by call to checkForLongPress before + */ + return (ButtonPressDurationMillis >= aLongPressThresholdMillis); +} + +/* + * Double press detection by computing difference between current (active) timestamp ButtonLastChangeMillis + * and last release timestamp ButtonReleaseMillis. + * !!!Works only reliable if called early in ButtonPress callback function!!! + * @return true if double press detected. + */ +bool EasyButton::checkForDoublePress(uint16_t aDoublePressDelayMillis) { + unsigned long tReleaseToPressTimeMillis = ButtonLastChangeMillis - ButtonReleaseMillis; + return (tReleaseToPressTimeMillis <= aDoublePressDelayMillis); +} + +/* + * Checks if button was not pressed in the last aTimeoutMillis + * Can be used to recognize timeout for user button actions + * @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(); + return (millis() - tButtonReleaseMillis >= aTimeoutMillis); +} + +/* + * 1. Read button pin level and invert logic level since we have negative logic because of using pullups. + * 2. Check for bouncing - state change during debounce period. We need millis() to be enabled to run in the background. + * 3. Check for spikes - interrupts but no level change. + * 4. Process valid button state change. If callback requested, call callback routine, get button pin level again and handle if button was released in the meantime. + */ +void EasyButton::handleINT01Interrupts() { + // Read button value + bool tCurrentButtonStateIsActive; + + /* + * 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 = 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 ! defined(BUTTON_IS_ACTIVE_HIGH) + tCurrentButtonStateIsActive = !tCurrentButtonStateIsActive; // negative logic for tCurrentButtonStateIsActive! true means button pin is LOW +#endif + +#ifdef TRACE + Serial.print(tCurrentButtonStateIsActive); + Serial.print('-'); +#endif + + unsigned long tMillis = millis(); + unsigned int tDeltaMillis = tMillis - ButtonLastChangeMillis; + // Check for bouncing - state change during debounce period + if (tDeltaMillis <= BUTTON_DEBOUNCING_MILLIS) { + /* + * Button is bouncing, signal is ringing - do nothing, ignore and wait for next interrupt + */ +#ifdef ANALYZE_MAX_BOUNCING_PERIOD + if (MaxBouncingPeriodMillis < tDeltaMillis) { + MaxBouncingPeriodMillis = tDeltaMillis; + Serial.print(F("Bouncing, MBP=")); + Serial.println(MaxBouncingPeriodMillis); + // Serial.print(F("ms=")); + // Serial.print(tMillis); + // Serial.print(F(" D=")); + // Serial.println(tDeltaMillis); + } +#endif + if (tCurrentButtonStateIsActive) { + LastBounceWasChangeToInactive = false; + } else { + /* + * Store, that switch goes inactive during debouncing period. + * This may be a bouncing issue (fine) but it can also be a very short button press. + * In this case we do not set the ButtonStateIsActive to false because we are in debouncing period. + * On the next press this will be detected as a spike, if not considered. + */ + LastBounceWasChangeToInactive = true; + } + + } else { + /* + * Here we are after debouncing period + */ + if (tCurrentButtonStateIsActive == ButtonStateIsActive) { + /* + * No valid change detected - current is equals last state + */ + if (tCurrentButtonStateIsActive && LastBounceWasChangeToInactive) { + // We assume we had a very short press before (or a strange spike), which was handled as a bounce. -> must adjust last button state + ButtonStateIsActive = false; +#ifdef TRACE + Serial.println(F("Preceding short press detected, which was handled as bounce")); +#endif + + } else { + /* + * tCurrentButtonStateIsActive == OldButtonStateIsActive. We had an interrupt, but nothing seems to have changed -> spike + * Do nothing, ignore and wait for next interrupt + */ +#ifdef TRACE + Serial.println(F("Spike")); +#endif + } + } + + // do not use else since we may have changed ButtonStateIsActive + if (tCurrentButtonStateIsActive != ButtonStateIsActive) { + /* + * Valid change detected + */ + ButtonLastChangeMillis = tMillis; + LastBounceWasChangeToInactive = false; +#ifdef TRACE + Serial.println(F("Change")); +#endif + ButtonStateIsActive = tCurrentButtonStateIsActive; + ButtonStateHasJustChanged = true; + if (tCurrentButtonStateIsActive) { + /* + * Button pressed + */ +#ifdef LED_FEEDBACK_TEST + digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, HIGH); +#endif + ButtonToggleState = !ButtonToggleState; + if (ButtonPressCallback != NULL) { + /* + * Call callback function. + * interrupts() is required if callback function needs more time to allow millis() to proceed. + * Otherwise we may see bouncing instead of button release followed by spike instead of button press + */ + interrupts(); + ButtonPressCallback(ButtonToggleState); + /* + * Check button again since it may changed back while processing callback function + */ + if (!readButtonState()) { + // button released now, so maintain status +#ifdef TRACE + Serial.println(F("Button release during callback processing detected.")); +#endif + ButtonStateIsActive = false; + tMillis = millis(); + ButtonPressDurationMillis = tMillis - ButtonLastChangeMillis; + ButtonLastChangeMillis = tMillis; + ButtonStateHasJustChanged = true; + ButtonReleaseMillis = tMillis; + } + } + } else { + /* + * Button release + */ + ButtonPressDurationMillis = tDeltaMillis; + ButtonReleaseMillis = tMillis; +#if ! defined(NO_BUTTON_RELEASE_CALLBACK) + if (ButtonReleaseCallback != NULL) { + /* + * Call callback function. + * interrupts() is required if callback function needs more time to allow millis() to proceed. + */ + interrupts(); + ButtonReleaseCallback(ButtonToggleState, ButtonPressDurationMillis); + /* + * Check button again since it may be activated while processing callback function + */ + if (readButtonState()) { + // button activated now, so maintain status +# ifdef TRACE + Serial.println(F("Button active after callback processing detected.")); +# endif + ButtonStateIsActive = true; + ButtonLastChangeMillis = millis(); + ButtonStateHasJustChanged = true; + LastBounceWasChangeToInactive = false; + } + } +#endif +#ifdef LED_FEEDBACK_TEST + digitalWriteFast(BUTTON_TEST_FEEDBACK_LED_PIN, LOW); +#endif + } + } + } +} + +// end of class definitions + +/* + * This functions are weak and can be replaced by your own code + */ +#if defined(USE_BUTTON_0) +void __attribute__ ((weak)) handleINT0Interrupt() { + EasyButton::sPointerToButton0ForISR->handleINT01Interrupts(); +} +#endif + +#if defined(USE_BUTTON_1) +void __attribute__ ((weak)) handleINT1Interrupt() { + EasyButton::sPointerToButton1ForISR->handleINT01Interrupts(); +} +#endif + +#if not defined(USE_ATTACH_INTERRUPT) +// ISR for PIN PD2 +// Cannot make the vector itself weak, since the vector table is already filled by weak vectors resulting in ignoring my weak one:-( +//ISR(INT0_vect, __attribute__ ((weak))) { +# if defined(USE_BUTTON_0) +ISR(INT0_vect) { +# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING + digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, HIGH); +# endif + handleINT0Interrupt(); +# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING + digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, LOW); +# endif +} +# endif + +# if defined(USE_BUTTON_1) +# if (! defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3) +// on ATtinyX5 we do not have a INT1_vect but we can use the PCINT0_vect +ISR(PCINT0_vect) +# elif INT1_PIN == 4 || INT1_PIN == 5 || INT1_PIN == 6 || INT1_PIN == 7 +// PCINT for ATmega328 Arduino pins 0 to 7 +ISR(PCINT2_vect) +# elif INT1_PIN == 8 || INT1_PIN == 9 || INT1_PIN == 10 || INT1_PIN == 11 || INT1_PIN == 12 || INT1_PIN == 13 +// PCINT for ATmega328 Arduino pins 8 (PB0) to 13 (PB5) - (PCINT 0 to 5) +ISR(PCINT0_vect) +# elif INT1_PIN == A0 || INT1_PIN == A1 || INT1_PIN == A2 || INT1_PIN == A3 || INT1_PIN == A4 || INT1_PIN == A5 +// PCINT for ATmega328 Arduino pins A1 (PC0) to A5 (PC5) - (PCINT 8 to 13) +ISR(PCINT1_vect) +# else +ISR(INT1_vect) +# endif +{ +# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING + digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, HIGH); +# endif + handleINT1Interrupt(); +# ifdef MEASURE_EASY_BUTTON_INTERRUPT_TIMING + digitalWriteFast(INTERRUPT_TIMING_OUTPUT_PIN, LOW); +# endif +} +# endif +#endif // not defined(USE_ATTACH_INTERRUPT) + +#endif // defined(__AVR__) diff --git a/src/digitalWriteFast.h b/src/digitalWriteFast.h index 6267ec1..31d7906 100644 --- a/src/digitalWriteFast.h +++ b/src/digitalWriteFast.h @@ -407,7 +407,7 @@ // --- ATtinyX4 + ATtinyX7 --- #elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) -# if defined(ARDUINO_AVR_DIGISPARKPRO) +# if defined(ARDUINO_AVR_DIGISPARKPRO) || PIN_PA7 == 5 // Strange enumeration of pins on Digispark board and core library #define __digitalPinToPortReg(P) (((P) <= 4) ? &PORTB : &PORTA) #define __digitalPinToDDRReg(P) (((P) <= 4) ? &DDRB : &DDRA)