L4 · PAI-110

GPIO, Peripherals & the Control Loop

Write your first no_std Rust firmware: own the rover's peripherals, blink a GPIO LED, and drive the motors from the control loop to reach the goal.

01
Challenge

Try this first — before any explanation.

The Bench opens on firmware whose control() only blinks the LED — the rover sits still. Press Compile & Run: it builds real Rust to WASM and the rover doesn't move. Your job: make this firmware drive to the green pad.

The Bench

Write no_std embedded Rust against the rover HAL. It compiles to WASM and runs as the real control loop.

! { loop {} }\nuse physical_ai_hal::*;\n\n// control() runs every control period (like a timer interrupt). Right now it\n// only blinks the LED — the rover never moves. Make it drive to the goal pad.\nfn control(p: &mut Peripherals) {\n let dist = p.goal.distance(); // metres to the goal\n let _bearing = p.goal.bearing(); // radians, + = goal is to your LEFT\n p.led.set(dist > 0.2); // GPIO: status LED on while still driving\n\n // TODO: steer toward the goal (use _bearing) and drive forward.\n p.motors.set(0.0, 0.0);\n}\ncontrol_loop!(control);\n","task":{"goal":[1.2,1.2],"time":12.0,"tol":0.2},"title":"GPIO, Peripherals & the Control Loop"}">
PYTHON · NUMPY · IN-BROWSER

GPIO, Peripherals & the Control Loop

Write no_std embedded Rust against the rover HAL. It compiles to WASM and runs as the real control loop.

02
Model

The idea, built visually.

On the Python tier you called set_motor(...). What ran underneath? A control loop on a chip, talking to motor and sensor peripherals it OWNS. In Rust that ownership is literal: you take() the Peripherals once, as a value — the compiler then guarantees no two parts of your code fight over the same motor. The loop runs every tick like a heartbeat: read sensors, decide, drive. The same embodied loop you've known since the blocks tier — now you're writing the metal that runs it.

▣ Stage animation: Peripherals fly out of a chip as labelled ownership tokens (motors, range, goal, led); a 'tick' heartbeat pulses; each beat shows sensor->compute->motor; a Rust 'one owner' overlay.

03
Guided practice

Build it up, step by step.

1. Take the peripherals. control_loop!(control) exports the tick; inside, p is your &mut Peripherals. 2. Read the goal beacon. p.goal.distance() (m) and p.goal.bearing() (rad, + = left). 3. Drive the motors. p.motors.set(left, right) in [-1, 1]. 4. Steer + go. let turn = 1.2 * bearing; then let forward = if dist > 0.2 { 0.6 } else { 0.0 }; and p.motors.set(forward - turn, forward + turn); 5. GPIO. Keep p.led.set(dist > 0.2).

04
Feedback

How the Bench grades your run.

PASS WHEN The firmware compiles as no_std and drives the rover within 0.2 m of the pad.

  • Closest approach barely changed — your motors are still set(0.0, 0.0). Compute turn from bearing and a forward term, then p.motors.set(forward - turn, forward + turn).
  • Rover spun/veered away — the steering sign is flipped. bearing is + when the goal is LEFT: set(forward - turn, forward + turn).
  • rustc error 'cannot find value `bearing`' — you read it into `_bearing`; drop the underscore to use it.
05
Retrieve & space

Bring back what you've already mastered.

  • Ownership: why does take() hand you the peripherals exactly once? (single guaranteed owner of each motor/pin).
  • Units: p.goal.bearing() is in ____ and + means the goal is to your ____ (radians; left).
  • What does control_loop!(control) generate? (a #[no_mangle] tick() the host calls each period).
06
Mastery gate

What you must demonstrate to advance.

Compile-and-run firmware (no_std, no emulator) that owns the peripherals and drives the rover into the pad.

07
Project

How this feeds your build.

Foundation of the metal capstone: every later firmware builds on this take → read → drive loop.