A rotary encoder is one of the most useful input components in electronics — and one of the most misunderstood. Whether you are building a menu navigation system, a motor controller, a volume knob, or a precision position sensor, understanding how a rotary encoder works will save you hours of debugging and help you pick the right part the first time.
In this guide you will learn:
- What a rotary encoder is and how it works
- The difference between incremental and absolute encoders
- How a rotary encoder differs from a potentiometer
- What PPR, quadrature, and detent mean
- How to interface a rotary encoder with Arduino — with working code
- Common problems (missed steps, double counting, noise) and how to fix them
- What to look for when buying an encoder module
What Is a Rotary Encoder?
A rotary encoder is an electromechanical position sensor that converts the angular position or motion of a shaft into a digital electrical signal. Unlike a potentiometer, which outputs an analog voltage proportional to angle, a rotary encoder outputs digital pulses — making it ideal for microcontrollers.
Every time the shaft rotates, the encoder generates a series of pulses on its output pins. By counting those pulses, your microcontroller knows how far the shaft has turned. By comparing the timing of two output signals, it also knows which direction the shaft is turning.
Rotary encoders are used in:
- Audio equipment (volume and tone controls)
- Industrial control panels and HMI interfaces
- Robotics (joint position feedback)
- CNC machines and stepper/servo motor control
- Menu navigation on LCD displays
- Medical devices and test equipment
Rotary Encoder vs Potentiometer — Key Differences
This is one of the most common questions beginners ask. Here is a clear comparison:
| Feature | Rotary Encoder | Potentiometer |
|---|---|---|
| Output type | Digital pulses | Analog voltage |
| Rotation | 360° continuous, no end stops | ~270° limited range |
| Wear | Specified cycle life (e.g. 30,000 cycles) | Resistive track wears over time |
| Direction detection | Yes (quadrature) | No |
| Microcontroller interface | Digital GPIO pins | ADC pin required |
| Position memory | No (relative, incremental) | Yes (absolute angle = voltage) |
| Best for | Menus, counters, motor control | Volume level, brightness, trim |
The short answer: use a potentiometer when you need a fixed analog value (like setting a threshold). Use a rotary encoder when you need infinite rotation, direction detection, or digital counting.
How Does a Rotary Encoder Work?
The Mechanical Principle
Inside a mechanical (contact-type) rotary encoder is a disc with conductive segments and a set of brushes (contacts). As the shaft turns, the brushes alternately make and break contact with the segments, generating pulses on the output pins.
Quadrature Output — A and B Channels
Most rotary encoders have two output channels: A (CLK) and B (DT). These two signals are 90 degrees out of phase with each other — this is called quadrature encoding.
Clockwise rotation:
A: ‾‾|__|‾‾|__|‾‾
B: ‾‾|__|‾‾|__|‾
Counter-clockwise rotation:
A: ‾‾|__|‾‾|__|‾‾
B: |__|‾‾|__|‾‾|
- If A leads B → clockwise rotation
- If B leads A → counter-clockwise rotation
This is how your microcontroller determines direction — no extra hardware needed.
What Is PPR (Pulses Per Revolution)?
PPR stands for Pulses Per Revolution — the number of complete A/B pulse cycles generated in one full 360° rotation. Common values are 12, 20, and 24 PPR.
A 24 PPR encoder produces 24 rising edges on channel A per revolution. If you count both rising and falling edges on both channels (4× decoding), you get 96 counts per revolution — much higher resolution from the same encoder.
What Are Detents?
A detent is the tactile "click" you feel when rotating the shaft. Most encoders have one detent per pulse cycle. A 24 PPR encoder has 24 detents per revolution — each click advances the count by one step.
Types of Rotary Encoders
1. Incremental Encoder
The most common type for hobbyist and control applications. Outputs A and B pulses relative to the current position — it does not know its absolute angle. When power is removed, position information is lost. This is the type used in panel knobs, menu navigation, and volume controls.
2. Absolute Encoder
Outputs a unique digital code for every angular position — it always knows exactly where the shaft is, even after a power cycle. Used in robotics, servo drives, and precision motion control. More complex and expensive than incremental types.
3. Optical Encoder
Uses a slotted disc and photodetector instead of mechanical contacts. No contact bounce, very long life, and capable of very high PPR (thousands of pulses per revolution). Used in industrial and precision applications.
4. Magnetic Encoder
Uses a magnet and Hall effect sensor. Extremely robust against dust, moisture, and vibration. Common in automotive and outdoor applications.
5. Mechanical (Contact) Encoder
Uses physical contacts and a conductive disc. Simple, low-cost, widely used in consumer electronics for panel controls. Requires debouncing due to contact bounce. This is the type found in most hobbyist encoder modules.
Common Rotary Encoder Problems and How to Fix Them
Why Does My Encoder Count 2 Steps Per Click Instead of 1?
Your encoder has 4 transitions per detent (two rising + two falling edges), but your code is only counting rising edges. Either adjust your code to count only on one edge per click, or check your encoder’s datasheet — some encoders have 2 detents per full pulse cycle, others have 1.
Why Does My Encoder Skip Counts or Count in the Wrong Direction?
This is almost always caused by contact bounce. When the mechanical contacts open and close, they vibrate rapidly for a few milliseconds, generating dozens of spurious pulses. Your microcontroller is fast enough to see all of them.
Fix: Use hardware debouncing — an RC low-pass filter (resistor + capacitor) on each signal line. A 10kΩ resistor in series with a 0.1µF capacitor gives a cutoff of ~159 Hz, which is well below encoder signal frequency for hand-operated knobs but far above the bounce frequency.
Why Does My Encoder Not Register When I Press the Switch?
The push-switch on most encoders is open-collector — the pin floats when not pressed. Without a pull-up resistor, the MCU reads noise, not a clean logic level.
Fix: Add a 10kΩ pull-up resistor from the switch pin to VCC. Do the same for A and B channels if your encoder is open-collector type.
Should I Use Hardware or Software Debouncing?
Both work. Hardware debouncing (RC filter) removes noise before it ever reaches the MCU — cleaner and simpler firmware. Software debouncing adds a time delay in code, which can cause missed inputs if the MCU is busy. For a reliable, production-quality design, hardware debouncing is the better choice.
How to Interface a Rotary Encoder with Arduino
Wiring
| Encoder Pin | Arduino Pin |
|---|---|
| VCC | 5V |
| GND | GND |
| CLK (A) | D2 |
| DT (B) | D3 |
| SW (Switch) | D4 |
If using a bare encoder (not a module), add:
- 10kΩ pull-up resistor from CLK to 5V
- 10kΩ pull-up resistor from DT to 5V
- 10kΩ pull-up resistor from SW to 5V
- 10kΩ series resistor + 0.1µF capacitor to GND on CLK and DT (hardware debounce)
If using a ready-made encoder module with built-in pull-ups and debounce filter, connect directly — no extra components needed.
Basic Arduino Code — Read Rotation and Direction
// Rotary Encoder - Basic Example
// Reads A/B channels, detects direction, tracks position
const int CLK = 2; // Encoder A channel
const int DT = 3; // Encoder B channel
const int SW = 4; // Push switch
int counter = 0;
int lastStateCLK;
void setup() {
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP);
Serial.begin(9600);
lastStateCLK = digitalRead(CLK);
Serial.println("Rotary Encoder Ready");
}
void loop() {
int currentStateCLK = digitalRead(CLK);
if (currentStateCLK != lastStateCLK && currentStateCLK == HIGH) {
if (digitalRead(DT) != currentStateCLK) {
counter++;
} else {
counter--;
}
Serial.print("Position: ");
Serial.println(counter);
}
lastStateCLK = currentStateCLK;
if (digitalRead(SW) == LOW) {
Serial.println("Switch Pressed!");
delay(200);
}
}
Interrupt-Based Code (Better for Faster Rotation)
For faster or more accurate reading, use hardware interrupts on pins 2 and 3:
volatile int counter = 0;
const int CLK = 2;
const int DT = 3;
const int SW = 4;
void setup() {
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP);
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(CLK), readEncoder, RISING);
}
void readEncoder() {
if (digitalRead(DT) != digitalRead(CLK)) {
counter++;
} else {
counter--;
}
}
void loop() {
static int lastCounter = 0;
if (counter != lastCounter) {
Serial.print("Position: ");
Serial.println(counter);
lastCounter = counter;
}
if (digitalRead(SW) == LOW) {
Serial.println("Switch Pressed!");
delay(200);
}
}
When to use interrupts: always prefer interrupts for encoders. Polling in loop() can miss transitions if other code is running. Interrupts fire immediately, regardless of what loop() is doing.
What to Look for When Buying a Rotary Encoder Module
Not all encoders are equal. Here is what separates a reliable encoder from a cheap one that causes problems after a few weeks:
Mechanical life: A quality encoder specifies a minimum rotational life — look for at least 15,000 cycles, ideally 30,000+. Cheap generic encoders often omit this specification entirely.
Shaft and bushing material: Metal shaft and bushing handle side-loads and knob pressure far better than plastic. A plastic shaft bends or cracks under repeated use.
Specified detent torque: Consistent detent torque (measured in gf·cm) means every unit feels the same. Cheap encoders feel different from unit to unit.
Temperature rating: Industrial and enclosure use needs at least -30°C to +70°C. Generic encoders often have no rating.
Built-in signal conditioning: A module with hardware pull-up resistors and an RC debounce filter on A, B, and Switch eliminates the need to add these components yourself — and more importantly, ensures the filter is correctly sized for the encoder’s bounce characteristics.
Compliance documentation: RoHS compliance, insulation resistance, and dielectric withstanding voltage specs tell you the part has been properly tested. If a datasheet doesn’t list these, the part probably hasn’t been tested.
Our Encoder Module — Built on the Bourns PEC12R
We have designed an encoder module around the Bourns PEC12R (PEC12R-4230F-S0024) — a Tier-1 industrial-grade incremental rotary encoder with a specified 30,000 cycle rotational life and 20,000 cycle switch life.
On top of the bare encoder, the module includes:
- 10kΩ pull-up resistors on A, B, and Switch — no external components needed
- Hardware RC debounce filter (10kΩ + 0.1µF) on A, B and Switch — 159 Hz cutoff, tuned for hand-operated knob use
- 30 mm insulated 6 mm D-shaft — long enough to turn directly, and any standard 6 mm D-shaft knob fits (knob not included)
- Compact PCB with 5-pin 2.54 mm header — direct MCU connection; mount it behind a panel on standoffs and pass the shaft through (no threaded bushing)
The result: plug in, connect five wires, and read clean quadrature signals immediately. No missed counts, no phantom steps, no firmware hacks.
→ View the Bourns Rotary Encoder Module — ₹299, debounced in hardware
Summary
| Cheap Generic EC11 | Our Bourns Module | |
|---|---|---|
| Mechanical life | ~15,000 cycles (often unspecified) | 30,000 cycles (Bourns datasheet) |
| Shaft | Usually short | 30 mm insulated 6 mm D-shaft |
| Pull-up resistors | Not included | Built-in (10kΩ) |
| Hardware debounce | Not included | Built-in (10kΩ + 0.1µF) |
| Push-switch debounce | None | Built-in (RC filter + pull-up) |
| Temp range | Unspecified | -30°C to +70°C |
| Compliance docs | None | RoHS, full datasheet |
| Out-of-box usability | Requires extra components | Plug in and use |
Great content! Keep up the good work!