Feedforward and feedback

A ramp leaves a proportional controller permanently behind, and the model knows how to fix it.

U10.5Following a trajectoryUniversity30 min

Do this lesson in the simulator

A proportional controller chasing a moving reference is always behind. Not because it is badly tuned, but because of what a proportional controller is: it produces a command only when there is an error, so if the command needs to be non-zero for ever, the error must be non-zero for ever.

The arithmetic

The reference moves at v cm/s. The controller commands a velocity K * e. In steady state the robot must be moving at v, so

K * e = v      ->      e = v / K

A constant error, for as long as the ramp lasts. At v = 10 cm/s and K = 1.5 that is 6.7 cm behind, permanently. Raise K to 5 and it is 2 cm behind. It never reaches zero, and the classical way to say that is that a type 0 loop has a finite velocity error constant.

The usual instinct is to raise the gain. It works, until it does not: the 0.25 s lag in the drive means that high gain turns into overshoot and then into oscillation, noise on the position estimate gets multiplied by K straight into the motors, and beyond about 30 percent command the response is saturating anyway.

The better answer

The error exists because the controller is being asked to produce a command it already knows the value of. So tell it.

command = feedforward(v_ref) + K * error

The feedforward term is the model running in reverse: you want the robot to move at v_ref, and U2 says the command for that is 100 * v_ref / 20. Send it. Now the feedback term has nothing left to do except correct the parts the model got wrong, and the steady error drops from v / K to (model error) / K.

With the model out by 10 percent, the residue is 0.1 * v / K, which at the same numbers is 0.7 cm rather than 6.7 cm. A tenfold improvement from one added term and no retuning.

This is the division of labour that the whole of control engineering is built on:

  • Feedforward makes it happen. Fast, no stability cost at all (it is not in the loop), only as good as the model.
  • Feedback fixes what feedforward got wrong. Handles everything the model does not know: a slope, a sticky wheel, a flat battery, a push.

Neither is sufficient. Feedforward alone drifts, because nothing measures the result. Feedback alone lags, as the arithmetic above shows.

from bugbot import *
connect()

V_REF, K, DT = 10.0, 1.5, 0.1
base = position()[1]
for tick in range(70):
    t = tick * DT
    ref = V_REF * t
    err = ref - (position()[1] - base)
    drive(100 * (K * err) / 20.0, 0, 0)      # gain alone
    plot("ref", ref)
    plot("error", err)
    wait(DT)
stop()
print("settled error about", round(V_REF / K, 2), "cm, as predicted")

Run this in the simulator

Change the one line to drive(100 * (V_REF + K * err) / 20.0, 0, 0) and watch the error line fall onto the axis.

Where the model comes from

Everything you measured earlier in the course is a feedforward term waiting to be used.

  • The speed per unit command, from U2. That is the basic one.
  • The dead band, from U2 and U5. A dead band inverse is feedforward: you are correcting a known nonlinearity before it happens.
  • The measured gain of this particular robot, from U3's calibration. Using the robot's own gain rather than the nominal 20 cm/s shrinks the residue further.
  • An acceleration term, m * a_ref, on a machine where inertia matters.

What feedforward cannot do

It cannot see. If the robot is stuck against a chair leg, the feedforward term happily commands cruise speed for ever. Everything that depends on what actually happened has to come from the feedback path, which is why the loop is never optional and why the error signal is still the thing to plot.

Task: how far behind a ramp

Drive the same 10 cm/s ramp twice: once with proportional control alone, once with the reference speed fed forward as well. Average the size of the error over the last few seconds of each leg, and print p only: and with ff:. Plot ref, actual and error.

from bugbot import *
import math
connect()

DT = 0.1
V_REF, K = 10.0, 1.5

Challenges

  1. Check v / K against your measured p only number. How close is the prediction?
  2. Run the proportional leg at K = 5. Does the error fall by the factor the formula says, and what does the motion look like?
  3. Deliberately break the feedforward by using 30 cm/s instead of 20 in the model. Where does that show up?