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

Encoder Resolution: Formulas and Worked Examples

For motor feedback and speed-sensing work, you often need to convert an encoder's pulse count into a real angle or speed. Three formulas cover most cases.

Degrees per pulse. Divide 360 by the encoder's PPR (pulses per revolution) rating:

degrees per pulse = 360 / PPR

A 600 PPR encoder -- a common rating for optical encoders used on motor shafts -- gives 360 / 600 = 0.6 degrees per pulse.

Effective resolution with quadrature decoding. If your code reads both edges of both A and B channels (extending the interrupt approach above to the DT pin), you get 4 counts per base pulse:

effective counts per revolution = PPR x 4

A 600 PPR encoder decoded this way yields 600 x 4 = 2400 counts per revolution, or 360 / 2400 = 0.15 degrees per count.

Pulses to RPM. Count pulses over a known time window and scale up to a full minute:

RPM = (pulse count / PPR) x (60 / window seconds)

Example: a 600 PPR encoder produces 50 pulses in a 0.1 second sampling window. RPM = (50 / 600) x (60 / 0.1) = 0.0833 x 600 = 50 RPM. If you count quadrature counts instead of raw pulses, use the effective PPR (PPR x 4) in the same formula -- the result comes out the same because both the count and the resolution scale by 4 together.

Optical vs. Mechanical Encoder Outputs

Not every rotary encoder uses mechanical contacts like the KY-040. Optical encoders use a slotted disc with a light source and photodetector pair instead of physical switches, and they turn up in motor feedback and industrial applications where higher resolution and longer service life matter.

The output stage determines whether you need pull-up resistors at all:

Output Type Behavior Pull-up needed?
Mechanical contact (KY-040 and similar) Switch to GND when closed, floats when open Yes -- external or internal
Open-collector (common on optical encoders) Transistor pulls the line LOW when active, floats HIGH otherwise Yes -- external or internal
Push-pull (totem-pole) Actively driven both HIGH and LOW No -- the output is already a clean logic level

Check the datasheet before wiring an unfamiliar encoder. Adding a pull-up to a push-pull output is harmless -- it just wastes a little current -- but skipping a required pull-up on an open-collector or mechanical output leaves the line floating and produces false transitions.

Contact Wear and Long-Term Reliability

Wiring mistakes show up immediately -- a swapped channel or a missing pull-up produces wrong or noisy readings from the first test. Contact wear is different: it develops over time and can make a previously reliable encoder start skipping or double-counting steps after months of use.

Cheap mechanical encoders with plastic detent mechanisms are the most affected, because the same metal contacts and wiper make repeated physical contact on every rotation. As the contact surface wears or picks up dust and oxidation, individual detents may stop registering cleanly, producing either a missed pulse or a bounce-like double pulse that a simple debounce timeout no longer filters.

If an encoder that worked correctly for weeks or months starts producing inconsistent counts, suspect contact wear before you go back and recheck the wiring. Contact-style encoders are sealed, so cleaning them is rarely practical -- the usual fix is replacement. For high-cycle applications, such as a knob turned thousands of times per day, an optical encoder rated for a longer mechanical life is a better fit than a mechanical detent encoder.

Key Takeaways

Rotary Encoder Wiring Diagram — circuit diagram showing component connectionsV+siga0siggndgndgndgnd+12V/24V SupplySensorPull-up RARDUINOUNOMCU / ReaderIndicatorSensor Circuit
Rotary Encoder Wiring Diagram — open the interactive version of this diagram to customise and export it.

Frequently asked questions

What is the difference between an incremental and an absolute rotary encoder?

An incremental encoder outputs a stream of pulses on its A/B channels -- your circuit counts them to track relative movement, and the position resets to zero on power-up. An absolute encoder outputs a unique digital code for every shaft position, so the exact angle is known immediately at power-on without counting from a reference point.

Why does my rotary encoder skip counts?

Skipped counts usually come from one of three causes: missing or too-weak pull-up resistors letting the signal float, polling the pins too slowly to catch fast transitions, or contact wear on an aging mechanical encoder. Check pull-ups first, switch to interrupts if you are polling, and suspect wear if a previously reliable encoder degrades gradually over time.

Can I use a rotary encoder without a microcontroller?

Yes, for simple cases. A dedicated up/down counter IC, such as the LS7366R, or discrete logic can decode quadrature signals and drive a display or counter without a microcontroller. For most maker projects, though, a microcontroller reading the A/B channels in software is simpler and more flexible than building the decode logic in hardware.

What resistor value do I need for encoder pull-ups?

10k ohm is the standard value used on breakout modules like the KY-040 and in most DIY wiring, and it works for both the A/B channels and the push-switch pin. Lower values, around 4.7k ohm, pull up faster and can help at high rotation speeds, but 10k ohm is a safe default for typical projects.

Why is my encoder reading the wrong direction?

This almost always means the A and B channel wires (CLK and DT on the KY-040) are swapped. The encoder still counts correctly since pulses arrive on both channels, but the phase comparison that determines direction is reversed. Fix it by swapping the two wires or by inverting the direction logic in your firmware.

Do I need to use both encoder channels A and B?

Only if you need direction. Reading a single channel lets you count pulses and estimate rotation amount, but you cannot tell clockwise from counterclockwise. Full quadrature decoding, comparing both A and B on every edge, is required for direction detection and also gives up to 4x the base pulse resolution.

Interactive diagrams for this guide

Related guides