Skip to content

Commit

Permalink
Version 2.1.0 - long and double press revisited
Browse files Browse the repository at this point in the history
  • Loading branch information
ArminJo committed May 14, 2020
1 parent 08b8def commit 30ffd66
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 489 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/LibraryBuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
107 changes: 80 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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.<br/>

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*.<br/>
E.g. `#define INT1_PIN 7`. See [EasyButtonExample.cpp](examples/EasyButtonExample/EasyButtonExample.ino#L46).

## Usage
To use a single button, it needs only:
Expand All @@ -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.<br/>
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**.<br/>
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)
Expand All @@ -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:-(.<br/>

## Consider to use [Sloeber](http://eclipse.baeyens.it/stable.php?OS=Windows) as IDE<br/>
If you are using Sloeber as your IDE, you can easily define global symbols at *Properties/Arduino/CompileOptions*.<br/>

Expand All @@ -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.
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions examples/Callback/Callback.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"));
}
}
2 changes: 2 additions & 0 deletions examples/DebounceTest/DebounceTest.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 16 additions & 16 deletions examples/EasyButtonExample/ATtinySerialOut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
}
Expand Down
24 changes: 12 additions & 12 deletions examples/EasyButtonExample/ATtinySerialOut.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 30ffd66

Please sign in to comment.