L4 · PAI-110

Timers, PWM & Proportional Drive

Use PWM motor channels and the millisecond clock to replace bang-bang control with a smooth proportional controller that settles in the pad without overshoot.

01
Challenge

Try this first — before any explanation.

The starter slams the motors to full, then to zero — watch it overshoot and wobble and miss the tighter tolerance. Rebuild it so the rover eases in and stops clean.

The Bench

PWM duty + a proportional law in real Rust — smooth the rover into the pad.

! { loop {} }\nuse physical_ai_hal::*;\n\n// Bang-bang: full power, then slam to zero. It overshoots and wobbles.\n// Rebuild forward as a PROPORTIONAL drive — effort shrinks with the error —\n// so the rover eases in and settles inside the tighter tolerance.\nfn control(p: &mut Peripherals) {\n let dist = p.goal.distance();\n let bearing = p.goal.bearing();\n let turn = 1.2 * bearing;\n let forward = if dist > 0.05 { 0.9 } else { 0.0 }; // <-- bang-bang; fix me\n p.motors.set(forward - turn, forward + turn);\n}\ncontrol_loop!(control);\n","task":{"goal":[1.2,1.2],"time":14.0,"tol":0.12},"title":"Timers, PWM & Proportional Drive"}">
PYTHON · NUMPY · IN-BROWSER

Timers, PWM & Proportional Drive

PWM duty + a proportional law in real Rust — smooth the rover into the pad.

02
Model

The idea, built visually.

A motor pin is digital — on or off. So how do you get HALF speed? You blink it fast: on 60% of each tiny window. That's PWM — the duty cycle IS the average power, and p.motors.set(0.6, ..) is that duty. Bang-bang uses only 0% or 100%, so it overshoots. Proportional control scales the duty by how far you still are: lots of error → more power, near the goal → gentle. The timer gives the steady tick that makes 'per second' mean something.

▣ Stage animation: A square wave whose duty morphs 20->80% with 'PWM = average power'; bang-bang overshoot vs proportional easing into the pad; a timer ticking the loop.

03
Guided practice

Build it up, step by step.

1. PWM is duty. p.motors.set(d, d) drives at duty d ∈ [-1,1] — the average power. 2. Proportional forward. Replace bang-bang with let forward = (0.8 * dist).min(0.6).max(0.0); 3. Keep steering. let turn = 1.2 * bearing; then p.motors.set(forward - turn, forward + turn); 4. Timing. millis() returns the loop clock in ms if you want to rate-limit.

04
Feedback

How the Bench grades your run.

PASS WHEN The rover settles within 0.12 m of the pad — proportional drive, no bang-bang overshoot.

  • Still overshooting past 0.12 m — you're using a constant forward. Make it proportional: forward = (0.8 * dist).min(0.6).
  • Rover crawls and times out — gain too low / clamp too small. Raise toward 0.8, cap at 0.6.
  • Wobbles at the pad — reduce forward near zero so it can settle.
05
Retrieve & space

Bring back what you've already mastered.

  • PWM: a duty of 0.6 means the pin is on ____ % of each window (60).
  • From the Python PID lesson: a proportional term scales effort by the ____ (error/distance).
  • Why .min(0.6).max(0.0) on forward? (keep duty in a safe, non-reversing range).
06
Mastery gate

What you must demonstrate to advance.

Firmware that drives into a TIGHT tolerance using proportional PWM — the bare-metal version of the PID intuition.

07
Project

How this feeds your build.

The smooth-drive primitive the capstone reuses; sets up timing for the async tier.