gwordal

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.

Drag to orbithorn 90°
Drag the slider to move the horn. The shaft turns through roughly 180 degrees.

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 widthAngle
1.0 ms0 degrees
1.5 ms90 degrees
2.0 ms180 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.

5V0Vaverage 1.25V
The pin is only ever fully on or fully off. The motor or LED reacts to the average.

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?