Precise DC motor speed control is fundamental to robotics, automation, and countless Arduino projects. Whether you are building a robot car, a conveyor belt, or a camera slider, understanding PWM, H-bridge drivers, and PID feedback loops will take your motor control from basic on/off switching to smooth, precise, and responsive operation. This guide covers all three concepts with practical wiring and Arduino code.
PWM Speed Control Basics
PWM (Pulse Width Modulation) is the most common method for controlling DC motor speed. Instead of varying the voltage directly, PWM rapidly switches the motor on and off. The ratio of on-time to total cycle time (duty cycle) determines the average voltage the motor sees.
- 0% duty cycle (analogWrite 0): Motor off
- 50% duty cycle (analogWrite 127): Motor at roughly half speed
- 100% duty cycle (analogWrite 255): Motor at full speed
Arduino’s PWM pins operate at approximately 490Hz or 980Hz depending on the pin. This frequency is high enough that the motor smooths out the pulses through its own inductance, resulting in steady rotation rather than jerky on-off behaviour.
Simple PWM Speed Control
const int motorPin = 9; // PWM pin
const int potPin = A0; // Potentiometer
void setup() {
pinMode(motorPin, OUTPUT);
}
void loop() {
int potValue = analogRead(potPin); // 0-1023
int speed = map(potValue, 0, 1023, 0, 255);
analogWrite(motorPin, speed);
}
Note: This only controls speed in one direction. You need a transistor or MOSFET to handle motor current — never connect a motor directly to an Arduino pin.
H-Bridge for Direction Control
To control both speed and direction, you need an H-bridge circuit. The H-bridge uses four switches to route current through the motor in either direction. Motor driver ICs like the L298N, L293D, and TB6612FNG integrate complete H-bridges with protection circuitry.
Choosing a Motor Driver
| Driver | Channels | Current | Voltage Drop | Best For |
|---|---|---|---|---|
| L293D | 2 | 600mA | 1.4-2.8V | Small BO motors |
| L298N | 2 | 2A | 1.4-3V | General robotics |
| TB6612FNG | 2 | 1.2A | 0.5V | Efficient, battery-powered |
| BTS7960 | 1 | 43A | Very low | High-power motors |
| DRV8833 | 2 | 1.5A | Low | Compact builds |
Practical Wiring Guide
L298N + DC Motor + Arduino
- Battery (+) → L298N +12V terminal
- Battery (-) → L298N GND + Arduino GND
- L298N ENA → Arduino pin 9 (PWM for speed)
- L298N IN1 → Arduino pin 8 (direction)
- L298N IN2 → Arduino pin 7 (direction)
- Motor → L298N OUT1 and OUT2
Speed + Direction Control Code
const int ENA = 9, IN1 = 8, IN2 = 7;
void setup() {
pinMode(ENA, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
Serial.begin(9600);
}
void motorControl(int speed) {
// speed: -255 to 255
if (speed >= 0) {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
} else {
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
speed = -speed;
}
analogWrite(ENA, constrain(speed, 0, 255));
}
void loop() {
motorControl(200); // Forward at ~78% speed
delay(2000);
motorControl(-150); // Reverse at ~59% speed
delay(2000);
motorControl(0); // Stop
delay(1000);
}
Encoder Feedback for Closed-Loop Control
Open-loop PWM control has a significant limitation: the motor speed changes with load. A motor running at 50% PWM might spin at 200 RPM with no load but drop to 100 RPM under load. Encoder feedback solves this by measuring actual speed.
A quadrature encoder produces two pulse trains (channels A and B) that are 90 degrees out of phase. This provides both speed (pulse frequency) and direction (which channel leads).
PID Speed Control Implementation
// PID Motor Speed Control with Encoder
volatile long encoderCount = 0;
const int encoderA = 2; // Interrupt pin
const int ENA = 9, IN1 = 8, IN2 = 7;
// PID variables
float Kp = 2.0, Ki = 0.5, Kd = 0.1;
float setpointRPM = 150;
float integral = 0, lastError = 0;
unsigned long lastTime = 0;
void encoderISR() {
encoderCount++;
}
void setup() {
pinMode(encoderA, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(encoderA), encoderISR, RISING);
pinMode(ENA, OUTPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
Serial.begin(9600);
lastTime = millis();
}
void loop() {
unsigned long now = millis();
if (now - lastTime >= 100) { // Update every 100ms
noInterrupts();
long count = encoderCount;
encoderCount = 0;
interrupts();
// Calculate RPM (assume 11 pulses per revolution)
float rpm = (count / 11.0) * (60000.0 / (now - lastTime));
lastTime = now;
float error = setpointRPM - rpm;
integral += error * 0.1;
integral = constrain(integral, -200, 200);
float derivative = (error - lastError) / 0.1;
lastError = error;
float output = Kp * error + Ki * integral + Kd * derivative;
int pwm = constrain((int)output, 0, 255);
analogWrite(ENA, pwm);
Serial.print("RPM:"); Serial.print(rpm);
Serial.print(" PWM:"); Serial.println(pwm);
}
}
Advanced Techniques
- Soft start/stop: Ramp PWM gradually to prevent mechanical shock and current spikes.
- PWM frequency tuning: Higher PWM frequencies reduce audible whine but may reduce efficiency. Use Timer registers to set custom frequencies.
- Current limiting: Monitor motor current with an ACS712 sensor and reduce PWM if current exceeds safe limits.
- Braking modes: Short the motor terminals (both IN1 and IN2 HIGH) for active braking, or leave floating for coast-to-stop.
Frequently Asked Questions
Why does my motor not start at low PWM values?
Every motor has a minimum starting voltage/current. Below this threshold, the motor stalls. Typically, PWM values below 50-80 (out of 255) will not produce enough torque to overcome static friction. This is normal behaviour.
Can I use PWM to control an AC motor?
No, standard Arduino PWM is for DC motors only. AC motors require a Variable Frequency Drive (VFD) or a triac-based controller for speed control.
How does load affect motor speed without PID?
Significantly. A motor at 50% PWM might run at 200 RPM unloaded but only 100 RPM loaded. PID control compensates automatically by increasing PWM when speed drops below the setpoint.
What is the difference between coast and brake?
Coast (both inputs LOW) lets the motor spin freely to a stop. Brake (both inputs HIGH) shorts the motor terminals, creating back-EMF resistance that stops the motor quickly. Use brake when you need precise positioning.
Conclusion
DC motor speed control progresses from simple PWM (open-loop) through H-bridge direction control to PID closed-loop control with encoder feedback. Each level adds precision and capability. Start with basic PWM to understand the concept, add an H-bridge for bidirectional control, and implement PID when your project demands consistent speed regardless of load.
Find DC motors, drivers, and encoders at Zbotic.in.
Add comment