From 0c1a9c584cf1e1eb039456b7d24801e305a114f5 Mon Sep 17 00:00:00 2001 From: Chris Leishman Date: Mon, 13 Oct 2025 12:46:31 -0700 Subject: [PATCH 1/2] Move constants and types into class scope Move constants and types into class scope. Also move mux into private scope. This is an API breaking change. Resolves #20. --- README.md | 2 +- .../BasicRotaryEncoder/BasicRotaryEncoder.ino | 10 ++-- .../ButtonPressDuration.ino | 10 ++-- examples/LeftOrRight/LeftOrRight.ino | 6 +-- .../TwoRotaryEncoders/TwoRotaryEncoders.ino | 18 +++---- src/ESP32RotaryEncoder.cpp | 14 ++--- src/ESP32RotaryEncoder.h | 52 ++++++------------- 7 files changed, 47 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 3164b27..e9a9f3a 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Adding a rotary encoder instance is easy: Serial.begin( 115200 ); // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( EncoderType::HAS_PULLUP ); + rotaryEncoder.setEncoderType( RotaryEncoder::Type::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 diff --git a/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino b/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino index 1a8bfed..b45b8df 100644 --- a/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino +++ b/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino @@ -1,13 +1,13 @@ /** * ESP32RotaryEncoder: BasicRotaryEncoder.ino - * + * * This is a basic example of how to instantiate a single Rotary Encoder. - * + * * Turning the knob will increment/decrement a value between 1 and 10 and * print it to the serial console. - * + * * Pressing the button will output "boop!" to the serial console. - * + * * Created 3 October 2023 * Updated 1 November 2023 * By Matthew Clark @@ -42,7 +42,7 @@ void setup() Serial.begin( 115200 ); // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( EncoderType::HAS_PULLUP ); + rotaryEncoder.setEncoderType( RotaryEncoder::Type::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 diff --git a/examples/ButtonPressDuration/ButtonPressDuration.ino b/examples/ButtonPressDuration/ButtonPressDuration.ino index cb1ed0e..6c85b73 100644 --- a/examples/ButtonPressDuration/ButtonPressDuration.ino +++ b/examples/ButtonPressDuration/ButtonPressDuration.ino @@ -1,14 +1,14 @@ /** * ESP32RotaryEncoder: ButtonPressDuration.ino - * + * * This example shows how to handle long button-presses differently * from long button-presses - * + * * Turning the knob will increment/decrement a value between 1 and 10 and * print it to the serial console. - * + * * Pressing the button will output "boop!" to the serial console. - * + * * Created 1 November 2023 * By Matthew Clark */ @@ -63,7 +63,7 @@ void setup() Serial.begin( 115200 ); // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( EncoderType::HAS_PULLUP ); + rotaryEncoder.setEncoderType( RotaryEncoder::Type::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 diff --git a/examples/LeftOrRight/LeftOrRight.ino b/examples/LeftOrRight/LeftOrRight.ino index fb7d370..aa7f08d 100644 --- a/examples/LeftOrRight/LeftOrRight.ino +++ b/examples/LeftOrRight/LeftOrRight.ino @@ -1,9 +1,9 @@ /** * ESP32RotaryEncoder: LeftOrRight.ino - * + * * This is a simple example of how to track whether the knob was * turned left or right instead of tracking a numeric value - * + * * Created 1 November 2023 * By Matthew Clark */ @@ -83,7 +83,7 @@ void setup() Serial.begin( 115200 ); // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( EncoderType::HAS_PULLUP ); + rotaryEncoder.setEncoderType( RotaryEncoder::Type::HAS_PULLUP ); // The encoder will only return -1, 0, or 1, and will not wrap around. rotaryEncoder.setBoundaries( -1, 1, false ); diff --git a/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino b/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino index 1e87400..48b14f9 100644 --- a/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino +++ b/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino @@ -1,23 +1,23 @@ /** * ESP32RotaryEncoder: TwoRotaryEncoders.ino - * + * * This is a basic example of how to instantiate two distinct Rotary Encoders. - * + * * Rotary Encoder #1: * - Turning the knob will increment/decrement a value between 1 and 10, * and print it to the serial console. - * + * * - Pressing the button will enable/disable Rotary Encoder #2. - * + * * Rotary Encoder #2: * - Turning the knob will increment/decrement a value between -100 and 100, * and print it to the serial console. - * + * * - Pressing the button will enable/disable Rotary Encoder #1. - * + * * While a rotary encoder is disabled, turning the knob or pressing the button * will have no effect. - * + * * Created 3 October 2023 * Updated 1 November 2023 * By Matthew Clark @@ -82,7 +82,7 @@ void button2ToggleRE1( unsigned long duration ) void setup_RE1() { // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder1.setEncoderType( EncoderType::HAS_PULLUP ); + rotaryEncoder1.setEncoderType( RotaryEncoder::Type::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 @@ -106,7 +106,7 @@ void setup_RE2() { // This tells the library that the encoder does not have its own pull-up // resistors, so the internal pull-up resistors will be enabled - rotaryEncoder2.setEncoderType( EncoderType::FLOATING ); + rotaryEncoder2.setEncoderType( RotaryEncoder::Type::FLOATING ); // 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 diff --git a/src/ESP32RotaryEncoder.cpp b/src/ESP32RotaryEncoder.cpp index 1987864..9b34de1 100644 --- a/src/ESP32RotaryEncoder.cpp +++ b/src/ESP32RotaryEncoder.cpp @@ -39,7 +39,7 @@ RotaryEncoder::~RotaryEncoder() esp_timer_delete( loopTimer ); } -void RotaryEncoder::setEncoderType( EncoderType type ) +void RotaryEncoder::setEncoderType( Type type ) { switch( type ) { @@ -127,12 +127,6 @@ void RotaryEncoder::onPressed( ButtonCallback f ) callbackButtonPressed = f; } -static void timerCallback( void *arg ) -{ - RotaryEncoder *instance = (RotaryEncoder *)arg; - instance->loop(); -} - void RotaryEncoder::beginLoopTimer() { /** @@ -159,6 +153,12 @@ void RotaryEncoder::beginLoopTimer() esp_timer_start_periodic( loopTimer, RE_LOOP_INTERVAL ); } +void RotaryEncoder::timerCallback( void *self ) +{ + RotaryEncoder *instance = (RotaryEncoder *)self; + instance->loop(); +} + void RotaryEncoder::attachInterrupts() { #if defined( BOARD_HAS_PIN_REMAP ) && ( ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(3,0,0) ) diff --git a/src/ESP32RotaryEncoder.h b/src/ESP32RotaryEncoder.h index 4ad62dc..8017742 100644 --- a/src/ESP32RotaryEncoder.h +++ b/src/ESP32RotaryEncoder.h @@ -15,20 +15,17 @@ #include -static constexpr int8_t RE_DEFAULT_PIN = -1; -static constexpr uint8_t RE_DEFAULT_STEPS = 4; -static constexpr uint64_t RE_LOOP_INTERVAL = 100000U; // 0.1 seconds - -typedef enum { - FLOATING, - HAS_PULLUP, - SW_FLOAT -} EncoderType; - class RotaryEncoder { + public: + static constexpr int8_t RE_DEFAULT_PIN = -1; + static constexpr uint8_t RE_DEFAULT_STEPS = 4; + static constexpr uint64_t RE_LOOP_INTERVAL = 100000U; // 0.1 seconds - protected: - mutable portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + typedef enum { + FLOATING, + HAS_PULLUP, + SW_FLOAT + } Type; #if defined( ESP32 ) typedef std::function EncoderCallback; @@ -38,9 +35,6 @@ class RotaryEncoder { typedef void (*ButtonCallback)(unsigned long); #endif - - public: - /** * @brief Construct a new Rotary Encoder instance * @@ -60,7 +54,6 @@ class RotaryEncoder { /** * @brief Responsible for detaching interrupts and clearing the loop timer - * */ ~RotaryEncoder(); @@ -73,7 +66,7 @@ class RotaryEncoder { * 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 ); + void setEncoderType( Type type ); /** * @brief Set the minimum and maximum values that the encoder will return. @@ -147,13 +140,11 @@ class RotaryEncoder { * @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(); @@ -161,7 +152,6 @@ class RotaryEncoder { * @brief Disables the encoder knob and pushbutton. * * Knob rotation and button presses will have no effect until after `enable()` is called - * */ void disable(); @@ -214,27 +204,12 @@ class RotaryEncoder { * @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 loop(); - private: const char *LOG_TAG = "ESP32RotaryEncoder"; - EncoderCallback callbackEncoderChanged = NULL; - ButtonCallback callbackButtonPressed = NULL; - typedef enum { LEFT = -1, STILL = 0, @@ -248,6 +223,11 @@ class RotaryEncoder { STILL, RIGHT, LEFT, STILL }; + mutable portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + + EncoderCallback callbackEncoderChanged = NULL; + ButtonCallback callbackButtonPressed = NULL; + int encoderPinMode = INPUT; int buttonPinMode = INPUT; @@ -279,6 +259,8 @@ class RotaryEncoder { esp_timer_handle_t loopTimer; void beginLoopTimer(); + static void timerCallback( void *self ); + void loop(); void attachInterrupts(); void detachInterrupts(); From acb7bca2beb43f48ca579d8f1c7317bdb1788e24 Mon Sep 17 00:00:00 2001 From: Chris Leishman Date: Tue, 14 Oct 2025 08:42:37 -0700 Subject: [PATCH 2/2] Replace `setEncoderType(...)` with individual methods --- README.md | 5 ++-- .../BasicRotaryEncoder/BasicRotaryEncoder.ino | 5 ++-- .../ButtonPressDuration.ino | 5 ++-- examples/LeftOrRight/LeftOrRight.ino | 5 ++-- .../TwoRotaryEncoders/TwoRotaryEncoders.ino | 11 ++++---- src/ESP32RotaryEncoder.cpp | 27 ------------------- src/ESP32RotaryEncoder.h | 27 ++++++++++++++----- 7 files changed, 39 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index e9a9f3a..1371a4e 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,9 @@ Adding a rotary encoder instance is easy: { Serial.begin( 115200 ); - // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( RotaryEncoder::Type::HAS_PULLUP ); + // Uncomment if your encoder does not have its own pull-up resistors + //rotaryEncoder.enableEncoderPinPullup(); + //rotaryEncoder.enableButtonPinPullup(); // 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 diff --git a/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino b/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino index b45b8df..b166cbf 100644 --- a/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino +++ b/examples/BasicRotaryEncoder/BasicRotaryEncoder.ino @@ -41,8 +41,9 @@ void setup() { Serial.begin( 115200 ); - // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( RotaryEncoder::Type::HAS_PULLUP ); + // Uncomment if your encoder does not have its own pull-up resistors + //rotaryEncoder.enableEncoderPinPullup(); + //rotaryEncoder.enableButtonPinPullup(); // 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 diff --git a/examples/ButtonPressDuration/ButtonPressDuration.ino b/examples/ButtonPressDuration/ButtonPressDuration.ino index 6c85b73..8561d34 100644 --- a/examples/ButtonPressDuration/ButtonPressDuration.ino +++ b/examples/ButtonPressDuration/ButtonPressDuration.ino @@ -62,8 +62,9 @@ void setup() { Serial.begin( 115200 ); - // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( RotaryEncoder::Type::HAS_PULLUP ); + // Uncomment if your encoder does not have its own pull-up resistors + //rotaryEncoder.enableEncoderPinPullup(); + //rotaryEncoder.enableButtonPinPullup(); // 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 diff --git a/examples/LeftOrRight/LeftOrRight.ino b/examples/LeftOrRight/LeftOrRight.ino index aa7f08d..b61de9e 100644 --- a/examples/LeftOrRight/LeftOrRight.ino +++ b/examples/LeftOrRight/LeftOrRight.ino @@ -82,8 +82,9 @@ void setup() { Serial.begin( 115200 ); - // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder.setEncoderType( RotaryEncoder::Type::HAS_PULLUP ); + // Uncomment if your encoder does not have its own pull-up resistors + //rotaryEncoder.enableEncoderPinPullup(); + //rotaryEncoder.enableButtonPinPullup(); // The encoder will only return -1, 0, or 1, and will not wrap around. rotaryEncoder.setBoundaries( -1, 1, false ); diff --git a/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino b/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino index 48b14f9..005fd7e 100644 --- a/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino +++ b/examples/TwoRotaryEncoders/TwoRotaryEncoders.ino @@ -81,8 +81,9 @@ void button2ToggleRE1( unsigned long duration ) void setup_RE1() { - // This tells the library that the encoder has its own pull-up resistors - rotaryEncoder1.setEncoderType( RotaryEncoder::Type::HAS_PULLUP ); + // Uncomment if your encoder does not have its own pull-up resistors + //rotaryEncoder.enableEncoderPinPullup(); + //rotaryEncoder.enableButtonPinPullup(); // 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 @@ -104,9 +105,9 @@ void setup_RE1() void setup_RE2() { - // This tells the library that the encoder does not have its own pull-up - // resistors, so the internal pull-up resistors will be enabled - rotaryEncoder2.setEncoderType( RotaryEncoder::Type::FLOATING ); + // Uncomment if your encoder does not have its own pull-up resistors + //rotaryEncoder.enableEncoderPinPullup(); + //rotaryEncoder.enableButtonPinPullup(); // 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 diff --git a/src/ESP32RotaryEncoder.cpp b/src/ESP32RotaryEncoder.cpp index 9b34de1..b2102ad 100644 --- a/src/ESP32RotaryEncoder.cpp +++ b/src/ESP32RotaryEncoder.cpp @@ -39,33 +39,6 @@ RotaryEncoder::~RotaryEncoder() esp_timer_delete( loopTimer ); } -void RotaryEncoder::setEncoderType( Type 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; - - default: - ESP_LOGE( LOG_TAG, "Invalid encoder type %i", type ); - return; - } - - ESP_LOGD( LOG_TAG, "Encoder type set to %i", type ); -} - void RotaryEncoder::setBoundaries( long minValue, long maxValue, bool circleValues ) { ESP_LOGD( LOG_TAG, "boundary minValue = %ld, maxValue = %ld, circular = %s", minValue, maxValue, ( circleValues ? "true" : "false" ) ); diff --git a/src/ESP32RotaryEncoder.h b/src/ESP32RotaryEncoder.h index 8017742..48d9128 100644 --- a/src/ESP32RotaryEncoder.h +++ b/src/ESP32RotaryEncoder.h @@ -58,15 +58,30 @@ class RotaryEncoder { ~RotaryEncoder(); /** - * @brief Specifies whether the encoder pins need to use the internal pull-up resistors. + * @brief Enable the internal pull-up resistor for the encoder pin. * - * @note Call this in `setup()`. + * By default, the encoder pin on the ESP32 is floating - which requires that + * the encoder module has its own pull-up resistors or external pull-ups are used. * - * @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) + * If the encoder module does not have its own pull-ups, calling this method + * will enable the internal pull-up in the ESP32. + * + * @note Call this before `begin()`. It has no effect after. + */ + void enableEncoderPinPullup() { encoderPinMode = INPUT_PULLUP; } + + /** + * @brief Enable the internal pull-up resistor for the button pin. + * + * By default, the button pin on the ESP32 is floating - which requires that + * the button has its own pull-up resistors or external pull-ups are used. + * + * If the button does not have its own pull-ups, calling this method + * will enable the internal pull-up in the ESP32. + * + * @note Call this before `begin()`. It has no effect after. */ - void setEncoderType( Type type ); + void enableButtonPinPullup() { buttonPinMode = INPUT_PULLUP; } /** * @brief Set the minimum and maximum values that the encoder will return.