(This is Modification #2 in a larger series on writing a solid PID algorithm)
The Problem
This modification is going to tweak the derivative term a bit. The goal is to eliminate a phenomenon known as “Derivative Kick”.
The image above illustrates the problem. Since error=Setpoint-Input, any change in Setpoint causes an instantaneous change in error. The derivative of this change is infinity (in practice, since dt isn’t 0 it just winds up being a really big number.) This number gets fed into the pid equation, which results in an undesirable spike in the output. Luckily there is an easy way to get rid of this.
The Solution
It turns out that the derivative of the Error is equal to negative derivative of Input, EXCEPT when the Setpoint is changing. This winds up being a perfect solution. Instead of adding (Kd * derivative of Error), we subtract (Kd * derivative of Input). This is known as using “Derivative on Measurement”
The Code
/*working variables*/ unsigned long lastTime; double Input, Output, Setpoint; double errSum, lastInput; double kp, ki, kd; int SampleTime = 1000; //1 sec void Compute() { unsigned long now = millis(); int timeChange = (now - lastTime); if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint - Input; errSum += error; double dInput = (Input - lastInput); /*Compute PID Output*/ Output = kp * error + ki * errSum - kd * dInput; /*Remember some variables for next time*/ lastInput = Input; lastTime = now; } } void SetTunings(double Kp, double Ki, double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000; kp = Kp; ki = Ki * SampleTimeInSec; kd = Kd / SampleTimeInSec; } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime; ki *= ratio; kd /= ratio; SampleTime = (unsigned long)NewSampleTime; } }
The modifications here are pretty easy. We’re replacing +dError with -dInput. Instead of remembering the lastError, we now remember the lastInput
The Result
Here’s what those modifications get us. Notice that the input still looks about the same. So we get the same performance, but we don’t send out a huge Output spike every time the Setpoint changes.
This may or may not be a big deal. It all depends on how sensitive your application is to output spikes. The way I see it though, it doesn’t take any more work to do it without kicking so why not do things right?
Next >>