%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#ECF0F1'}}}%%
flowchart LR
subgraph Servo[Servo PWM Timing at 50Hz 20ms period]
P1[1ms pulse<br/>0 degrees<br/>5% duty cycle]
P2[1.5ms pulse<br/>90 degrees<br/>7.5% duty cycle]
P3[2ms pulse<br/>180 degrees<br/>10% duty cycle]
end
MCU[Microcontroller<br/>sends 50Hz PWM] --> P1
MCU --> P2
MCU --> P3
P1 -.-> S1[Servo arm<br/>rotates to 0 deg]
P2 -.-> S2[Servo arm<br/>rotates to 90 deg]
P3 -.-> S3[Servo arm<br/>rotates to 180 deg]
style MCU fill:#2C3E50,stroke:#16A085,color:#fff
style P1 fill:#E67E22,stroke:#2C3E50,color:#fff
style P2 fill:#16A085,stroke:#2C3E50,color:#fff
style P3 fill:#E74C3C,stroke:#2C3E50,color:#fff
style S1 fill:#ECF0F1,stroke:#2C3E50,color:#2C3E50
style S2 fill:#ECF0F1,stroke:#2C3E50,color:#2C3E50
style S3 fill:#ECF0F1,stroke:#2C3E50,color:#2C3E50
575 Servo Motors
Learning Objectives
After completing this chapter, you will be able to:
- Understand servo motor construction and operating principles
- Control servo position using PWM pulse width
- Interface single and multiple servos with ESP32
- Implement smooth motion profiles using interpolation
- Build coordinated multi-servo systems (robotic arms, pan-tilt)
- Choose between servo and stepper motors for positioning applications
575.1 Servo Motor Fundamentals
Servo motors provide precise angular positioning (typically 0-180 degrees) using PWM signals. Unlike DC motors that spin continuously, servos move to specific angles and hold position.
Characteristics:
- Precise position control
- Built-in feedback loop
- Limited rotation range (standard servos: 0-180 degrees)
- Easy to control with PWM
- Self-correcting (closed-loop)
575.1.1 Internal Components
Servo motors integrate a complete feedback control system in a compact package:
- DC Motor Core: Provides rotational force
- Gear Reduction System: Amplifies torque, reduces speed
- Potentiometer: Position feedback sensor
- Control Circuit: Compares commanded vs actual position
The internal potentiometer continuously measures shaft position, and the control circuit compares this to the commanded position from the PWM signal. This closed-loop design maintains position accuracy even under varying loads.
575.1.2 PWM Position Control
Servo motors use pulse width (not duty cycle) to determine position:
Key parameters:
- 50 Hz frequency (20ms period) - Standard for hobby servos
- 1ms pulse = 0 degrees (5% duty cycle)
- 1.5ms pulse = 90 degrees (7.5% duty cycle, center position)
- 2ms pulse = 180 degrees (10% duty cycle, maximum rotation)
Important: Servo libraries handle the timing. You just write servo.write(90) for 90 degrees!
575.2 Servo vs Stepper Comparison
Option A: Servo motor (SG90/MG996R): Range 0-180 degrees, resolution ~1 degree, holding torque 1.8-10 kg-cm, power 5V @ 100-500mA active, cost $2-15, closed-loop feedback maintains position under load, simple PWM control (1 GPIO pin)
Option B: Stepper motor (NEMA17): Range unlimited rotation, resolution 1.8 degrees/step (200 steps/rev), micro-stepping achieves 0.05 degrees, holding torque 40+ kg-cm, power 12V @ 400mA-2A, cost $8-25, open-loop control requires calibration/homing, requires driver board + 2-4 GPIO pins
Decision Factors: For IoT applications needing simple angular positioning within 180 degrees (smart vents, valve actuators, pan-tilt cameras), servos win on simplicity and cost. For continuous rotation, high precision (<0.1 degree), or high torque (3D printer axes, CNC, telescope mounts), steppers are mandatory. Servos consume power only when moving; steppers draw holding current continuously. For battery IoT, this makes servos 5-10x more energy efficient for intermittent positioning tasks.
575.3 Basic Servo Control
575.3.1 Single Servo Control
#include <ESP32Servo.h>
Servo myServo;
#define SERVO_PIN 18
void setup() {
Serial.begin(115200);
// Attach servo (ESP32 uses different timers)
myServo.attach(SERVO_PIN, 500, 2400); // Min/max pulse width in microseconds
Serial.println("Servo Controller Ready");
}
void loop() {
// Sweep from 0 to 180 degrees
for (int pos = 0; pos <= 180; pos++) {
myServo.write(pos);
Serial.print("Position: ");
Serial.println(pos);
delay(15);
}
delay(1000);
// Sweep back from 180 to 0 degrees
for (int pos = 180; pos >= 0; pos--) {
myServo.write(pos);
Serial.print("Position: ");
Serial.println(pos);
delay(15);
}
delay(1000);
}575.3.2 Multi-Servo Robotic Arm
#include <ESP32Servo.h>
// Define servos
Servo baseServo; // Base rotation
Servo shoulderServo; // Shoulder joint
Servo elbowServo; // Elbow joint
Servo gripperServo; // Gripper
// Servo pins
#define BASE_PIN 18
#define SHOULDER_PIN 19
#define ELBOW_PIN 21
#define GRIPPER_PIN 22
// Robot position structure
struct RobotPosition {
int base;
int shoulder;
int elbow;
int gripper;
};
// Predefined positions
RobotPosition homePos = {90, 90, 90, 90};
RobotPosition pickPos = {45, 45, 45, 90};
RobotPosition placePos = {135, 45, 45, 45};
void setup() {
Serial.begin(115200);
// Attach servos
baseServo.attach(BASE_PIN, 500, 2400);
shoulderServo.attach(SHOULDER_PIN, 500, 2400);
elbowServo.attach(ELBOW_PIN, 500, 2400);
gripperServo.attach(GRIPPER_PIN, 500, 2400);
// Move to home position
moveToPosition(homePos, 1000);
Serial.println("Robotic Arm Ready");
Serial.println("Commands: h=home, p=pick, l=place, s=sequence");
}
void loop() {
if (Serial.available()) {
char cmd = Serial.read();
switch(cmd) {
case 'h':
Serial.println("Moving to home position");
moveToPosition(homePos, 1500);
break;
case 'p':
Serial.println("Moving to pick position");
moveToPosition(pickPos, 1500);
break;
case 'l':
Serial.println("Moving to place position");
moveToPosition(placePos, 1500);
break;
case 's':
Serial.println("Running pick and place sequence");
pickAndPlaceSequence();
break;
}
}
}
void moveToPosition(RobotPosition target, int duration) {
// Get current positions
int currentBase = baseServo.read();
int currentShoulder = shoulderServo.read();
int currentElbow = elbowServo.read();
int currentGripper = gripperServo.read();
int steps = duration / 20; // 20ms per step (50Hz)
// Smooth interpolation
for (int i = 0; i <= steps; i++) {
float progress = (float)i / (float)steps;
int base = currentBase + (target.base - currentBase) * progress;
int shoulder = currentShoulder + (target.shoulder - currentShoulder) * progress;
int elbow = currentElbow + (target.elbow - currentElbow) * progress;
int gripper = currentGripper + (target.gripper - currentGripper) * progress;
baseServo.write(base);
shoulderServo.write(shoulder);
elbowServo.write(elbow);
gripperServo.write(gripper);
delay(20);
}
}
void pickAndPlaceSequence() {
// Move to pick position
moveToPosition(pickPos, 1500);
delay(500);
// Close gripper
gripperServo.write(45);
delay(500);
// Move to place position
moveToPosition(placePos, 2000);
delay(500);
// Open gripper
gripperServo.write(90);
delay(500);
// Return home
moveToPosition(homePos, 1500);
}575.4 Smooth Motion with Interpolation
For professional-quality motion, use smooth acceleration/deceleration profiles:
// Ease-in-out interpolation for smooth motion
float easeInOut(float t) {
return t < 0.5 ? 2 * t * t : 1 - pow(-2 * t + 2, 2) / 2;
}
void smoothMove(Servo& servo, int startPos, int endPos, int duration) {
int steps = duration / 20;
for (int i = 0; i <= steps; i++) {
float t = (float)i / (float)steps;
float easedT = easeInOut(t);
int pos = startPos + (endPos - startPos) * easedT;
servo.write(pos);
delay(20);
}
}575.5 Interactive Lab: Servo Control
Interactive Challenges:
- Potentiometer Control: Use a potentiometer to control servo position (0-180 degrees)
- Button Presets: Add buttons for preset positions (0, 45, 90, 135, 180 degrees)
- Smooth Sweep: Implement smooth back-and-forth sweeping motion
575.6 Common Servo Specifications
| Model | Voltage | Torque | Speed | Weight | Use Case |
|---|---|---|---|---|---|
| SG90 | 4.8-6V | 1.8 kg-cm | 0.1s/60 deg | 9g | Light loads, prototyping |
| MG90S | 4.8-6V | 2.2 kg-cm | 0.1s/60 deg | 13.4g | Metal gears, higher torque |
| MG996R | 4.8-7.2V | 11 kg-cm | 0.17s/60 deg | 55g | Robotic arms, heavy loads |
| DS3218 | 4.8-6.8V | 21 kg-cm | 0.16s/60 deg | 60g | Industrial, waterproof |
Do NOT power multiple servos from the ESP32 5V pin! Use an external 5V 2A power supply. Connect ESP32 GND to power supply GND (common ground).
SG90: Up to 600mA stall current each MG996R: Up to 2.5A stall current each
A 3-servo arm with MG996R servos may need 5V @ 5A or more!
575.7 Pan-Tilt Camera Mount
A common IoT application is a pan-tilt camera mount:
#include <ESP32Servo.h>
Servo panServo; // Horizontal rotation
Servo tiltServo; // Vertical rotation
#define PAN_PIN 18
#define TILT_PIN 19
// Position limits
#define PAN_MIN 0
#define PAN_MAX 180
#define TILT_MIN 0
#define TILT_MAX 90 // Limit tilt to prevent looking backward
int panPos = 90; // Start center
int tiltPos = 45; // Start mid-height
void setup() {
Serial.begin(115200);
panServo.attach(PAN_PIN, 500, 2400);
tiltServo.attach(TILT_PIN, 500, 2400);
panServo.write(panPos);
tiltServo.write(tiltPos);
Serial.println("Pan-Tilt Camera Ready");
Serial.println("Commands: w=up, s=down, a=left, d=right, c=center");
}
void loop() {
if (Serial.available()) {
char cmd = Serial.read();
switch(cmd) {
case 'w': // Tilt up
tiltPos = min(tiltPos + 10, TILT_MAX);
break;
case 's': // Tilt down
tiltPos = max(tiltPos - 10, TILT_MIN);
break;
case 'a': // Pan left
panPos = min(panPos + 10, PAN_MAX);
break;
case 'd': // Pan right
panPos = max(panPos - 10, PAN_MIN);
break;
case 'c': // Center
panPos = 90;
tiltPos = 45;
break;
}
panServo.write(panPos);
tiltServo.write(tiltPos);
Serial.print("Pan: ");
Serial.print(panPos);
Serial.print(" Tilt: ");
Serial.println(tiltPos);
}
}575.8 What’s Next?
Now that you understand servo motors, you’re ready to explore stepper motors for precise step-by-step positioning.