Learning Goals 5 min
- Wire and control your first servo motor — the SG90 — using the built-in
Servolibrary. Move it to specific angles (0° closed, 90° open). - Combine the L02-23 distance helper with the servo to build a hands-free rubbish bin: lid opens when a hand approaches within 15 cm, closes after a 2-second timeout.
- Add a state-machine layer (CLOSED, OPENING, OPEN, CLOSING) that guarantees clean motion — no flapping, no half-open, no surprise close while a hand is still nearby.
Warm-Up 10 min
Cluster D ends with a real thing: a small servo that opens a paper or cardboard "lid" when your hand approaches. The product question is "should the bin lid be open right now?" and the answer is a state machine: closed by default, open when a hand is near, closed again after the hand leaves, with a buffer so it doesn't slam shut prematurely.
Quick predictions
Before any wiring, predict three things:
- What should happen if a hand sits motionless at 10 cm for a long time? (Lid stays open? Closes anyway?)
- What if the sensor briefly loses the hand (timeout reading) but the hand was clearly there a moment ago?
- What angle range will the servo need? (Lids that open 90° vs 180° feel very different.)
One reasonable answer set
- Stay open — humans hover their hand to drop something. Closing on them is rude (and could crush a finger on a real bin).
- Hold the open state for a couple of seconds before deciding to close. The last-seen timestamp matters more than the current reading.
- 0° (lid down) to 90° (lid vertical, fully open). 180° puts the lid behind the bin — visually weird.
New Concept · Servos and four-state motion 25 min
What an SG90 servo is
The SG90 is the classroom workhorse: a tiny plastic gearbox + DC motor + position sensor + control circuit all in one cheap package. Three wires:
| Wire colour | Role | Connect to |
|---|---|---|
| Brown (or black) | GND | GND rail |
| Red | +5 V | +5 V rail |
| Orange (or yellow / white) | Signal | D11 (any digital pin, PWM preferred) |
You don't talk to it with analogWrite directly — instead, use the built-in Servo library which handles the precise PWM timing the servo expects.
On the breadboard what you build
On paper what engineers draw
The minimum-viable servo sketch
#include <Servo.h>
Servo lid;
void setup() {
lid.attach(11); // signal wire on D11
lid.write(0); // closed at 0°
}
void loop() {
lid.write(90); // open
delay(2000);
lid.write(0); // closed
delay(2000);
}Three new APIs:
Servo lid;— creates a servo object.lid.attach(11)— binds it to pin 11. Do this once in setup.lid.write(angle)— commands the servo to a target angle (0–180). The servo takes a fraction of a second to physically get there.
Power warning
The SG90 is small but it pulls real current when it's moving — up to 500 mA in short bursts. The UNO's 5 V regulator is rated for 500 mA total, including the rest of your circuit. For demo purposes one SG90 is usually fine from the UNO's 5 V. For two or more servos, or a servo plus other power-hungry parts, run the servo's red wire from a separate 4 × AA battery pack or a regulated supply, and tie all the GNDs together. We'll meet this issue again in L3.
The state machine
For the smart bin, four states cleanly capture the behaviour:
| State | Servo | Exits to | When |
|---|---|---|---|
| CLOSED | at 0° | OPENING | hand seen within 15 cm |
| OPENING | commanded to 90°, settling | OPEN | after 300 ms (motion time) |
| OPEN | at 90° | CLOSING | 2 seconds have passed since hand was last seen |
| CLOSING | commanded to 0°, settling | CLOSED | after 300 ms (motion time) |
Four states, four transitions, all driven by either a sensor event or a time deadline. This is the simplest interesting state machine — once you can write this one, you can write any of them.
Worked Example · The full smart bin 25 min
Step 1 — wiring
| Component | Pin |
|---|---|
| HC-SR04 TRIG | D9 |
| HC-SR04 ECHO | D10 |
| SG90 signal (orange) | D11 |
| SG90 red | +5 V |
| SG90 brown | GND |
Plus +5 V and GND for the HC-SR04. Tape a 5 × 2 cm strip of cardboard to the servo horn as the "lid". Mount the servo on the rim of a small box or cup so the cardboard lid sits flat across the opening.
Step 2 — the sketch
Save as smart-bin.ino:
// L02-26: Smart Bin Lid
// Lid opens when a hand approaches within 15 cm, holds open until
// the hand has been gone for 2 s, then closes. State machine:
// CLOSED -> OPENING -> OPEN -> CLOSING -> CLOSED.
//
// Pins: TRIG=D9 ECHO=D10 SERVO=D11
#include <Servo.h>
const int TRIG = 9;
const int ECHO = 10;
const int SERVO_PIN = 11;
const int OPEN_TRIGGER_CM = 15;
const int CLOSED_ANGLE = 0;
const int OPEN_ANGLE = 90;
const unsigned long MOTION_MS = 300;
const unsigned long HOLD_MS = 2000;
Servo lid;
enum State { CLOSED, OPENING, OPEN_, CLOSING };
State state = CLOSED;
unsigned long stateEnteredAt = 0;
unsigned long lastHandSeenAt = 0;
unsigned long lastRead = 0;
float readDistanceCm() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
unsigned long w = pulseIn(ECHO, HIGH, 25000);
if (w == 0) return -1;
float cm = w / 58.0;
if (cm < 2 || cm > 400) return -1;
return cm;
}
void enterState(State next) {
state = next;
stateEnteredAt = millis();
switch (next) {
case OPENING: lid.write(OPEN_ANGLE);
Serial.println(">> OPENING");
break;
case CLOSING: lid.write(CLOSED_ANGLE);
Serial.println(">> CLOSING");
break;
case OPEN_: Serial.println(">> OPEN");
break;
case CLOSED: Serial.println(">> CLOSED");
break;
}
}
bool handNearby() {
return (millis() - lastHandSeenAt) < HOLD_MS;
}
void setup() {
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
lid.attach(SERVO_PIN);
lid.write(CLOSED_ANGLE);
Serial.begin(9600);
Serial.println("# Smart Bin Lid armed.");
}
void loop() {
// 1. Sensor read (every 100 ms)
if (millis() - lastRead >= 100) {
lastRead = millis();
float d = readDistanceCm();
if (d > 0 && d < OPEN_TRIGGER_CM) {
lastHandSeenAt = millis();
}
}
// 2. State transitions
switch (state) {
case CLOSED:
if (handNearby()) enterState(OPENING);
break;
case OPENING:
if (millis() - stateEnteredAt >= MOTION_MS) enterState(OPEN_);
break;
case OPEN_:
if (!handNearby()) enterState(CLOSING);
break;
case CLOSING:
if (millis() - stateEnteredAt >= MOTION_MS) enterState(CLOSED);
break;
}
}The naming trick: OPEN_ with an underscore avoids a name collision with the C++ keyword OPEN. Likewise CLOSE_ isn't needed because we used CLOSED, which isn't reserved.
Step 3 — upload, wait for armed message
Open Serial Monitor. After the "armed" line the lid should physically move to its closed position and stay there. If it twitches at boot, the start angle and the servo's rest position were different — that's normal, it's just snapping to the commanded position.
Step 4 — wave a hand
Approach the sensor with your hand within 15 cm. You should see:
>> OPENING >> OPEN ... (hand leaves) ... ... (2-second hold) ... >> CLOSING >> CLOSED
The lid rises smoothly, holds while your hand is present, holds for 2 more seconds after you remove the hand, then drops back. A clean four-state cycle.
Step 5 — test the "hover" case
Now hold your hand at 12 cm and don't move it. The lid should stay OPEN — every loop iteration the sensor sees the hand, updates lastHandSeenAt, and handNearby() keeps returning true. Move your hand away → 2 seconds later the lid closes.
Step 6 — test the "brief drop-out" case
Wave your hand IN front of the sensor, then quickly OUT of range, then back IN within 1 second. The lid should never close — the 2-second hold from each detection keeps it open. This is the "don't close on me while I'm fishing for the bin" behaviour we wanted.
Basic 5 min
Goal: Add an LED on D6 that's ON whenever the lid is OPEN_ or OPENING. Visual indicator that the bin is "active".
Challenge 1 5 min
Goal: Make the lid open more slowly — over 1 second — by sweeping from CLOSED_ANGLE to OPEN_ANGLE in small steps in the OPENING state. Same for closing.
Challenge 2 5 min
Goal: Add a counter that tracks how many times the lid has opened since power-on. Print it in each "OPEN" transition. Useful metadata for a real product — "your bin has been opened 47 times today".
Challenge 3 · Ship the smart bin 10 min
Apply the "ship-ready" polish from L02-12 / L02-20 to today's sketch:
- Top-of-file spec. Already in the example sketch — make sure it actually matches your final behaviour.
- All thresholds named at the top. No magic numbers in the state-machine logic.
- Boot sequence: physically demonstrate the working state on power-up — sweep the lid from CLOSED → OPEN → CLOSED once to prove the servo is wired right and you've attached the cardboard the right way around.
- Status print every 5 seconds while in OPEN_ state — "still open, waiting for hand to leave". Helps you debug if a stuck reading is keeping the lid open.
- Mount the assembly on something real. A small cardboard box, an old yoghurt pot — whatever you have. Take a photo or short video of the working lid.
Done when:
- A classmate (not a teacher) can drop a piece of paper in the "bin" hands-free.
- The lid never closes while their hand is still over the sensor.
- The lid never randomly opens with no hand nearby.
- The serial output is clean — only state changes plus the optional 5-second status line.
Recap 5 min
Cluster D ends with the Smart Bin Lid — a four-state state machine driving a servo, fed by the distance helper from L02-23. The new primitives were the Servo library (attach(), write(angle)) and the "last-seen timestamp + hold time" idiom: instead of asking "is there a hand right now?" we ask "has there been a hand in the last 2 seconds?". That little shift turns a flappy bin into a usable product. The architectural lesson stays the same: sensor function returns a clean number, state machine decides what to do, output layer drives the actuator. Cluster E starts tomorrow with the 16×2 LCD — putting the same kind of state on a screen instead of a serial monitor.
- Servo motor
- A small motor with a built-in position sensor and controller that lets you command an angle (0–180°) and have it physically rotate there. Three wires: GND, +5V, signal.
- SG90
- The cheapest hobby servo — ~10 g, draws ~250 mA average / 500 mA peak, runs from 5 V. The default classroom servo.
Servolibrary- Built into the Arduino IDE. Handles the precise pulse timing servos need. API:
Servo s; s.attach(pin); s.write(angle); - State machine
- A program organised around a small fixed set of named states (CLOSED, OPENING, OPEN_, CLOSING) and the rules for transitioning between them. Tools:
enum,switch, transition function. - State entry function
- A helper like
enterState(next)that bundles state change + side effects (servo move, log line) so the transitions stay tidy. - Last-seen timestamp
- Instead of checking a sensor's current value to decide on action, store the timestamp of the last positive reading and check whether it was recent. Smooths over brief sensor dropouts.
- Hold time
- The grace period after the trigger condition disappears, during which we still treat it as "active". Stops the system from flapping on every momentary drop-out.
- Power budget
- The total current your circuit draws vs what the regulator can supply. One SG90 from a UNO's 5 V is fine; multiple servos or motors need a separate power rail.
Extra Mission 5 min
Part 1 — Design a hands-free helper
Pick an everyday object people touch with dirty or busy hands. Ideas: a soap dispenser, a pet-food tub, a toy box, a sweet jar. On paper, design how a distance sensor and a servo could open or move it for you.
Your design must include:
- A name and a one-sentence job for the object.
- The trigger distance in cm and the hold time in seconds.
- The servo's closed angle and open angle.
- A state list, such as CLOSED → OPENING → OPEN → CLOSING, with what causes each change.
- One safety rule, such as "never close while a hand is near".
Part 2 — Make it
Build a cardboard model of your object. Mount the servo and the HC-SR04 on it. Adapt today's state-machine sketch to your angles, distances and timings. Test it ten times in a row and fix any false openings.
Bring back next class: your design sheet, your hw-l02-26.ino sketch, and a short video of one full open-and-close cycle.