Zbotic Logo Zbotic Logo
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

  • Shop
  • About Us
  • Contact Us
  • Reseller
  • Blogs
020 69134444
1800 209 0998
[email protected]
Help Desk
Facebook Twitter Instagram Linkedin YouTube
Zbotic Logo Zbotic Logo
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

All departments
  • 3D Print Service
  • 3D Printer
  • Batteries & Chargers
  • Development Boards
  • Drone Parts
  • EBike parts
  • Sensor Modules
  • Electronic Components
  • Electronic Modules
  • IoT and Wireless
  • Mechanical Parts and Workbench Tools
  • Motors & Drivers & Pumps & Actuators
  • DIY and Robot Kits
  • Show more
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
Return to previous page
Home Arduino & Microcontrollers

Arduino Encoder Motor Control: Closed-Loop Speed and Position

Arduino Encoder Motor Control: Closed-Loop Speed and Position

March 11, 2026 /Posted byJayesh Jain / 0

Open-loop motor control — simply sending a PWM signal and hoping for the best — is fine for fans and simple actuators. But any serious robotic or automation project requires Arduino encoder motor control with closed-loop feedback. A motor encoder tells your Arduino exactly how far and how fast the shaft has turned, enabling precise speed regulation and position targeting that open-loop control simply cannot achieve. This guide covers everything from reading quadrature encoder pulses to implementing a full PID controller for arduino encoder motor control.

Table of Contents

  • Encoder Types and How They Work
  • Wiring a Quadrature Encoder to Arduino
  • Reading Encoder Pulses with Interrupts
  • Calculating Speed from Encoder Data
  • PID Control for Motor Speed
  • Position Control and Homing
  • Using the Encoder Library
  • FAQ

Encoder Types and How They Work

Motor encoders come in two main flavours:

Incremental encoders output a series of pulses as the shaft rotates. A basic single-channel encoder gives pulses proportional to speed but cannot determine direction. A quadrature encoder has two channels (A and B) 90° out of phase — the phase relationship between A and B reveals both speed and direction. Most DC gearmotor encoders found in robotics kits are quadrature incremental encoders with resolution ranging from 12 CPR (counts per revolution) to 2000+ CPR depending on the gear ratio and disc resolution.

Absolute encoders output a unique code for every shaft position, so they know absolute position even after power-off. They are more expensive and typically communicate over SPI or I2C. For most Arduino projects, quadrature incremental encoders are the practical choice.

Encoder resolution: Resolution is specified in CPR (counts per revolution of the encoder disc) or PPR (pulses per revolution). With quadrature decoding (counting both edges of both channels — 4X decoding), you get 4× the CPR in actual position counts. A 48 CPR encoder with 4X decoding gives 192 counts/revolution of the encoder shaft, and with a 30:1 gearbox you get 5,760 counts per output shaft revolution — approximately 0.0625° resolution.

Recommended: Arduino Mega 2560 R3 Board — with 6 external interrupt pins, the Mega is ideal for multi-motor encoder systems; read up to 3 quadrature encoders simultaneously with dedicated interrupt pins for each channel.

Wiring a Quadrature Encoder to Arduino

A typical DC gearmotor with encoder has 6 wires:

  • M+ / M−: motor power (connect to motor driver, not Arduino)
  • VCC: encoder power supply (3.3V or 5V — check datasheet)
  • GND: common ground
  • Channel A (ENC_A): first quadrature channel
  • Channel B (ENC_B): second quadrature channel

Connect ENC_A and ENC_B to two interrupt-capable pins on the Arduino. On an Uno, use pins 2 and 3. On a Mega, you have pins 2, 3, 18, 19, 20, 21 available. Always connect encoder VCC to the correct voltage rail — many encoders are 3.3V and will be damaged by 5V supply.

// Pin assignment
#define ENC_A 2  // Interrupt pin
#define ENC_B 3  // Interrupt pin
#define MOTOR_PWM 9
#define MOTOR_DIR 8

Also connect the motor driver (e.g., L298N, TB6612) power inputs to an appropriate external supply — never power a motor directly from Arduino’s 5V or Vin pin. Keep encoder signal wires short and away from motor power lines to minimise noise.

Reading Encoder Pulses with Interrupts

The correct way to read a quadrature encoder on Arduino is with hardware interrupts on both encoder channels. Polling-based reading misses pulses at high speed and produces unreliable position data.

Simple 2X decoding (count on rising edge of A only, use B for direction):

volatile long encoderCount = 0;
volatile bool lastA = false;

void encoderISR() {
  bool stateA = digitalRead(ENC_A);
  bool stateB = digitalRead(ENC_B);
  if (stateA != lastA) {   // A changed
    if (stateA == stateB) {
      encoderCount--;
    } else {
      encoderCount++;
    }
    lastA = stateA;
  }
}

