A rotary encoder makes a satisfying scroll wheel for long documents, timelines in video editors or CAD zoom. With the Leonardo’s native USB it shows up on the PC as an ordinary mouse wheel — no software needed.
CLK → D7, DT → D8, SW → D9. Turning the knob scrolls one step per click; pressing it is a middle click (open link in new tab, pan in CAD).
Wiring
| Arduino Leonardo | KY-040 rotary encoder | Wire | Notes |
|---|---|---|---|
| D7 | CLK | yellow | |
| D8 | DT | green | |
| D9 | SW | blue | Internal pull-up; press = middle click. |
| 5V | + | red | |
| 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
- The Leonardo’s interrupt pins are 0, 1, 2, 3 and 7 — but 0/1 are Serial1 and 2/3 are I2C. A hand-turned knob doesn’t need interrupts: the sketch polls CLK/DT thousands of times a second with a bounce-proof state table.
Mouse.move(0, 0, wheel)sends scroll steps. Flip the sign if clockwise scrolls the wrong way for you.- Volume and media keys need a “consumer control” HID report, which the basic Keyboard library doesn’t send. The HID-Project library adds them.
- Most KY-040s give 4 transitions per click. If one click scrolls twice or every other click, change the
4to2.
Code
// KY-040 rotary encoder as a USB scroll wheel on an Arduino Leonardo.
// CLK → D7, DT → D8, SW → D9, + → 5V, GND → GND. Turn = scroll, press = middle click.
// Polled with a state table (fast enough for a hand-turned knob, immune to bounce).
#include <Mouse.h>
const uint8_t PIN_CLK = 7, PIN_DT = 8, PIN_SW = 9;
const int8_t TABLE[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
uint8_t lastState;
int8_t quarter = 0;
void setup() {
pinMode(PIN_CLK, INPUT); // KY-040 has 10 kΩ pull-ups on CLK/DT
pinMode(PIN_DT, INPUT);
pinMode(PIN_SW, INPUT_PULLUP);
lastState = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
Serial.begin(115200);
while (!Serial && millis() < 3000) {}
Mouse.begin();
Serial.println(F("Scroll wheel ready"));
}
void loop() {
uint8_t s = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
if (s != lastState) {
quarter += TABLE[(lastState << 2) | s];
lastState = s;
if (quarter >= 4 || quarter <= -4) { // one detent = 4 transitions
int8_t dir = quarter > 0 ? 1 : -1;
quarter = 0;
Mouse.move(0, 0, -dir); // clockwise scrolls down
Serial.println(dir > 0 ? F("Scroll down") : F("Scroll up"));
}
}
static bool wasPressed = false;
bool pressed = digitalRead(PIN_SW) == LOW;
if (pressed != wasPressed) {
wasPressed = pressed;
if (pressed) Mouse.press(MOUSE_MIDDLE);
else Mouse.release(MOUSE_MIDDLE);
delay(15); // debounce the switch
}
}// KY-040 rotary encoder as a USB scroll wheel on an Arduino Leonardo.
// CLK → D7, DT → D8, SW → D9, + → 5V, GND → GND. Turn = scroll, press = middle click.
// Polled with a state table (fast enough for a hand-turned knob, immune to bounce).
#include <Mouse.h>
const uint8_t PIN_CLK = 7, PIN_DT = 8, PIN_SW = 9;
const int8_t TABLE[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
uint8_t lastState;
int8_t quarter = 0;
void setup() {
pinMode(PIN_CLK, INPUT); // KY-040 has 10 kΩ pull-ups on CLK/DT
pinMode(PIN_DT, INPUT);
pinMode(PIN_SW, INPUT_PULLUP);
lastState = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
Serial.begin(115200);
while (!Serial && millis() < 3000) {}
Mouse.begin();
Serial.println(F("Scroll wheel ready"));
}
void loop() {
uint8_t s = (digitalRead(PIN_CLK) << 1) | digitalRead(PIN_DT);
if (s != lastState) {
quarter += TABLE[(lastState << 2) | s];
lastState = s;
if (quarter >= 4 || quarter <= -4) { // one detent = 4 transitions
int8_t dir = quarter > 0 ? 1 : -1;
quarter = 0;
Mouse.move(0, 0, -dir); // clockwise scrolls down
Serial.println(dir > 0 ? F("Scroll down") : F("Scroll up"));
}
}
static bool wasPressed = false;
bool pressed = digitalRead(PIN_SW) == LOW;
if (pressed != wasPressed) {
wasPressed = pressed;
if (pressed) Mouse.press(MOUSE_MIDDLE);
else Mouse.release(MOUSE_MIDDLE);
delay(15); // debounce the switch
}
}Library: Encoder (Paul Stoffregen) — compiled with Mouse 1.0.1.
Expected serial output
Scroll wheel ready
Scroll down
Scroll down
Scroll upScroll wheel ready Scroll down Scroll down Scroll up
Common errors
Scrolls the wrong way
Swap CLK and DT, or change -dir to dir in Mouse.move().
COM port vanishes after upload
Normal for a moment: the Leonardo re-enumerates as a composite USB device. If it doesn’t come back, press RESET once.