Module 6 - Drivers & Actuators

17. Servo Motors

See how pulse width sets a servo's angle, drive it with Servo.h in a safe range, and power it from its own supply with a common GND.

Estimated time 1 session + optional extrasIn class Week 6 · Session 27

Learning outcomes

  • Describe the motor, gears, feedback pot and control board inside a hobby servo
  • Explain closed-loop position control (command vs feedback)
  • Relate pulse width (~1-2 ms in a ~20 ms frame) to requested angle
  • Use Servo.h attach and write with a safe angle range
  • Power the servo from a suitable external supply with common GND

Parts and preparation

Uno, small hobby servo, 10 k ohm potentiometer and regulated 5-6 V supply.

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

Libraries for this lesson

In Arduino IDE 2 open Tools → Manage Libraries…. Search by the Library Manager name and install the package by the exact author below. Similar names from other authors can use different APIs and will break the example.

IncludeLibrary Manager nameAuthorInstall note
Servo.hServoArduinoBuilt-in with Arduino AVR Boards. No Library Manager install required.
Servo Motors instructional connection diagram

What a hobby servo is

A hobby servo is a position actuator in a small package. Inside you typically find a DC motor, a reduction gear train, an output shaft with a horn, a feedback potentiometer linked to the shaft, and a control board.

The gears trade speed for torque so a small motor can hold useful loads. Most positional hobby servos travel about 180 degrees (exact travel varies). Continuous-rotation 'servos' are different: they use the same pulse interface for speed/direction, not angle.

Side cutaway of a hobby servo: a small DC motor drives a gear train that turns the output shaft and horn, a feedback pot sits on the same shaft, a control board drives the motor, and a three-wire cable (brown GND, red +V, orange signal) leaves the case
Motor drives gears; pot reports shaft angle; control board closes the loop.

Closed-loop position control

The Arduino does not force the shaft like an open-loop stepper step. It sends a position command. The servo compares that command with the feedback pot reading and drives the motor until the error is small.

If you push the horn by hand, the servo fights back toward the commanded angle (while powered). write() requests a position; it does not wait for, or prove, that the shaft has arrived.

Loop diagram: the pulse from D9 sets a target angle, the control board subtracts the actual angle from the feedback pot to get the error, and drives the motor until the error is zero. Four dials follow write(90) from 30 degrees: error +60, +20, 0, then -30 when the horn is pushed to 120 degrees
Error = requested angle - measured angle. Drive until the error is near zero.

Control signal: pulse width

The signal wire carries a digital pulse train. Frames typically repeat about every 20 ms (50 Hz). The HIGH pulse width encodes the requested angle.

A common mapping is about 1.0 ms for one end, 1.5 ms for centre, and 2.0 ms for the other end. Real servos differ - some need slightly shorter or longer pulses at the ends. Driving past the mechanical stops can stall the motor, draw high current and strip gears.

Three waveform panels showing approximately 1 ms, 1.5 ms and 2 ms HIGH pulses within a 20 ms frame corresponding to end, centre and other end angles
Same frame rate; wider HIGH pulse = larger requested angle (typical mapping).
Typical pulseTypical meaning
~1.0 ms HIGHOne end of travel
~1.5 ms HIGHCentre
~2.0 ms HIGHOther end of travel
Frame ~20 msPulse repeated regularly

Servo.h and write(angle)

Servo.h generates the pulse train for you. arm.attach(9) starts pulses on D9. arm.write(angle) requests an angle in degrees; the library converts that to pulse width.

This lesson maps a potentiometer to a safe range such as 10-170 degrees so the horn is less likely to slam into hard stops. Calibrate limits for your specific servo if you need full travel.

arm.write(90) goes to Servo.h, which makes the pulse on D9, and the servo moves its horn. A dial shows the horn at write(10), write(90) and write(170), the 10 to 170 degree safe range, and end-stop zones at each end
write requests a position. Use a limited range until you know your servo's safe travel.
#include <Servo.h>
Servo arm;
void setup() { arm.attach(9); }
void loop() {
  int angle = map(analogRead(A0), 0, 1023, 10, 170);
  arm.write(angle);
  delay(15);
}

Power and common ground

Servo current rises while moving and can jump sharply when stalled or heavily loaded. USB/Uno 5 V is often not enough and can cause brown-outs, resets and jitter.

Use a regulated external 5-6 V supply sized for the servo (check its stall current). Connect servo red to that +V, brown/black to supply GND, signal to D9, and join supply GND to Uno GND so the pulse has a valid voltage reference.

Uno D9 to the servo's orange signal wire; servo red to a +5-6 V rail from a separate regulated supply; servo brown, supply negative and Uno GND joined on a shared GND rail; the Uno 5V pin is crossed out
Signal from the Uno; motor power from an external supply; GND shared.

Wiring and safe build sequence

Breadboard wiring for 17. Servo Motors
Breadboard layout for this lesson. Match colours and pins before powering the circuit. Click the image for a larger view.
  1. Servo signal -> D9
  2. Servo red -> external regulated +5 V
  3. Servo brown/black -> external GND
  4. External GND -> Uno GND
  5. Pot wiper -> A0; outer legs -> 5 V/GND
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.

Worked sketch

Download .ino
#include <Servo.h>

Servo arm;

void setup() {
  arm.attach(9);
}