void setup() {
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENC_B), encoderISR, CHANGE);
}

By attaching CHANGE interrupts on both channels and comparing their states inside the ISR, this gives full 4X quadrature decoding — maximum resolution from your encoder. The encoderCount increases for forward rotation and decreases for reverse.

Recommended: Arduino Uno R3 Beginners Kit — a great starting platform for single-motor encoder projects; the Uno’s two interrupt pins are sufficient for one quadrature encoder, and the kit includes all jumpers and breadboard for prototyping your motor circuit.

Calculating Speed from Encoder Data

Speed is the rate of change of position. Sample encoderCount at a fixed time interval and divide the delta count by the interval and encoder resolution:

const int COUNTS_PER_REV = 1440;  // Adjust for your encoder + gearing
const float SAMPLE_TIME_S = 0.05;  // 50ms sample period

long prevCount = 0;
float currentRPM = 0;
unsigned long lastSampleTime = 0;

void updateSpeed() {
  unsigned long now = millis();
  if (now - lastSampleTime >= (unsigned long)(SAMPLE_TIME_S * 1000)) {
    noInterrupts();
    long currentCount = encoderCount;
    interrupts();
    long delta = currentCount - prevCount;
    prevCount = currentCount;
    // RPM = (counts/sample) / (counts/rev) / (seconds/sample) * 60
    currentRPM = ((float)delta / COUNTS_PER_REV) / SAMPLE_TIME_S * 60.0;
    lastSampleTime = now;
  }
}

Always use noInterrupts() / interrupts() when reading the 32-bit encoderCount in main code on an 8-bit AVR to prevent corrupted reads mid-ISR-update.

PID Control for Motor Speed

With speed measurement in place, a PID controller closes the loop. The controller computes an error (setpoint RPM − measured RPM) and adjusts the motor PWM output to drive the error toward zero.

PID output = Kp × error + Ki × ∫error dt + Kd × d(error)/dt

float setpointRPM = 100.0;
float Kp = 2.0, Ki = 5.0, Kd = 0.1;
float integral = 0, prevError = 0;

int computePID(float measured) {
  float error = setpointRPM - measured;
  integral += error * SAMPLE_TIME_S;
  integral = constrain(integral, -50, 50);  // Anti-windup
  float derivative = (error - prevError) / SAMPLE_TIME_S;
  prevError = error;
  float output = Kp * error + Ki * integral + Kd * derivative;
  return constrain((int)output, 0, 255);
}

void loop() {
  updateSpeed();
  if (millis() - lastSampleTime == 0) {  // Fresh sample
    int pwm = computePID(currentRPM);
    analogWrite(MOTOR_PWM, pwm);
  }
}

Tuning PID gains: Start with Ki = Kd = 0. Increase Kp until the motor responds quickly but oscillates slightly. Then add Ki slowly to eliminate steady-state error. Add a small Kd to damp oscillations. The integral anti-windup clamp (constrain) is essential — without it, the integral term accumulates during stall and causes violent overshoot on recovery.

For a cleaner implementation, use the Arduino PID Library by Brett Beauregard (available in Library Manager) which handles the timing, output limits, and directional logic robustly.

Position Control and Homing

Position control targets a specific encoder count rather than a velocity setpoint. The approach is similar — compute error between target count and current count, run through a P or PD controller:

long targetCount = 0;  // Target position in encoder counts
float Kp_pos = 1.5;
float Kd_pos = 0.05;
long prevPosError = 0;

void positionControl() {
  noInterrupts();
  long pos = encoderCount;
  interrupts();
  long error = targetCount - pos;
  long dError = error - prevPosError;
  prevPosError = error;
  float output = Kp_pos * error + Kd_pos * dError;
  output = constrain(output, -255, 255);
  if (output > 0) {
    digitalWrite(MOTOR_DIR, HIGH);
    analogWrite(MOTOR_PWM, (int)output);
  } else {
    digitalWrite(MOTOR_DIR, LOW);
    analogWrite(MOTOR_PWM, (int)(-output));
  }
}

Homing routine: Drive the motor slowly in one direction until a limit switch triggers. Reset encoderCount = 0 at the limit. Now all positions are relative to the known home position — exactly how CNC machines and 3D printers home their axes.

Recommended: 3D Printer Controller Board RAMPS 1.4 for Arduino Mega Shield — combines Arduino Mega with stepper motor drivers and limit switch inputs; an excellent platform for multi-axis position-controlled systems using the encoder techniques in this guide.

Using the Encoder Library

Paul Stoffregen’s Encoder library handles the ISR setup, quadrature decoding, and thread-safe count access for you. It automatically uses interrupt pins where available and falls back to pin-change interrupts or polling for non-interrupt pins.

