From ff4bfdbb11a6b0dc8dda1f28654e085e84b26152 Mon Sep 17 00:00:00 2001 From: Matthew Clark Date: Tue, 31 Oct 2023 11:10:49 -0500 Subject: [PATCH] Fixing inconsistent indentation Adding a .editorconfig file, too --- .editorconfig | 20 + README.md | 38 +- .../BasicRotaryEncoder/BasicRotaryEncoder.ino | 2 +- .../TwoRotaryEncoders/TwoRotaryEncoders.ino | 4 +- library.json | 62 +- src/ESP32RotaryEncoder.cpp | 372 +++++----- src/ESP32RotaryEncoder.h | 673 +++++++++--------- 7 files changed, 596 insertions(+), 575 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2aae756 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + + +[*.{h,cpp,json,yml,yaml}] +indent_style = space +indent_size = 2 + +[*.ino] +indent_style = tab +indent_size = 4 + +[*.{md,txt}] +indent_style = space +indent_size = 4 diff --git a/README.md b/README.md index fb3360f..b52eaa6 100644 --- a/README.md +++ b/README.md @@ -73,42 +73,42 @@ RotaryEncoder rotaryEncoder( DI_ENCODER_A, DI_ENCODER_B, DI_ENCODER_SW, DO_ENCOD void knobCallback( int value ) { - Serial.printf( PSTR("Value: %i\n"), value ); + Serial.printf( PSTR("Value: %i\n"), value ); } void buttonCallback() { - Serial.println( PSTR("boop!") ); + Serial.println( PSTR("boop!") ); } void setup() { - Serial.begin( 115200 ); + Serial.begin( 115200 ); - // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( EncoderType::HAS_PULLUP ); + // This tells the library that the encoder has its own pull-up resistors + rotaryEncoder.setEncoderType( EncoderType::HAS_PULLUP ); - // Range of values to be returned by the encoder: minimum is 1, maximum is 10 - // The third argument specifies whether turning past the minimum/maximum will - // wrap around to the other side: - // - true = turn past 10, wrap to 1; turn past 1, wrap to 10 - // - false = turn past 10, stay on 10; turn past 1, stay on 1 - rotaryEncoder.setBoundaries( 1, 10, true ); + // Range of values to be returned by the encoder: minimum is 1, maximum is 10 + // The third argument specifies whether turning past the minimum/maximum will + // wrap around to the other side: + // - true = turn past 10, wrap to 1; turn past 1, wrap to 10 + // - false = turn past 10, stay on 10; turn past 1, stay on 1 + rotaryEncoder.setBoundaries( 1, 10, true ); - // The function specified here will be called every time the knob is turned - // and the current value will be passed to it - rotaryEncoder.onTurned( &knobCallback ); + // The function specified here will be called every time the knob is turned + // and the current value will be passed to it + rotaryEncoder.onTurned( &knobCallback ); - // The function specified here will be called every time the button is pushed - rotaryEncoder.onPressed( &buttonCallback ); + // The function specified here will be called every time the button is pushed + rotaryEncoder.onPressed( &buttonCallback ); - // This is where the inputs are configured and the interrupts get attached - rotaryEncoder.begin(); + // This is where the inputs are configured and the interrupts get attached + rotaryEncoder.begin(); } void loop() { - // Your stuff here + // Your stuff here } ``` diff --git a/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino b/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino index e71faee..c2945d9 100644 --- a/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino +++ b/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino @@ -61,4 +61,4 @@ void setup() void loop() { // Your stuff here -} \ No newline at end of file +} diff --git a/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino b/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino index 092ec4d..7df3e1f 100644 --- a/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino +++ b/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino @@ -108,7 +108,7 @@ void setup_RE2() // Range of values to be returned by the encoder: minimum is -100, maximum is 100 // The third argument specifies whether turning past the minimum/maximum will wrap // around to the other side. - // In this example, turn past 100, stay on 100; turn past -100, stay on -100 + // In this example, turn past 100, stay on 100; turn past -100, stay on -100 rotaryEncoder2.setBoundaries( -100, 100, false ); // The function specified here will be called every time the knob is turned @@ -133,4 +133,4 @@ void setup() void loop() { // Your stuff here -} \ No newline at end of file +} diff --git a/library.json b/library.json index c6d22e2..fc07ec1 100644 --- a/library.json +++ b/library.json @@ -1,33 +1,33 @@ { - "name": "ESP32RotaryEncoder", - "keywords": "arduino, rotary encoder, button, gpio, interrupts", - "description": "ESP32RotaryEncoder is a small library that makes implementing a rotary encoder on ESP32 easy. It uses interrupts for instant detection of knob turns or button presses without blocking or other delays.", - "version": "1.0.4", - "authors": [ - { - "name": "Matthew Clark", - "url": "https://github.com/MaffooClock", - "maintainer": true - } - ], - "repository": { - "type": "git", - "url": "https://github.com/MaffooClock/ESP32RotaryEncoder" + "name": "ESP32RotaryEncoder", + "keywords": "arduino, rotary encoder, button, gpio, interrupts", + "description": "ESP32RotaryEncoder is a small library that makes implementing a rotary encoder on ESP32 easy. It uses interrupts for instant detection of knob turns or button presses without blocking or other delays.", + "version": "1.0.4", + "authors": [ + { + "name": "Matthew Clark", + "url": "https://github.com/MaffooClock", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/MaffooClock/ESP32RotaryEncoder" + }, + "headers": "ESP32RotaryEncoder.h", + "examples": [ + { + "name": "Basic Example", + "base": "examples/BasicRotaryEncoder", + "files": [ "BasicRotaryEncoder.ino" ] }, - "headers": "ESP32RotaryEncoder.h", - "examples": [ - { - "name": "Basic Example", - "base": "examples/BasicRotaryEncoder", - "files": [ "BasicRotaryEncoder.ino" ] - }, - { - "name": "Two Rotary Encoders", - "base": "examples/TwoRotaryEncoders", - "files": [ "TwoRotaryEncoders.ino" ] - } - ], - "frameworks": "arduino", - "platforms": "espressif32", - "license": "MIT" -} \ No newline at end of file + { + "name": "Two Rotary Encoders", + "base": "examples/TwoRotaryEncoders", + "files": [ "TwoRotaryEncoders.ino" ] + } + ], + "frameworks": "arduino", + "platforms": "espressif32", + "license": "MIT" +} diff --git a/src/ESP32RotaryEncoder.cpp b/src/ESP32RotaryEncoder.cpp index f12accd..98b1331 100644 --- a/src/ESP32RotaryEncoder.cpp +++ b/src/ESP32RotaryEncoder.cpp @@ -2,294 +2,294 @@ RotaryEncoder::RotaryEncoder( uint8_t encoderPinA, uint8_t encoderPinB, int8_t encoderPinButton, int8_t encoderPinVcc, uint8_t encoderSteps ) { - this->encoderPinA = encoderPinA; - this->encoderPinB = encoderPinB; - this->encoderPinButton = encoderPinButton; - this->encoderPinVcc = encoderPinVcc; - this->encoderTripPoint = encoderSteps - 1; + this->encoderPinA = encoderPinA; + this->encoderPinB = encoderPinB; + this->encoderPinButton = encoderPinButton; + this->encoderPinVcc = encoderPinVcc; + this->encoderTripPoint = encoderSteps - 1; } RotaryEncoder::~RotaryEncoder() { - detachInterrupts(); + detachInterrupts(); - esp_timer_stop( loopTimer ); - esp_timer_delete( loopTimer ); + esp_timer_stop( loopTimer ); + esp_timer_delete( loopTimer ); } void RotaryEncoder::setEncoderType( EncoderType type ) { - switch( type ) - { - case FLOATING: - encoderPinMode = INPUT_PULLUP; - buttonPinMode = INPUT_PULLUP; - break; - - case HAS_PULLUP: - encoderPinMode = INPUT; - buttonPinMode = INPUT; - break; - - case SW_FLOAT: - encoderPinMode = INPUT; - buttonPinMode = INPUT_PULLUP; - break; - } + switch( type ) + { + case FLOATING: + encoderPinMode = INPUT_PULLUP; + buttonPinMode = INPUT_PULLUP; + break; + + case HAS_PULLUP: + encoderPinMode = INPUT; + buttonPinMode = INPUT; + break; + + case SW_FLOAT: + encoderPinMode = INPUT; + buttonPinMode = INPUT_PULLUP; + break; + } } void RotaryEncoder::setBoundaries( long minEncoderValue, long maxEncoderValue, bool circleValues ) { - this->minEncoderValue = minEncoderValue; - this->maxEncoderValue = maxEncoderValue; + this->minEncoderValue = minEncoderValue; + this->maxEncoderValue = maxEncoderValue; - this->circleValues = circleValues; + this->circleValues = circleValues; } void RotaryEncoder::onTurned( EncoderCallback f ) { - callbackEncoderChanged = f; + callbackEncoderChanged = f; } void RotaryEncoder::onPressed( ButtonCallback f ) { - callbackButtonPressed = f; + callbackButtonPressed = f; } void RotaryEncoder::beginLoopTimer() { - /** - * We're using esp_timer.h from ESP32 SDK rather than esp32-hal-timer.h from Arduino API - * because the `timerAttachInterrupt()` won't accept std::bind like `attachInterrupt()` in - * FunctionalInterrupt will. We have a static method that `timerAttachInterrupt()` will take, - * but we'd lose instance context. But the `esp_timer_create_args_t` will let us set a callback - * argument, which we set to `this`, so that the static method maintains instance context. - * - * As of 29 September 2023, there is an open issue to allow `std::function` in esp32-hal-timer: - * https://github.com/espressif/arduino-esp32/issues/8427 - * - * ...for now (and maybe forever?), we'll do it this way. - */ - - esp_timer_create_args_t _timerConfig; - _timerConfig.arg = this; - _timerConfig.callback = reinterpret_cast( timerCallback ); - _timerConfig.dispatch_method = ESP_TIMER_TASK; - _timerConfig.skip_unhandled_events = true; - _timerConfig.name = PSTR("RotaryEncoder::loop_ISR"); - - esp_timer_create( &_timerConfig, &loopTimer ); - esp_timer_start_periodic( loopTimer, RE_LOOP_INTERVAL ); + /** + * We're using esp_timer.h from ESP32 SDK rather than esp32-hal-timer.h from Arduino API + * because the `timerAttachInterrupt()` won't accept std::bind like `attachInterrupt()` in + * FunctionalInterrupt will. We have a static method that `timerAttachInterrupt()` will take, + * but we'd lose instance context. But the `esp_timer_create_args_t` will let us set a callback + * argument, which we set to `this`, so that the static method maintains instance context. + * + * As of 29 September 2023, there is an open issue to allow `std::function` in esp32-hal-timer: + * https://github.com/espressif/arduino-esp32/issues/8427 + * + * ...for now (and maybe forever?), we'll do it this way. + */ + + esp_timer_create_args_t _timerConfig; + _timerConfig.arg = this; + _timerConfig.callback = reinterpret_cast( timerCallback ); + _timerConfig.dispatch_method = ESP_TIMER_TASK; + _timerConfig.skip_unhandled_events = true; + _timerConfig.name = PSTR("RotaryEncoder::loop_ISR"); + + esp_timer_create( &_timerConfig, &loopTimer ); + esp_timer_start_periodic( loopTimer, RE_LOOP_INTERVAL ); } void RotaryEncoder::attachInterrupts() { - #if defined( BOARD_HAS_PIN_REMAP ) && ( ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(3,0,0) ) - /** - * The io_pin_remap.h in Arduino-ESP32 cores of the 2.0.x family - * (since 2.0.10) define an `attachInterrupt()` macro that folds-in - * a call to `digitalPinToGPIONumber()`, but FunctionalInterrupt.cpp - * does this too, so we actually don't need the macro at all. - * Since 3.x the call inside the function was removed, so the wrapping - * macro is useful again. - */ - #undef attachInterrupt - #endif - - attachInterrupt( encoderPinA, std::bind( &RotaryEncoder::_encoder_ISR, this ), CHANGE ); - attachInterrupt( encoderPinB, std::bind( &RotaryEncoder::_encoder_ISR, this ), CHANGE ); - - if( encoderPinButton >= 0 ) - attachInterrupt( encoderPinButton, std::bind( &RotaryEncoder::_button_ISR, this ), RISING ); + #if defined( BOARD_HAS_PIN_REMAP ) && ( ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(3,0,0) ) + /** + * The io_pin_remap.h in Arduino-ESP32 cores of the 2.0.x family + * (since 2.0.10) define an `attachInterrupt()` macro that folds-in + * a call to `digitalPinToGPIONumber()`, but FunctionalInterrupt.cpp + * does this too, so we actually don't need the macro at all. + * Since 3.x the call inside the function was removed, so the wrapping + * macro is useful again. + */ + #undef attachInterrupt + #endif + + attachInterrupt( encoderPinA, std::bind( &RotaryEncoder::_encoder_ISR, this ), CHANGE ); + attachInterrupt( encoderPinB, std::bind( &RotaryEncoder::_encoder_ISR, this ), CHANGE ); + + if( encoderPinButton >= 0 ) + attachInterrupt( encoderPinButton, std::bind( &RotaryEncoder::_button_ISR, this ), RISING ); } void RotaryEncoder::detachInterrupts() { - detachInterrupt( encoderPinA ); - detachInterrupt( encoderPinB ); - detachInterrupt( encoderPinButton ); + detachInterrupt( encoderPinA ); + detachInterrupt( encoderPinB ); + detachInterrupt( encoderPinButton ); } void RotaryEncoder::begin( bool useTimer ) { - resetEncoderValue(); + resetEncoderValue(); - encoderChangedFlag = false; - buttonPressedFlag = false; + encoderChangedFlag = false; + buttonPressedFlag = false; - pinMode( encoderPinA, encoderPinMode ); - pinMode( encoderPinB, encoderPinMode ); + pinMode( encoderPinA, encoderPinMode ); + pinMode( encoderPinB, encoderPinMode ); - if( encoderPinButton > RE_DEFAULT_PIN ) - pinMode( encoderPinButton, buttonPinMode ); + if( encoderPinButton > RE_DEFAULT_PIN ) + pinMode( encoderPinButton, buttonPinMode ); - if( encoderPinVcc > RE_DEFAULT_PIN ) - { - pinMode( encoderPinVcc, OUTPUT ); - digitalWrite( encoderPinVcc, HIGH ); - } + if( encoderPinVcc > RE_DEFAULT_PIN ) + { + pinMode( encoderPinVcc, OUTPUT ); + digitalWrite( encoderPinVcc, HIGH ); + } - delay( 20 ); - attachInterrupts(); + delay( 20 ); + attachInterrupts(); - if( useTimer ) - beginLoopTimer(); + if( useTimer ) + beginLoopTimer(); } bool RotaryEncoder::isEnabled() { - return _isEnabled; + return _isEnabled; } void RotaryEncoder::enable() { - if( _isEnabled ) - return; + if( _isEnabled ) + return; - attachInterrupts(); + attachInterrupts(); - _isEnabled = true; + _isEnabled = true; } void RotaryEncoder::disable() { - if( !_isEnabled ) - return; + if( !_isEnabled ) + return; - detachInterrupts(); + detachInterrupts(); - _isEnabled = false; + _isEnabled = false; } bool RotaryEncoder::buttonPressed() { - if( !_isEnabled ) - return false; + if( !_isEnabled ) + return false; - bool wasPressed = buttonPressedFlag; + bool wasPressed = buttonPressedFlag; - buttonPressedFlag = false; + buttonPressedFlag = false; - return wasPressed; + return wasPressed; } bool RotaryEncoder::encoderChanged() { - if( !_isEnabled ) - return false; + if( !_isEnabled ) + return false; - bool hasChanged = encoderChangedFlag; + bool hasChanged = encoderChangedFlag; - encoderChangedFlag = false; + encoderChangedFlag = false; - return hasChanged; + return hasChanged; } long RotaryEncoder::getEncoderValue() { - constrainValue(); + constrainValue(); - return currentValue; + return currentValue; } void RotaryEncoder::constrainValue() { - if( currentValue < minEncoderValue ) - currentValue = circleValues ? maxEncoderValue : minEncoderValue; + if( currentValue < minEncoderValue ) + currentValue = circleValues ? maxEncoderValue : minEncoderValue; - else if( currentValue > maxEncoderValue ) - currentValue = circleValues ? minEncoderValue : maxEncoderValue; + else if( currentValue > maxEncoderValue ) + currentValue = circleValues ? minEncoderValue : maxEncoderValue; } void RotaryEncoder::setEncoderValue( long newValue ) { - currentValue = newValue; + currentValue = newValue; - constrainValue(); + constrainValue(); } void ARDUINO_ISR_ATTR RotaryEncoder::loop() { - if( callbackEncoderChanged != NULL && encoderChanged() ) - callbackEncoderChanged( getEncoderValue() ); + if( callbackEncoderChanged != NULL && encoderChanged() ) + callbackEncoderChanged( getEncoderValue() ); - if( callbackButtonPressed != NULL && buttonPressed() ) - callbackButtonPressed(); + if( callbackButtonPressed != NULL && buttonPressed() ) + callbackButtonPressed(); } void ARDUINO_ISR_ATTR RotaryEncoder::_button_ISR() { - static unsigned long _lastInterruptTime = 0; + static unsigned long _lastInterruptTime = 0; - if( ( millis() - _lastInterruptTime ) < 30 ) - return; + if( ( millis() - _lastInterruptTime ) < 30 ) + return; - buttonPressedFlag = true; + buttonPressedFlag = true; - _lastInterruptTime = millis(); + _lastInterruptTime = millis(); } void ARDUINO_ISR_ATTR RotaryEncoder::_encoder_ISR() { - /** - * Almost all of this came from a blog post by Garry on GarrysBlog.com: - * https://garrysblog.com/2021/03/20/reliably-debouncing-rotary-encoders-with-arduino-and-esp32/ - * - * Read more about how this works here: - * https://www.best-microcontroller-projects.com/rotary-encoder.html - */ - - static uint8_t _previousAB = 3; - static int8_t _encoderPosition = 0; - static unsigned long _lastInterruptTime = 0; - - unsigned long _interruptTime = millis(); - - bool valueChanged = false; - - _previousAB <<=2; // Remember previous state - - if( digitalRead( encoderPinA ) ) _previousAB |= 0x02; // Add current state of pin A - if( digitalRead( encoderPinB ) ) _previousAB |= 0x01; // Add current state of pin B - - _encoderPosition += encoderStates[( _previousAB & 0x0f )]; - - // Update counter if encoder has rotated a full detent - // For the following comments, we'll assume it's 4 steps per detent - // The tripping point is `STEPS - 1` (so, 3 in this example) - - if( _encoderPosition > encoderTripPoint ) // Four steps forward - { - if( _interruptTime - _lastInterruptTime > 40 ) // Greater than 40 milliseconds - this->currentValue++; // Increase by 1 - - else if( _interruptTime - _lastInterruptTime > 20 ) // Greater than 20 milliseconds - this->currentValue += 3; // Increase by 3 - - else // Faster than 20 milliseconds - this->currentValue += 10; // Increase by 10 - - valueChanged = true; - } - else if( _encoderPosition < -encoderTripPoint ) // Four steps backwards - { - if( _interruptTime - _lastInterruptTime > 40 ) // Greater than 40 milliseconds - this->currentValue--; // Increase by 1 - - else if( _interruptTime - _lastInterruptTime > 20 ) // Greater than 20 milliseconds - this->currentValue -= 3; // Increase by 3 - - else // Faster than 20 milliseconds - this->currentValue -= 10; // Increase by 10 - - valueChanged = true; - } - - if( valueChanged ) - { - encoderChangedFlag = true; - - _encoderPosition = 0; - _lastInterruptTime = millis(); // Remember time - } -} \ No newline at end of file + /** + * Almost all of this came from a blog post by Garry on GarrysBlog.com: + * https://garrysblog.com/2021/03/20/reliably-debouncing-rotary-encoders-with-arduino-and-esp32/ + * + * Read more about how this works here: + * https://www.best-microcontroller-projects.com/rotary-encoder.html + */ + + static uint8_t _previousAB = 3; + static int8_t _encoderPosition = 0; + static unsigned long _lastInterruptTime = 0; + + unsigned long _interruptTime = millis(); + + bool valueChanged = false; + + _previousAB <<=2; // Remember previous state + + if( digitalRead( encoderPinA ) ) _previousAB |= 0x02; // Add current state of pin A + if( digitalRead( encoderPinB ) ) _previousAB |= 0x01; // Add current state of pin B + + _encoderPosition += encoderStates[( _previousAB & 0x0f )]; + + // Update counter if encoder has rotated a full detent + // For the following comments, we'll assume it's 4 steps per detent + // The tripping point is `STEPS - 1` (so, 3 in this example) + + if( _encoderPosition > encoderTripPoint ) // Four steps forward + { + if( _interruptTime - _lastInterruptTime > 40 ) // Greater than 40 milliseconds + this->currentValue++; // Increase by 1 + + else if( _interruptTime - _lastInterruptTime > 20 ) // Greater than 20 milliseconds + this->currentValue += 3; // Increase by 3 + + else // Faster than 20 milliseconds + this->currentValue += 10; // Increase by 10 + + valueChanged = true; + } + else if( _encoderPosition < -encoderTripPoint ) // Four steps backwards + { + if( _interruptTime - _lastInterruptTime > 40 ) // Greater than 40 milliseconds + this->currentValue--; // Increase by 1 + + else if( _interruptTime - _lastInterruptTime > 20 ) // Greater than 20 milliseconds + this->currentValue -= 3; // Increase by 3 + + else // Faster than 20 milliseconds + this->currentValue -= 10; // Increase by 10 + + valueChanged = true; + } + + if( valueChanged ) + { + encoderChangedFlag = true; + + _encoderPosition = 0; + _lastInterruptTime = millis(); // Remember time + } +} diff --git a/src/ESP32RotaryEncoder.h b/src/ESP32RotaryEncoder.h index 4a875ca..471e2cc 100644 --- a/src/ESP32RotaryEncoder.h +++ b/src/ESP32RotaryEncoder.h @@ -2,37 +2,38 @@ #define _RotaryEncoder_h #if defined( ARDUINO ) && ARDUINO >= 100 - #include + #include #elif defined( WIRING ) - #include + #include #else - #include - #include + #include + #include + #endif #if defined( ESP32 ) - #define RE_ISR_ATTR IRAM_ATTR - - #ifdef ARDUINO_ISR_ATTR - #undef ARDUINO_ISR_ATTR - #define ARDUINO_ISR_ATTR IRAM_ATTR - #endif - - #if defined( ESP_ARDUINO_VERSION ) && ( ESP_ARDUINO_VERSION == ESP_ARDUINO_VERSION_VAL(2,0,10) ) - /** - * BUG ALERT! - * - * With Arduino-ESP32 core 2.0.10, the #include statement below - * fails to compile due to a bug. - * Also see `attachInterrupts()` in ESP32RotaryEncoder.cpp for - * the note about the `attachInterrupt()` macro in 2.x cores. - */ - #error Please upgrade the Arduino-ESP32 core to use this library. - #else - #include - #endif + #define RE_ISR_ATTR IRAM_ATTR + + #ifdef ARDUINO_ISR_ATTR + #undef ARDUINO_ISR_ATTR + #define ARDUINO_ISR_ATTR IRAM_ATTR + #endif + + #if defined( ESP_ARDUINO_VERSION ) && ( ESP_ARDUINO_VERSION == ESP_ARDUINO_VERSION_VAL(2,0,10) ) + /** + * BUG ALERT! + * + * With Arduino-ESP32 core 2.0.10, the #include statement below + * fails to compile due to a bug. + * Also see `attachInterrupts()` in ESP32RotaryEncoder.cpp for + * the note about the `attachInterrupt()` macro in 2.x cores. + */ + #error Please upgrade the Arduino-ESP32 core to use this library. + #else + #include + #endif #endif #define RE_DEFAULT_PIN -1 @@ -40,321 +41,321 @@ #define RE_LOOP_INTERVAL 100000U // 0.1 seconds typedef enum { - FLOATING, - HAS_PULLUP, - SW_FLOAT + FLOATING, + HAS_PULLUP, + SW_FLOAT } EncoderType; class RotaryEncoder { - protected: - - #if defined( ESP32 ) - typedef std::function EncoderCallback; - typedef std::function ButtonCallback; - #else - typedef void (*EncoderCallback)(long); - typedef void (*ButtonCallback)(); - #endif - - - public: - - /** - * @brief Construct a new Rotary Encoder instance - * - * @param encoderPinA The A pin on the encoder, sometimes marked "CLK" - * @param encoderPinB The B pin on the encoder, sometimes marked "DT" - * @param encoderPinButton The pushbutton pin, could be marked "SW" - * @param encoderPinVcc Optional; the voltage reference input, could be marked "+" or "V+" or "VCC"; defaults to -1, which is ignored - * @param encoderSteps Optional; the number of steps per detent; usually 4 (default), could be 2 - */ - RotaryEncoder( - uint8_t encoderPinA, - uint8_t encoderPinB, - int8_t encoderPinButton = RE_DEFAULT_PIN, - int8_t encoderPinVcc = RE_DEFAULT_PIN, - uint8_t encoderSteps = RE_DEFAULT_STEPS - ); - - /** - * @brief Responsible for detaching interrupts and clearing the loop timer - * - */ - ~RotaryEncoder(); - - /** - * @brief Specifies whether the encoder pins need to use the internal pull-up resistors. - * - * @note Call this in `setup()`. - * - * @param type FLOATING if you're using a raw encoder not mounted to a PCB (internal pull-ups will be used); - * HAS_PULLUP if your encoder is a module that has pull-up resistors, (internal pull-ups will not be used); - * SW_FLOAT your encoder is a module that has pull-up resistors, but the resistor for the switch is missing (internal pull-up will be used for switch input only) - */ - void setEncoderType( EncoderType type ); - - - /** - * @brief Set the minimum and maximum values that the encoder will return. - * - * @note Call this in `setup()` - * - * @param minValue Minimum value (e.g. 0) - * @param maxValue Maximum value (e.g. 10) - * @param circleValues If true, turning past the maximum will wrap around to the minimum and vice-versa - * If false (default), turning past the minimum or maximum will return that boundary - */ - void setBoundaries( long minValue, long maxValue, bool circleValues = false ); - - /** - * @brief Set a function to fire every time the value tracked by the encoder changes. - * - * @note Call this in `setup()`. May be set/changed at runtime if needed. - * - * @param handler The function to call; it must accept one parameter of type long, which will be the current value - */ - void onTurned( EncoderCallback f ); - - /** - * @brief Set a function to fire every time the the pushbutton is pressed. - * - * @note Call this in `setup()`. May be set/changed at runtime if needed. - * - * @param handler The function to call - */ - void onPressed( ButtonCallback f ); - - /** - * @brief Sets up the GPIO pins specified in the constructor and attaches the ISR callback for the encoder. - * - * @note Call this in `setup()` after other "set" methods. - * - */ - void begin( bool useTimer = true ); - - /** - * @brief Enables the encoder knob and pushbutton if `disable()` was previously used. - * - */ - void enable(); - - /** - * @brief Disables the encoder knob and pushbutton. - * - * Knob rotation and button presses will have no effect until after `enable()` is called - * - */ - void disable(); - - /** - * @brief Confirms whether the encoder knob and pushbutton have been disabled. - * - */ - bool isEnabled(); - - /** - * @brief Check if the pushbutton has been pressed. - * - * @note Call this in `loop()` to fire a handler. - * - * @return true if the button was pressed since the last time it was checked, - * false if the button has not been pressed since the last time it was checked - */ - bool buttonPressed(); - - /** - * @brief Check if the value tracked by the encoder has changed. - * - * @note Call this in `loop()` to fire a handler for the new value. - * - * @return true if the value is different than the last time it was checked, - * false if the value is the same as the last time it was checked - */ - bool encoderChanged(); - - /** - * @brief Get the current value tracked by the encoder. - * - * @return A value between the minimum and maximum configured by `setBoundaries()` - */ - long getEncoderValue(); - - /** - * @brief Override the value tracked by the encoder. - * - * @note If the new value is outside the minimum or maximum configured - * by `setBoundaries()`, it will be adjusted accordingly - * - * @param newValue - */ - void setEncoderValue( long newValue ); - - /** - * @brief Reset the value tracked by the encoder. - * - * @note This will try to set the value to 0, but if the minimum and maximum configured - * by `setBoundaries()` does not include 0, then the minimum or maximum will be - * used instead - * - */ - void resetEncoderValue() { setEncoderValue( 0 ); } - - /** - * @brief Synchronizes the encoder value and button state from ISRs. - * - * Runs on a timer and calls `encoderChanged()` and `buttonPressed()` to determine - * if user-specified callbacks should be run. - * - * This would normally be called in userspace `loop()`, but we're using the `loopTimer` instead. - * - */ - void ARDUINO_ISR_ATTR loop(); - - private: - - EncoderCallback callbackEncoderChanged = NULL; - ButtonCallback callbackButtonPressed = NULL; - - typedef enum { - LEFT = -1, - STILL = 0, - RIGHT = 1 - } Rotation; - - Rotation encoderStates[16] = { - STILL, LEFT, RIGHT, STILL, - RIGHT, STILL, STILL, LEFT, - LEFT, STILL, STILL, RIGHT, - STILL, RIGHT, LEFT, STILL - }; - - int encoderPinMode = INPUT; - int buttonPinMode = INPUT; - - uint8_t encoderPinA; - uint8_t encoderPinB; - int8_t encoderPinButton; - int8_t encoderPinVcc; - uint8_t encoderTripPoint; - - /** - * @brief Determines whether knob turns or button presses will be ignored. ISRs still fire, - * - * Set by `enable()` and `disable()`. - * - */ - bool _isEnabled = true; - - /** - * @brief Sets the minimum and maximum values of `currentValue`. - * - * Set in `setBoundaries()` and used in `constrainValue()`. - * - */ - long minEncoderValue = -1; long maxEncoderValue = 1; - - /** - * @brief Determines whether attempts to increment or decrement beyond - * the boundaries causes `currentValue` to wrap to the other boundary. - * - * Set in `setBoundaries()`. - * - */ - bool circleValues = false; - - /** - * @brief The value tracked by `encoder_ISR()` when the encoder knob is turned. - * - * This value can be overwritten in `constrainValue()` whenever - * `getEncoderValue()` or `setEncoderValue()` are called. - * - */ - volatile long currentValue; - - /** - * @brief Becomes `true` when `encoder_ISR()` changes `currentValue`, - * then becomes `false` when caught by `loop()` via `encoderChanged()` - * - */ - volatile bool encoderChangedFlag; - - /** - * @brief Becomes `true` when `button_ISR()` changes `currentValue`, - * then becomes `false` when caught by `loop()` via `buttonPressed()` - * - */ - volatile bool buttonPressedFlag; - - /** - * @brief The loop timer configured and started in `beginLoopTimer()`. - * - * This replaces the need to run the class loop in userspace `loop()`. - * - */ - esp_timer_handle_t loopTimer; - - /** - * @brief Constrains the value set by `encoder_ISR()` and `setEncoderValue()` - * to be in the range set by `setBoundaries()`. - * - */ - void constrainValue(); - - /** - * @brief Attaches ISRs to encoder and button pins. - * - * Used in `begin()` and `enable()`. - * - */ - void attachInterrupts(); - - /** - * @brief Detaches ISRs from encoder and button pins. - * - * Used in the destructor and in `disable()`. - * - */ - void detachInterrupts(); - - /** - * @brief Sets up the loop timer and starts it. - * - * Called in `begin()`. - * - */ - void beginLoopTimer(); - - /** - * @brief Static method called by the loop timer, which calls the loop function on a given instance. - * - * We need this because the timer cannot call a class method directly, but it can - * call a static method with an argument, so this gets around that limitation. - * - * @param arg - */ - static void ARDUINO_ISR_ATTR timerCallback( void *arg ) - { - RotaryEncoder *instance = (RotaryEncoder *)arg; - instance->loop(); - } - - /** - * @brief Interrupt Service Routine for the encoder. - * - * Detects direction of knob turn and increments/decrements `currentValue`, and sets - * the `encoderChangedFlag` to be picked up by `encoderChanged()` in `_loop()`. - * - */ - void ARDUINO_ISR_ATTR _encoder_ISR(); - - /** - * @brief Interrupt Service Routine for the pushbutton. - * - * Sets the `buttonPressedFlag` to be picked up by `buttonPressed()` in `_loop()`. - * - */ - void ARDUINO_ISR_ATTR _button_ISR(); + protected: + + #if defined( ESP32 ) + typedef std::function EncoderCallback; + typedef std::function ButtonCallback; + #else + typedef void (*EncoderCallback)(long); + typedef void (*ButtonCallback)(); + #endif + + + public: + + /** + * @brief Construct a new Rotary Encoder instance + * + * @param encoderPinA The A pin on the encoder, sometimes marked "CLK" + * @param encoderPinB The B pin on the encoder, sometimes marked "DT" + * @param encoderPinButton Optional; the pushbutton pin, could be marked "SW" + * @param encoderPinVcc Optional; the voltage reference input, could be marked "+" or "V+" or "VCC"; defaults to -1, which is ignored + * @param encoderSteps Optional; the number of steps per detent; usually 4 (default), could be 2 + */ + RotaryEncoder( + uint8_t encoderPinA, + uint8_t encoderPinB, + int8_t encoderPinButton = RE_DEFAULT_PIN, + int8_t encoderPinVcc = RE_DEFAULT_PIN, + uint8_t encoderSteps = RE_DEFAULT_STEPS + ); + + /** + * @brief Responsible for detaching interrupts and clearing the loop timer + * + */ + ~RotaryEncoder(); + + /** + * @brief Specifies whether the encoder pins need to use the internal pull-up resistors. + * + * @note Call this in `setup()`. + * + * @param type FLOATING if you're using a raw encoder not mounted to a PCB (internal pull-ups will be used); + * HAS_PULLUP if your encoder is a module that has pull-up resistors, (internal pull-ups will not be used); + * SW_FLOAT your encoder is a module that has pull-up resistors, but the resistor for the switch is missing (internal pull-up will be used for switch input only) + */ + void setEncoderType( EncoderType type ); + + + /** + * @brief Set the minimum and maximum values that the encoder will return. + * + * @note Call this in `setup()` + * + * @param minValue Minimum value (e.g. 0) + * @param maxValue Maximum value (e.g. 10) + * @param circleValues If true, turning past the maximum will wrap around to the minimum and vice-versa + * If false (default), turning past the minimum or maximum will return that boundary + */ + void setBoundaries( long minValue, long maxValue, bool circleValues = false ); + + /** + * @brief Set a function to fire every time the value tracked by the encoder changes. + * + * @note Call this in `setup()`. May be set/changed at runtime if needed. + * + * @param handler The function to call; it must accept one parameter of type long, which will be the current value + */ + void onTurned( EncoderCallback f ); + + /** + * @brief Set a function to fire every time the the pushbutton is pressed. + * + * @note Call this in `setup()`. May be set/changed at runtime if needed. + * + * @param handler The function to call + */ + void onPressed( ButtonCallback f ); + + /** + * @brief Sets up the GPIO pins specified in the constructor and attaches the ISR callback for the encoder. + * + * @note Call this in `setup()` after other "set" methods. + * + */ + void begin( bool useTimer = true ); + + /** + * @brief Enables the encoder knob and pushbutton if `disable()` was previously used. + * + */ + void enable(); + + /** + * @brief Disables the encoder knob and pushbutton. + * + * Knob rotation and button presses will have no effect until after `enable()` is called + * + */ + void disable(); + + /** + * @brief Confirms whether the encoder knob and pushbutton have been disabled. + * + */ + bool isEnabled(); + + /** + * @brief Check if the pushbutton has been pressed. + * + * @note Call this in `loop()` to fire a handler. + * + * @return true if the button was pressed since the last time it was checked, + * false if the button has not been pressed since the last time it was checked + */ + bool buttonPressed(); + + /** + * @brief Check if the value tracked by the encoder has changed. + * + * @note Call this in `loop()` to fire a handler for the new value. + * + * @return true if the value is different than the last time it was checked, + * false if the value is the same as the last time it was checked + */ + bool encoderChanged(); + + /** + * @brief Get the current value tracked by the encoder. + * + * @return A value between the minimum and maximum configured by `setBoundaries()` + */ + long getEncoderValue(); + + /** + * @brief Override the value tracked by the encoder. + * + * @note If the new value is outside the minimum or maximum configured + * by `setBoundaries()`, it will be adjusted accordingly + * + * @param newValue + */ + void setEncoderValue( long newValue ); + + /** + * @brief Reset the value tracked by the encoder. + * + * @note This will try to set the value to 0, but if the minimum and maximum configured + * by `setBoundaries()` does not include 0, then the minimum or maximum will be + * used instead + * + */ + void resetEncoderValue() { setEncoderValue( 0 ); } + + /** + * @brief Synchronizes the encoder value and button state from ISRs. + * + * Runs on a timer and calls `encoderChanged()` and `buttonPressed()` to determine + * if user-specified callbacks should be run. + * + * This would normally be called in userspace `loop()`, but we're using the `loopTimer` instead. + * + */ + void ARDUINO_ISR_ATTR loop(); + + private: + + EncoderCallback callbackEncoderChanged = NULL; + ButtonCallback callbackButtonPressed = NULL; + + typedef enum { + LEFT = -1, + STILL = 0, + RIGHT = 1 + } Rotation; + + Rotation encoderStates[16] = { + STILL, LEFT, RIGHT, STILL, + RIGHT, STILL, STILL, LEFT, + LEFT, STILL, STILL, RIGHT, + STILL, RIGHT, LEFT, STILL + }; + + int encoderPinMode = INPUT; + int buttonPinMode = INPUT; + + uint8_t encoderPinA; + uint8_t encoderPinB; + int8_t encoderPinButton; + int8_t encoderPinVcc; + uint8_t encoderTripPoint; + + /** + * @brief Determines whether knob turns or button presses will be ignored. ISRs still fire, + * + * Set by `enable()` and `disable()`. + * + */ + bool _isEnabled = true; + + /** + * @brief Sets the minimum and maximum values of `currentValue`. + * + * Set in `setBoundaries()` and used in `constrainValue()`. + * + */ + long minEncoderValue = -1; long maxEncoderValue = 1; + + /** + * @brief Determines whether attempts to increment or decrement beyond + * the boundaries causes `currentValue` to wrap to the other boundary. + * + * Set in `setBoundaries()`. + * + */ + bool circleValues = false; + + /** + * @brief The value tracked by `encoder_ISR()` when the encoder knob is turned. + * + * This value can be overwritten in `constrainValue()` whenever + * `getEncoderValue()` or `setEncoderValue()` are called. + * + */ + volatile long currentValue; + + /** + * @brief Becomes `true` when `encoder_ISR()` changes `currentValue`, + * then becomes `false` when caught by `loop()` via `encoderChanged()` + * + */ + volatile bool encoderChangedFlag; + + /** + * @brief Becomes `true` when `button_ISR()` changes `currentValue`, + * then becomes `false` when caught by `loop()` via `buttonPressed()` + * + */ + volatile bool buttonPressedFlag; + + /** + * @brief The loop timer configured and started in `beginLoopTimer()`. + * + * This replaces the need to run the class loop in userspace `loop()`. + * + */ + esp_timer_handle_t loopTimer; + + /** + * @brief Constrains the value set by `encoder_ISR()` and `setEncoderValue()` + * to be in the range set by `setBoundaries()`. + * + */ + void constrainValue(); + + /** + * @brief Attaches ISRs to encoder and button pins. + * + * Used in `begin()` and `enable()`. + * + */ + void attachInterrupts(); + + /** + * @brief Detaches ISRs from encoder and button pins. + * + * Used in the destructor and in `disable()`. + * + */ + void detachInterrupts(); + + /** + * @brief Sets up the loop timer and starts it. + * + * Called in `begin()`. + * + */ + void beginLoopTimer(); + + /** + * @brief Static method called by the loop timer, which calls the loop function on a given instance. + * + * We need this because the timer cannot call a class method directly, but it can + * call a static method with an argument, so this gets around that limitation. + * + * @param arg + */ + static void ARDUINO_ISR_ATTR timerCallback( void *arg ) + { + RotaryEncoder *instance = (RotaryEncoder *)arg; + instance->loop(); + } + + /** + * @brief Interrupt Service Routine for the encoder. + * + * Detects direction of knob turn and increments/decrements `currentValue`, and sets + * the `encoderChangedFlag` to be picked up by `encoderChanged()` in `_loop()`. + * + */ + void ARDUINO_ISR_ATTR _encoder_ISR(); + + /** + * @brief Interrupt Service Routine for the pushbutton. + * + * Sets the `buttonPressedFlag` to be picked up by `buttonPressed()` in `_loop()`. + * + */ + void ARDUINO_ISR_ATTR _button_ISR(); }; -#endif \ No newline at end of file +#endif