ESP32 S3 - Rotary Encoder

Learn how to use a rotary encoder with your ESP32 S3 to detect rotation direction and position. This tutorial walks you through wiring, basic polling code, and a more efficient interrupt-based approach — giving you everything you need to integrate a rotary encoder into your ESP32 S3 projects.

What you'll build:

  1. A circuit connecting a rotary encoder to the ESP32 S3
  2. A sketch that detects clockwise and counterclockwise rotation
  3. An interrupt-driven version for improved performance and reliability
  4. A foundation for real-world applications such as menus, motor control, and volume adjustment
ESP32 S3 - Rotary Encoder

Hardware Preparation

1×ESP32 S3 WROOM N16R8
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×Rotary Encoder
1×Breadboard
1×Jumper Wires

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

Overview of Rotary Encoder

A rotary encoder is a position sensor that converts rotational movement into electrical signals, allowing a microcontroller like the ESP32 S3 to determine both the direction and the amount of rotation. Unlike a potentiometer, a rotary encoder can spin continuously without hitting a mechanical stop, making it ideal for navigation menus, motor speed control, and other applications where unlimited rotation is needed.

This tutorial focuses on incremental rotary encoders — the most common and beginner-friendly type for ESP32 S3 projects.

Key Specifications

Incremental encoders generate two digital pulse signals (A and B) that are 90 degrees out of phase. By comparing which signal changes first, the ESP32 S3 can determine whether the knob was turned clockwise or counterclockwise. Most modules also include an integrated push-button switch, activated by pressing down on the knob. The encoder can rotate continuously in either direction with no end stops, and the onboard module typically accepts a supply voltage of 3.3V to 5V.

Rotary Encoder Module Pinout

rotary encoder pinout

The rotary encoder module exposes five pins that connect to your ESP32 S3:

  1. CLK pin (Output A): Primary pulse output that signals rotation — outputs one complete LOW-to-HIGH-to-LOW cycle per detent click
  2. DT pin (Output B): Secondary pulse output that lags CLK by 90 degrees, used to determine rotation direction
  3. SW pin: Push-button connection — normally open, reads HIGH when idle and LOW when the knob is pressed (requires a pull-up resistor)
  4. VCC pin (+): Power supply — connect to 3.3V on the ESP32 S3
  5. GND pin: Ground reference — connect to GND

Rotary Encoder vs Potentiometer

You might be tempted to confuse a rotary encoder with a potentiometer, but they serve fundamentally different purposes. A potentiometer outputs an analog voltage proportional to a fixed rotation range (typically 270°), making it best for reading an absolute position. A rotary encoder outputs digital pulses over unlimited rotation, making it ideal for tracking relative movement — how much and in which direction the knob has turned.

Key Differences

Rotary encoders rotate continuously 360° while potentiometers have a fixed mechanical limit. Encoders output digital pulses; potentiometers output analog voltage. Encoders track relative movement; potentiometers track absolute position. For projects on the ESP32 S3 that need menu navigation, motor speed adjustment, or any knob that keeps spinning, a rotary encoder is the right choice.

How Rotary Encoder Works

rotary encoder output

Inside the encoder, a slotted disc connects to a common ground (pin C). As the knob rotates, pins A and B alternately make contact with the common ground, generating two pulse trains that are 90 degrees out of phase — a technique called quadrature encoding.

Internal mechanism:

  1. A slotted disc inside connects to pin C (common ground)
  2. Pins A and B make contact with common ground as the knob rotates
  3. The order of contact between A and B determines rotation direction
  4. Two pulse signals 90 degrees out of phase are produced (quadrature encoding)

Detecting rotation direction:

  1. Clockwise rotation: Pin A contacts ground before pin B
  2. Counterclockwise rotation: Pin B contacts ground before pin A
  3. The ESP32 S3 reads pin B's state the moment pin A transitions, and uses that to determine direction
How rotary encoder works

Direction detection logic:

When pin A changes from LOW to HIGH:

  • If pin B is HIGH: The knob was turned counterclockwise
  • If pin B is LOW: The knob was turned clockwise

※ NOTE THAT:

Pins A and B connect to CLK and DT pins. Depending on the manufacturer, the order may vary. The code below is tested with DIYables rotary encoders.

How To Program For Rotary Encoder

The ESP32 S3 reads the rotary encoder by monitoring the CLK pin for state changes. When CLK transitions from LOW to HIGH, the sketch immediately samples the DT pin:

  1. The ESP32 S3 monitors the CLK pin continuously
  2. When CLK rises from LOW to HIGH, the DT pin is read immediately
  3. If DT is HIGH: Counterclockwise rotation → increment the counter by 1
  4. If DT is LOW: Clockwise rotation → decrement the counter by 1
  5. This method reliably tracks both direction and cumulative rotation amount

Wiring Diagram

Connect the rotary encoder to your ESP32 S3 as shown in the diagram below. Use short jumper wires to keep signal lines clean and avoid noise that could cause false counts.

