Sense, decide, act
The control loop, its period, and the deadman that stops a robot whose program has gone quiet.
Do this lesson in the simulatorPrint it: worksheet (PDF) · answers (PDF) · as a web page
Guide: How do robots see and move?
Every robot program you will write in this course is the same shape underneath. Read the sensors. Work out what to do. Tell the motors. Wait a moment. Do it again.
while running:
measurement = sense()
command = decide(measurement)
act(command)
wait(period)
That is the control loop, and the rest of the course is about what goes inside decide.
A loop you can watch
from bugbot import *
connect()
# ten times a second for three seconds
for tick in range(30):
ahead = distance()
print(tick, ahead)
wait(0.1)
Thirty readings, a tenth of a second apart, and every one of them is 91.0: the wall is 91 cm away and nothing is moving. The simulator's depth sensor has no noise unless you ask for it with set_noise(); it just reads to the nearest centimetre. A real one is not, and Module U4 is about what to do with that.
The period
wait(0.1) sets the period of the loop: 0.1 seconds, so the loop runs at 10 Hz. Everything else follows from it.
- A controller can only react as often as its loop runs. At 10 Hz the robot does whatever it was last told for a tenth of a second, whatever the world does in between.
- The measurements arrive at that rate too, so anything that happens faster than about half the loop rate cannot be seen properly. That is the sampling theorem, and it is the reason a fast disturbance can look like a slow one.
- Real loops are not free. Reading the camera takes time, and
wait()is what is left over.
The deadman
The robot stops itself half a second after it last heard from your program. A program that crashes, or wanders off into a long calculation, leaves a robot that stops rather than one that drives into a wall. When a program finishes normally, the simulator switches the motors off at once. When it crashes, the simulator does what the robot does: nothing tells the motors, so the robot carries on until the deadman stops it.
from bugbot import *
connect()
forward(60)
wait(1)
print("about to crash at", position())
speed = 60 / 0 # a crash: ZeroDivisionError
It prints about to crash at (-0.2, 8.4) and then the error, but watch the replay: the robot keeps driving. Half a second after the program last spoke to it, the deadman switches the motors off, and it rolls to a stop 16 cm from the start, nearly twice as far as it had got when the program died.
wait() counts as hearing from the program, so a command lasts as long as the program is still running and still waiting:
from bugbot import *
connect()
forward(60)
wait(2) # wait() keeps the deadman fed, so this drives for the full 2 s
print("after 2 s:", position())
It prints after 2 s: (-0.2, 19.3): two seconds at 60, less the quarter second it takes to get up to speed. This matters for how you write the loop: a command is not a promise that lasts beyond the program. The moment your code stops talking to the robot, by crashing or by getting stuck, the robot gives up. A loop that drives keeps saying so, and keeps waiting in small steps.
Blocking and non-blocking
Two ways to say the same thing:
forward(60, distance=40) |
blocking: comes back when the robot has gone 40 cm |
forward(60) then wait(0.1) |
non-blocking: sets the motors and returns at once |
Blocking calls are convenient and are what you have used until now. Every controller in this course uses the non-blocking form, because a controller has to be able to change its mind half way.
Task: run a loop at 10 Hz
Without driving, read the depth sensor thirty times at ten times a second, print each reading, and print how long the loop actually took on average, as period: 0.1.
from bugbot import *
connect()
# clock() is the seconds since the program started
start = clock()
Challenges
- Print
clock()inside the loop. Is the spacing exactly 0.1 s? - Change the wait to 0.02 s and run it. What is the period now, and what does that tell you about how much work the loop itself costs?
- Put
forward(60)in a loop of thirty with no wait at all in it. Why does the robot not move in the simulator, and what would a real BugBot do?