Building and Programming a No-Display STEM Robot¶
Summary¶
This chapter covers the ~$19 no-display STEM robot kit from chassis assembly through autonomous behavior. It explains DC motors, motor drivers, H-bridges, and differential drive -- how independently controlling a left and right motor produces forward motion and turns -- along with battery power, voltage regulation, and safe power switching. It then covers the sensors that give a robot awareness of its environment (time-of-flight distance sensors -- this book's preferred choice for measuring distance -- plus infrared and reflectance sensors for line following) and the behavior-loop programming pattern used to combine sensing and motor control into obstacle avoidance and line-following behaviors, including safety stops. Students finishing this chapter will be able to assemble a robot chassis and program a basic autonomous behavior.
Concepts Covered¶
This chapter covers the following 34 concepts from the learning graph:
- Robot Chassis
- DC Motor
- Motor Driver
- H Bridge
- Motor Polarity
- Wheel Encoder
- Differential Drive
- Left Motor
- Right Motor
- Forward Motion
- Turning Radius
- Motor Speed Control
- Battery Pack
- Rechargeable Battery
- Power Switch
- Voltage Regulator
- Ultrasonic Sensor
- Time Of Flight Sensor
- Distance Measurement
- Obstacle Detection
- Collision Avoidance
- Line Following
- Infrared Sensor
- Reflectance Sensor
- Robot Behavior Loop
- State Based Behavior
- Autonomous Navigation
- Robot Calibration
- Chassis Assembly
- Wheel Alignment
- Robot Testing Arena
- Robot Safety Stop
- Emergency Stop Button
- Motor Stall
Prerequisites¶
This chapter builds on concepts from:
- Chapter 1: Computational Thinking and Debugging for Physical Computing
- Chapter 3: Breadboard Wiring and Electrical Fundamentals
- Chapter 4: Digital I/O, PWM, and the MicroPython Workflow
- Chapter 6: Buttons, Photoresistors, and Analog Sensor Input
Let's Build Something That Moves
Everything up to this point has stayed put on your breadboard. Not anymore. This chapter takes your Pico, straps it to a chassis and a pair of motors, and turns your code into motion you can chase across a table. By the end of it, your ~$19 robot will sense a wall and stop before it hits it -- all on its own. Let's build something!
From Kit to Chassis: Assembling the Robot¶
Every robot in this book starts as a bag of parts, and the first job is turning that bag into a structure everything else can attach to. A robot chassis is the rigid frame or platform that holds a robot's motors, wheels, battery, and electronics in fixed positions relative to each other -- without it, nothing else in this chapter has anywhere to be mounted. Chassis assembly is the hands-on process of physically building that frame: attaching motor mounts, standoffs, and the platform layers according to the kit's instructions, before a single wire gets connected.
One assembly detail matters more than it looks like it should: wheel alignment is the process of mounting both drive wheels so they are parallel to each other and perpendicular to the chassis, with no twist or lean in either wheel. A misaligned wheel doesn't just look wrong -- it actively fights the other wheel, pulling the robot off a straight line even when both motors are told to spin at exactly the same speed, which makes every later behavior (driving straight, following a line, stopping at a measured distance) harder to get right than it needs to be.
Berry's Tip
Before you mount a single wire, spin each wheel by hand and sight down the chassis from the front. A wheel that visibly leans in or out is worth five minutes of re-mounting now -- it'll save you an hour of "why does my robot curve when I told it to go straight" debugging later.
How a Robot Moves: DC Motors and H-Bridges¶
The motion itself comes from a DC motor -- a motor that spins continuously in one direction when a direct-current voltage is applied across its two terminals, and spins in the opposite direction when that voltage is reversed. A DC motor by itself is simple, almost dumb: give it power, it spins; that's the whole interface. The complexity lives in what controls the power reaching it.
A Pico's GPIO pins can safely supply only a few milliamps -- nowhere near enough current to spin a motor, and reversing a pin's voltage doesn't reverse a motor's direction the way a motor needs. A motor driver is a small chip or board that sits between the Pico's low-current logic signals and the motor's higher-current power needs, amplifying a weak control signal into something strong enough to actually turn the motor. Inside nearly every motor driver used in these kits is an H bridge: an arrangement of four electronic switches wired in the shape of the letter H, with the motor connected across the crossbar. By closing different pairs of those four switches, an H-bridge can route current through the motor in either direction, stop current entirely (motor coasts), or actively resist motion (motor brakes) -- all from simple logic-level signals a Pico can safely produce.
That direction-reversing trick is exactly how a motor driver controls motor polarity -- which of a DC motor's two terminals is positive and which is negative at any given moment, and therefore which way the motor spins. Code never physically swaps wires; it tells the H-bridge which pair of internal switches to close, and the H-bridge handles the polarity reversal electrically.
from machine import Pin, PWM
left_forward = PWM(Pin(2))
left_backward = PWM(Pin(3))
left_forward.freq(1000)
left_backward.freq(1000)
def left_motor(speed):
# speed: -100 (full reverse) to 100 (full forward)
duty = int(abs(speed) / 100 * 65535)
if speed >= 0:
left_forward.duty_u16(duty)
left_backward.duty_u16(0)
else:
left_forward.duty_u16(0)
left_backward.duty_u16(duty)
This left_motor() function drives one motor through two PWM-controlled pins, left_forward and left_backward, that connect to two of the H-bridge's four internal switch pairs. Passing a positive speed sends a PWM signal only to left_forward, closing the switch pair that spins the motor forward; a negative speed does the reverse. The duty calculation converts a simple -100-to-100 speed number into the 0-65535 duty-cycle range the Pico's PWM hardware expects, so a larger magnitude produces a stronger average voltage and a faster spin.
Berry's Gentle Warning
Never send both left_forward and left_backward a nonzero signal at the same time. That commands the H-bridge to fight itself -- both switch pairs trying to drive current in opposite directions through the same motor -- and it's a fast way to overheat the driver chip. A motor stall happens when a motor is powered but physically prevented from turning, whether by an electrical short like this, a jammed wheel, or too much load; a stalled motor draws far more current than a spinning one and can overheat the motor or its driver within seconds. Always let one direction's signal drop to zero before commanding the other.
Now that H-bridges, motor drivers, and polarity have all been explained in prose, the diagram below lets you click through an H-bridge's four internal switches and watch how each combination changes the motor's behavior.
Diagram: H-Bridge Motor Driver Explorer¶
Run the H-Bridge Motor Driver Explorer MicroSim fullscreen
H-Bridge Motor Driver Explorer (MicroSim)
Type: microsim
sim-id: h-bridge-motor-driver-explorer
Library: p5.js
Status: Specified
Learning objective: Students will analyze (Bloom L4: Analyze) how an H-bridge's four switch positions determine a DC motor's direction, braking, or stall condition.
Canvas: 700x460px, responsive -- recompute the H-bridge schematic's scale from windowWidth in windowResized() so the diagram shrinks rather than clipping below 500px wide.
Layout: a schematic H-bridge drawn in the shape of the letter H using p5.js line() and rect() primitives -- four labeled switches (S1 top-left, S2 bottom-left, S3 top-right, S4 bottom-right) at the four corners of the H, a motor symbol (circle with "M") on the crossbar, and power rail (+) at top, ground rail at bottom. Each switch is drawn as a clickable toggle (open gap or closed line). A small animated arrow inside the motor circle shows current direction and spins visually when the motor is "on."
Controls: four createButton() elements, one per switch (S1-S4), toggling that switch open/closed; a createButton() labeled "Preset: Forward" that sets S1+S4 closed, S2+S3 open; a createButton() labeled "Preset: Reverse" that sets S2+S3 closed, S1+S4 open; a createButton() labeled "Preset: Brake" that closes S1+S2 (or S3+S4); a createButton() labeled "Preset: Stall (Danger)" that closes all four switches simultaneously.
Interaction: the motor symbol's arrow animates clockwise for the Forward preset, counter-clockwise for Reverse, freezes immediately for Brake, and for the Stall preset the entire H-bridge schematic flashes red with a text warning "Both directions active -- overcurrent risk!" reusing the safety warning from the surrounding chapter text. Clicking any individual switch button that creates an invalid same-side-closed combination (e.g., S1 and S2 both closed) triggers the same red-flash stall warning. Hovering any switch shows a tooltip naming which transistor pair it represents.
Implementation: p5.js. Switch states stored as four booleans in an array; current path and motor rotation direction computed each frame from the boolean combination via a small lookup table (forward / reverse / brake / stall / off). Redraw the full schematic every frame from state. Use windowResized() to rescale all drawing coordinates as fractions of canvas width/height rather than fixed pixels.
"Motor driver" and "H-bridge" so far have been generic terms -- the poster below grounds them in three real driver chips you're likely to find in an actual kit.
Diagram: Motor Driver ICs Compared¶
Run the Motor Driver ICs Compared poster fullscreen
Motor Driver ICs Compared (reused poster)
Type: interactive-infographic-poster
sim-id: motor-drivers
Status: Reused
Source: https://dmccreary.github.io/learning-micropython/posters/motor-drivers/
Source Repo: https://github.com/dmccreary/learning-micropython/tree/main/docs/posters/motor-drivers
Reused from the companion book Learning MicroPython's poster catalog. Names three real H-bridge driver chips (L293D, DRV8833, TB6612FNG) behind this section's generic "motor driver" concept, with voltage, current, and cost specifics.
Steering Without Wheels That Turn: Differential Drive¶
A car steers by physically turning its front wheels. Most two-wheeled robots in this book's price range have no steering mechanism at all -- both wheels always point straight ahead. Instead, they steer using differential drive: a drive system where two independently controlled motors, one per side, produce turning by running at different speeds rather than by pivoting any wheel.
To make that concrete, this book (and most kit documentation) refers to the left motor and right motor as the two independently addressable motors on either side of the chassis -- each with its own pair of driver pins, exactly like the left_motor() function above. When both motors spin forward at matching speed, the robot produces forward motion: straight-line movement with no turning. The moment the two motors' speeds differ, the robot curves toward whichever side is spinning slower, and the tightness of that curve is described by its turning radius -- the radius of the circular arc the robot's path traces, where a smaller turning radius means a sharper turn. Set one motor to a positive speed and the other to the same magnitude but negative, and the turning radius shrinks to zero: the robot pivots in place around a point exactly between its two wheels.
Reliably producing a specific speed -- not just "fast" or "slow," but a controlled, repeatable value -- is called motor speed control, and it's exactly what the PWM duty cycle in the left_motor() function above provides: a higher duty cycle delivers more average voltage to the motor, which spins it faster. Some higher-end robot kits add a wheel encoder -- a sensor mounted on a wheel or motor shaft that reports how far (or how fast) the wheel has actually rotated, typically by counting stripes or slots as they pass a small optical or magnetic sensor. Where PWM duty cycle only tells a motor how hard to try, a wheel encoder tells the program what actually happened, which matters because two "identical" motors rarely spin at exactly the same real-world speed even when given the exact same duty cycle -- a wheel encoder lets code detect and correct that mismatch instead of just hoping for the best.
def drive(left_speed, right_speed):
left_motor(left_speed)
right_motor(right_speed)
drive(80, 80) # straight forward
drive(80, 20) # gentle right turn -- large turning radius
drive(80, -80) # pivot in place -- turning radius of zero
The drive() function is the standard differential-drive interface used throughout the rest of this chapter: it takes one speed value for each motor and lets every other behavior -- obstacle avoidance, line following -- express itself purely in terms of what the left and right motors should do, without needing to know anything about H-bridges or PWM duty cycles underneath.
Berry's Key Insight
Differential drive means every single robot behavior in this chapter -- driving straight, curving gently, spinning in place -- boils down to just two numbers passed into drive(). Once that clicks, "programming a robot" mostly becomes "deciding what those two numbers should be right now," which is a much smaller problem than it first sounds like.
Before moving on to power and sensing, it helps to actually watch how two speed values become a curved path rather than just reading the code.
Diagram: Differential Drive Path Simulator¶
Run the Differential Drive Path Simulator MicroSim fullscreen
Differential Drive Path Simulator (MicroSim)
Type: microsim
sim-id: differential-drive-path-simulator
Library: p5.js
Template: https://github.com/dmccreary/stem-robots/tree/main/docs/sims/differential-drive-simulator
Status: Specified
Learning objective: Students will predict (Bloom L3: Apply) a two-wheeled robot's path -- straight, curved, or pivoting -- from a chosen pair of left and right motor speed values.
Canvas: 700x480px, responsive -- recompute the drawing area and top-down robot icon scale from windowWidth/windowHeight in windowResized().
Layout: a top-down view of an open arena (light gray background with a faint grid) containing a simple top-down robot icon (rounded rectangle body with two wheel rectangles). As the simulation runs, the robot icon moves and turns according to the current left/right speed values, leaving a fading trail line showing its path. A live readout above the arena displays "Left: [value] | Right: [value] | Turning radius: [value or 'straight' or 'pivot']".
Controls: two createSlider() elements, "Left Motor Speed" and "Right Motor Speed" (range -100 to 100, default 80 each); a createButton() labeled "Reset Position" that returns the robot icon to the center and clears the trail; three createButton() presets labeled "Straight," "Gentle Turn," and "Pivot in Place" that set both sliders to the matching example values used in the surrounding chapter text.
Interaction: moving either slider updates the robot's curving motion live and the turning-radius readout recalculates continuously from the ratio of the two speeds. When both sliders are set to equal magnitude but opposite sign, the robot visibly pivots around its own center and the readout displays "pivot -- radius 0." Clicking the robot icon itself pauses the simulation and opens a small infobox reading "Speed difference between wheels is what creates a turn -- not steering."
Implementation: p5.js. Robot pose (x, y, heading) updated each frame using a simple differential-drive kinematics formula: forward speed as the average of left/right speed, angular velocity as proportional to their difference divided by an assumed wheel-base width constant. Trail stored as a capped-length array of past positions, drawn with decreasing opacity toward the older end. Arena bounds wrap or clamp so the robot never permanently leaves the visible canvas.
Differential drive is only one way to make a robot move -- the poster below shows where it fits among four other common drive configurations, including some you may meet again in a future robotics project.
Diagram: Robot Drive Configurations¶
Run the Robot Drive Configurations poster fullscreen
Robot Drive Configurations (reused poster)
Type: interactive-infographic-poster
sim-id: robot-configurations
Status: Reused
Source: https://dmccreary.github.io/learning-micropython/posters/robot-configurations/
Source Repo: https://github.com/dmccreary/learning-micropython/tree/main/docs/posters/robot-configurations
Reused from the companion book Learning MicroPython's poster catalog. Places this chapter's differential drive alongside four-wheel drive, tricycle/Ackermann, mecanum, and legged configurations, for context on why this book's robot steers the way it does.
Powering the Robot Safely¶
None of this moves without power, and a mobile robot can't stay tethered to a wall outlet. A battery pack is a housing that holds one or more battery cells wired together to supply the robot's motors and electronics from a single pair of leads. Most kits in this price tier use a rechargeable battery -- typically a lithium-ion or NiMH pack -- specifically because a robot that gets tested, adjusted, and re-tested dozens of times in a single class period would burn through disposable batteries fast enough to blow a classroom budget.
A power switch is a simple mechanical switch wired directly into the battery's power path, letting a builder cut power to the entire robot with one flip rather than unplugging a battery connector every time. Get in the habit of using it: it protects the connector from wear and, more importantly, it's the fastest way to fully de-energize a robot that's doing something unexpected.
Motors, motor drivers, and the Pico itself often need different voltages to run correctly, and a battery's voltage drifts as it discharges. A voltage regulator is a small component that takes a variable input voltage (like a battery that's anywhere from fully charged to nearly dead) and outputs a steady, fixed voltage regardless of that input's fluctuation, protecting sensitive electronics like the Pico from ever seeing more voltage than they're rated for.
Berry's Gentle Warning
Battery safety is not a place to improvise. Always connect a battery pack's polarity exactly as the kit documentation shows -- reversed polarity can destroy a voltage regulator, a motor driver, or the Pico itself instantly, with no warning first. Use the power switch to power the robot fully off before you change any wiring, and never leave a rechargeable battery charging unattended.
Sensing Distance: Ultrasonic and Time-of-Flight¶
A robot that only drives forward is a robot that eventually hits something. Two sensor types give it the ability to notice what's ahead before that happens. An ultrasonic sensor measures distance by emitting a short burst of high-frequency sound above human hearing range and timing how long the echo takes to bounce back off a nearby object -- the same basic principle a bat uses to navigate in the dark. A time-of-flight sensor measures distance using the same core idea, timing, but with a pulse of infrared light instead of sound, timing how long that light takes to travel out to an object and reflect back.
This book prefers time-of-flight sensors over ultrasonic ones for STEM robot projects: light travels far faster and in a narrower beam than sound, so time-of-flight readings tend to be quicker, more precise, and less prone to picking up stray echoes off nearby walls or other robots sharing the same testing space. Both sensor types produce the same kind of output, though -- a distance measurement, a single number representing how far away the nearest object in front of the sensor is, usually reported in centimeters or millimeters.
from machine import Pin, time_pulse_us
import time
trigger = Pin(6, Pin.OUT)
echo = Pin(7, Pin.IN)
def read_distance_cm():
trigger.low()
time.sleep_us(2)
trigger.high()
time.sleep_us(10)
trigger.low()
duration = time_pulse_us(echo, 1, 30000)
return (duration / 2) / 29.1
This function shows the ultrasonic timing pattern directly: it pulses the trigger pin HIGH for 10 microseconds to fire a sound burst, then time_pulse_us() measures how long the echo pin stays HIGH while it waits for that burst's reflection to return. Dividing by 2 accounts for the sound traveling to the object and back (a round trip), and dividing by 29.1 converts a travel time in microseconds into a distance in centimeters, based on the known speed of sound. A time-of-flight sensor module typically hides this timing math inside its own driver library and simply returns a distance value directly, but the underlying idea -- measure elapsed time, convert it to distance -- is the same for both.
A raw distance measurement only becomes useful once a program decides what to do with it. Obstacle detection is the act of comparing a distance measurement against a threshold to decide whether something is close enough to count as an obstacle, using exactly the threshold-value pattern from the previous chapter. Collision avoidance is the broader robot behavior built on top of that detection: reacting to a detected obstacle by stopping, slowing, or steering around it, rather than continuing forward blindly.
SAFE_DISTANCE_CM = 15
while True:
distance = read_distance_cm()
if distance < SAFE_DISTANCE_CM:
drive(0, 0) # stop -- obstacle detected
else:
drive(80, 80) # path clear -- keep driving
SAFE_DISTANCE_CM is the calibrated threshold value that separates "keep driving" from "obstacle detected," and drive(0, 0) sends a zero speed to both motors, which is the simplest possible collision-avoidance response: stop completely rather than attempt to steer around.
Now that ultrasonic sensing, time-of-flight sensing, and threshold-based obstacle detection have all been defined, the diagram below lets you drive a simulated robot toward a wall and adjust its safe distance in real time.
Diagram: Ultrasonic and Time-of-Flight Obstacle Avoidance Simulator¶
Run the Ultrasonic and Time-of-Flight Obstacle Avoidance Simulator MicroSim fullscreen
Ultrasonic and Time-of-Flight Obstacle Avoidance Simulator (MicroSim)
Type: microsim
sim-id: obstacle-avoidance-simulator
Library: p5.js
Template: https://github.com/dmccreary/learning-micropython/tree/main/docs/sims/collision-avoidance-flowchart
Status: Specified
Learning objective: Students will apply (Bloom L3: Apply) a calibrated safe-distance threshold to trigger a simulated robot's obstacle avoidance and safety stop before it reaches a wall.
Canvas: 700x460px, responsive -- recompute arena and robot icon scale from windowWidth in windowResized().
Layout: a side-view arena showing a robot icon on the left and a wall on the right, with a numeric "Distance to wall" readout above the arena updating live as the robot moves. A shaded "safe zone" band is drawn on the floor at the current threshold distance from the wall.
Controls: a createSlider() labeled "Safe Distance Threshold (cm)" (range 5-50, default 15); a createButton() labeled "Drive Forward" that starts the robot moving toward the wall at a constant simulated speed; a createButton() labeled "Reset" that returns the robot to its starting position; a createCheckbox() labeled "Simulate Sensor Noise" that, when checked, adds small random jitter to the displayed distance reading (reusing the noise-filtering idea from the previous chapter) without changing the robot's true position.
Interaction: as the robot drives forward, its distance readout counts down; the moment it crosses the threshold line, the robot icon visibly stops and a text banner reads "Obstacle detected -- safety stop." If "Simulate Sensor Noise" is checked and the threshold is set very close to the wall, occasional noisy readings can cause a visibly late stop, letting students discover experientially why a threshold needs a safety margin. Clicking the wall opens an infobox explaining time-of-flight versus ultrasonic sensing, matching the chapter's stated preference for time-of-flight sensors.
Implementation: p5.js, robot position advanced each frame by a fixed step while a driving boolean is true, with the true distance computed geometrically each frame and an optional noise term added before display. Threshold comparison drives both the stop logic and the safe-zone band's rendering, recomputed from the slider value every frame so the two always stay visually consistent.
Following a Line: Infrared and Reflectance Sensors¶
Obstacle avoidance answers "is something in my way?" A different sensing job -- staying on a path -- calls for a different sensor entirely. An infrared sensor in this context shines infrared light downward at the floor and measures how much of it bounces back, which is exactly what makes it useful as a reflectance sensor: a sensor whose reading depends on how reflective the surface beneath it is, since a light-colored floor bounces much more infrared light back than a dark line painted or taped onto it.
Line following is the robot behavior built on that reflectance difference: using one or more reflectance sensors mounted along the bottom-front of the chassis to continuously detect a dark line against a light floor (or vice versa) and steer to keep that line centered beneath the sensors as the robot moves forward.
left_sensor = Pin(10, Pin.IN)
right_sensor = Pin(11, Pin.IN)
while True:
left_on_line = left_sensor.value()
right_on_line = right_sensor.value()
if left_on_line and right_on_line:
drive(70, 70) # centered -- go straight
elif left_on_line and not right_on_line:
drive(30, 70) # drifting right -- correct left
elif right_on_line and not left_on_line:
drive(70, 30) # drifting left -- correct right
else:
drive(0, 0) # line lost
This loop reads two reflectance sensors, mounted slightly left and right of center, as simple digital values -- 1 when a sensor is over the dark line, 0 when it's over the light floor (the exact polarity depends on the specific module). When both sensors agree the robot is centered, it drives straight; when only one sensor reports the line, the robot slows the motor on that side to steer back toward center, using exactly the differential-drive drive() function from earlier in this chapter.
Before moving from sensing into behavior programming, it's worth seeing this steering correction happen continuously along a curving line rather than just at one instant.
Diagram: Line Following Reflectance Sensor Simulator¶
Run the Line Following Reflectance Sensor Simulator MicroSim fullscreen
Line Following Reflectance Sensor Simulator (MicroSim)
Type: microsim
sim-id: line-following-reflectance-simulator
Library: p5.js
Status: Specified
Learning objective: Students will analyze (Bloom L4: Analyze) how a pair of reflectance sensor readings determines a line-following robot's steering correction at each moment along a curving path.
Canvas: 700x480px, responsive -- recompute the drawn line path and robot icon scale from windowWidth/windowHeight in windowResized().
Layout: a top-down arena with a dark curving line pre-drawn on a light floor (an S-curve or similar simple path). A robot icon with two small sensor indicator dots (left and right) drives along the path automatically once started, each dot lighting up green when it detects the line beneath it and gray otherwise. A live readout beneath the arena shows the current sensor state pair (e.g., "Left: ON, Right: OFF -- correcting right") matching the branches of the pseudocode in the surrounding chapter text.
Controls: a createButton() labeled "Start/Pause" toggling the robot's automatic movement along the path; a createSlider() labeled "Base Speed" (range 20-100, default 70) scaling the robot's forward progress speed; a createButton() labeled "Reset to Start" that returns the robot to the path's beginning; a createSelect() dropdown labeled "Path Shape" with options "S-Curve," "Zig-Zag," and "Loop" that swaps the pre-drawn line path.
Interaction: as the robot drives, each of the four sensor-state branches from the chapter's code (both on, left only, right only, both off) is visually distinguished by a different robot icon border color, and the matching branch in a small on-screen pseudocode panel is highlighted in sync, echoing the flowchart/pseudocode highlighting pattern from Chapter 1. Clicking anywhere on the drawn line path teleports the robot to that point along the line for quick testing of a specific curve section.
Implementation: p5.js, path stored as an array of points defining a spline curve; robot position advanced along an interpolated point on the curve each frame, with simulated sensor readings computed by checking whether each sensor dot's offset position (left/right of the robot's centerline) falls within a threshold distance of the nearest path point. Recompute path and robot scaling in windowResized().
Programming Robot Behavior¶
Every behavior demonstrated so far -- stop-before-wall, follow-the-line -- shares the same underlying shape: read a sensor, decide, command the motors, repeat. Formalizing that shape is what turns a collection of separate code snippets into an actual robot program. A robot behavior loop is the main while True: loop that structures a robot's entire program around exactly that repeating cycle -- sense, decide, act -- running continuously for as long as the robot is powered on.
Real robot behavior loops rarely run just one behavior at a time, though. State-based behavior organizes a robot's program around named states -- like "Driving," "Avoiding," or "Following Line" -- where the current state determines which sensing-and-acting logic actually runs on a given pass through the loop, exactly like the state machine concept introduced back in Chapter 1. Combine a well-structured behavior loop with state-based behavior and the result is autonomous navigation: a robot moving through and reacting to its environment entirely under its own program's control, with no human providing moment-to-moment driving input.
Berry's Key Insight
Notice that "autonomous" doesn't mean "unpredictable." A well-written state-based behavior loop is completely deterministic -- the exact same sensor readings will always produce the exact same motor commands. If your robot ever seems to behave randomly, the sensor readings feeding it are almost always less consistent than they look, not the code.
Calibration and Testing¶
A robot behavior that works perfectly in one spot on one floor can fail somewhere else, for the same reason a photoresistor threshold from the last chapter can fail in a different room. Robot calibration is the process of testing a robot's sensors and behaviors under its actual operating conditions and adjusting thresholds, speeds, or timing values to match -- the exact same discipline as sensor calibration, just applied to a whole robot rather than one component. A distance threshold tuned on a carpeted floor may need retuning on smooth tile, since carpet can absorb sound and infrared differently than a hard surface.
Doing that testing reliably needs a consistent place to do it. A robot testing arena is a defined, repeatable physical space -- a taped-off section of floor, a table with walls, a printed line-following course -- used specifically for testing robot behavior under controlled, repeatable conditions, so a change in behavior between two test runs can be attributed to the code change rather than to a randomly different environment.
Now that state-based behavior, autonomous navigation, and calibration have all been introduced, the table below organizes the behaviors built earlier in this chapter by the states and sensors each one relies on.
| Behavior | Primary Sensor | Key States | Calibrated Value |
|---|---|---|---|
| Collision avoidance | Time-of-flight or ultrasonic | Driving, Stopped | Safe distance threshold |
| Line following | Infrared reflectance (left/right) | Centered, Correcting-left, Correcting-right, Line-lost | Reflectance on/off threshold |
| Autonomous navigation | Multiple, combined | Idle, Driving, Avoiding, Following-line | Per-behavior thresholds |
Safety First: Stopping a Robot¶
A robot that can drive on its own also needs a reliable way to stop on command -- not just when its own logic decides to, but whenever a human needs it to, immediately. A robot safety stop is any mechanism that halts a robot's motors regardless of what its current behavior state says it should be doing, overriding the normal behavior loop entirely rather than waiting for the loop's next decision point. The most direct implementation of a safety stop is an emergency stop button -- a dedicated physical button, wired to trigger an interrupt handler, whose entire job is setting a flag that the behavior loop checks (or that immediately halts the motors from within the interrupt itself) no matter what the robot was doing a moment before.
from machine import Pin
estop_button = Pin(20, Pin.IN, Pin.PULL_UP)
emergency_stop = False
def estop_handler(pin):
global emergency_stop
emergency_stop = True
estop_button.irq(trigger=Pin.IRQ_FALLING, handler=estop_handler)
while True:
if emergency_stop:
drive(0, 0)
continue
# ... normal behavior loop logic goes here
estop_button.irq(trigger=Pin.IRQ_FALLING, handler=estop_handler) registers estop_handler as an interrupt handler -- the exact concept introduced in the previous chapter -- that runs the instant the button's pin transitions from HIGH to LOW, rather than waiting for the main loop to get around to checking it. Because the check for emergency_stop sits at the very top of the loop, no other behavior logic below it ever gets a chance to command the motors once the flag is set, which is what makes this a true override rather than just one more competing behavior.
You've Got This!
If your first obstacle-avoidance test ends with your robot bonking gently into a book, that's not a failed robot -- that's a threshold that needs recalibrating, and now you know exactly which number to adjust. Every robot in this book's classrooms has bonked into something at least once during testing. That's what the testing arena is for.
Bringing It Together¶
You now have a complete, if simple, autonomous robot: a chassis that holds together, motors that move it under precise differential-drive control, sensors that notice walls and lines, a behavior loop that ties sensing to action, and a safety stop that can override all of it instantly. That's the full sense-think-act cycle from Chapter 1, now running on wheels. The next chapter builds directly on this same chassis and behavior-loop pattern, adding an OLED display so your robot can show a face, react with expressions, and give the people around it a much richer sense of what it's "thinking" as it drives.
You Unlocked a Superpower!
Look at that -- a robot that senses, decides, and moves entirely on its own, with a safety stop ready if it ever needs one. That's berry impressive engineering. Let's build something with a face on it next. See you in Chapter 8!