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 $3 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.
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.
Try It Yourself 15 min
Goal: Add an LED on D6 that's ON whenever the lid is OPEN_ or OPENING. Visual indicator that the bin is "active".
Hint
One line in loop() after the state-transition block:
digitalWrite(6, (state == OPENING || state == OPEN_) ? HIGH : LOW);Don't forget pinMode(6, OUTPUT) in setup.
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.
Hint
Replace the one-shot lid.write(OPEN_ANGLE) in OPENING's entry with a per-loop interpolation:
case OPENING: {
unsigned long elapsed = millis() - stateEnteredAt;
int angle = map(min(elapsed, MOTION_MS), 0, MOTION_MS,
CLOSED_ANGLE, OPEN_ANGLE);
lid.write(angle);
if (elapsed >= MOTION_MS) enterState(OPEN_);
break;
}Update enterState not to call lid.write for OPENING and CLOSING — the per-loop interpolation handles motion. Bump MOTION_MS to 1000.
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".
Hint
unsigned int openCount = 0;
// in enterState(OPENING):
openCount++;
Serial.print(">> OPENING — count: "); Serial.println(openCount);For a long-running count that survives power cycles, you'd save the count to EEPROM (lesson L02-39/40). For now, in-RAM is fine.
Mini-Challenge · 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.
Homework 5 min
Build, photograph, share. The smart bin is the most "showable" project so far in L2 — it's the first one a non-programmer would actually want. Your homework is to physically build a usable version and document it.
- Mount the servo on the rim of a real container (small cardboard box, a wide cup, an old shoebox — anything).
- Tape a piece of cardboard (or thin foam, or stiff paper) to the servo horn as the lid. Trim it so the lid covers most of the opening when at 0° and is fully out of the way at 90°.
- Mount the HC-SR04 above and slightly in front of the opening, pointed upward. Make sure the lid's motion arc isn't in the sensor's cone.
- Run the sketch. Drop in a piece of paper, a tissue, an eraser — anything bin-sized. See the lid open, see it close again 2 seconds later.
- Take three photos: lid closed, lid mid-open, lid fully open with hand in frame. Optionally a 5-second phone video of one full open/close cycle.
Then write a short reflection (3–5 sentences):
- One thing about the build that surprised you (positively or negatively).
- One improvement you'd make for a v2 (different sensor, different motor, different timing, etc.).
- What real consumer product could be built on this same pattern? Think for 2 minutes — there are dozens.
Bring back next class:
- Your 3 photos (and optional video).
- Your written reflection.
- Your
hw-l02-26.inosketch. - Cluster E (LCDs) starts next lesson — same data, different output medium.