Arduino Tutorial: KY-040 Rotary Encoder Module
Abstract
Learn how to use the KY-040 Rotary Encoder Module with Arduino. This device converts angular position into a series of digital pulses, allowing for precise control of menus, volume, or rotational position. This tutorial focuses on using digital input pins and hardware interrupts for accurate, high-speed detection of rotation and button presses.
1. Introduction
The KY-040 is an incremental rotary encoder. Unlike a potentiometer (which gives an absolute analog position), the encoder reports only the change in position. It features:
- Rotational Sensing: Two output pins (CLD and DT) provide quadrature-encoded signals (two square waves slightly out of phase). By observing the sequence of these signals, the Arduino determines the direction (clockwise or counter-clockwise) and the number of steps taken.
- Push Button: An integrated switch (SW) provides a simple digital input when the shaft is pressed.
In this episode, you’ll learn:
- The quadrature encoding principle for direction detection.
- The critical role of hardware interrupts for reliable encoder reading.
- How to decode rotation to increment/decrement a counter.
- How to read the integrated push button.
This project provides a robust, digital alternative to the potentiometer for user input.
2. Prerequisites
Make sure you have:
- An Arduino Uno or compatible board.
- One KY-040 Rotary Encoder Module.
- Jumper Wires.
- Arduino IDE
3. Wiring and Setup for Arduino
The KY-040 module has five key pins and requires specific connections for rotation.
Step 1 – Identify Pins
The module has five main pins: GND, VCC, SW (Switch/Button), DT (Data), and CLK (Clock).
Step 2 – Connect the Module (Using Interrupts)
To reliably catch every pulse, we must connect the rotational pins (CLK and DT) to the Arduino’s external interrupt pins.
- Connect the GND pin of the KY-040 to the GND pin on the Arduino.
- Connect the VCC pin of the KY-040 to the 5V pin on the Arduino.
- Connect the CLK pin to Arduino Digital Pin 2 (Interrupt 0 on Uno).
- Connect the DT pin to Arduino Digital Pin 3 (Interrupt 1 on Uno).
- Connect the SW pin to Arduino Digital Pin 4 (Standard Digital Input).
This image was created with Fritzing
Step 3 – Initialize Pin Modes
The CLK and DT pins must be configured for input and attached to an interrupt.
4. Writing Interrupt-Driven Encoder Code
The best practice for reading a rotary encoder is to use a hardware interrupt on one channel (CLK or DT) and then check the state of the other channel to determine the direction.
Open main.ino and implement the following code.
// Rotary Encoder Pins
const int CLK_PIN = 2; // Must be an Interrupt Pin (Interrupt 0)
const int DT_PIN = 3; // Must be an Interrupt Pin (Interrupt 1)
const int SW_PIN = 4; // Button Pin
volatile int counter = 0;
int encoderCLK_prev;
int encoderCLK_value;
unsigned char bool_CW;
int count = 0;
void setup() {
Serial.begin(9600);
// Set all encoder pins as INPUT
pinMode(CLK_PIN, INPUT);
pinMode(DT_PIN, INPUT);
pinMode(SW_PIN, INPUT_PULLUP); // Use internal pull-up for the button
// Read initial CLK state
encoderCLK_prev = digitalRead(CLK_PIN);
Serial.println("KY-040 Encoder Ready. Rotate or Press Button...");
}
void loop() {
// Read the button state (Active LOW with PULLUP)
if (digitalRead(SW_PIN) == LOW) {
Serial.println("--- BUTTON PRESSED! ---");
// Simple debounce: wait until released
while (digitalRead(SW_PIN) == LOW);
}
encoderCLK_value = digitalRead(CLK_PIN);
if (encoderCLK_value != encoderCLK_prev) { // check if knob is rotating
// if pin A state changed before pin B, rotation is clockwise
if (digitalRead(DT_PIN) != encoderCLK_value) {
count ++;
bool_CW = true;
} else {
// if pin B state changed before pin A, rotation is counter-clockwise
bool_CW = false;
count--;
}
if (bool_CW) {
Serial.print("\r\nClockwise");
} else {
Serial.print("\r\nCounter-Clockwise");
}
Serial.print(count);
Serial.print("\r\n");
}
encoderCLK_prev = encoderCLK_value;
}
Code Explanation
- Direction Logic: When a change on the CLK pin is captured, the code checks the state of DT. Due to the quadrature nature, if DT is at a different state than CLK, it signifies one direction; if they are the same, it signifies the opposite direction.
- Debouncing: The button (SW) requires a software debounce (while (digitalRead(SW_PIN) == LOW);) to prevent multiple press events from a single physical push.
5. Uploading and Running the Project
Step 1 – Build
Click the Verify button (checkmark icon) in the Arduino IDE to compile the sketch.
Step 2 – Upload
- Connect your Arduino board via USB.
- Select the correct board and COM port.
- Click the Upload (arrow icon) button.
Step 3 – Test
- Open the Serial Monitor (Tools > Serial Monitor).
- Rotate the shaft clockwise and counter-clockwise. The Encoder Position should reliably increase or decrease by 1 for each audible “click” (detent) of the encoder.
- Press the button (push down on the shaft). The Serial Monitor should display the “BUTTON PRESSED!” message once per push.
6. Hands-On Lab Recap
You’ve learned:
- The principle of quadrature encoding for rotation.
- The vital importance of hardware interrupts (attachInterrupt) for high-speed digital input like encoders.
- How to implement state change detection to determine rotation direction.
- How to handle the integrated push button input with software debouncing.
This completes your foundation in all three major I/O types: Digital Input, Digital Output (PWM), and Analog Input.
7. Common Issues & Fixes
| Issue | Cause | Solution |
|---|---|---|
| Counter jumps wildly or counts only one direction. | Signal noise or wrong interrupt edge. | Try changing the interrupt from FALLING to CHANGE or RISING. Ensure CLK and DT are on correct Interrupt Pins (2/3 on Uno). |
| Button triggers multiple times on one press. | Missing or ineffective debounce logic. | Ensure the while (digitalRead(SW_PIN) == LOW); line is present after registering the button press. |
| Button doesn't work. | Missing Internal Pull-up. | Ensure pinMode(SW_PIN, INPUT_PULLUP); is used, as the button is typically wired to pull the pin LOW when pressed. |


