Understanding encoder feedback motor control quadrature signals is essential for anyone building precise positioning systems — from CNC machines to robotic arms. An incremental encoder attached to a motor shaft generates two square wave signals (Phase A and Phase B) that together tell a controller not just how fast the motor is spinning, but in which direction. This guide explains how quadrature encoders work, how to wire them to Arduino, and how to decode A and B phase signals for closed-loop motor control.
Table of Contents
- How Incremental Encoders Work
- Quadrature Encoding: A Phase vs B Phase
- Types of Encoders: Optical, Magnetic, and Capacitive
- Wiring an Encoder to Arduino
- Arduino Code for Quadrature Decoding
- Closed-Loop Position Control Example
- Frequently Asked Questions
How Incremental Encoders Work
An incremental encoder converts mechanical rotation into electrical pulses. Inside a typical optical encoder, a disc with alternating transparent and opaque slots rotates between an LED and a photodetector. Each slot generates one pulse cycle. The number of pulses per revolution (PPR) — also called resolution — determines positioning accuracy. Common values are 100 PPR, 400 PPR, 1000 PPR, and 2500 PPR.
The encoder outputs two signals — A and B — offset by 90 degrees (one quarter cycle). This 90-degree offset is the "quadrature" in quadrature encoding. A third signal, Z (or Index), provides one pulse per revolution for absolute position reference.
With 4x decoding (counting all edges of both A and B channels), a 1000 PPR encoder provides 4000 counts per revolution, giving 0.09° resolution — more than adequate for most industrial positioning tasks.
Quadrature Encoding: A Phase vs B Phase
The relationship between A and B phases determines rotation direction:
- Clockwise rotation: Phase A leads Phase B by 90°. When A rises, B is LOW.
- Counter-clockwise rotation: Phase B leads Phase A by 90°. When A rises, B is HIGH.
By sampling B at the rising edge of A, the controller determines direction: B=LOW means CW, B=HIGH means CCW. The counter increments for CW and decrements for CCW.
With 4x decoding, the controller counts on all four edges (A rise, A fall, B rise, B fall). This quadruples the effective resolution. The state machine for 4x decoding tracks the previous and current states of A and B, and increments or decrements based on the valid state transitions.
Types of Encoders: Optical, Magnetic, and Capacitive
Optical Encoders
Optical encoders are the most common in industrial applications. They offer high resolution (up to 10,000 PPR) and excellent accuracy. However, they are sensitive to dust, oil, and contamination. Use sealed optical encoders (IP65 or higher) in dirty environments. Price in India: ₹500–₹5,000 depending on PPR and brand.
Magnetic Encoders
Magnetic encoders use a magnet and Hall-effect sensor to detect rotation. They are more robust in contaminated environments and handle vibration better than optical encoders. Resolution is typically lower (up to 4096 PPR). The AS5048 and AS5600 are popular magnetic encoder ICs used in DIY projects. Price: ₹150–₹800 for the IC module.
Capacitive Encoders
Capacitive encoders are a newer technology offering very high accuracy and immunity to magnetic fields. They are used in precision instruments and medical devices. Less common in standard industrial automation.
Wiring an Encoder to Arduino
Most incremental encoders have 5 wires: VCC, GND, A, B, and Z (index). Signal voltage is typically 5V or 24V push-pull. For 5V encoders, connect directly to Arduino digital pins. For 24V encoders, use a resistor voltage divider or a dedicated level shifter.
// Encoder Wiring for Arduino UNO
// Encoder VCC → Arduino 5V
// Encoder GND → Arduino GND
// Encoder A → Arduino Pin 2 (INT0 - hardware interrupt)
// Encoder B → Arduino Pin 3 (INT1 - hardware interrupt)
// Encoder Z → Arduino Pin 4 (optional, for index reset)
// Note: Use interrupt-capable pins (2 and 3 on UNO) for reliable
// high-speed counting. Missing pulses causes position error!
Pull-up resistors: Some encoders have open-collector outputs that require external pull-up resistors (1kΩ to 10kΩ to VCC). Check your encoder datasheet. If using Arduino’s INPUT_PULLUP, the internal pull-up (20–50kΩ) may be too weak for long cable runs — use external 4.7kΩ resistors.
Cable shielding: In industrial environments, encoder cables pick up EMI from VFDs and high-current conductors. Use shielded twisted-pair cable (e.g., Alpha Wire 5174) and ground the shield at one end (the controller end) to prevent ground loops.
Arduino Code for Quadrature Decoding
// Quadrature Encoder Decoding with Arduino
// 4x decoding using interrupts on both A and B channels
const int ENCODER_A = 2; // Interrupt pin INT0
const int ENCODER_B = 3; // Interrupt pin INT1
volatile long encoderCount = 0;
volatile byte lastState = 0;
void setup() {
pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_B), encoderISR, CHANGE);
Serial.begin(115200);
}
void encoderISR() {
byte stateA = digitalRead(ENCODER_A);
byte stateB = digitalRead(ENCODER_B);
byte state = (stateA << 1) | stateB; // 2-bit state: 00, 01, 10, 11
// Valid CW transitions: 00→10→11→01→00
// Valid CCW transitions: 00→01→11→10→00
static const int8_t table[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
encoderCount += table[lastState * 4 + state];
lastState = state;
}
void loop() {
static long lastCount = 0;
long count;
noInterrupts();
count = encoderCount;
interrupts();
if (count != lastCount) {
float angle = (count % 4000) * (360.0 / 4000.0); // 1000PPR × 4x
Serial.print("Count: "); Serial.print(count);
Serial.print(" | Angle: "); Serial.println(angle, 2);
lastCount = count;
}
}
Closed-Loop Position Control Example
With encoder feedback, you can implement a simple proportional (P) controller to move the motor to a target position and hold it:
// Simple P-controller for position control
// Motor driven by L298N or similar H-bridge
const int MOTOR_EN = 9; // PWM enable
const int MOTOR_IN1 = 7; // Direction
const int MOTOR_IN2 = 8;
long targetPosition = 2000; // Encoder counts to target
float Kp = 2.0; // Proportional gain
void setMotor(int speed) {
if (speed > 0) {
digitalWrite(MOTOR_IN1, HIGH);
digitalWrite(MOTOR_IN2, LOW);
analogWrite(MOTOR_EN, constrain(speed, 0, 255));
} else if (speed < 0) {
digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, HIGH);
analogWrite(MOTOR_EN, constrain(-speed, 0, 255));
} else {
analogWrite(MOTOR_EN, 0); // Stop
}
}
void loop() {
long currentPos;
noInterrupts();
currentPos = encoderCount;
interrupts();
long error = targetPosition - currentPos;
int motorSpeed = (int)(Kp * error);
motorSpeed = constrain(motorSpeed, -200, 200);
setMotor(motorSpeed);
delay(10);
}
For production use, add the I (integral) and D (derivative) terms for a full PID controller. The integral term eliminates steady-state position error; the derivative term dampens oscillation.
Frequently Asked Questions
What is the difference between an incremental and absolute encoder?
An incremental encoder counts pulses from a reference point and loses position on power-off. An absolute encoder stores position across power cycles, outputting a unique binary code for every shaft position. Absolute encoders are essential for robots and CNC machines that cannot afford to home every startup. They cost significantly more — ₹3,000–₹30,000 vs ₹300–₹3,000 for incremental.
Why do I miss encoder counts at high speeds?
If the encoder pulse rate exceeds the interrupt handling speed of the microcontroller, pulses are missed. Arduino UNO (16MHz) can reliably decode up to ~50,000 counts/second. For faster motors, use a dedicated encoder counter IC (like LS7366R) or a higher-speed microcontroller (STM32 at 72–168MHz), which has hardware quadrature decoder peripherals.
What is the Index (Z) pulse used for?
The Z pulse fires once per revolution, providing a reference point. On startup, most servo systems home to the Z pulse to establish an absolute position reference. After homing, the incremental counts from Z give precise position throughout the operating range.
Can I use a rotary encoder (like KY-040) instead of a motor encoder?
KY-040 is a mechanical rotary encoder for user input (volume knob, menu navigation). It is not suitable for motor speed feedback — it has very low PPR (typically 20), slow response, and mechanical contact bounce. Use optical or magnetic encoders designed for motor feedback applications.
Add comment