The derivative term
Damping the overshoot, and why D on a noisy signal needs a filter or it is useless.
Do this lesson in the simulatorProportional 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()
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:
- Filter the measurement before differentiating. Costs delay, which partly defeats the D term.
- Filter the derivative itself, which is the same idea with a different knob.
- 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
- Use
-flow()[1]as the derivative instead of differencing the distance. Compare the command traces. - Turn
Kdup until the robot buzzes while standing still. What is the noise doing? - Differentiate the measurement rather than the error, then change the setpoint mid-run and compare.