Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

Rotary encoder with Arduino Leonardo — a USB scroll wheel

Wire a KY-040 rotary encoder to an Arduino Leonardo (CLK D7, DT D8, SW D9) and use it as a USB mouse scroll wheel with a middle-click button.

Arduino C++Verified — core avr 1.8.8, Mouse 1.0.1, 3 Nov 2026Compile-only

Community draft — report a mistake

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 LeonardoOnly the pins used are shown · 5 V logicKY-040 rotary encoder5VGNDD7D8D9CLKDTSW+GND
Every connection
Arduino LeonardoKY-040 rotary encoderWireNotes
D7CLKyellow
D8DTgreen
D9SWblueInternal pull-up; press = middle click.
5V+red
GNDGNDblack

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 4 to 2.

Code

rotary-encoder-leonardo.inoArduino C++Open in PlaygroundOpen in Playground
// 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

Serial Monitor · 115200Output
Scroll 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.

↑↓ to move · Enter to open