ESP32-S3 Lab · Day 23 of 30

Swing an arm
to any angle

Today the board gets an arm. You wire a small servo to one pin, upload a sketch that counts through the angles, and the horn moves from 0° to 180° and back — the same steady nudge an autopilot gives a tiller. The idea underneath is small and worth keeping: a single timed pulse names an angle, and the servo's own circuit drives itself there and holds against a push.

About 25 minutesArduino firstMicroPython optionalNo electronics assumed
Agent assist code TSK-DAY23-SERVOSWEEP

Hand this to an agent so it can pull the lesson packet and coach you step by step.

01 First, know the pieces

What you need

Five things, and one of them moves. Tap Define on any part you haven't met — the answer opens as a field note you can read and dismiss without losing your place.

Official manual photo of the ESP32-S3 development board.
Manual photo

ESP32-S3 board

The brain that runs your uploaded sketch.

Official manual image of the ESP32-S3 GPIO extension board.
Manual photo

GPIO extension board

Spreads the pins into rows you can reach and label.

Official manual illustration of a servo with its three wires labelled 1 signal, 2 power, and 3 ground.
Manual photo

Servo

A small geared motor that turns its horn to the angle you ask for and holds it.

Official manual image of a jumper wire.
Manual photo

3 jumper wires (M/M)

Temporary, solder-free connections.

Official manual screenshot of the Arduino IDE interface.
Manual screenshot

Arduino IDE

Uploads the sketch to the board.

02 Make the physical circuit

Chart the circuit

The official Freenove diagram is your chart — schematic on top, the same circuit built on a breadboard below. Click it to enlarge. The servo's three wires are colour-coded, so the colours are your map.

Official Freenove circuit — C Tutorial, Chapter 17 (Servo), page 154.
Servo signal (orange) GPIO 21 Carries the timed pulse that names the angle.
Servo power (red) 5V Feeds the servo's motor with enough current to move.
Servo ground (brown) GND Gives board and servo the same zero point.

5V for the servo. The manual's own caution: "Use caution when supplying power to the servo, it should be 5V." The red wire goes to the 5V pin with its own jumper — and unplug USB before you move any wire.

03 One action at a time

Build it

This is the main path — you can finish the day without opening a single field note. Tap each step as you go to keep your place.

0 / 8 done
  1. Seat the ESP32-S3 on the GPIO extension board and keep USB unplugged while you wire.

  2. Push a jumper into each of the servo's three sockets — orange, red, and brown.

  3. Run the orange signal wire to GPIO 21.

  4. Run the red wire to 5V and the brown wire to GND.

  5. Press a horn onto the servo's shaft so you can see it move.

  6. Compare every wire to the chart before you plug in USB.

  7. Open Sketch_17.1_Servo_Sweep.ino in Arduino IDE and upload it.

  8. Watch the horn sweep from one end of its travel to the other and back.

The arm answers.

The horn is swinging on your command. Head to Test & debug to confirm the pace and the full 180° reach.

04 Read just enough code

Read the code

The whole sketch is short, and it drives the servo with the same LEDC hardware that faded your LEDs — set to 50 Hz and 12 bits, the timing a servo listens for. The loop counts angles up and down; one map() line turns each angle into a pulse. Switch to MicroPython if you'd rather see the same idea in Python — the wiring never changes.

Sketch_17.1_Servo_Sweep.ino
#define SERVO_PIN 21  //define the pwm pin
#define SERVO_CHN 0   //define the pwm channel
#define SERVO_FRQ 50  //define the pwm frequency
#define SERVO_BIT 12  //define the pwm precision

void servo_set_pin(int pin);
void servo_set_angle(int angle);

void setup() {
  servo_set_pin(SERVO_PIN);
}

void loop() {
  for (int i = 0; i < 180; i++) {
    servo_set_angle(i);
    delay(10);
  }
  for (int i = 180; i > 0; i--) {
    servo_set_angle(i);
    delay(10);
  }
}

void servo_set_pin(int pin) {
  ledcAttachChannel(pin, SERVO_FRQ, SERVO_BIT, SERVO_CHN);
}

