diff --git a/ClickEncoder.cpp b/ClickEncoder.cpp deleted file mode 100644 index 4ca135b..0000000 --- a/ClickEncoder.cpp +++ /dev/null @@ -1,221 +0,0 @@ -// ---------------------------------------------------------------------------- -// Rotary Encoder Driver with Acceleration -// Supports Click, DoubleClick, Long Click -// -// (c) 2010 karl@pitrich.com -// (c) 2014 karl@pitrich.com -// -// Timer-based rotary encoder logic by Peter Dannegger -// http://www.mikrocontroller.net/articles/Drehgeber -// ---------------------------------------------------------------------------- - -#include "ClickEncoder.h" - -// ---------------------------------------------------------------------------- -// 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()) -// -#define ENC_ACCEL_TOP 3072 // max. acceleration: *12 (val >> 8) -#define ENC_ACCEL_INC 25 -#define ENC_ACCEL_DEC 2 - -// ---------------------------------------------------------------------------- - -#if ENC_DECODER != ENC_NORMAL -# ifdef 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 - }; -# else - // decoding table for normal hardware - const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = { - 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0 - }; -# endif -#endif - -// ---------------------------------------------------------------------------- - -ClickEncoder::ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN, uint8_t stepsPerNotch, bool active) - : doubleClickEnabled(true), accelerationEnabled(true), - delta(0), last(0), acceleration(0), - button(Open), steps(stepsPerNotch), - pinA(A), pinB(B), pinBTN(BTN), pinsActive(active) -{ - uint8_t configType = (pinsActive == LOW) ? INPUT_PULLUP : INPUT; - pinMode(pinA, configType); - pinMode(pinB, configType); - pinMode(pinBTN, configType); - - if (digitalRead(pinA) == pinsActive) { - last = 3; - } - - if (digitalRead(pinB) == pinsActive) { - last ^=1; - } -} - -// ---------------------------------------------------------------------------- -// call this every 1 millisecond via timer ISR -// -void ClickEncoder::service(void) -{ - bool moved = false; - unsigned long now = millis(); - - if (accelerationEnabled) { // decelerate every tick - acceleration -= ENC_ACCEL_DEC; - if (acceleration & 0x8000) { // handle overflow of MSB is set - acceleration = 0; - } - } - -#if ENC_DECODER == ENC_FLAKY - last = (last << 2) & 0x0F; - - if (digitalRead(pinA) == pinsActive) { - last |= 2; - } - - if (digitalRead(pinB) == pinsActive) { - last |= 1; - } - - uint8_t tbl = pgm_read_byte(&table[last]); - if (tbl) { - delta += tbl; - moved = true; - } -#elif ENC_DECODER == ENC_NORMAL - int8_t curr = 0; - - if (digitalRead(pinA) == pinsActive) { - curr = 3; - } - - if (digitalRead(pinB) == pinsActive) { - curr ^= 1; - } - - int8_t diff = last - curr; - - if (diff & 1) { // bit 0 = step - last = curr; - delta += (diff & 2) - 1; // bit 1 = direction (+/-) - moved = true; - } -#else -# error "Error: define ENC_DECODER to ENC_NORMAL or ENC_FLAKY" -#endif - - if (accelerationEnabled && moved) { - // increment accelerator if encoder has been moved - if (acceleration <= (ENC_ACCEL_TOP - ENC_ACCEL_INC)) { - 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 - { - lastButtonCheck = now; - - if (digitalRead(pinBTN) == pinsActive) { // key is down - keyDownTicks++; - if (keyDownTicks > (ENC_HOLDTIME / ENC_BUTTONINTERVAL)) { - button = Held; - } - } - - if (digitalRead(pinBTN) == !pinsActive) { // key is now up - if (keyDownTicks /*> ENC_BUTTONINTERVAL*/) { - 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)) { - button = DoubleClicked; - doubleClickTicks = 0; - } - } - else { - doubleClickTicks = (doubleClickEnabled) ? (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL) : ENC_SINGLECLICKONLY; - } - } - } - - keyDownTicks = 0; - } - - if (doubleClickTicks > 0) { - doubleClickTicks--; - if (--doubleClickTicks == 0) { - button = Clicked; - } - } - } -#endif // WITHOUT_BUTTON - -} - -// ---------------------------------------------------------------------------- - -int16_t ClickEncoder::getValue(void) -{ - int16_t val; - - cli(); - 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; - - int16_t r = 0; - int16_t accel = ((accelerationEnabled) ? (acceleration >> 8) : 0); - - if (val < 0) { - r -= 1 + accel; - } - else if (val > 0) { - r += 1 + accel; - } - - return r; -} - -// ---------------------------------------------------------------------------- - -#ifndef WITHOUT_BUTTON -ClickEncoder::Button ClickEncoder::getButton(void) -{ - ClickEncoder::Button ret = button; - if (button != ClickEncoder::Held) { - button = ClickEncoder::Open; // reset - } - return ret; -} -#endif diff --git a/README.md b/README.md index 30dbc66..ea518ac 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ or see it in action at my modified [reflow oven controller] ### Encoder The library supports **acceleration**, so when the encoder is rotated faster, the encoders value will increment faster. -Acceleration can be enabled or disabled at runtine using `setAccelerationEnabled(bool)`. +Acceleration can be enabled or disabled at runtime using `setAccelerationEnabled(bool)`. For instance, it makes sense to disable acceleration when entering a configuration menu that will be navigated using the encoder. diff --git a/examples/ArduinoStyle/ArduinoStyle.ino b/examples/ArduinoStyle/ArduinoStyle.ino new file mode 100644 index 0000000..fcf5983 --- /dev/null +++ b/examples/ArduinoStyle/ArduinoStyle.ino @@ -0,0 +1,68 @@ +#include +#include + +int16_t oldEncPos, encPos; +uint8_t buttonState; + +#define pinA A2 +#define pinB A1 +#define pinSw A0 //switch +#define STEPS 4 + +ClickEncoder encoder(pinA, pinB, pinSw, STEPS); + +void setup() { + Serial.begin(9600); + + Timer1.initialize(1000); + Timer1.attachInterrupt(timerIsr); + + encoder.setAccelerationEnabled(true); + + Serial.print("Acceleration is "); + Serial.println((encoder.getAccelerationEnabled()) ? "enabled" : "disabled"); + + oldEncPos = -1; +} + +void loop() { + encPos += encoder.getValue(); + + if (encPos != oldEncPos) { + oldEncPos = encPos; + Serial.print("Encoder Value: "); + Serial.println(encPos); + } + + buttonState = encoder.getButton(); + + if (buttonState != 0) { + Serial.print("Button: "); Serial.println(buttonState); + switch (buttonState) { + case ClickEncoder::Open: //0 + break; + + case ClickEncoder::Closed: //1 + break; + + case ClickEncoder::Pressed: //2 + break; + + case ClickEncoder::Held: //3 + break; + + case ClickEncoder::Released: //4 + break; + + case ClickEncoder::Clicked: //5 + break; + + case ClickEncoder::DoubleClicked: //6 + break; + } + } +} + +void timerIsr() { + encoder.service(); +} diff --git a/examples/ESP8266Example/ESP8266Example.ino b/examples/ESP8266Example/ESP8266Example.ino new file mode 100644 index 0000000..c651a9e --- /dev/null +++ b/examples/ESP8266Example/ESP8266Example.ino @@ -0,0 +1,66 @@ +/* + +This example demonstrates the use of the library on an ESP8266. without using a timer interrupt + + */ + +#include +//#include + + +#define ENCODER_PINA 13 +#define ENCODER_PINB 14 +#define ENCODER_BTN 0 + +#define ENCODER_STEPS_PER_NOTCH 4 // Change this depending on which encoder is used + +ClickEncoder encoder = ClickEncoder(ENCODER_PINA,ENCODER_PINB,ENCODER_BTN,ENCODER_STEPS_PER_NOTCH); + +void setup() { + Serial.begin(115200); + + encoder.setButtonHeldEnabled(true); + encoder.setDoubleClickEnabled(true); + + // Enable the button to be on pin 0. Normally pin 0 is not recognized as a valid pin for a button, + // this is to maintain backward compatibility with an old version of the library + // This version can have the button on pin zero, and this call enables the feature. + // in this version best to use pin -1 instead of 0 to disable button functions + encoder.setButtonOnPinZeroEnabled(true); + +} + +void loop() { + + //Call Service in loop becasue using timer interrupts may affect ESP8266 WIFI + //however call no more than 1 time per millisecond to reduce encoder bounce + static uint32_t lastService = 0; + if (micros() - lastService >= 1000) { + lastService = micros(); + encoder.service(); + } + + +static int16_t last, value; + value += encoder.getValue(); + + if (value != last) { + last = value; + Serial.print("Encoder Value: "); + Serial.println(value); + + } + + ClickEncoder::Button b = encoder.getButton(); + if (b != ClickEncoder::Open) { + Serial.print("Button: "); + #define VERBOSECASE(label) case label: Serial.println(#label); break; + switch (b) { + VERBOSECASE(ClickEncoder::Pressed); + VERBOSECASE(ClickEncoder::Held) + VERBOSECASE(ClickEncoder::Released) + VERBOSECASE(ClickEncoder::Clicked) + VERBOSECASE(ClickEncoder::DoubleClicked) + } + } +} diff --git a/examples/TwoDevices/TwoDevices.ino b/examples/TwoDevices/TwoDevices.ino new file mode 100644 index 0000000..9352c7b --- /dev/null +++ b/examples/TwoDevices/TwoDevices.ino @@ -0,0 +1,85 @@ +/* + +This example demonstrates the use of 2 devices controlled by the ClickEncoder library + +In thie example, the 2 devices are a rotary encoder with built in button, and a normal momentary button + + */ +#include +#include + +#define ENCODER_PINA 5 // If the encoder moved in the wrong direction, swap PINA and PINB +#define ENCODER_PINB 6 +#define ENCODER_BTN 7 + +#define ENCODER_STEPS_PER_NOTCH 4 // Change this depending on which encoder is used + +#define EXTRA_BUTTON_PIN 8 + +// Note, using ClickEncoder to define a stand alone button has been depricated. Use DigitalButton instead + +DigitalButton button = DigitalButton(EXTRA_BUTTON_PIN); +ClickEncoder encoder = ClickEncoder(ENCODER_PINA,ENCODER_PINB,ENCODER_BTN,ENCODER_STEPS_PER_NOTCH); + +void timerIsr() { //Service methods from both instances must be included + encoder.service(); + button.service(); +} + +void setup() { + Serial.begin(115200); + + Timer1.initialize(1000); + Timer1.attachInterrupt(timerIsr); + + encoder.setButtonHeldEnabled(false); + encoder.setDoubleClickEnabled(true); + + button.setButtonHeldEnabled(true); + button.setDoubleClickEnabled(false); + + // Enable the button to be on pin 0. Normally pin 0 is not recognized as a valid pin for a button, + // this is to maintain backward compatibility with an old version of the library + // This version can have the button on pin zero, and this call enables the feature. + // in this version best to use pin -1 instead of 0 to disable button functions + encoder.setButtonOnPinZeroEnabled(false); + +} + +void loop() { +static int16_t last, value; + value += encoder.getValue(); + + if (value != last) { + last = value; + Serial.print("Encoder Value: "); + Serial.println(value); + + } + + ClickEncoder::Button b = encoder.getButton(); + if (b != ClickEncoder::Open) { + Serial.print("Button: "); + #define VERBOSECASE(label) case label: Serial.println(#label); break; + switch (b) { + VERBOSECASE(ClickEncoder::Pressed); + VERBOSECASE(ClickEncoder::Held) + VERBOSECASE(ClickEncoder::Released) + VERBOSECASE(ClickEncoder::Clicked) + VERBOSECASE(ClickEncoder::DoubleClicked) + } + } + + ClickEncoder::Button b2 = button.getButton(); + if (b2 != ClickEncoder::Open) { + Serial.print("Button2: "); + #define VERBOSECASE(label) case label: Serial.println(#label); break; + switch (b2) { + VERBOSECASE(ClickEncoder::Pressed); + VERBOSECASE(ClickEncoder::Held) + VERBOSECASE(ClickEncoder::Released) + VERBOSECASE(ClickEncoder::Clicked) + VERBOSECASE(ClickEncoder::DoubleClicked) + } + } +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..7802efc --- /dev/null +++ b/keywords.txt @@ -0,0 +1,33 @@ +####################################### +# Syntax Coloring Map for ClickEncoder +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ClickEncoder KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setAccelerationEnabled KEYWORD2 +getAccelerationEnabled KEYWORD2 +getButton KEYWORD2 +getValue KEYWORD2 +service KEYWORD2 +setDoubleClickTime KEYWORD2 +setHoldTime KEYWORD2 +setDoubleClickEnabled KEYWORD2 +getDoubleClickEnabled KEYWORD2 +setButtonHeldEnabled KEYWORD2 +getButtonHeldEnabled KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/library.json b/library.json new file mode 100644 index 0000000..af03521 --- /dev/null +++ b/library.json @@ -0,0 +1,17 @@ +{ + "name": "ClickEncoder", + "keywords": "encoder, button, input", + "description": "Arduino library to handle rotary encoders with buttons as a user input device", + "repository": + { + "type": "git", + "url": "https://github.com/soligen2010/encoder.git" + }, + "frameworks": "arduino", + "platforms": [ + "atmelavr", + "atmelsam", + "teensy", + "espressif" + ] +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..6bb5570 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=ClickEncoder +version=1.0.1 +author=soligen2010 +maintainer=Dennis +sentence=Arduino library to handle rotary encoders with buttons as a user input device. +paragraph= +category=Display +url=https://github.com/soligen2010/encoder +architectures=* \ No newline at end of file diff --git a/src/ClickEncoder.cpp b/src/ClickEncoder.cpp new file mode 100644 index 0000000..3684db3 --- /dev/null +++ b/src/ClickEncoder.cpp @@ -0,0 +1,313 @@ +// ---------------------------------------------------------------------------- +// Rotary Encoder Driver with Acceleration +// Supports Click, DoubleClick, Long Click +// +// (c) 2010 karl@pitrich.com +// (c) 2014 karl@pitrich.com +// +// Timer-based rotary encoder logic by Peter Dannegger +// http://www.mikrocontroller.net/articles/Drehgeber +// ---------------------------------------------------------------------------- + +#include "ClickEncoder.h" + +// ---------------------------------------------------------------------------- +// Button configuration (values for 1ms timer service calls) +// +#define ENC_BUTTONINTERVAL 10 // check button every x milliseconds, also debouce time + +// ---------------------------------------------------------------------------- +// Acceleration configuration (for 1000Hz calls to ::service()) +// +#define ENC_ACCEL_TOP 3072 // max. acceleration: *12 (val >> 8) +#define ENC_ACCEL_INC 25 +#define ENC_ACCEL_DEC 2 + +// ---------------------------------------------------------------------------- + +#if ENC_DECODER != ENC_NORMAL +# 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 + }; +# else + // decoding table for normal hardware + const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = { + 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0 + }; +# endif +#endif + +// ---------------------------------------------------------------------------- + +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 +{ + pinMode_t configType = (pinsActive == LOW) ? INPUT_PULLUP : INPUT; + 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; + } + + if (digitalRead(pinB) == pinsActive) { + last ^=1; + } +} + +// ---------------------------------------------------------------------------- +#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) +{ + pinMode_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, (bool)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; + + if (pinA >= 0 && pinB >= 0) { + if (accelerationEnabled) { // decelerate every tick + acceleration -= ENC_ACCEL_DEC; + if (acceleration & 0x8000) { // handle overflow of MSB is set + acceleration = 0; + } + } + + +#if ENC_DECODER == ENC_FLAKY + last = (last << 2) & 0x0F; + + if (digitalRead(pinA) == pinsActive) { + last |= 2; + } + + if (digitalRead(pinB) == pinsActive) { + last |= 1; + } + + int8_t tbl = pgm_read_byte(&table[last]); + if (tbl) { + delta += tbl; + moved = true; + } +#elif ENC_DECODER == ENC_NORMAL + int8_t curr = 0; + + if (digitalRead(pinA) == pinsActive) { + curr = 3; + } + + if (digitalRead(pinB) == pinsActive) { + curr ^= 1; + } + + int8_t diff = last - curr; + + if (diff & 1) { // bit 0 = step + last = curr; + delta += (diff & 2) - 1; // bit 1 = direction (+/-) + moved = true; + } +#else +# error "Error: define ENC_DECODER to ENC_NORMAL or ENC_FLAKY" +#endif + + if (accelerationEnabled && moved) { + // increment accelerator if encoder has been moved + if (acceleration <= (ENC_ACCEL_TOP - ENC_ACCEL_INC)) { + acceleration += ENC_ACCEL_INC; + } + } +} + // handle button + // +#ifndef WITHOUT_BUTTON + unsigned long currentMillis = millis(); + unsigned long millisSinceLastCheck = currentMillis - lastButtonCheck; + if ((pinBTN > 0 || (pinBTN == 0 && buttonOnPinZeroEnabled)) // check button only, if a pin has been provided + && (millisSinceLastCheck >= ENC_BUTTONINTERVAL)) // checking button is sufficient every 10-30ms + { + lastButtonCheck = currentMillis; + + bool pinRead = getPinState(); + + + + + + + + + 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 < (buttonDoubleClickTime)) { + button = DoubleClicked; + doubleClickTicks = 0; + } + } + else { + doubleClickTicks = (doubleClickEnabled) ? (buttonDoubleClickTime) : ENC_SINGLECLICKONLY; + } + } + } + + keyDownTicks = 0; + } + + if (pinRead == pinsActive) { // key is down + if ((keyDownTicks > (buttonHoldTime)) && (buttonHeldEnabled)) { + button = Held; + } + keyDownTicks += millisSinceLastCheck; + } + + if (doubleClickTicks > 0) { + doubleClickTicks -= (uint16_t)constrain(min(millisSinceLastCheck, (unsigned long)doubleClickTicks), 0, 65536); + if (doubleClickTicks == 0) { + button = Clicked; + } + } + } +#endif // WITHOUT_BUTTON + +} + +// ---------------------------------------------------------------------------- + +int16_t ClickEncoder::getValue(void) +{ + int16_t val; + int16_t r = 0; + + noInterrupts(); + val = delta; + + delta = val % steps; + + + + + + + + + + + + int16_t accel = ((accelerationEnabled) ? (acceleration >> 8) : 0); + interrupts(); + + val /= steps; + + if (val < 0) { + r -= 1 + accel; + } + else if (val > 0) { + r += 1 + accel; + } + + + return r; +} + +// ---------------------------------------------------------------------------- +// This is used to reset the encoder. If the encoder gets 'between' dedents +// (for example only needs 3 more dedents for next click instead of 4) +// it could cause changing direction of encoder to do nothing on first turn +// this could happen if teh encoder misses a reansition, or if the encoder +// is being turned during initialization +// This routine resets the encoder to re-sync it with the dedents + +void ClickEncoder::resetEncoder(void) +{ + noInterrupts(); + delta = 0; + acceleration = 0; + interrupts(); +} + +// ---------------------------------------------------------------------------- + +#ifndef WITHOUT_BUTTON +ClickEncoder::Button ClickEncoder::getButton(void) +{ + noInterrupts(); + ClickEncoder::Button ret = button; + 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 diff --git a/ClickEncoder.h b/src/ClickEncoder.h similarity index 53% rename from ClickEncoder.h rename to src/ClickEncoder.h index 73f9f41..9bbe856 100644 --- a/ClickEncoder.h +++ b/src/ClickEncoder.h @@ -1,118 +1,197 @@ -// ---------------------------------------------------------------------------- -// Rotary Encoder Driver with Acceleration -// Supports Click, DoubleClick, Long Click -// -// (c) 2010 karl@pitrich.com -// (c) 2014 karl@pitrich.com -// -// Timer-based rotary encoder logic by Peter Dannegger -// http://www.mikrocontroller.net/articles/Drehgeber -// ---------------------------------------------------------------------------- - -#ifndef __have__ClickEncoder_h__ -#define __have__ClickEncoder_h__ - -// ---------------------------------------------------------------------------- - -#include -#include -#include -#include -#include "Arduino.h" - -// ---------------------------------------------------------------------------- - -#define ENC_NORMAL (1 << 1) // use Peter Danneger's decoder -#define ENC_FLAKY (1 << 2) // use Table-based decoder - -// ---------------------------------------------------------------------------- - -#ifndef ENC_DECODER -# define ENC_DECODER ENC_NORMAL -#endif - -#if ENC_DECODER == ENC_FLAKY -# ifndef ENC_HALFSTEP -# define ENC_HALFSTEP 1 // use table for half step per default -# endif -#endif - -// ---------------------------------------------------------------------------- - -class ClickEncoder -{ -public: - typedef enum Button_e { - Open = 0, - Closed, - - Pressed, - Held, - Released, - - Clicked, - DoubleClicked - - } Button; - -public: - ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN = -1, - uint8_t stepsPerNotch = 1, bool active = LOW); - - void service(void); - int16_t getValue(void); - -#ifndef WITHOUT_BUTTON -public: - Button getButton(void); -#endif - -#ifndef WITHOUT_BUTTON -public: - void setDoubleClickEnabled(const bool &d) - { - doubleClickEnabled = d; - } - - const bool getDoubleClickEnabled() - { - return doubleClickEnabled; - } -#endif - -public: - void setAccelerationEnabled(const bool &a) - { - accelerationEnabled = a; - if (accelerationEnabled == false) { - acceleration = 0; - } - } - - const bool getAccelerationEnabled() - { - return accelerationEnabled; - } - -private: - const uint8_t pinA; - const uint8_t pinB; - const uint8_t pinBTN; - const bool pinsActive; - volatile int16_t delta; - volatile int16_t last; - uint8_t steps; - volatile uint16_t acceleration; -#if ENC_DECODER != ENC_NORMAL - static const int8_t table[16]; -#endif -#ifndef WITHOUT_BUTTON - volatile Button button; - bool doubleClickEnabled; - bool accelerationEnabled; -#endif -}; - -// ---------------------------------------------------------------------------- - -#endif // __have__ClickEncoder_h__ +// ---------------------------------------------------------------------------- +// Rotary Encoder Driver with Acceleration +// Supports Click, DoubleClick, Long Click +// +// (c) 2010 karl@pitrich.com +// (c) 2014 karl@pitrich.com +// +// Timer-based rotary encoder logic by Peter Dannegger +// http://www.mikrocontroller.net/articles/Drehgeber +// ---------------------------------------------------------------------------- + +#ifndef __have__ClickEncoder_h__ +#define __have__ClickEncoder_h__ + +// ---Button defaults------------------------------------------------------------- + +#define BTN_DOUBLECLICKTIME 400 // second click within 400ms +#define BTN_HOLDTIME 1000 // report held button after 1s + + +// ---------------------------------------------------------------------------- + +#include +#if defined(__AVR__) +#include +#include +#include +#endif +#include "Arduino.h" + +// ---------------------------------------------------------------------------- + +#define ENC_NORMAL (1 << 1) // use Peter Danneger's decoder +#define ENC_FLAKY (1 << 2) // use Table-based decoder + +// ---------------------------------------------------------------------------- + +#ifndef ENC_DECODER +# define ENC_DECODER ENC_NORMAL +#endif + +#if ENC_DECODER == ENC_FLAKY +# ifndef ENC_HALFSTEP +# define ENC_HALFSTEP 1 // use table for half step per default +# endif +#endif + +// ---------------------------------------------------------------------------- +#if defined(__arm__) && (defined (__STM32F1__) || defined (__STM32F4__) ) + typedef WiringPinMode pinMode_t; +#else + typedef uint8_t pinMode_t; +#endif + +class ClickEncoder +{ +public: + typedef enum Button_e { + Open = 0, + Closed, + + Pressed, + Held, + Released, + + Clicked, + DoubleClicked + + } Button; + +public: + ClickEncoder(int8_t A, int8_t B, int8_t BTN = -1, + uint8_t stepsPerNotch = 4, bool active = LOW); + +#ifndef WITHOUT_BUTTON + explicit ClickEncoder(int8_t BTN, bool active = false); // Depricated. Use Digtial Button instead + +#endif + + void service(void); + void resetEncoder(void); + int16_t getValue(void); + +#ifndef WITHOUT_BUTTON +public: + Button getButton(void); +#endif + +#ifndef WITHOUT_BUTTON +public: + void setDoubleClickTime(uint16_t durationMilliseconds) + { + buttonDoubleClickTime = durationMilliseconds; + } + +public: + void setHoldTime(uint16_t durationMilliseconds) + { + buttonHoldTime = durationMilliseconds; + } + +public: + void setDoubleClickEnabled(const bool &d) + { + doubleClickEnabled = d; + } + + const bool getDoubleClickEnabled() + { + return doubleClickEnabled; + } + +public: + void setButtonHeldEnabled(const bool &d) + { + buttonHeldEnabled = d; + } + + const bool getButtonHeldEnabled() + { + return buttonHeldEnabled; + } + +public: + void setButtonOnPinZeroEnabled(const bool &d) + { + buttonOnPinZeroEnabled = d; + } + + const bool getButtonOnPinZeroEnabled() + { + return buttonOnPinZeroEnabled; + } +#endif + +public: + void setAccelerationEnabled(const bool &a) + { + accelerationEnabled = a; + if (accelerationEnabled == false) { + acceleration = 0; + } + } + + const bool getAccelerationEnabled() + { + return accelerationEnabled; + } + +protected: + int8_t pinA; + int8_t pinB; + int8_t pinBTN; + bool pinsActive; + volatile int16_t delta; + volatile int16_t last; + uint8_t steps; + volatile uint16_t acceleration; + bool accelerationEnabled; +#if ENC_DECODER != ENC_NORMAL + static const int8_t table[16]; +#endif +#ifndef WITHOUT_BUTTON + volatile Button button; + bool doubleClickEnabled; + bool buttonHeldEnabled; + bool buttonOnPinZeroEnabled = false; + bool analogInput = false; + uint16_t keyDownTicks = 0; + uint16_t doubleClickTicks = 0; + uint16_t buttonHoldTime = BTN_HOLDTIME; + uint16_t buttonDoubleClickTime = BTN_DOUBLECLICKTIME; + unsigned long lastButtonCheck = 0; + int16_t anlogActiveRangeLow = 0; + int16_t anlogActiveRangeHigh = 0; + bool getPinState(); +#endif +}; + +#ifndef WITHOUT_BUTTON +class AnalogButton : public ClickEncoder +{ + public: + explicit AnalogButton(int8_t BTN, int16_t rangeLow, int16_t rangeHigh); // Constructor for using analog input range as a button +}; + +class DigitalButton : public ClickEncoder +{ + public: + explicit DigitalButton(int8_t BTN, bool active = false); // Constructor for using a button only +}; + +#endif + +// ---------------------------------------------------------------------------- + +#endif // __have__ClickEncoder_h__