Table of Contents
- Introduction
- What Is a Rotary Encoder?
- KY-040 Module Specifications
- How the KY-040 Generates Pulses
- Wiring KY-040 to Arduino
- Basic Code: Polling Method
- Advanced Code: Interrupt-Based (Recommended)
- Debouncing the Push Button
- Clockwise vs Counter-Clockwise Detection
- Project Ideas with KY-040
- Troubleshooting
- Frequently Asked Questions
- Conclusion
Introduction
If you have ever adjusted the volume on a car stereo or navigated a menu on a 3D printer, you have used a rotary encoder. Unlike a potentiometer, a rotary encoder has no beginning or end — it can spin indefinitely and counts every click (detent). The KY-040 rotary encoder module is the most popular encoder module in the Arduino maker community, thanks to its integrated pull-up resistors, built-in push button, and low price.
This tutorial explains everything from how the encoder generates signals to writing robust, interrupt-based Arduino code that correctly detects direction and handles switch debouncing. We also cover five practical project ideas you can build with a KY-040.
What Is a Rotary Encoder?
A rotary encoder is an electromechanical device that converts rotational position or movement into digital pulses. There are two main types:
- Absolute encoder: Reports the exact angular position at all times (even after power loss). Used in industrial servo drives. Expensive.
- Incremental encoder: Counts steps relative to a reference point. Reports direction and number of clicks since startup. Loses count on power loss. Much cheaper — this is the KY-040.
Incremental encoders like the KY-040 are perfect for user interface applications: volume knobs, menu selectors, manual jogging of CNC axes, and speed control.
KY-040 Module Specifications
| Parameter | Value |
|---|---|
| Supply Voltage | 3.3 V – 5 V |
| Output Pins | CLK (A), DT (B), SW (button) |
| Output Type | Digital (TTL) |
| Pulses per Revolution | 20 detents / 20 pulses per 360° |
| Integrated Pull-ups | Yes (10 kΩ on CLK, DT, SW) |
| Push Button | Yes (momentary, NO) |
| Shaft Diameter | 6 mm D-shaft |
| Dimensions | 32 × 19 mm (module) |
| Price in India | ₹30–₹80 |
The module has 5 pins: CLK (also called A or DT), DT (also called B), SW (push button switch), + (VCC), and GND.
How the KY-040 Generates Pulses
Inside the encoder, a rotating disc with notched contacts slides across two contact pads (A and B) that are physically offset by 90° (quarter phase). As you turn the shaft, A and B alternately connect to GND through the notches, producing a sequence of LOW/HIGH pulses.
The sequence of A (CLK) and B (DT) states determines direction:
- Clockwise rotation: CLK goes LOW first, then DT goes LOW
- Counter-clockwise: DT goes LOW first, then CLK goes LOW
In code, we detect a falling edge on CLK (interrupt) and then immediately read the state of DT. If DT is HIGH at that moment, the encoder is turning clockwise; if DT is LOW, it is counter-clockwise.
Quadrature signal table (simplified):
| Step | CLK | DT | Direction |
|---|---|---|---|
| 1 | HIGH | HIGH | — |
| 2 | LOW | HIGH | CLK fell first → CW |
| 3 | LOW | LOW | — |
| 4 | HIGH | LOW | — |
| 5 | HIGH | HIGH | Back to start |
Wiring KY-040 to Arduino
| KY-040 Pin | Arduino Uno Pin | Notes |
|---|---|---|
| + (VCC) | 5V | Or 3.3V for ESP32 |
| GND | GND | — |
| CLK (A) | D2 | Interrupt pin INT0 |
| DT (B) | D3 | Or any digital pin |
| SW (button) | D4 | Active LOW |
The KY-040 module has built-in 10 kΩ pull-up resistors, so you do not need to enable Arduino’s internal pull-ups for CLK and DT. For the SW button, the module also has a pull-up, so you read HIGH when not pressed and LOW when pressed.
Basic Code: Polling Method
The polling method checks CLK state in every loop iteration. Simple but can miss fast rotations if the loop has other time-consuming tasks.
#define CLK_PIN 2
#define DT_PIN 3
#define SW_PIN 4
int counter = 0;
int lastCLK = HIGH;
void setup() {
pinMode(CLK_PIN, INPUT);
pinMode(DT_PIN, INPUT);
pinMode(SW_PIN, INPUT);
Serial.begin(9600);
Serial.println("Rotary Encoder Test");
}
void loop() {
int currentCLK = digitalRead(CLK_PIN);
// Detect falling edge on CLK
if (currentCLK == LOW && lastCLK == HIGH) {
if (digitalRead(DT_PIN) == HIGH) {
counter++; // Clockwise
} else {
counter--; // Counter-clockwise
}
Serial.print("Counter: "); Serial.println(counter);
}
lastCLK = currentCLK;
// Button press (active LOW)
if (digitalRead(SW_PIN) == LOW) {
Serial.println("Button Pressed!");
counter = 0; // Reset counter
delay(300); // Simple debounce delay
}
}
Advanced Code: Interrupt-Based (Recommended)
The interrupt method fires immediately on a CLK edge, regardless of what the main loop is doing. This ensures no pulses are missed — even if you have display updates or serial prints in the main loop.
#define CLK_PIN 2
#define DT_PIN 3
#define SW_PIN 4
volatile int counter = 0;
volatile bool buttonPressed = false;
unsigned long lastButtonTime = 0;
void IRAM_ATTR handleEncoder() {
// CLK has fallen — read DT to determine direction
if (digitalRead(DT_PIN) == HIGH) {
counter++;
} else {
counter--;
}
}
void setup() {
pinMode(CLK_PIN, INPUT);
pinMode(DT_PIN, INPUT);
pinMode(SW_PIN, INPUT);
Serial.begin(9600);
// Attach interrupt on falling edge of CLK
attachInterrupt(digitalPinToInterrupt(CLK_PIN), handleEncoder, FALLING);
Serial.println("Encoder Ready");
}
void loop() {
// Print counter when changed
static int lastCounter = 0;
if (counter != lastCounter) {
Serial.print("Counter: "); Serial.println(counter);
lastCounter = counter;
}
// Debounced button read
if (digitalRead(SW_PIN) == LOW) {
unsigned long now = millis();
if (now - lastButtonTime > 200) {
Serial.println("Button Pressed — Counter Reset!");
counter = 0;
lastButtonTime = now;
}
}
}
Note for ESP32 users: Replace IRAM_ATTR qualifier on the ISR function. On Arduino Uno, IRAM_ATTR is not needed — simply declare as void handleEncoder().
Debouncing the Push Button
Mechanical switches bounce — on press and release, the contacts make and break contact multiple times in the space of a few milliseconds. Without debouncing, a single press can register as 3–10 presses in software.
Two debounce approaches:
- Time-based debounce (software): Record the timestamp of the first LOW reading. Ignore further changes until 50–200 ms have passed. This is the approach in the code above (
lastButtonTime). - Hardware debounce: Add a 0.1 µF capacitor between SW and GND, and a 10 kΩ resistor in series with SW. The RC filter smooths the bounce. Eliminates the need for software debouncing.
For the encoder’s CLK and DT signals, bouncing is less of an issue because the encoder mechanism is different from a simple switch — the sliding contacts produce cleaner signals. However, if you see double-counting at certain positions, add a small 10 nF capacitor from CLK to GND.
Clockwise vs Counter-Clockwise Detection
If your project is not detecting direction correctly (or reversing CW and CCW), try these fixes:
- Swap CLK and DT pins — CW/CCW depends on physical orientation of the encoder. Swapping CLK and DT in your wiring (or in your code) reverses the direction logic.
- Check interrupt edge — This code triggers on FALLING (CLK goes LOW). Some encoders work better with RISING. Try changing
FALLINGtoRISINGin the attachInterrupt call. - Four-step per detent encoders: Some encoders produce 4 quadrature transitions per detent. If your counter increments by 4 per click, divide by 4 in software, or use a state-machine decoder that only counts on complete quadrature cycles.
Project Ideas with KY-040
1. Digital Volume Control
Pair the KY-040 with a digital potentiometer IC (MCP4131) over SPI. Each encoder step sends an SPI command to the digital pot, incrementing or decrementing a DAC output that controls audio amplifier gain. Display current volume on a 7-segment display.
2. 3D Printer Manual Jog Control
Replace repetitive menu navigation on a 3D printer or CNC machine with a KY-040. Rotate to jog X/Y/Z axis by 0.1 mm per detent. Press the button to switch between axes. Faster rotation speed maps to 1 mm steps (detect RPM from pulse timing).
3. OLED Menu Navigator
Drive an SSD1306 OLED display with a scrollable settings menu. The encoder scrolls through menu options; pressing the button selects. This is a common pattern in any device with configurable parameters — weather stations, data loggers, audio synthesizers.
4. Stepper Motor Position Controller
Each encoder step advances a stepper motor by one step via an A4988 driver. Perfect for manual focusing of a camera lens, positioning a laser cutter’s mirror, or adjusting a spectrometer’s diffraction grating.
5. Frequency-Controlled LED Blinker
A beginner project: rotate the encoder to change the blink frequency of an LED from 0.5 Hz to 20 Hz. Great for understanding how encoder counts translate to real-world parameters using the map() function.
20A Range Current Sensor Module ACS712
Pair a rotary encoder with the ACS712 current sensor for a manual current-setpoint controller in a bench power supply or motor drive project.
BMP280 Barometric Pressure & Altitude Sensor
Build a rotary-controlled weather station dashboard — use the KY-040 to scroll between sensor readings on an OLED while BMP280 measures pressure and altitude.
Troubleshooting
Counter jumps by 2 or 4 per click
You are triggering on both RISING and FALLING edges, or the encoder generates multiple quadrature pulses per detent. Confirm you are using only FALLING edge trigger. If still jumping by 4, add a divider: counter = rawCount / 4;
Rotation direction is reversed
Swap the CLK and DT pin definitions in your code (or physically swap the wires). Alternatively, negate the counter increment logic.
Button registers multiple presses on one click
Your debounce delay is too short. Increase from 200 ms to 300 ms. Or add a 100 nF capacitor hardware debounce between SW and GND.
Encoder works but misses steps at high rotation speed
Switch from polling to interrupt-based code. If already using interrupts, check that your ISR is short (no Serial.print inside the ISR — use flags instead).
No signal on CLK or DT
Check that VCC is connected (modules without power have no pull-ups). Verify with a multimeter: CLK and DT should read ~5 V when at rest.
Frequently Asked Questions
Q1: Can I use KY-040 with ESP32 or ESP8266?
Yes. Both work at 3.3 V. Connect VCC to 3.3 V. Use any GPIO pins — on ESP32, all GPIO pins support interrupts (unlike Arduino Uno which only has 2 interrupt pins).
Q2: How many KY-040 encoders can I use on one Arduino?
Arduino Uno has only 2 external interrupt pins (D2, D3). For multiple encoders on Uno, use pin change interrupts (PCINT) via the EnableInterrupt library, which enables interrupts on any digital pin. On Arduino Mega, you have 6 external interrupt pins. On ESP32, you have dozens.
Q3: What is the difference between KY-040 and a bare EC11 encoder?
EC11 is the bare encoder mechanism (the rotating shaft component). KY-040 is a PCB module with an EC11 encoder soldered to it, plus pull-up resistors for CLK, DT, and SW. The KY-040 is easier to use because you plug it directly into a breadboard without needing external resistors.
Q4: Can a rotary encoder replace a potentiometer?
For user interface applications (volume, menu navigation, parameter adjustment), a rotary encoder is usually better — it has no end stops, gives positive click feedback, and the value is defined in software (unlimited range). For analog circuits that need a physical voltage divider, a potentiometer is still the right tool.
Q5: Why does my encoder lose count when I reset the Arduino?
Incremental encoders have no memory — they lose position on power loss. If you need to preserve the position across resets, save the counter value to EEPROM periodically and restore it on startup.
Conclusion
The KY-040 rotary encoder is a small, inexpensive module that adds a powerful user input mechanism to any Arduino project. The key to using it well is understanding the quadrature signal, using interrupts rather than polling, and implementing proper debouncing for the push button. Once you have the interrupt-based code running reliably, you can build everything from simple volume knobs to sophisticated multi-axis CNC control panels.
Available from Zbotic for under ₹80, the KY-040 delivers excellent value for makers and students across India. Pair it with sensors, displays, and actuators from our sensor collection to build interactive, responsive projects.
Add comment