#include <Encoder.h>

Encoder myEnc(2, 3);  // Channel A on pin 2, B on pin 3

void setup() {
  Serial.begin(115200);
}

void loop() {
  long position = myEnc.read();
  Serial.println(position);
  delay(100);
}

Install via Library Manager: search for “Encoder” by Paul Stoffregen. This library is highly optimised and handles all the edge cases discussed in this article, including 4X decoding and atomic reads on AVR. It is the recommended approach for new projects where you do not need full control over the ISR implementation.

Recommended: Arduino Nano Every with Headers — the ATMega4809’s hardware event system and configurable custom logic can decode quadrature signals in hardware, offloading the CPU entirely from encoder ISR overhead at high pulse rates.

FAQ

What encoder resolution do I need for a robotics project?

For wheeled robots, 500–2000 effective counts per output shaft revolution gives good odometry accuracy. With a 48 CPR encoder and 30:1 gearbox with 4X decoding, you get 5,760 counts/rev — about 0.06° resolution, sufficient for most navigation tasks. Higher resolution is better for position control but generates more ISR load at high speeds.

Can I use a single encoder channel (no quadrature) on Arduino?

Yes, but you lose direction information. A single-channel encoder can only measure speed magnitude — you need external direction logic (from the motor driver H-bridge direction pin) to determine motor direction. For basic speed monitoring this is acceptable, but for position control quadrature is essential.

Why does my encoder count jump unexpectedly?

Common causes: (1) Electrical noise — keep encoder wires short, add 100 nF capacitors between each encoder output and ground near the Arduino pin. (2) Missing interrupts — if the motor spins faster than the ISR can handle, use a lower-resolution encoder or a faster MCU. (3) Bouncing mechanical encoder — less common on optical encoders, common on cheap magnetic encoders. (4) Non-atomic reads — always use noInterrupts() around 32-bit reads on AVR.

How do I control two motors independently with one Arduino?

Use Arduino Mega which has 6 interrupt pins — allocate 2 per encoder (4 total for 2 motors), leaving 2 spare. Run two independent PID loops in your code, one per motor, sharing the same sample period. Alternatively, use a dedicated motor controller IC (e.g., RMCS-220x) that handles encoder feedback internally and accepts speed commands over UART or I2C, freeing the Arduino from ISR load entirely.

What is the difference between speed control and position control?

Speed control maintains a target RPM regardless of load. Position control moves the shaft to a target angle and holds it there. Speed control typically uses a PI controller (no derivative needed). Position control benefits from a PD controller (derivative damps overshoot at target). Some applications combine both: an outer position loop generates a speed setpoint for an inner speed loop — called cascaded or nested PID control.

Build your next precision motor project with the right Arduino hardware. Browse Arduino boards and shields at Zbotic — everything you need for encoder motor control projects, shipped across India.

Tags: Arduino, closed-loop, DC motor, encoder, motor control, PID, Robotics, tutorial
Share Post
  • Facebook
  • Linkedin
  • Whatsapp
Arduino CNC Shield: Run 3-Axis...
blog arduino cnc shield run 3 axis cnc with grbl firmware 594780
blog how to make custom arduino pcb with easyeda and jlcpcb 594782
How to Make Custom Arduino PCB...

Related posts

Svg%3E
Read more

Arduino Batch Programming: Flash Multiple Boards Quickly

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Based Radar System with Ultrasonic Sensor

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Automatic Plant Monitor: Sunlight, Moisture, Temperature

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Lie Detector: GSR Sensor Polygraph Project

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Metal Detector: Build a Treasure Finder

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading

Add comment Cancel reply

Your email address will not be published. Required fields are marked

Facebook Twitter Instagram Pinterest Linkedin Youtube

Get the latest deals and more.

Download on Google Play Download on the App Store

Call us: 020 69134444 / 1800 209 0998

Monday - Saturday 09:30 AM - 06:00 PM
For Technical Supports Email: [email protected]
For Sales / Enquiries Email: [email protected]

  • My Account

    • Cart

    • Wishlist

    • Checkout

    • My Orders

    • Track Order

    • My Account

  • Information

    • FAQs

    • Blogs

    • Career

    • About Us

    • Contact Us

    • Payment Options

  • Policies

    • Privacy Policy

    • Terms & Conditions

    • GST Input Tax Credit

    • Shipping Return Policy

    • E-Waste Collection Points

    • Our Sitemap

© Zbotic.in is registered trademark of Moxie Supply Pvt Ltd – All Rights Reserved
Login
Use Phone Number
Use Email Address
Not a member yet? Register Now
Reset Password
Use Phone Number
Use Email Address
Register
Already a member? Login Now