The wiring diagram between ESP32 S3 rotary encoder

This image is created using Fritzing. Click to enlarge image

Safety Notes

Always connect VCC to the 3.3V pin on the ESP32 S3 rather than 5V to stay within the board's GPIO voltage tolerance. The SW pin requires a pull-up resistor — most rotary encoder modules include one onboard, but verify this on your module before skipping an external resistor.

Rotary Encoder Pin ESP32 S3 Pin
CLK GPIO19
DT GPIO20
SW GPIO21
VCC 3.3V
GND GND

ESP32 S3 Code – Rotary Encoder

The following sketch demonstrates basic rotary encoder reading on the ESP32 S3 using a polling approach. It detects the rotation direction, maintains a running counter of detent clicks, and reports button presses — all printed to the Serial Monitor. The ezButton library handles switch debouncing automatically, making button detection reliable without extra boilerplate code.

/* * This ESP32 S3 code was developed by newbiely.com * * This ESP32 S3 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3/esp32-s3-rotary-encoder */ #include <ezButton.h> // The library to use for SW pin #define CLK_PIN 19 // The ESP32 S3 pin 19 connected to the rotary encoder's CLK pin #define DT_PIN 20 // The ESP32 S3 pin 20 connected to the rotary encoder's DT pin #define SW_PIN 21 // The ESP32 S3 pin 21 connected to the rotary encoder's SW pin #define DIRECTION_CW 0 // clockwise direction #define DIRECTION_CCW 1 // counter-clockwise direction int counter = 0; int direction = DIRECTION_CW; int CLK_state; int prev_CLK_state; ezButton button(SW_PIN); // create ezButton object for pin 21; void setup() { Serial.begin(115200); // Configure encoder pins as inputs pinMode(CLK_PIN, INPUT); pinMode(DT_PIN, INPUT); button.setDebounceTime(50); // set debounce time to 50 milliseconds // read the initial state of the rotary encoder's CLK pin prev_CLK_state = digitalRead(CLK_PIN); } void loop() { button.loop(); // MUST call the loop() function first // read the current state of the rotary encoder's CLK pin CLK_state = digitalRead(CLK_PIN); // If the state of CLK is changed, then pulse occurred // React to only the rising edge (from LOW to HIGH) to avoid double count if (CLK_state != prev_CLK_state && CLK_state == HIGH) { // if the DT state is HIGH // The encoder is rotating in counter-clockwise direction => decrease the counter if (digitalRead(DT_PIN) == HIGH) { counter--; direction = DIRECTION_CCW; } else { // The encoder is rotating in clockwise direction => increase the counter counter++; direction = DIRECTION_CW; } Serial.print("Rotary Encoder:: direction: "); if (direction == DIRECTION_CW) Serial.print("Clockwise"); else Serial.print("Counter-clockwise"); Serial.print(" - count: "); Serial.println(counter); } // save last CLK state prev_CLK_state = CLK_state; if (button.isPressed()) { Serial.println("The button is pressed"); } }

Note: This code uses the ezButton library to simplify button debouncing for reliable button press detection.

Detailed Instructions

  1. New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
  2. Install Library: Add the ezButton library through Arduino IDE Library Manager
  3. Open Code: Copy the code above and paste it into Arduino IDE
  4. Upload: Click the Upload button to transfer the sketch to your ESP32 S3
  5. Test Rotation: Turn the encoder knob clockwise several clicks, then counterclockwise
  6. Test Button: Press down on the encoder knob
  7. View Results: Open Serial Monitor (115200 baud) to see rotation direction and count
  8. Pro Tip: Each tactile "click" you feel when turning the knob is called a detent — the encoder produces exactly one complete pulse cycle per detent, so your click count and the counter value will always match

Serial Monitor Output:

Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
[2026-06-16 10:23:01] Rotary Encoder:: direction: CLOCKWISE - count: 1 [2026-06-16 10:23:01] Rotary Encoder:: direction: CLOCKWISE - count: 2 [2026-06-16 10:23:02] Rotary Encoder:: direction: CLOCKWISE - count: 3 [2026-06-16 10:23:02] Rotary Encoder:: direction: CLOCKWISE - count: 4 [2026-06-16 10:23:03] Rotary Encoder:: direction: CLOCKWISE - count: 5 [2026-06-16 10:23:04] Rotary Encoder:: direction: ANTICLOCKWISE - count: 4 [2026-06-16 10:23:04] Rotary Encoder:: direction: ANTICLOCKWISE - count: 3 [2026-06-16 10:23:05] Rotary Encoder:: direction: ANTICLOCKWISE - count: 2 [2026-06-16 10:23:05] Rotary Encoder:: direction: ANTICLOCKWISE - count: 1 [2026-06-16 10:23:06] Rotary Encoder:: direction: ANTICLOCKWISE - count: 0 [2026-06-16 10:23:08] The button is pressed [2026-06-16 10:23:09] The button is pressed
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

Code Explanation

