Learning Goals 5 min
Yesterday you learned how pulse-position control commands an angle. Today you stop thinking about pulses and let the library do it. By the end of this lesson you will be able to:
- Use the four core members of
Servo:attach(),write(),read()anddetach()— and explain when to call each one. - Wire and command an SG90 from a UNO with the correct pin, the correct power and the correct decoupling — and recognise the symptoms when one of those is wrong.
- Write a tiny test sketch that drives the servo to three named positions (LEFT, CENTRE, RIGHT) using
constvalues and a short serial-monitor input — your reusable "servo bench-test rig".
Warm-Up 10 min
Pull out your SG90. If you took the horn off for last lesson's homework, leave it off for the very first sketch — that way you can verify the shaft is moving without anything attached to fight against.
Sketch-without-running game
Look at the sketch below. Before uploading it, write in your notebook what you predict the servo will physically do after power-up:
#include <Servo.h>
Servo s;
void setup() {
s.attach(9);
s.write(0);
delay(500);
s.write(180);
delay(500);
s.write(90);
}
void loop() { }Reveal
On power-up: the shaft snaps to 0° (full one way), waits half a second, snaps to 180° (full the other way), waits half a second, snaps to 90° (centre), and then stays there forever because loop() is empty. If you watch carefully you'll see a brief twitch right after upload before the first write(0) takes effect — that's the moment attach() begins sending pulses at the servo's last-known position.
New Concept · The four members you actually use 25 min
Step 1 · Include the library
Servo is bundled with the Arduino IDE — you don't need to install anything. Add this one line at the top of your sketch:
#include <Servo.h>The angle brackets (vs quotes) tell the compiler "look in the standard library folder, not next to my sketch".
Step 2 · Create a Servo object
Above setup(), declare a variable of the Servo type:
Servo myServo;You can name it anything — panServo, tiltServo, lid, steering — but the type is always Servo (capital S). Each physical servo needs its own object. We'll use multiple in L03-04.
Step 3 · attach(pin)
Inside setup(), tell the object which pin its signal wire is on:
myServo.attach(9);What attach does behind the scenes: it sets up a hardware timer to start generating the 50 Hz pulse signal on pin 9. From this moment, the servo will hold whatever position it's currently at, until you tell it otherwise.
Step 4 · write(angle)
This is the command you'll call most:
myServo.write(90);Pass an integer 0–180. The library adjusts its outgoing pulse width to match. The function returns immediately — it doesn't wait for the servo to physically arrive at the new angle. You'll need to budget 3 ms per degree of travel as a rough guide for a slow SG90 — i.e. a sweep from 0° to 180° takes about 540 ms.
Step 5 · read()
Returns the last angle you wrote (not the angle the servo is actually at — there's no feedback to the Arduino from the internal pot):
int target = myServo.read(); // returns 90, the last value we wroteThis is useful when one part of your code needs to know what another part has commanded — e.g. a sweep function asking "where am I right now?" before deciding the next step.
Step 6 · detach() — and why you might want it
Stops generating the pulse on the signal pin. The servo relaxes (no holding torque) and the pin can be used for other things:
myServo.detach();Two reasons you might use it:
- Save power. An idle (no command) SG90 draws ~6 mA; a holding (commanded) one draws ~25 mA constantly. For battery projects this matters.
- Stop the servo's "hold whine". Cheap servos buzz audibly while holding. Detaching kills the buzz when you don't need precise position.
Pin gotcha — which pins can attach?
On a UNO you can attach a servo to any digital pin, but starting the Servo library disables analogWrite() on pins 9 and 10 (it commandeers their timer). If you also need PWM on pin 9 for an LED later, move the servo to a different pin first.
| Pin | UNO behaviour with Servo attached |
|---|---|
| 0 / 1 (RX/TX) | Avoid — they collide with serial upload. |
| 2–8, 11–13 | Fine. PWM unaffected on other pins. |
| 9 | Fine for the servo. analogWrite(9) stops working. |
| 10 | Fine for the servo. analogWrite(10) stops working. |
Worked Example · Bench-test rig 25 min
Today's build is the small reusable test rig you'll use every time you wire a new servo. It commands three named positions — LEFT, CENTRE, RIGHT — based on a character typed in the Serial Monitor.
Step 1 — wiring
| SG90 wire | UNO pin |
|---|---|
| Brown (GND) | GND |
| Red (+5 V) | 5V |
| Orange (Signal) | D9 |
Optional: a 100 µF electrolytic cap between the 5V and GND rails (long leg to +5 V) helps if the UNO resets when the servo moves.
Step 2 — the sketch
// L03-02 · Servo bench-test rig
// Type 'l', 'c' or 'r' in the Serial Monitor to command the servo
// to LEFT (0°), CENTRE (90°) or RIGHT (180°). Prints the new target.
#include <Servo.h>
const int SERVO_PIN = 9;
const int LEFT_ANGLE = 0;
const int CENTRE_ANGLE = 90;
const int RIGHT_ANGLE = 180;
Servo myServo;
void setup() {
Serial.begin(9600);
myServo.attach(SERVO_PIN);
myServo.write(CENTRE_ANGLE); // park at centre on boot
Serial.println("# Bench-test ready. Type l / c / r + Enter.");
}
void loop() {
if (Serial.available() == 0) return;
char cmd = Serial.read();
int target;
switch (cmd) {
case 'l':
case 'L': target = LEFT_ANGLE; break;
case 'c':
case 'C': target = CENTRE_ANGLE; break;
case 'r':
case 'R': target = RIGHT_ANGLE; break;
default: return; // ignore newlines, spaces, etc.
}
myServo.write(target);
Serial.print(">> ");
Serial.print(cmd);
Serial.print(" -> ");
Serial.print(target);
Serial.println(" deg");
}Step 3 — upload and test
Open the Serial Monitor at 9600 baud with line ending set to "Newline". You should see the "Bench-test ready" line and the servo should be at 90°.
Type l + Enter — the servo snaps to 0° and you see:
>> l -> 0 deg
Then r + Enter — 180°. c + Enter — 90°. Any other letter is ignored (the default case in the switch).
Step 4 — observe the "snap"
Switch from l to r rapidly (no pauses). The servo moves at its natural maximum speed — it's not interpolating, it's slewing as fast as the motor can drive the gears, which on an SG90 is about 0.10 s per 60° (so ~0.30 s end-to-end). Listen for the brief whine while moving and the quieter hold-buzz once it arrives.
Step 5 — try read()
Add a 6th case to the switch (use ? as the trigger):
case '?':
Serial.print("Last commanded angle: ");
Serial.println(myServo.read());
return;After typing r, type ?. You see Last commanded angle: 180. Confirms read() returns the most recent write() argument — not what the servo is physically doing.
Step 6 — try detach()
Add another case:
case 'x':
myServo.detach();
Serial.println("Servo detached. Try moving the horn by hand.");
return;After typing x, the holding torque drops to zero — you can now nudge the horn with your finger and it stays where you put it. Press c to re-attach (in this minimal sketch we re-attach by writing again — strictly we'd call attach(9) too; try it both ways and see).
Save this sketch as servo-bench.ino. Open it whenever you receive a new servo to verify it works before integrating it into a project.
Try It Yourself 15 min
Goal: Change the three named angles to 30°, 90° and 150° (a narrower sweep). What surprises you about the "feel" of the rig with a smaller travel range? Why might a real product (a key in a lock, a window latch) use a narrow range rather than the full 0°–180°?
Hint & discussion
The smaller sweep is faster (less distance to travel) and quieter (no end-stop bumps). Real products often limit the sweep to the actually-needed mechanical range — saving wear on the gears and avoiding the "crash into the wall" sound when the servo hits its limit. The smart-bin lid in L02-26 only used 0°–90° for exactly this reason.
Goal: Replace the three discrete positions with continuous control. Read a number 0–180 typed in the Serial Monitor (use Serial.parseInt()) and pass it directly to write(). Validate the input so anything < 0 or > 180 is rejected with an error message.
Hint
if (Serial.available() == 0) return;
int target = Serial.parseInt();
if (target < 0 || target > 180) {
Serial.println("# rejected (must be 0..180)");
return;
}
myServo.write(target);
Serial.print(">> "); Serial.print(target); Serial.println(" deg");parseInt() waits for digits then returns the number it built. It returns 0 if it times out — that's why "0" is a valid input we accept. For stricter input handling, see L01-26.
Goal: Add a cooldown: ignore any new command that arrives within 200 ms of the previous one. This prevents you from queueing up a long burst of commands the servo can't physically follow.
Hint
unsigned long lastWrite = 0;
const unsigned long COOLDOWN_MS = 200;
void loop() {
if (Serial.available() == 0) return;
if (millis() - lastWrite < COOLDOWN_MS) {
// throw away pending input during cooldown
while (Serial.available()) Serial.read();
Serial.println("# cooldown — try again in a moment");
return;
}
// ... rest of the command handler ...
lastWrite = millis();
}The "throw away pending input" line matters: if you don't drain Serial, the queued bytes pile up and the rig reacts late.
Mini-Challenge · Build a re-usable header 10 min
You'll use this rig many times. Refactor the constants into a small header file so future sketches can #include them.
- In your sketch folder, create
servo-config.hwith this content:
// servo-config.h — pin and angle constants shared between sketches
#ifndef SERVO_CONFIG_H
#define SERVO_CONFIG_H
const int SERVO_PIN = 9;
const int LEFT_ANGLE = 0;
const int CENTRE_ANGLE = 90;
const int RIGHT_ANGLE = 180;
#endif- In your main
.inofile, replace the top-of-file constants with#include "servo-config.h"(note: quotes, not angle brackets — quotes mean "look next to my sketch"). - Upload. The behaviour should be identical, but your main sketch is now 4 lines shorter.
Why this matters: next lesson we'll write a sweep sketch and a fade-style sketch — both will need the same pin number and angle range. A shared header keeps them consistent. You'll see the same pattern when we wrap a sensor into a library in L03-41.
Recap 5 min
Three lines of boilerplate, three core methods. #include <Servo.h>, Servo s;, s.attach(pin), s.write(angle). The library hides every gory detail of pulse-width timing. read() tells you the last commanded angle (not the physical one). detach() relaxes the servo. PWM on pins 9 and 10 is collateral damage on the UNO — plan your pin map accordingly. You now have a bench-test rig ready for every servo you'll meet in the next 46 lessons.
#include <Servo.h>- Imports the built-in Arduino servo library. Angle brackets = standard / library location; quotes = same folder as your sketch.
Servo(the type)- A C++ class. Each instance represents one physical servo and remembers its pin assignment and last-commanded angle.
attach(pin)- Starts generating the 50 Hz pulse signal on the given pin. Disables
analogWrite()on pins 9 and 10 on the UNO. write(angle)- Sets the target angle in degrees (0–180). Returns immediately — the servo takes ~3 ms per degree of travel.
read()- Returns the last value passed to
write(). There is no physical-position feedback to the Arduino. detach()- Stops the pulse signal. Servo relaxes — no holding torque, no current draw.
- Decoupling capacitor
- A small (typically 100 µF) capacitor placed across a power rail close to a noisy device. Smooths brief current spikes so the rail doesn't sag and cause brownouts.
- Bench-test rig
- A minimal sketch + wiring you use to verify a component works in isolation before integrating it into a project. The discipline that turns "why isn't this working?" into "which of these five known-good parts is the new problem?".
Homework 5 min
Three to do at home.
- Save the bench-test rig with your homework header (
servo-bench.ino) somewhere you'll find it again — this is the first reusable utility of L3. - Type-and-trace. Open your L02-26 Smart Bin Lid sketch. Annotate, in pencil, every
Servolibrary call (attach,write) with a one-line comment explaining what is happening internally. Use the table from §3 as a reference. - Predict: in tomorrow's lesson we'll sweep the servo smoothly using a
forloop. Write a one-line guess of what the sketch will look like, ahead of the lesson. Don't look it up — your guess is correct enough if you wrotefor,write(i)and a smalldelay. We'll compare notes.
Bring back next class:
- Your
servo-bench.inofile (working). - Your annotated L02-26 sketch.
- Your one-line guess at tomorrow's sweep sketch.