Sense, decide, act

The control loop, its period, and the deadman that stops a robot whose program has gone quiet.

U1.1The robot as a systemUniversity20 min

Do this lesson in the simulator

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)

Run this in the simulator

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())

Run this in the simulator

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

  1. Print clock() inside the loop. Is the spacing exactly 0.1 s?
  2. 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?
  3. Drive at 60 with no wait at all in the loop, and explain what the deadman does.