The derivative term

Damping the overshoot, and why D on a noisy signal needs a filter or it is useless.

U5.3Feedback controlUniversity30 min

Do this lesson in the simulator

Proportional control looks only at where you are. The derivative term looks at how fast you are getting there.

command = Kp * error + Kd * d(error)/dt

When the error is shrinking quickly, the derivative is large and negative, and the D term subtracts from the command. The controller eases off before it arrives. That is damping, and it is what removes overshoot without giving up the gain that made the loop fast.

In code

from bugbot import *
connect()

KP, KD = 3.0, 1.2
TARGET = 30.0
DT = 0.1
last = distance() - TARGET

for tick in range(120):
    error = distance() - TARGET
    d = (error - last) / DT
    last = error
    plot("error", error)
    plot("d term", KD * d)
    drive(max(-70, min(70, KP * error + KD * d)), 0, 0)
    wait(DT)
stop()

Run this in the simulator

Watch the two lines. The D term spikes early, while the error is changing fastest, and goes quiet as the robot settles. It does its work on the way in and then gets out of the way.

Why D is difficult in practice

It amplifies noise. Differentiating means subtracting two nearly equal numbers and dividing by a small one. With 3 cm of noise and a 0.1 s step, two consecutive readings can differ by 6 cm, which the derivative reports as 60 cm/s of approach speed that is not happening. Multiply by Kd and the motors buzz.

Three standard treatments:

  1. Filter the measurement before differentiating. Costs delay, which partly defeats the D term.
  2. Filter the derivative itself, which is the same idea with a different knob.
  3. Use a measured rate instead of a difference. flow() measures speed directly, and a measured derivative is far better behaved than a computed one.

That third option is the best available, and it is why serious systems put a rate sensor on anything they intend to differentiate. On this robot it means using flow() rather than differencing distance().

Derivative kick. If the setpoint jumps, the error jumps, and the derivative of a jump is enormous. The standard fix is to differentiate the measurement rather than the error, since the measurement does not jump. It gives the same damping and no kick.

The shape it produces

With D added, the classic step response changes character:

  • No D: overshoot, a couple of swings, settles.
  • A little D: a small overshoot, settles quickly. Usually the best behaved.
  • A lot of D: no overshoot, slow approach, and a twitchy command trace.
  • Too much D: noise everywhere and a robot that buzzes while standing still.

Task: damp the overshoot

Settle 30 cm from the wall, within 5 cm, from nine seconds onwards, with an overshoot of no more than 6 cm. Plot error and d term, and print the overshoot: you achieved.

from bugbot import *
connect()

KP, KD = 3.0, 1.0
TARGET = 30.0
DT = 0.1
last = distance() - TARGET
overshoot = 0.0

Challenges

  1. Use -flow()[1] as the derivative instead of differencing the distance. Compare the command traces.
  2. Turn Kd up until the robot buzzes while standing still. What is the noise doing?
  3. Differentiate the measurement rather than the error, then change the setpoint mid-run and compare.