Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition causing lost events #9

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
625b9a3
Fix race condition causing lost events
soligen2010 Feb 19, 2016
97dd97e
Fix bug when double click disabled
soligen2010 Feb 19, 2016
3831274
accelerationEnabled
soligen2010 Feb 19, 2016
e08e7fa
Added method to enable/disable the button hold/release events
soligen2010 Feb 19, 2016
9937dd4
Adjusted double click time
soligen2010 Feb 19, 2016
9798ec8
Allow multiple instances of object to co-exist
soligen2010 Feb 19, 2016
5b63c46
Fixed "FLAKY" mode
soligen2010 Feb 19, 2016
3546f7a
Added ability to use button without encoder and allow pin 0 to be used
soligen2010 Feb 19, 2016
14e6607
Added example showing how to use 2 devices simultaneously
soligen2010 Feb 19, 2016
f60058c
Restored original behavior for button on pin 0, but can now optionall…
soligen2010 Feb 20, 2016
d9e6f8a
Added support for ESP8266 and example
soligen2010 Feb 20, 2016
97fd7e5
Filter out transient events on the button
soligen2010 Mar 20, 2016
4124fc6
@PlatformIO Library Registry manifest file
valeros May 4, 2016
2ea30f4
Merge pull request #1 from valeros/patch-2
soligen2010 May 4, 2016
2ad751f
Change conditional on AVR included
soligen2010 Jun 23, 2016
c471bc1
Revert "Change conditional on AVR included"
soligen2010 Jun 24, 2016
98982a5
Revert "Revert "Change conditional on AVR included""
soligen2010 Jun 24, 2016
a351ccb
Change cli and sei to noInterrupts and interrupts
soligen2010 Jun 24, 2016
b1ee276
Added methods to customize double click and hold/release times for bu…
soligen2010 Oct 15, 2016
9efede6
Added ability to have multiple buttons on one analog pin/Button race …
soligen2010 Nov 1, 2016
4dacba3
moved interupts() in getValue to be sure no race condition happens
soligen2010 Nov 1, 2016
dba405b
Steps Per Notch default changes to 4
soligen2010 Feb 12, 2017
e2cf703
Fixed ambiguous constructor. Analog buttons are now a seperate object
soligen2010 Feb 13, 2017
e30f7ac
Changed TwoDevices example to use DigitalButton
soligen2010 Feb 13, 2017
31ca2f7
Corrected IF statement to check both pinA and pinB
soligen2010 Feb 18, 2017
16505d9
Support for STM23duino
soligen2010 Apr 11, 2017
f376418
Fix Typo
soligen2010 Apr 21, 2017
4b3b30b
Fix for Due
soligen2010 Apr 29, 2017
9337a0c
Another way to access the clickEncoder class (#9)
aster94 Jan 15, 2018
9d0d226
Update ESP8266Example.ino
soligen2010 Jan 23, 2021
2081c04
CCW Negative Bit Fix
soligen2010 Jan 23, 2021
7d11d48
Reset Encoder Method; Improved Timing
soligen2010 Feb 13, 2022
0de753b
Add library.properties and update folder structure for Arduino librar…
robaol Sep 8, 2023
58a6c82
ESP32 Fix
soligen2010 Sep 8, 2023
6c41ff6
improved ESP32 fix
soligen2010 Sep 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 89 additions & 29 deletions ClickEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
// Button configuration (values for 1ms timer service calls)
//
#define ENC_BUTTONINTERVAL 10 // check button every x milliseconds, also debouce time
#define ENC_DOUBLECLICKTIME 600 // second click within 600ms
#define ENC_HOLDTIME 1200 // report held button after 1.2s

// ----------------------------------------------------------------------------
// Acceleration configuration (for 1000Hz calls to ::service())
Expand All @@ -28,7 +26,7 @@
// ----------------------------------------------------------------------------

#if ENC_DECODER != ENC_NORMAL
# ifdef ENC_HALFSTEP
# if ENC_HALFSTEP
// decoding table for hardware with flaky notch (half resolution)
const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = {
0, 0, -1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, 0, 0
Expand All @@ -43,16 +41,21 @@

// ----------------------------------------------------------------------------

ClickEncoder::ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN, uint8_t stepsPerNotch, bool active)
: doubleClickEnabled(true), accelerationEnabled(true),
ClickEncoder::ClickEncoder(int8_t A, int8_t B, int8_t BTN, uint8_t stepsPerNotch, bool active)
: doubleClickEnabled(true),buttonHeldEnabled(true), accelerationEnabled(true),
delta(0), last(0), acceleration(0),
button(Open), steps(stepsPerNotch),
pinA(A), pinB(B), pinBTN(BTN), pinsActive(active)
#ifndef WITHOUT_BUTTON
, analogInput(false)
#endif
{
uint8_t configType = (pinsActive == LOW) ? INPUT_PULLUP : INPUT;
pinMode(pinA, configType);
pinMode(pinB, configType);
pinMode(pinBTN, configType);
if (pinA >= 0) {pinMode(pinA, configType);}
if (pinB >= 0) {pinMode(pinB, configType);}
#ifndef WITHOUT_BUTTON
if (pinBTN >= 0) {pinMode(pinBTN, configType);}
#endif

if (digitalRead(pinA) == pinsActive) {
last = 3;
Expand All @@ -63,14 +66,57 @@ ClickEncoder::ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN, uint8_t stepsPerNo
}
}

// ----------------------------------------------------------------------------
#ifndef WITHOUT_BUTTON


// Depricated. Use DigitalButton instead
ClickEncoder::ClickEncoder(int8_t BTN, bool active)
: doubleClickEnabled(true),buttonHeldEnabled(true), accelerationEnabled(true),
delta(0), last(0), acceleration(0),
button(Open), steps(1), analogInput(false),
pinA(-1), pinB(-1), pinBTN(BTN), pinsActive(active)
{
uint8_t configType = (pinsActive == LOW) ? INPUT_PULLUP : INPUT;
if (pinBTN >= 0) {pinMode(pinBTN, configType);}
}

// ----------------------------------------------------------------------------
// Constructor for using digital input as a button

DigitalButton::DigitalButton(int8_t BTN, bool active) : ClickEncoder(BTN, active)

{

}

// ----------------------------------------------------------------------------
// Constructor for using analog input range as a button

AnalogButton::AnalogButton(int8_t BTN, int16_t rangeLow, int16_t rangeHigh) : ClickEncoder(BTN, false)
{
pinMode(pinBTN, INPUT);

anlogActiveRangeLow = rangeLow;
anlogActiveRangeHigh = rangeHigh;
analogInput = true;

if (anlogActiveRangeLow > anlogActiveRangeHigh) { // swap values if provided in the wrong order
int16_t t = anlogActiveRangeLow;
anlogActiveRangeLow = anlogActiveRangeHigh;
anlogActiveRangeHigh = t;
}
}
#endif

// ----------------------------------------------------------------------------
// call this every 1 millisecond via timer ISR
//
void ClickEncoder::service(void)
{
bool moved = false;
unsigned long now = millis();

if (pinA >= 0 && pinA >= 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why double check pinA? Do you mean pinA and pinB instead?

if (accelerationEnabled) { // decelerate every tick
acceleration -= ENC_ACCEL_DEC;
if (acceleration & 0x8000) { // handle overflow of MSB is set
Expand All @@ -89,7 +135,7 @@ void ClickEncoder::service(void)
last |= 1;
}

uint8_t tbl = pgm_read_byte(&table[last]);
int8_t tbl = pgm_read_byte(&table[last]);
if (tbl) {
delta += tbl;
moved = true;
Expand Down Expand Up @@ -122,42 +168,42 @@ void ClickEncoder::service(void)
acceleration += ENC_ACCEL_INC;
}
}

}
// handle button
//
#ifndef WITHOUT_BUTTON
static uint16_t keyDownTicks = 0;
static uint8_t doubleClickTicks = 0;
static unsigned long lastButtonCheck = 0;

if (pinBTN > 0 // check button only, if a pin has been provided
&& (now - lastButtonCheck) >= ENC_BUTTONINTERVAL) // checking button is sufficient every 10-30ms
unsigned long currentMillis = millis();
if (currentMillis < lastButtonCheck) lastButtonCheck = 0; // Handle case when millis() wraps back around to zero
if ((pinBTN > 0 || (pinBTN == 0 && buttonOnPinZeroEnabled)) // check button only, if a pin has been provided
&& ((currentMillis - lastButtonCheck) >= ENC_BUTTONINTERVAL)) // checking button is sufficient every 10-30ms
{
lastButtonCheck = now;
lastButtonCheck = currentMillis;

bool pinRead = getPinState();

if (digitalRead(pinBTN) == pinsActive) { // key is down
if (pinRead == pinsActive) { // key is down
keyDownTicks++;
if (keyDownTicks > (ENC_HOLDTIME / ENC_BUTTONINTERVAL)) {
if ((keyDownTicks > (buttonHoldTime / ENC_BUTTONINTERVAL)) && (buttonHeldEnabled)) {
button = Held;
}
}

if (digitalRead(pinBTN) == !pinsActive) { // key is now up
if (keyDownTicks /*> ENC_BUTTONINTERVAL*/) {
if (pinRead == !pinsActive) { // key is now up
if (keyDownTicks > 1) { //Make sure key was down through 1 complete tick to prevent random transients from registering as click
if (button == Held) {
button = Released;
doubleClickTicks = 0;
}
else {
#define ENC_SINGLECLICKONLY 1
if (doubleClickTicks > ENC_SINGLECLICKONLY) { // prevent trigger in single click mode
if (doubleClickTicks < (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL)) {
if (doubleClickTicks < (buttonDoubleClickTime / ENC_BUTTONINTERVAL)) {
button = DoubleClicked;
doubleClickTicks = 0;
}
}
else {
doubleClickTicks = (doubleClickEnabled) ? (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL) : ENC_SINGLECLICKONLY;
doubleClickTicks = (doubleClickEnabled) ? (buttonDoubleClickTime / ENC_BUTTONINTERVAL) : ENC_SINGLECLICKONLY;
}
}
}
Expand All @@ -167,7 +213,7 @@ void ClickEncoder::service(void)

if (doubleClickTicks > 0) {
doubleClickTicks--;
if (--doubleClickTicks == 0) {
if (doubleClickTicks == 0) {
button = Clicked;
}
}
Expand All @@ -182,15 +228,13 @@ int16_t ClickEncoder::getValue(void)
{
int16_t val;

cli();
noInterrupts();
val = delta;

if (steps == 2) delta = val & 1;
else if (steps == 4) delta = val & 3;
else delta = 0; // default to 1 step per notch

sei();

if (steps == 4) val >>= 2;
if (steps == 2) val >>= 1;

Expand All @@ -203,6 +247,7 @@ int16_t ClickEncoder::getValue(void)
else if (val > 0) {
r += 1 + accel;
}
interrupts();

return r;
}
Expand All @@ -212,10 +257,25 @@ int16_t ClickEncoder::getValue(void)
#ifndef WITHOUT_BUTTON
ClickEncoder::Button ClickEncoder::getButton(void)
{
noInterrupts();
ClickEncoder::Button ret = button;
if (button != ClickEncoder::Held) {
if (button != ClickEncoder::Held && ret != ClickEncoder::Open) {
button = ClickEncoder::Open; // reset
}
interrupts();

return ret;
}

bool ClickEncoder::getPinState() {
bool pinState;
if (analogInput) {
int16_t pinValue = analogRead(pinBTN);
pinState = ((pinValue >= anlogActiveRangeLow) && (pinValue <= anlogActiveRangeHigh)) ? LOW : HIGH; // set result to LOW (button pressed) if analog input is in range
} else {
pinState = digitalRead(pinBTN);
}
return pinState;
}

#endif
Loading