Lesson 6 of 6 · 18 min
Driving a servo
A motor spins; a servo goes to an angle and stays there. That one difference is why servos move robot arms, steer RC cars and aim camera gimbals: you tell them where to be, not how fast to spin. In this lesson you will see what is inside one, how to talk to it, and how to power it so it does not crash your Arduino.
What is inside a hobby servo
A hobby servo (an SG90 or MG996R, for example) is a small closed-loop system in one plastic case:
- a DC motor,
- a gearbox that trades speed for torque,
- a potentiometer attached to the output shaft, which reports the current angle,
- a tiny control board that compares the wanted angle with the measured angle and drives the motor to reduce the error.
That is a feedback control loop: measure, compare, correct, repeat. Push the horn away by hand and the board feels the potentiometer change and pushes back. This is why a servo holds its position under load while a plain motor does not.
Talking to it: a pulse every 20 ms
The wanted angle arrives on the signal wire as a train of pulses, 50 per second, so the period is 1 / 50 Hz = 20 ms. The width of each pulse encodes the angle:
| Pulse width | Angle |
|---|---|
| 1.0 ms | 0 degrees |
| 1.5 ms | 90 degrees |
| 2.0 ms | 180 degrees |
As a formula: pulse = 1000 us + angle * 1000 / 180. So 45 degrees is 1000 + 45 * 5.56 = 1250 us. The duty cycle is tiny: 1.5 ms of 20 ms is 7.5 percent. This is PWM again, but here the information is in the pulse width itself rather than in the average voltage.
Real servos vary, which is why the Arduino library lets you tune the limits. The default range is 544 to 2400 us.
The Servo library
You never need to count microseconds yourself. The Servo library generates the pulses in the background using a hardware timer, so your loop() can do other things.
#include <Servo.h>
Servo arm; // one object per servo
void setup() {
arm.attach(9); // signal wire on pin 9
}
void loop() {
arm.write(0); // go to 0 degrees
delay(1000);
arm.write(90); // go to 90 degrees (1.5 ms pulses)
delay(1000);
arm.write(180); // go to 180 degrees
delay(1000);
}
write(angle) takes degrees. writeMicroseconds(us) takes the pulse width directly, for finer control. On an Uno the library uses Timer1, so analogWrite() stops working on pins 9 and 10 while it is active.
Power: the part that bites everyone
The signal wire draws almost nothing. The motor does not. A small SG90 idles near 10 mA but can pull 650 mA or more when it stalls against a load. A pin on the Arduino can give 40 mA, and the 5 V rail fed from USB is limited to about 500 mA for the entire board.
The wiring then looks like this: servo red wire to the external 5 V, servo brown or black wire to external GND and to Arduino GND, servo orange or yellow wire to pin 9. A 470 uF capacitor across the servo supply absorbs the current spikes when the motor starts.
Sweeping
Move one degree at a time with a short delay and you get smooth motion instead of a snap:
#include <Servo.h>
Servo arm;
void setup() {
arm.attach(9);
}
void loop() {
for (int angle = 0; angle <= 180; angle++) {
arm.write(angle); // one degree further
delay(15); // 180 * 15 ms = 2.7 s per sweep
}
for (int angle = 180; angle >= 0; angle--) {
arm.write(angle); // and back again
delay(15);
}
}
The 15 ms pause matters: a servo needs time to physically arrive. The SG90 turns about 60 degrees in 0.1 s, so 1 degree takes roughly 1.7 ms; waiting 15 ms leaves a comfortable margin.
Controlling it with a potentiometer
Combine lesson 5 with this one. The pot gives 0 to 1023, map() rescales it to 0 to 180:
#include <Servo.h>
Servo arm;
void setup() {
arm.attach(9);
}
void loop() {
int raw = analogRead(A0); // 0..1023 from the pot
int angle = map(raw, 0, 1023, 0, 180); // rescale: 512 gives 90
arm.write(angle);
delay(15); // let the servo catch up
}
Jitter, attach and detach
If the servo buzzes or twitches while the pot is still, the cause is ADC noise: a flicker between 510 and 513 changes the angle by half a degree, and the control loop chases it. Fixes, from easiest to best: average several readings (lesson 5), only call write() when the angle changed by 2 or more degrees, and give the servo a clean supply.
arm.detach() stops the pulses. The servo goes limp and silent and draws almost no current, which suits a mechanism that only needs to move occasionally. The catch: with no pulses there is no holding torque, so a loaded arm will fall. Call attach() again when you need it.
Check yourself
Which pulse width, repeated every 20 ms, drives a typical hobby servo to its middle position of 90 degrees?
Check yourself
Your Arduino keeps resetting whenever the servo moves a load, and it is powered from the Arduino 5 V pin. What is the best fix?