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 simulatorEvery 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. Nothing is driving yet, and the numbers still move: that is sensor noise, and Module U4 is about what to do with it.
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 the last command. A program that crashes, or wanders off into a long calculation, leaves a robot that stops rather than one that drives into a wall.
This matters for how you write the loop: a command is not a promise that lasts. forward(60) on its own keeps going for half a second and then gives up. A loop that drives has to keep saying so.
from bugbot import *
connect()
forward(60)
wait(2) # the motors stop themselves half way through this
print("after 2 s:", position())
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?
- Drive at 60 with no wait at all in the loop, and explain what the deadman does.