Learning Goals 5 min
Two servos + a tiny mount + a phone holder = a remote-controlled camera you can pan and tilt from across the room. Drive it from a physical joystick (potentiometer × 2) or from a phone webpage. Last of the three L3 builds. By the end of this lesson you will:
- Wire two SG90 servos in a pan-tilt configuration: one rotates the base (pan), one tilts the top platform (tilt).
- Control both with two pots (analog joystick) and apply slew-limiting + dead-zone (from L03-04) for smooth motion.
- Add a WiFi web UI with a touch-pad control so the phone can frame the shot remotely.
Warm-Up 10 min
Hardware:
- 2 × SG90 (or MG90S — better gears).
- Plastic pan-tilt bracket (sold cheaply as "FPV camera mount") or hand-build from cardboard / 3D-print.
- Phone or small camera as "payload" — anything under 100 g works with SG90.
- 2 × potentiometers for the analog joystick.
- ESP (for the WiFi version) or UNO (for pots-only).
- External 5 V supply for the servos (4 × AA pack) — UNO 5V can't supply two servos at once.
Plan the axes
Pan = horizontal rotation. The base servo turns left/right. Tilt = vertical rotation. The top servo tilts up/down. Mount the tilt servo on top of the pan servo so the tilt axis moves with the pan.
New Concept · Two-axis control with smoothing 20 min
Wire-up summary
| Wire | UNO / ESP pin |
|---|---|
| Pan servo signal | D9 |
| Tilt servo signal | D10 |
| Servo + 5 V | 4 × AA pack + |
| Servo GND + UNO GND + battery GND | common GND |
| Pan pot wiper | A0 |
| Tilt pot wiper | A1 |
The slew-limited mapping
Same pattern as L03-04 (indicator needle), now applied to two axes:
#include <Servo.h>
const int PAN_PIN = 9, TILT_PIN = 10;
const int PAN_MIN = 20, PAN_MAX = 160; // mechanical limits
const int TILT_MIN = 30, TILT_MAX = 150;
const int MAX_DEG_PER_SEC = 90; // slew limit
Servo pan, tilt;
int currentPan = 90, currentTilt = 90;
unsigned long lastSlewAt = 0;
int slewTowards(int current, int target, int dtMs) {
int maxStep = (long)MAX_DEG_PER_SEC * dtMs / 1000;
if (maxStep < 1) maxStep = 1;
int diff = target - current;
if (diff > maxStep) diff = maxStep;
if (diff < -maxStep) diff = -maxStep;
return current + diff;
}
void setup() {
pan.attach(PAN_PIN);
tilt.attach(TILT_PIN);
pan.write(90);
tilt.write(90);
lastSlewAt = millis();
}
void loop() {
unsigned long now = millis();
int dt = now - lastSlewAt;
lastSlewAt = now;
int rawPan = analogRead(A0);
int rawTilt = analogRead(A1);
int targetPan = constrain(map(rawPan, 0, 1023, PAN_MIN, PAN_MAX), PAN_MIN, PAN_MAX);
int targetTilt = constrain(map(rawTilt, 0, 1023, TILT_MIN, TILT_MAX), TILT_MIN, TILT_MAX);
currentPan = slewTowards(currentPan, targetPan, dt);
currentTilt = slewTowards(currentTilt, targetTilt, dt);
pan.write(currentPan);
tilt.write(currentTilt);
delay(20); // ~50 fps update
}Without slew limiting, fast pot moves cause the servos to snap, jarring the camera. With it, the camera glides smoothly even if the pots are flicked.
WiFi version — a touch-pad in the browser
Replace the pots with a small HTML touch pad that POSTs ?pan=N&tilt=M. The ESP receives, sets targetPan / targetTilt, and the slew limiter does the rest. The touch pad is just a square <div> on the page; JS reads touch X/Y and maps to angles.
Worked Example · Pots-driven version 25 min
Step 1 — assemble the mount
Mount pan servo to a heavy base (taped to a brick / wood block — anything that won't tip). Glue tilt servo on top of pan servo's horn, perpendicular axis. Tape the phone/camera to the tilt horn.
Step 2 — upload the §3 sketch
Step 3 — calibrate the mechanical limits
Manually pan to far left / far right while watching for the gears straining. Note the angles where the servo starts to bind; set PAN_MIN / PAN_MAX 5° inside those points. Same for tilt.
Step 4 — joystick test
Turn pot A — pan moves smoothly. Turn pot B — tilt moves smoothly. Flick both fast simultaneously — slew limiter keeps them gentle. The camera tracks like a real director-controlled rig.
Step 5 — add a return-to-centre button
Button on D2 with INPUT_PULLUP. When pressed, sets targets to 90, 90. Useful as a "home" pose after wild panning.
Step 6 — record a panning shot
Point camera at a scene. Slowly turn pan from one end to the other while filming. The slew limiter produces a smooth cinematic pan — much nicer than a hand-held attempt.
Basic 5 min
Goal: Add a centre dead-zone on the pots so the camera doesn't drift while you're "not touching" (raw 480..540 → centre, ignore).
Challenge 1 5 min
Goal: Add a "sweep" mode — button press triggers an auto-pan from PAN_MIN to PAN_MAX over 10 seconds, then returns. Useful for unattended timelapse.
Challenge 2 5 min
Goal: WiFi touch-pad version. ESP serves a webpage with a <div> that captures touch / mouse drag; JS POSTs the X/Y as pan/tilt percentages. Replace the pot inputs with the values from the most recent POST.
Challenge 3 · The slow slew 10 min
The sketch says MAX_DEG_PER_SEC = 90. So a full pan from 20° to 160° should take about 1.6 seconds. Time it with the pot flicked from one end to the other. It takes much longer.
- Each pass of
loop()takes about 20 ms. Work outmaxStepby hand:(long)MAX_DEG_PER_SEC * dtMs / 1000, using whole-number maths. - From that, calculate the real top speed in degrees per second.
- Calculate how long the 140° pan really takes. Check it against your stopwatch.
- Fix the sketch so the servos really move at 90°/s. Keep the slew limit and the mechanical limits.
It works if a flicked full pan takes about 1.6 seconds, and slow pot turns still glide smoothly.
Recap 5 min
Pan-tilt = two servos + slew-limited targets + dead-zones + mechanical limits in software. Pots for hand control; phone touch-pad for remote. The slew limiter (L03-04) is the difference between "jerky toy" and "cinematic shot". Three L3 builds done. Last few lessons: power budgeting, schematic reading III, recap.
- Pan / tilt
- Horizontal rotation (pan) + vertical rotation (tilt). The standard 2-DOF camera mount.
- Slew limit
- Maximum rate of change per second. Keeps motion smooth even when the input is jumpy.
- Mechanical end-stop
- The physical angle at which the servo can't turn further. Forcing past = stripped gears. Clamp in software with
constrain(). - Centre dead-zone
- Input range near the middle that's treated as "exactly centre". Stops jitter.
- External servo power
- A separate 4–6 V supply for the servos so the microcontroller doesn't brown out under peak load. Common ground to the controller.
- Cinematic pan
- A slow, steady camera rotation — the visual signature of professional video. Achieved here with the slew limiter.
Extra Mission 5 min
Part 1 — Design a two-axis gadget
Two servos at right angles can point anything anywhere. Design a gadget that uses a pan-tilt mount. Ideas: a desk light that follows where you are reading, a robot head that looks around a room, a small solar panel that turns to face the brightest light.
Your design must include:
- The gadget's name and what sits on the tilt arm.
- What decides where it points: pots, a web page, light sensors or a timed sweep.
- The pan and tilt limits in degrees, and why.
- The top speed in degrees per second, and why that speed suits the job.
- How the servos get power, with the shared ground shown.
Part 2 — Make it
Build your gadget on the pan-tilt mount with the fixed slew code from Challenge 3. Use the input your design chose. Keep the software limits and the separate servo battery.
Bring back next class: your design, your uploaded sketch, and a 30-second video of the gadget aiming. Also bring a multimeter for L03-46.