Unit 07 · lesson
Timing, Loops, and Nonblocking Code
A robot cannot freeze the rest of its brain every time one mechanism waits.
Suppose you write:
motor_on()
sleep(5)
motor_off()
For a desktop script, that may be acceptable. In a robot control loop, a blocking delay can prevent other work from happening at the expected rate.
What still needs attention?
During those five seconds, the robot may still need to:
- read a stop input;
- update wheel control;
- watch current or temperature;
- receive messages;
- publish telemetry;
- detect a collision.
A behavior that monopolizes execution can make unrelated subsystems unreliable.
Time as state
Instead of "wait here," store when the action began.
if state == EJECTING:
motor = reverse
if now - state_start >= 0.5 s:
state = IDLE
The loop can keep running. Each pass checks whether the transition condition has become true.
That is the core idea behind nonblocking behavior.
A timing trace
| Time | State | Sensor | Motor | Transition? |
|---|---|---|---|---|
| 0.00 s | INTAKING | clear | +0.6 | no |
| 0.10 s | INTAKING | clear | +0.6 | no |
| 0.20 s | INTAKING | object | +0.6 | yes |
| 0.22 s | HOLDING | object | +0.15 | no |
A trace exposes behavior that prose can hide.
Failure mode: timer without state
A single global timer reused by several behaviors can create confusing interactions. The value may be correct but belong to the wrong operation.
Tie timing to the state or command that owns it.
Make a trace
Take the behavior specification from Lesson 1.
Create a table for at least six time steps. Show:
- current state;
- one important input;
- output;
- transition decision.
Then introduce one delayed sensor event and rerun the trace.
If the entire behavior falls apart because one event occurs 200 ms later, you have found a design weakness before touching hardware.
Blocking code steals time from other responsibilities
Imagine a robot program that checks sensors every 20 ms. Inside the loop, someone adds a two-second delay after turning on an actuator.
During those two seconds the program may stop:
- reading new sensor data;
- refreshing safety logic;
- updating other actuators;
- logging state;
- responding to operator input.
The actuator command itself may be fine. The timing architecture is not.
A nonblocking design records when an action started and keeps cycling:
if state == "ACTUATE":
motor_on()
if now - started_at >= 2.0:
motor_off()
state = "NEXT"
The exact code depends on the platform. The important idea is that elapsed time becomes data instead of a reason to stop the entire program.
Measure loop timing
For a controller expected to run every 20 ms, record several observed loop durations:
| Cycle | Duration |
|---|---|
| 1 | 18 ms |
| 2 | 19 ms |
| 3 | 21 ms |
| 4 | 87 ms |
| 5 | 19 ms |
The average looks acceptable, but cycle 4 is a serious clue. Real-time-ish systems care about worst cases and jitter, not only averages.
If robot behavior occasionally feels late, inspect timing before changing gains or motor power.