void servo_set_angle(int angle) {
  if (angle > 180 || angle < 0)
    return;
  long pwm_value = map(angle, 0, 180, 103, 512);
  ledcWrite(SERVO_PIN, pwm_value);
}
ledcAttachChannel(pin, SERVO_FRQ, SERVO_BIT, SERVO_CHN)Sets GPIO 21 switching at 50 Hz with 12-bit precision — the frame rate a servo expects.
map(angle, 0, 180, 103, 512)Translates degrees into pulse counts — 103 counts for 0° up to 512 counts for 180°.
ledcWrite(SERVO_PIN, pwm_value)Sends a pulse of that width every 20 ms; the servo turns until its shaft matches.

05 Understand, don't memorise

The pulse is the position

A plain motor spins for as long as you feed it current. A servo does something harder — it arrives at an exact angle and holds it against a push. Today is about the one signal that tells it which angle, and how the servo itself does all the work of getting there.

Inside

A motor that knows where it is

The case holds a small motor, reduction gears, a sensor on the shaft, and a control circuit comparing where the shaft is against where you asked.

Beat

A pulse every 20 ms

The signal wire carries one short pulse repeated 50 times a second. Its width is the whole message; the gaps between pulses carry nothing.

Width

Width names the angle

A 0.5 ms pulse asks for 0°, 1.5 ms for the middle, and 2.5 ms for 180° — and every angle between gets its own width.

Hold

The servo closes the gap

Its control circuit drives the geared motor until the shaft sensor reads the angle you asked for, then holds it there against a nudge.

The model angle = pulse width — 0.5 ms is 0° · 1.5 ms is 90° · 2.5 ms is 180°

You name the destination

On Day 22 you set a DC motor's speed and direction and it spun freely for as long as it had power. A servo takes an angle instead — you hand it a position and its own electronics decide how far and how fast to turn, then hold.

Why the timing is visible here

The Arduino Servo library packages all of this into servo.write(90) and keeps the timing out of sight. This sketch drives the LEDC PWM hardware directly — 50 Hz, 12-bit — so the frame rate and the map() line that becomes the pulse width are yours to read.

Where 103 and 512 come from

At 50 Hz and 12 bits, each 20 ms frame is 4096 counts — so 103 counts lasts about 0.5 ms and 512 counts about 2.5 ms, the two ends of the servo's travel.

06 Know it worked

Test & debug

Nothing prints to the screen today — the proof is the horn swinging in front of you.

What you should see
Servo horn
  • The horn glides smoothly from 0° all the way round to 180°.
  • It reverses and glides back, then repeats — about 1.8 seconds each way.
  • The pace is steady, like a metronome.

A soft hum while it moves is normal. Each sweep should be one continuous glide, never a twitchy stutter.

If it doesn't
  • Horn twitches or judders? Power the servo from the 5V pin with its own jumper — a GPIO can't feed a motor.
  • Sweeps once, then jams at an end? The horn is hitting something — pull it off the shaft and refit it clear.
  • No motion at all? Check the orange signal wire on GPIO 21, then the red and brown power pair.
  • Board resets mid-sweep? The USB supply is weak — unplug other loads from the board.

07 Make the idea yours

Try this: post three angles by hand

The sweep hides the mechanism inside a fast counter. Slow it down to three fixed angles and the pulse-width story turns into something you can predict before you upload, then watch confirm itself.

Command three angles

Replace both for loops with three fixed commands — servo_set_angle(0); delay(1000); servo_set_angle(90); delay(1000); servo_set_angle(180); delay(1000);. Now the horn jumps to each position and waits a second before the next.

Predict the pulse width

Before uploading, run each angle through map(angle, 0, 180, 103, 512): 0° gives 103 counts (about 0.5 ms), 90° gives about 308 (1.5 ms), 180° gives 512 (2.5 ms). Note where you expect the horn to point for each.

Confirm the line is straight

Upload and watch. The three stops land evenly spaced — one far end, the exact middle, the other far end — because width maps to angle in a straight line. Halfway in counts is halfway in degrees.

08 Learn it with a hand on the tiller

Coach me through it

Every lesson ships with a code and a machine-readable packet, so an agent can guide you with full context.

Lesson code

TSK-DAY23-SERVOSWEEP

How the agent should behave: teach the servo idea plainly — a pulse's width names an angle and the servo's own circuit drives there and holds — then guide one physical connection at a time, wait for confirmation, explain terms on request, and always check wiring, board, port, and USB before changing code.

Keep your place

Finished Day 23?

Mark it complete — it shows on your course map, and your place is saved on this device.

Field note

Shortcut

Prompt copied