Project: Your Robot
Time: 135 min per session
Background: The Integration Challenge
Components work alone; systems fail together. The integration iceberg means the happy path is visible, but hidden issues lurk below: timing conflicts, resource contention (e.g., I2C bus shared by OLED and IMU), motor noise corrupting ADC readings, and race conditions. Defensive patterns help: validate sensor readings (reject impossible values), timeout operations (fail-safe if incomplete), and use a watchdog timer (reset if main loop hangs). Always test edge cases: line lost, obstacle sudden, battery low.
Lab Setup
Connect your Pico via USB, then from the picobot/ directory:
Verify in REPL: from picobot import Robot; Robot() → "Robot ready!"
First time? See the full setup guide.
Learning Objectives
By the end of this lab you will be able to:
- Apply concepts from previous labs (sensors, control, state machines) to a new problem
- Define clear success criteria for a project
- Design a state machine for a chosen application
- Implement and test a complete embedded system project
- Document design decisions and trade-offs
You will choose or define a project, design success criteria, sketch a state machine, and build incrementally. The lab is about integration and project discipline.
Introduction
This lab provides time to design and build a project that combines concepts from the course: motor control, sensor feedback, control algorithms, state machines, and code organization. You will choose from several project options or propose your own.
Why This Lab Matters
This is where integration happens. Real embedded systems are not a single feature — they are multiple subsystems working together. You will practice defining success criteria, designing behavior as a state machine, and managing trade-offs under real constraints.
Choose Your Project
Option A: Enhanced Delivery Robot
Extend the State Machines delivery robot with new capabilities: - Multiple delivery zones (A, B, C) - Return to start automatically - Avoid obstacles on the track - Speed optimization (fastest delivery time) - Status display on LEDs (progress indicator)
Option B: Maze Solver
Make the robot navigate a simple maze: - Right-hand rule (always turn right at walls) - Left-hand rule (always turn left) - Dead-end detection and backtracking - Record the path and optimize on second run
Option C: Remote Control
Add wireless control via Bluetooth or IR: - Control motors with buttons/joystick - Display sensor readings on phone - Switch between manual and autonomous modes - Record and replay movements
Option D: Musical Robot
Create a performance robot: - Play songs with buzzer while dancing - React to obstacles with sounds - Light show synchronized with music - Rhythm-based movement patterns
Option E: Your Own Idea
Something we haven't thought of!
Requirements: - Uses at least 3 things you learned (sensors, control, state machine, etc.) - Has clear success criteria - Achievable in one lab session
⚡Hands-on tasks
Design Your Project
✅ Task 1 - Define Your Project
Project name: _____
One-sentence description: _____
Success criteria (how will you know it works?): - [ ] Criterion 1: ___ - [ ] Criterion 2: ___ - [ ] Criterion 3: _____
State machine sketch:
Checkpoint
You have a project name, a one-sentence description, at least 3 testable success criteria, and a state machine sketch with named states and transition conditions. Each criterion is binary (pass/fail), not vague ("works well").
Starter Template
Don't start from a blank file! Copy this skeleton and modify it:
# PROJECT: [Your Project Name]
# Description: [One sentence]
# Author: [Your name]
from picobot import Robot
import time
# Configuration
BASE_SPEED = 80
KP = 32
OBSTACLE_DISTANCE = 15
LOOP_PERIOD_MS = 20
# Initialization
robot = Robot()
print("Calibrating IMU...")
robot.imu.calibrate()
# Non-blocking distance (from Timing & Ultrasonic)
last_distance_check = 0
DISTANCE_CHECK_INTERVAL = 100
cached_distance = 100
# State machine
state = "IDLE"
state_start = time.ticks_ms()
def enter_state(new_state):
global state, state_start
print(f"[{time.ticks_ms():6d}] {state} → {new_state}")
state = new_state
state_start = time.ticks_ms()
robot.leds.show_state(state)
def time_in_state():
return time.ticks_diff(time.ticks_ms(), state_start)
def update_distance():
global cached_distance, last_distance_check
now = time.ticks_ms()
if time.ticks_diff(now, last_distance_check) >= DISTANCE_CHECK_INTERVAL:
cached_distance = robot.read_distance()
last_distance_check = now
return cached_distance
# Main loop
print("\nReady! Press Enter to start...")
input()
enter_state("START")
try:
while True:
update_distance()
# YOUR STATE MACHINE GOES HERE
if state == "START":
pass # TODO: What happens first?
elif state == "YOUR_STATE":
pass # TODO: Add your states
elif state == "DONE":
robot.stop()
robot.set_leds((0, 255, 0))
print("Complete!")
break
time.sleep(LOOP_PERIOD_MS / 1000)
except KeyboardInterrupt:
robot.stop()
robot.leds_off()
print("\nStopped by user")
✅ Task 2 - Start Building
Copy the template and start building!
Expected output when the template runs successfully (before you add your own states):
Checkpoint
Your first core feature works in isolation. For example, if your project is a maze solver, the robot can detect a wall and turn away from it. You tested this single behavior at least twice before moving on.
Stuck?
If the template runs but your robot does nothing, check that you replaced the pass placeholders in the state machine with actual actions. A common mistake is writing code outside the while True loop -- it runs once at startup and never again. All recurring behavior must be inside the loop, inside a state.
Tips for Building
Start simple: 1. Get the core functionality working first (just one feature) 2. Then add features one at a time 3. Test after EVERY addition
Use what you know:
# State machine pattern (State Machines)
state = "START"
# Non-blocking pattern (Timing & Ultrasonic)
if timer.ready():
do_slow_thing()
# Line following (Seeing the Line)
robot.line_follow_step(speed, kp)
# Precise turns (Precise Turns)
robot.turn_degrees(90)
Debug as you go: - Print statements are your friend - Use LEDs to show state (different colors = different states) - Log data if behavior is confusing
Expected output from a well-instrumented state machine during a run:
[ 234] IDLE → START
[ 235] START → FOLLOW
[ 1842] FOLLOW → JUNCTION
[ 2355] JUNCTION → TURN
[ 2890] TURN → DELIVER
[ 5102] DELIVER → DONE
Complete!
Timestamps help you diagnose timing issues -- if a state lasts too long or too short, the numbers tell you immediately.
Common Pitfalls
These are the problems students hit EVERY semester. Learn from their pain:
Common Pitfalls
Pitfall 1: Too Many Features At Once - Symptom: "I tried to add everything and now nothing works" - Fix: Add ONE feature → Test → Add next feature → Test
Pitfall 2: Forgot Non-Blocking Pattern - Symptom: "My robot stutters" or "Line following is jerky" - Fix: Use the non-blocking pattern from Ultrasonic & Timing for ANY slow operation
Pitfall 3: State Machine Doesn't Transition - Symptom: "It gets stuck in one state forever" - Fix: Is the transition condition EVER true? Add a print to check!
Pitfall 4: Works On Table, Fails On Track - Symptom: "It worked perfectly when I tested by hand!" - Fix: Test on the ACTUAL track early and often
Integration Checklist
Before combining features, verify each works independently:
Core Functions: - [ ] Line following works alone - [ ] Turns work alone (accurate to ±5°) - [ ] Obstacle detection works alone - [ ] LEDs work alone
Timing: - [ ] Loop runs at expected rate - [ ] Slow operations use non-blocking pattern - [ ] No operations take >50ms
State Machine: - [ ] All states have actions defined - [ ] All transitions have conditions - [ ] No "impossible" states - [ ] State transitions are logged
Reliability: - [ ] Works 3 times in a row - [ ] Recovers from line lost - [ ] Handles obstacles without crashing
Checkpoint
Multiple features work together in sequence. Your state machine transitions correctly between at least 3 states, and the robot completes its task end-to-end. Running it 3 times in a row produces consistent results.
Stuck?
If individual features work but break when combined, the most common cause is timing conflicts -- two features both trying to control the motors in the same loop iteration. Make sure only the active state sends motor commands. Also check that you are not blocking the loop with time.sleep() calls longer than your loop period. If the robot "forgets" sensor readings between states, verify that your sensor update code runs every iteration (outside the state-specific blocks), not only inside one state.
Document Your Project
✅ Task 3 - Document Your Work
Write a brief description:
What does it do?
What was the hardest part?
What would you improve with more time?
Add a header to your code:
# MY PROJECT: [Name]
# Description: [What it does]
# Key features: [Feature 1], [Feature 2], [Feature 3]
# Author: [Your name]
Preparation for Demo Day
Checklist: - [ ] Project works reliably (3+ successful runs) - [ ] Code is organized and commented - [ ] You can explain how it works - [ ] You know what parameters to adjust - [ ] Batteries are charged - [ ] Backup of code saved
Practice your demo: 1. Setup (30 sec): Place robot, explain what it will do 2. Run (60 sec): Show it working 3. Explain (60 sec): Key technical decisions 4. Questions (30 sec): Answer one question
Recap
Integration is where most bugs appear. Clear success criteria prevent endless tweaking, and state machines keep complex projects manageable.
Need Ideas?
Quick Wins: - Add sound effects to delivery robot - Make LEDs show distance (closer = more red) - Add "dance" celebration when delivery completes
Medium Challenge: - Two-destination delivery (A then B then home) - Speed run mode (optimize for fastest time) - "Scared robot" that backs away from hands
Hard Mode: - Learn the track (first run slow, second run fast) - Follow a moving target (using ultrasonic) - Self-calibrating Kp (measure and adjust)
Go Build!
You have the skills. You have the tools. Make something cool.