void loop() {
  int raw = analogRead(A0);
  int angle = map(raw, 0, 1023, 10, 170);
  arm.write(angle);
  delay(15);
}

How the code works

  1. The 10-170 degree limits reduce the risk of driving against mechanical end stops.
  2. write requests a position; it does not confirm the shaft reached it.
  3. delay(15) gives the servo time to move between small updates without flooding the loop.

Test and record evidence

Expected result: The servo follows the potentiometer smoothly over a safe range.

Practical evidence checklist

Common faults and checks
  • Resetting or jitter usually indicates weak power or missing common GND.
  • Disconnect the mechanical load while testing limits.
  • If travel looks wrong, your servo's us endpoints may differ - stay inside a safe write range first.
Challenge (extension - optional): Add two calibrated angle limits and print the requested angle.

Check your understanding

Q1. Why is a hobby servo called closed-loop?

Show answer

It compares the commanded position with internal feedback and drives the motor to reduce the error.

Q2. What mainly encodes the requested angle on the signal wire?

Show answer

The HIGH pulse width (about 1-2 ms) within a roughly 20 ms frame.

Q3. Why use external power?

Show answer

Movement and stall current can exceed the Uno regulator/USB capability.

Q4. Does write() prove the horn reached the angle?

Show answer

No - it requests a position; the servo tries to get there under its own control loop.

Code Along 12: Servos & Position

Practise in your browser

Write and test this lesson's code on a virtual Uno. Work through the 1 exercises in order. Press Test and each one checks itself.

Exercises passed 0 / 1About 70-90 minOpen as its own page

Telling a servo where to go

An LED takes a brightness and a motor takes a speed. A hobby servo takes a position. You say arm.write(90) and the servo's own electronics drive its motor until the horn is at 90°, then hold it there.

Two lines get you going:

  • #include <Servo.h> at the top, and Servo arm; above setup() - note there are no brackets after the name
  • arm.attach(9); in setup() - this claims the pin and starts the pulses

After that, arm.write(angle) asks for an angle from 0 to 180. Underneath, the library is sending a pulse about every 20 ms; a pulse near 1 ms means one end of the travel, about 1.5 ms means the middle, and about 2 ms means the other end. The board shows you both the angle and the pulse width while the sketch runs.

What the simulator does not model. It shows the angle your sketch *asked for*, the instant you ask for it. A real horn takes roughly 0.2 s to swing 60°, it can be pushed off position by a load, and it draws far more current while it is moving. That is why real sketches wait between steps - and why the servo gets its own power supply.

Three waveform panels showing about 1 ms, 1.5 ms and 2 ms HIGH pulses within a 20 ms frame
Same frame, different pulse width - that is the whole control signal.
Worked example

End to end and back

Press Run and watch the dial. Notice that attached appears as soon as attach() runs, before any angle is written.

#include <Servo.h>

Servo arm;

void setup() {
  Serial.begin(9600);
  arm.attach(9);
}

void loop() {
  arm.write(0);
  Serial.println("0 degrees");
  delay(800);

  arm.write(180);
  Serial.println("180 degrees");
  delay(800);
}

Turn on JavaScript to edit, run and test this code in the browser.

Expected output
0 degrees
180 degrees
0 degrees
  • Servo arm; has no brackets. arm is the name you chose - gate, vent or steering work just as well.
  • On the bench the horn is still travelling when write() returns. The delay(800) is what gives it time to arrive.
Exercise 12.1

Attach and centre

Not started

The shortest useful servo sketch. In setup(), attach the servo to pin 9 and send it to 90° - the middle of its travel. loop() stays empty.

#include <Servo.h>

Servo arm;

void setup() {
  // Attach the servo to pin 9, then centre it

}

void loop() {
}

Turn on JavaScript to edit, run and test this code in the browser.

Safe limits

write(0) and write(180) are the ends of what the *library* will ask for. They are not necessarily the ends of what your servo can reach. Push a horn into its own mechanical end stop and the motor stalls: it buzzes, gets hot, draws a lot of current and can strip its gear train.

So real sketches keep away from the ends. The lesson uses 10° to 170°; the next exercise uses 20° to 160°, which is a sensible starting point for a servo you have not calibrated yet. Once the mechanism is built you find the real limits by hand, slowly, with the load disconnected.

map() does the scaling for you, and it is happy to map straight into a limited range.

Try it on a real Uno

Three things the simulator cannot warn you about, and all three bite on the bench:

  • Power. A servo is a motor. Running it from the Uno's 5 V pin browns the board out and resets it, usually just as you press the button. Give it a regulated 5-6 V supply of its own.
  • Common ground. The supply GND and the Uno GND must be joined, or the pulse has nothing to be measured against and the servo twitches or ignores you.
  • Pins 9 and 10. Servo.h uses the same timer as PWM on those two pins, so analogWrite() on 9 or 10 stops working once any servo is attached. Put LEDs on 3, 5, 6 or 11 instead. The simulator warns you if you try it.

Wire the servo signal to D9, upload Sweep it smoothly, and watch how far behind the commands the horn actually runs. Then add the potentiometer from Lesson 14 and upload The knob steers the servo. This is the actuator the greenhouse vent in Lesson 27 is built around, so the sweep and the safe limits are worth getting into your fingers now.

Want more? Code Along chapter 12 has 5 Deeper exercises that go further than this lesson. They are optional.