A rotary encoder is a knob that turns without end and reports each click and its direction. It suits menus, volume control or setting a timer on an LCD. The KY-040 module adds a push switch and the pull-up resistors.
What matters on the Uno is which pins you use. The encoder sends two square waves, CLK and DT. If you turn it fast, a sketch that only polls them in loop() misses steps. The Uno has exactly two external-interrupt pins, D2 and D3. Put CLK and DT there and Paul Stoffregen’s Encoder library counts every edge in an interrupt, however busy the rest of your code is.
The sketch prints the position and direction on every click, and resets to zero when you press the knob.
Wiring
| Arduino Uno R3 | KY-040 rotary encoder | Wire | Notes |
|---|---|---|---|
| D2 | CLK | yellow | INT0 |
| D3 | DT | green | INT1 |
| D4 | SW | blue | Push switch to GND; the sketch uses the internal pull-up. |
| 5V | + | red | Powers the module’s 10 kΩ pull-ups on CLK and DT. |
| GND | GND | black |
3.3–5 V part, 5 V board. Power the module from your board’s logic voltage (3.3 V on ESP32/Pico) — its pull-ups go to the + pin, so CLK and DT will never exceed that voltage.
Board quirks
- D2 and D3 are the Uno’s only external-interrupt pins. With both encoder pins there, the Encoder library catches every edge even while
loop()is busy. On other pins it falls back to polling and skips steps when you turn fast. - The KY-040 gives 4 counts per click, so the sketch divides by 4. Some bare encoders give 2 or 1 — change the divisor if one click moves more than one step.
- Turning the wrong way? Swap CLK and DT, or swap the 2 and 3 in
Encoder knob(2, 3). - The library uses
attachInterrupt()on D2/D3, so those pins are no longer free for a PIR or a flow sensor’s interrupt.
Code
// KY-040 rotary encoder on an Arduino Uno R3, with Paul Stoffregen's Encoder library.
// CLK → D2, DT → D3 (both external-interrupt pins), SW → D4, + → 5V, GND → GND.
#include <Encoder.h>
const uint8_t SW_PIN = 4;
Encoder knob(2, 3); // both pins have interrupts on the Uno = no missed steps
long lastPos = 0;
bool lastSw = HIGH;
void setup() {
pinMode(SW_PIN, INPUT_PULLUP);
Serial.begin(9600);
Serial.println(F("Turn the knob or press it"));
}
void loop() {
long pos = knob.read() / 4; // KY-040: 4 counts per click (detent)
if (pos != lastPos) {
Serial.print(pos > lastPos ? F("Clockwise ") : F("Anticlockwise "));
Serial.print(F("position "));
Serial.println(pos);
lastPos = pos;
}
bool sw = digitalRead(SW_PIN);
if (sw != lastSw) {
delay(5); // simple debounce
if (digitalRead(SW_PIN) == sw) {
lastSw = sw;
if (sw == LOW) {
knob.write(0);
lastPos = 0;
Serial.println(F("Pressed - position reset to 0"));
}
}
}
}// KY-040 rotary encoder on an Arduino Uno R3, with Paul Stoffregen's Encoder library.
// CLK → D2, DT → D3 (both external-interrupt pins), SW → D4, + → 5V, GND → GND.
#include <Encoder.h>
const uint8_t SW_PIN = 4;
Encoder knob(2, 3); // both pins have interrupts on the Uno = no missed steps
long lastPos = 0;
bool lastSw = HIGH;
void setup() {
pinMode(SW_PIN, INPUT_PULLUP);
Serial.begin(9600);
Serial.println(F("Turn the knob or press it"));
}
void loop() {
long pos = knob.read() / 4; // KY-040: 4 counts per click (detent)
if (pos != lastPos) {
Serial.print(pos > lastPos ? F("Clockwise ") : F("Anticlockwise "));
Serial.print(F("position "));
Serial.println(pos);
lastPos = pos;
}
bool sw = digitalRead(SW_PIN);
if (sw != lastSw) {
delay(5); // simple debounce
if (digitalRead(SW_PIN) == sw) {
lastSw = sw;
if (sw == LOW) {
knob.write(0);
lastPos = 0;
Serial.println(F("Pressed - position reset to 0"));
}
}
}
}Library: Encoder (Paul Stoffregen) — compiled with Encoder 1.4.4.
Expected serial output
Turn the knob or press it
Clockwise position 1
Clockwise position 2
Anticlockwise position 1
Pressed - position reset to 0Turn the knob or press it Clockwise position 1 Clockwise position 2 Anticlockwise position 1 Pressed - position reset to 0
Common errors
Position jumps by 2 or goes back and forth
Contact bounce on cheap encoders. Interrupt pins help a lot; add 10 nF capacitors from CLK and DT to GND if it still happens.
Counts only one direction
DT or CLK not connected, or the + pin unpowered (so the pull-ups don’t work).
“Encoder.h: No such file”
Install “Encoder” by Paul Stoffregen from the Library Manager — not one of the other encoder libraries with similar names.