DC Motors and Encoders
Introduction
Brushed DC motors are the most basic motor type. When paired with encoders, they enable precise speed and position control. They are widely used in educational robots and small wheeled platforms.
Internal Structure of Brushed DC Motors
Core Components
| Component | Function |
|---|---|
| Permanent magnets (stator) | Provide a constant magnetic field |
| Armature windings (rotor) | Current-carrying conductors that produce torque |
| Commutator | Copper segment ring that mechanically switches current direction |
| Brushes | Carbon or metal brushes that conduct current into the commutator |
| Bearings | Support rotor rotation |
Operating Principle
- Current flows through the brushes into the commutator and enters the armature windings
- Current-carrying conductors experience Ampere's force in the permanent magnetic field: \(F = BIL\)
- The resulting torque drives the rotor to rotate
- The commutator switches the current direction at the appropriate position, maintaining consistent rotation
Common Models
| Model | Voltage | No-load Speed | Stall Torque | Typical Application |
|---|---|---|---|---|
| 130 micro motor | 3–6V | 8000 rpm | 0.02 N·m | Toys, small experiments |
| 370 motor | 6–12V | 6000 rpm | 0.1 N·m | Small robots |
| 550 motor | 12–24V | 5000 rpm | 0.5 N·m | Medium platforms |
| 775 motor | 12–36V | 4000 rpm | 1.5 N·m | Large chassis |
Geared DC Motors
Why Gear Reduction Is Needed
Bare motors run at high speeds (thousands of rpm) with low torque, making them unsuitable for directly driving robot wheels or joints. Adding a gearbox achieves:
Where \(N\) is the gear ratio and \(\eta_{gear}\) is the gear efficiency (typically 0.7–0.9).
Common Geared Motors
| Model | Voltage | Gear Ratio | Output Speed | Output Torque | With Encoder |
|---|---|---|---|---|---|
| JGA25-370 | 6–12V | 1:21.3 | ~280 rpm | 1.5 kg·cm | Yes |
| JGB37-520 | 6–12V | 1:30 | ~200 rpm | 3 kg·cm | Yes |
| GM25-370 | 12V | 1:34 | ~180 rpm | 2.5 kg·cm | Yes |
Gearbox Types
- Spur gear train: Low cost, higher noise, ~80% efficiency
- Planetary gear: Compact coaxial design, 85–95% efficiency, common in high-end geared motors
- Worm gear: Self-locking characteristic, high gear ratio, lower efficiency (40–70%)
Encoders
An encoder is a sensor that measures the angular position and speed of a motor shaft. It is the core component for closed-loop control.
Optical Incremental Encoder
Principle
- A code disc has evenly spaced slits
- A light source (LED) and a photosensitive element are placed on opposite sides of the disc
- Rotation produces pulse signals
Parameters
- PPR (Pulses Per Revolution): Number of pulses per revolution, e.g., 100/200/400/600
- CPR (Counts Per Revolution): Counts per revolution after quadrature decoding, \(CPR = 4 \times PPR\)
- Resolution: \(\Delta\theta = \frac{360°}{CPR}\)
Magnetic Encoder
Operating Principle
- A diametrically magnetized small magnet is mounted on the shaft end
- A Hall/magnetoresistive sensor IC detects the magnetic field angle
AS5600 Magnetic Encoder
| Parameter | Value |
|---|---|
| Resolution | 12-bit (4096 positions/rev) |
| Interface | I2C / Analog output |
| Accuracy | ±1° |
| Operating voltage | 3.3V / 5V |
| Size | Very compact |
# AS5600 angle reading via I2C (MicroPython example)
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
AS5600_ADDR = 0x36
RAW_ANGLE_REG = 0x0C
def read_angle():
data = i2c.readfrom_mem(AS5600_ADDR, RAW_ANGLE_REG, 2)
raw = (data[0] << 8) | data[1]
angle = raw * 360.0 / 4096.0
return angle
Absolute Encoder
- Each position has a unique code (Gray code or binary)
- Position is known immediately upon power-up, no homing required
- Single-turn absolute: unique position within one revolution
- Multi-turn absolute: can record multiple revolutions
Encoder Type Comparison
| Feature | Optical Incremental | Magnetic Encoder | Absolute |
|---|---|---|---|
| Cost | Low | Medium | High |
| Resolution | Medium | High | High |
| Noise immunity | Medium | Strong | Strong |
| Homing on power-up | Required | Not required | Not required |
| Typical application | Geared motor speed measurement | Servos / joints | Industrial servos |
Quadrature Decoding
Dual-Channel Signals
Incremental encoders typically output two signals, A and B, with a 90° phase difference:
┌──┐ ┌──┐ ┌──┐
Ch A: │ │ │ │ │ │
───┘ └──┘ └──┘ └──
┌──┐ ┌──┐ ┌──┐
Ch B: │ │ │ │ │ │
───┘ └──┘ └──┘ └──
Direction Detection
- Forward rotation: B is LOW on the rising edge of A
- Reverse rotation: B is HIGH on the rising edge of A
Quadrature Counting
By using all edges (rising + falling) of both A and B channels, the counting resolution is increased by a factor of 4:
Speed Calculation
Count the number of pulses \(\Delta n\) within a fixed time interval \(\Delta t\):
PID Speed Control
PID Controller
PID (Proportional-Integral-Derivative) is the most common algorithm for motor speed control:
Where:
- \(e(t) = \omega_{target} - \omega_{actual}\) — Speed error
- \(K_p\) — Proportional gain: larger error produces larger output
- \(K_i\) — Integral gain: eliminates steady-state error
- \(K_d\) — Derivative gain: suppresses overshoot and oscillation
Discrete Implementation
Practical controllers run at a fixed period \(T_s\):
Arduino/ESP32 Code Example
// Encoder pin definitions
#define ENCODER_A 2 // Interrupt pin
#define ENCODER_B 3
#define MOTOR_PWM 5
#define MOTOR_DIR 4
// Encoder count (volatile for interrupt safety)
volatile long encoderCount = 0;
// PID parameters
float Kp = 2.0, Ki = 0.5, Kd = 0.1;
float targetRPM = 100.0;
float integral = 0, prevError = 0;
// Encoder interrupt service routine
void encoderISR() {
if (digitalRead(ENCODER_B) == LOW) {
encoderCount++;
} else {
encoderCount--;
}
}
void setup() {
pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
pinMode(MOTOR_PWM, OUTPUT);
pinMode(MOTOR_DIR, OUTPUT);
attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderISR, RISING);
Serial.begin(115200);
}
// PID control loop (runs every 20ms)
unsigned long lastTime = 0;
long lastCount = 0;
const float CPR = 4 * 11 * 21.3; // 4x quadrature × PPR × gear ratio
const float dt = 0.02; // 20ms
void loop() {
unsigned long now = millis();
if (now - lastTime >= 20) {
// Calculate current RPM
noInterrupts();
long count = encoderCount;
interrupts();
float deltaCount = count - lastCount;
float currentRPM = (deltaCount / CPR) * (60.0 / dt);
// PID calculation
float error = targetRPM - currentRPM;
integral += error * dt;
integral = constrain(integral, -100, 100); // Integral windup limit
float derivative = (error - prevError) / dt;
float output = Kp * error + Ki * integral + Kd * derivative;
output = constrain(output, -255, 255);
// Output to motor
if (output >= 0) {
digitalWrite(MOTOR_DIR, HIGH);
analogWrite(MOTOR_PWM, (int)output);
} else {
digitalWrite(MOTOR_DIR, LOW);
analogWrite(MOTOR_PWM, (int)(-output));
}
prevError = error;
lastCount = count;
lastTime = now;
// Debug output
Serial.print("Target:"); Serial.print(targetRPM);
Serial.print(" Current:"); Serial.print(currentRPM);
Serial.print(" Output:"); Serial.println(output);
}
}
PID Tuning Tips
| Step | Action | Expected Effect |
|---|---|---|
| 1 | Set \(K_i=0, K_d=0\), gradually increase \(K_p\) | System begins to respond, may oscillate |
| 2 | Slightly reduce \(K_p\), gradually increase \(K_i\) | Eliminates steady-state error |
| 3 | Increase \(K_d\) | Reduces overshoot, faster convergence |
| 4 | Fine-tune all three parameters | Achieve satisfactory response |
Ziegler-Nichols Method
- Use P-only control, increase \(K_p\) until sustained oscillation, record as \(K_u\) (ultimate gain)
- Measure the oscillation period \(T_u\)
- Set parameters: \(K_p = 0.6K_u\), \(K_i = 2K_p/T_u\), \(K_d = K_p T_u/8\)
ESP32 Special Considerations
Hardware Encoder Interface
The ESP32 has a built-in PCNT (Pulse Counter) hardware module for efficient encoder signal processing:
// Using ESP32 PCNT hardware decoding
#include "driver/pcnt.h"
void setupPCNT() {
pcnt_config_t pcnt_config;
pcnt_config.pulse_gpio_num = ENCODER_A;
pcnt_config.ctrl_gpio_num = ENCODER_B;
pcnt_config.channel = PCNT_CHANNEL_0;
pcnt_config.unit = PCNT_UNIT_0;
pcnt_config.pos_mode = PCNT_COUNT_INC;
pcnt_config.neg_mode = PCNT_COUNT_DEC;
pcnt_config.lctrl_mode = PCNT_MODE_REVERSE;
pcnt_config.hctrl_mode = PCNT_MODE_KEEP;
pcnt_config.counter_h_lim = 32767;
pcnt_config.counter_l_lim = -32768;
pcnt_unit_config(&pcnt_config);
pcnt_counter_pause(PCNT_UNIT_0);
pcnt_counter_clear(PCNT_UNIT_0);
pcnt_counter_resume(PCNT_UNIT_0);
}
PWM Resolution
The ESP32's LEDC PWM supports up to 16-bit resolution:
// ESP32 high-resolution PWM
ledcSetup(0, 20000, 10); // Channel 0, 20kHz, 10-bit resolution (0-1023)
ledcAttachPin(MOTOR_PWM, 0);
ledcWrite(0, dutyCycle); // dutyCycle: 0-1023
Summary
- Brushed DC motors have a simple structure, suitable for introductory and small robot projects
- Geared motors balance speed and torque through a gearbox
- Encoders are the key sensor for closed-loop control
- Quadrature decoding uses A/B channels for direction detection and 4x counting
- PID control is the foundational algorithm for motor speed/position control
- The ESP32's PCNT hardware and high-resolution PWM make it an ideal platform for motor control