How far behind

Dead band, lag and loop period, measured as the centimetres the robot is behind its own plan.

U10.6Following a trajectoryUniversity30 min

Do this lesson in the simulator

A trajectory is a promise about time, and the robot does not keep it. This lesson is about measuring the shortfall, in centimetres and in seconds, and deciding what to do about it.

Where the delay comes from

source on this robot fix
dead band the first 3 cm/s of the profile produce nothing, so the robot sets off late dead band inverse
first order lag the velocity reaches the demand with a time constant of 0.25 s feedforward the acceleration, or accept it
loop period a command is up to 0.1 s stale before it is acted on run the loop faster
estimator lag a filtered position is by definition old news (U4, U6) do not over-filter the signal you control on

They add. A position error of e while travelling at v is a time error of e / v, and it is often more useful to quote it that way: "the robot is a fifth of a second behind" is a statement anyone can act on, while "the robot is 3 cm behind" depends on knowing how fast it was going.

Reference and robot position against time during the cruise, with the gap read vertically in centimetres and horizontally in seconds22.42.83.22530354045time, sposition, cm3 cm behind0.21 s behinddashed: the reference, 14 cm/sred: the robot
The same gap measured two ways. Straight down it is 3 cm. Straight across it is 0.21 s, about a fifth of a second: the time the robot takes to reach where the reference already is, at 14 cm/s.

The shape of the error

Run the trapezoid with feedforward and a correction, plot the error, and it has a shape that is the same on every machine.

  1. It grows on the accelerating ramp. The first order lag holds the velocity roughly a * tau below the demand. At a = 12 cm/s/s and tau = 0.25 s that is 3 cm/s, and over a 1.2 s ramp it builds a couple of centimetres of deficit. This is the peak of the whole run.
  2. It shrinks during the cruise. The demand has stopped changing, so the lag has nothing to do, and the feedback works the deficit off down to whatever the model error leaves behind: the residue of U10.5.
  3. It goes negative on the decelerating ramp. The same lag, with the sign reversed. The reference slows down and the robot, still carrying its velocity, runs past it. The robot is now early, and then it overshoots the end point.
  4. It settles. With the reference standing still only the correction K * e is left, and the dead band swallows it once it is under 3 cm/s, which at K = 1.5 is any error under 2 cm. The run below comes to rest 1.6 cm past the target and stays there. Put the dead band inverse from U10.3 on the command and the same run settles to about 0.1 cm.
The demanded speed and the tracking error from the lesson's run, on the same time axis, in four phases0714speed, cm/s-2020246810time, serror, cm behind12342.8 on the first rampdown to 0.7 in the cruisesettles at -1.6
The code below, run on the mat. The error (red) builds while the speed is changing, not while it is steady: 2.8 cm at its worst on the first ramp (1), worked off to 0.7 in the cruise (2), pushed below zero as the robot runs past on the last ramp (3), and left at -1.6 cm by the dead band (4).

The useful consequence: the error is built during the changes in the profile, not during the cruise. A gentler acceleration is kinder to the machine and gives a smaller error at both ends, and the overshoot at the finish is the acceleration limit asking to be lowered.

Two ways to deal with it

Plan for it. Add a margin to the duration, and do not treat the end of the profile as the moment of arrival. Real systems quote a settling time after the profile ends, and check the position then.

Close the loop on time as well as space. Instead of advancing the reference by the clock, advance it by where the robot actually got to: take the projected arc length, add a step, and use that as the next target. The reference then waits for the robot instead of running away from it, and the tracking error can never grow without bound.

Where the target goes while the robot is held still: driven by the clock, or by the robot's own positiontarget from the clockrobot, heldt = 1 st = 2 st = 3 sruns away at 14 cm/starget from the robot's projectionrobot, heldone stepwaits one step ahead
With the robot held up for 3 s, a target moved on by the clock is 42 cm away and still going, so the error grows without limit. A target placed a step beyond the robot's own projection waits for it, which is what pure pursuit does. It stays on the path, but it no longer knows it is late.

That second idea is worth recognising, because you have already implemented it. Pure pursuit advances its target from the robot's own projection onto the path, not from a clock. It is a path follower rather than a trajectory follower, and that is exactly why it degrades gracefully when the robot falls behind. The price is that it has no opinion about timing at all: a pure pursuit controller will happily take all day.

Serious systems do both: a trajectory with a clock, and a rule that slows the clock down when the tracking error grows. That is called time scaling, and it is how a machine tool stays on the geometry when it cannot keep up with the feed rate. Geometry first, timing second, because being in the wrong place is worse than being late.

from bugbot import *
connect()

# a trapezoid to 80 cm, followed with feedforward and a correction
D, A, V, K, DT = 80.0, 12.0, 14.0, 1.5, 0.1
t_acc = V / A
d_acc = 0.5 * A * t_acc * t_acc
t_flat = (D - 2 * d_acc) / V
duration = 2 * t_acc + t_flat
print("the profile says", round(duration, 2), "s")

base, t0 = position()[1], clock()
while clock() - t0 < duration + 3:
    t = clock() - t0
    if t < t_acc:
        s, v = 0.5 * A * t * t, A * t
    elif t < t_acc + t_flat:
        s, v = d_acc + V * (t - t_acc), V
    elif t < duration:
        td = t - t_acc - t_flat
        s, v = d_acc + V * t_flat + V * td - 0.5 * A * td * td, V - A * td
    else:
        s, v = D, 0.0
    err = s - (position()[1] - base)
    drive(max(-100, min(100, 100 * (v + K * err) / 20.0)), 0, 0)
    plot("error", err)      # centimetres behind the plan
    wait(DT)
stop()

Run this in the simulator

Watch the error line and count the four phases off against the list above: up to 2.8 cm on the first ramp, down to about 0.8 cm through the cruise, below zero on the last ramp, and flat at -1.6 cm once the reference stops. The peak is on the first ramp, the dip is on the last one, and neither of them is anywhere near the cruise, which is the part everybody instinctively worries about.

Task: how far behind the plan

Build a trapezoid to 80 cm at 12 cm/s/s and 14 cm/s, follow it with feedforward and a correction, and keep holding the target for a few seconds afterwards. Plot ref, actual and error, and print duration:, what the profile says the move takes, and worst:, the furthest the robot ever fell behind the reference in centimetres.

from bugbot import *
connect()

DT = 0.1
DISTANCE, A_MAX, V_CRUISE, K = 80.0, 12.0, 14.0, 1.5

Challenges

  1. Convert your worst into seconds using the speed at the moment it happened. Which number is more useful to whoever asked?
  2. Halve the acceleration limit and measure again. The move takes longer on paper: does it finish sooner in practice?
  3. Advance the reference from the robot's own position instead of from the clock. What can no longer go wrong, and what have you given up?