Review the detailed line-by-line comments in the code above to understand how each section works.

ESP32 S3 Code – Rotary Encoder with Interrupt

The polling approach in the previous sketch works well for simple projects, but it consumes ESP32 S3 processing cycles even when the knob is not moving — and can miss pulses if other code runs too slowly. Interrupts solve both problems by letting the hardware notify the ESP32 S3 the instant a CLK transition occurs, freeing the main loop for other tasks.

The following sketch uses a hardware interrupt on the CLK pin to read the rotary encoder efficiently. The direction logic is identical to the polling version, but the encoder state is captured inside an interrupt service routine (ISR) so no pulses are ever missed.

/* * This ESP32 S3 code was developed by newbiely.com * * This ESP32 S3 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3/esp32-s3-rotary-encoder */ #include <ezButton.h> // The library to use for SW pin #define CLK_PIN 19 // The ESP32 S3 pin 19 connected to the rotary encoder's CLK pin #define DT_PIN 20 // The ESP32 S3 pin 20 connected to the rotary encoder's DT pin #define SW_PIN 21 // The ESP32 S3 pin 21 connected to the rotary encoder's SW pin #define DIRECTION_CW 0 // clockwise direction #define DIRECTION_CCW 1 // counter-clockwise direction volatile int counter = 0; volatile int direction = DIRECTION_CW; volatile unsigned long last_time; // for debouncing int prev_counter; ezButton button(SW_PIN); // create ezButton object for pin 21; void IRAM_ATTR ISR_encoder() { if ((millis() - last_time) < 50) // debounce time is 50ms return; if (digitalRead(DT_PIN) == HIGH) { // The encoder is rotating in counter-clockwise direction => decrease the counter counter--; direction = DIRECTION_CCW; } else { // The encoder is rotating in clockwise direction => increase the counter counter++; direction = DIRECTION_CW; } last_time = millis(); } void setup() { Serial.begin(115200); // Configure encoder pins as inputs pinMode(CLK_PIN, INPUT); pinMode(DT_PIN, INPUT); button.setDebounceTime(50); // set debounce time to 50 milliseconds // use interrupt for CLK pin is enough // call ISR_encoder() when CLK pin changes from LOW to HIGH attachInterrupt(digitalPinToInterrupt(CLK_PIN), ISR_encoder, RISING); } void loop() { button.loop(); // MUST call the loop() function first if (prev_counter != counter) { Serial.print("Rotary Encoder:: direction: "); if (direction == DIRECTION_CW) Serial.print("CLOCKWISE"); else Serial.print("ANTICLOCKWISE"); Serial.print(" - count: "); Serial.println(counter); prev_counter = counter; } if (button.isPressed()) { Serial.println("The button is pressed"); } // TO DO: your other work here }

When you turn the knob, the Serial Monitor displays the same rotation information as the previous example, but with improved performance and reliability on the ESP32 S3.

※ NOTE THAT:

  • One interrupt is enough: Many tutorials attach interrupts to both CLK and DT, but a single interrupt on CLK is sufficient and uses fewer resources
  • Use volatile keyword: Always declare variables that are modified inside an ISR as volatile so the compiler does not optimize them away
  • Keep ISRs short: Avoid calling Serial.print() or Serial.println() inside an interrupt — set a flag or update a counter, then handle output in the main loop
  • Fast execution: The shorter and faster your ISR, the more reliable encoder reading will be under load

ESP32 S3 Rotary Encoder Applications

Rotary encoders pair naturally with the ESP32 S3's processing power and connectivity, enabling a wide range of interactive and control-oriented projects.

  1. Servo motor control: Rotate the encoder to set servo position with precise, click-by-click angular control
  2. LED brightness dimmer: Map the encoder counter to a PWM duty cycle for smooth, stepless brightness adjustment
  3. Stepper motor speed control: Regulate stepper speed in real time for robotics and CNC applications
  4. Digital volume control: Adjust audio output level in home automation or Bluetooth speaker projects
  5. OLED menu navigation: Scroll through menu items by turning the knob and select with a button press
  6. Smart thermostat: Set target temperature with the encoder and display the value on a screen
  7. RGB LED color picker: Map encoder position to hue, cycling through the full color spectrum

Video Tutorial

Watch the step-by-step video walkthrough for this ESP32 S3 project below.

Challenge Yourself

Now that you have a working rotary encoder on the ESP32 S3, try these exercises to deepen your understanding and push the project further.

  1. Beginner: Wire up an LED that blinks once each time the encoder button is pressed
  2. Beginner: Display the current counter value on a 4-digit 7-segment display instead of the Serial Monitor
  3. Intermediate: Use the encoder counter to control a servo motor angle — map the counter range to 0–180 degrees
  4. Intermediate: Build a PWM-based LED brightness dimmer that responds in real time to encoder rotation
  5. Advanced: Create a multi-level menu system on an OLED or LCD display, with the encoder for scrolling and the button for selection

Learn More

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!