Rotary Encoder Circuit Diagram: Wiring and Reading

A rotary encoder converts shaft rotation into digital pulses. Unlike a potentiometer, it has no end stops and produces absolute position information in its incremental form -- you count pulses, not voltage levels. This makes it the right choice for menu navigation, motor position feedback, tuning controls, and any application where you need to track relative rotation reliably.

The KY-040 is the most common breakout module for maker projects. Under the hood it is a mechanical incremental encoder with an integrated push switch. This guide covers the quadrature output, pull-up resistors, debouncing, and how to read it correctly on an Arduino.

How an Incremental Rotary Encoder Works

Inside a mechanical rotary encoder, two rows of contacts sit 90 degrees out of phase with each other relative to the mechanical step angle. These produce two output signals, A and B (also called CLK and DT on breakout modules).

When you rotate the shaft clockwise, A leads B:

Counterclockwise rotation reverses this:

This two-channel quadrature arrangement is what allows direction detection from a purely incremental (pulse-counting) sensor. A single-channel encoder can count steps but cannot determine direction.

Resolution

Resolution is specified in PPR (pulses per revolution) or detents. A typical KY-040 has 20 detents per revolution and produces 20 pulses per revolution per channel. Some encoders produce pulses between detents as well, giving effective 4x resolution per revolution with quadrature decoding.

KY-040 Pinout

Pin Label Function
+ (or VCC) 3.3--5V supply
GND Ground
SW Push switch output (active LOW)
DT (B) Quadrature output channel B
CLK (A) Quadrature output channel A

The KY-040 breakout module includes onboard 10kΩ pull-up resistors on CLK, DT, and SW connected to VCC. When outputs are open (shaft at rest between contacts), all three pins read HIGH. Contact closure pulls them LOW.

If you are using a bare encoder (without the KY-040 breakout), you must add your own pull-up resistors -- see below.

Pull-Up Resistors: Required

Encoder contacts are simple mechanical switches that connect the output to GND when closed and float when open. Without pull-up resistors, the "open" state is not a clean logic level -- the pin floats and picks up noise.

For a bare encoder:

Arduino's internal pull-ups (~20kΩ--50kΩ) can substitute for external ones: use pinMode(pin, INPUT_PULLUP). At slow rotation speeds, internal pull-ups are fine. For high-speed counting, external 10kΩ pull-ups are more reliable because the impedance is lower and pull up faster.

Connecting KY-040 to Arduino

KY-040 Pin Arduino Pin
+ (VCC) 5V
GND GND
CLK D2 (interrupt pin)
DT D3 (interrupt pin)
SW D4 (or any digital pin)

Using interrupt-capable pins (D2, D3 on Arduino Uno) for CLK and DT is important at higher rotation speeds. Polling works for a menu knob turned slowly by hand, but polling in the main loop will miss pulses if the code is doing anything else.

Reading the Encoder: Code Structure

The cleanest approach for direction detection reads both channels on a CLK interrupt:

const int clkPin = 2;
const int dtPin  = 3;
volatile int position = 0;

void setup() {
  pinMode(clkPin, INPUT_PULLUP);
  pinMode(dtPin,  INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(clkPin), encoderISR, CHANGE);
}

void encoderISR() {
  int clk = digitalRead(clkPin);
  int dt  = digitalRead(dtPin);
  if (clk != dt) {
    position++;   // clockwise
  } else {
    position--;   // counterclockwise
  }
}

This triggers on every CLK edge (CHANGE). At each interrupt, compare CLK and DT: if they differ, increment; if they match, decrement. The logic follows from the quadrature phase relationship.

For applications where you need 4x resolution (detecting both edges of both channels), attach an interrupt to DT as well, with the same comparison reversed.

Debouncing

Mechanical encoders bounce. Each contact transition produces multiple brief open/close events before settling. Without debouncing, one physical detent click can register as 2--5 pulses.

Software debouncing approaches:

  1. Timeout: After detecting a transition, ignore any further transitions for 1--5ms.
  2. State machine: Track the full four-state quadrature sequence (00 → 01 → 11 → 10 → 00 for CW) and only count complete valid sequences. Invalid transitions (skipped states, bounce) are discarded.
  3. Hardware RC filter: A 10kΩ resistor in series with each encoder output and a 100nF capacitor to GND forms a low-pass filter that absorbs bounce spikes. The KY-040 breakout does not include this -- add it on a breadboard if you see spurious counts.

For menu navigation (slow rotation), a 5ms timeout debounce in software is usually sufficient. For motor feedback encoders, use a hardware RC filter or a dedicated encoder IC (LS7366R).

Push Switch (SW Pin)

The SW pin on the KY-040 is active LOW -- pressing the shaft down pulls SW to GND. The KY-040 includes a pull-up, so the idle state is HIGH.

On Arduino, read it with digitalRead() and check for LOW:

if (digitalRead(swPin) == LOW) {
  // button pressed -- debounce as needed
}

Add software debouncing (check that LOW persists for > 20ms) to avoid multiple triggers from a single press.

Common Wiring Mistakes

Swapping CLK and DT: The encoder works but direction is reversed. Swap the wires or invert the direction in firmware.

No pull-up resistors on a bare encoder: Outputs float and register constant noise. Always add pull-ups.

Polling instead of interrupts for fast rotation: Pulses are missed. Use interrupts for anything faster than manual hand rotation.

Connecting both CLK and DT to non-interrupt pins on Uno: D2 and D3 are the two interrupt pins on Arduino Uno and Nano. If both are already used, consider using an ATmega328P with pin-change interrupts instead, or switch to a Teensy or STM32 which have many more interrupt inputs.

Simulating the Circuit

Before finalizing the encoder wiring in a project, sketch the full connection in CircuitDiagramMaker -- encoder symbol, pull-up resistors, optional RC debounce filter, and the Arduino connections. For complex encoder interfaces (multiple encoders, dedicated counter ICs like the LS7366R), a clear circuit diagram is essential to verify signal routing before soldering.

Create Your Own Rotary Encoder Circuit Diagram

Create your own rotary encoder circuit diagram -- free

Key Takeaways