最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助。作者Brett Beauregard的原文网址:http://brettbeauregard.com/blog/2011/04/improving-the-beginner‘s-pid-initialization/
1、问题所在
在前一节中,我们实现了关闭和打开 PID 的功能。我们将其关闭,但现在让我们来看看当我们重新打开它时会发生什么:
呵!PID跳回到它发送的最后一个输出值,然后从那里开始调整。这将导致我们不希望出现的输入颠簸。
2、解决方案
这个很容易解决。因为我们现在知道什么时候打开 (从手动到自动),我们只需为一个平稳的过渡做一些初始化。这意味着对2个工作变量的存储 (积分项和最后的输入项) 进行处理,以防止输出跳转。
3、代码
1 /*working variables*/ 2 unsigned long lastTime; 3 double Input,Output,Setpoint; 4 double ITerm,lastInput; 5 double kp,ki,kd; 6 int SampleTime = 1000; //1 sec 7 double outMin,outMax; 8 bool inAuto = false; 9 10 #define MANUAL 0 11 #define AUTOMATIC 1 12 13 void Compute() 14 { 15 if(!inAuto) return; 16 unsigned long now = millis(); 17 int timeChange = (now - lastTime); 18 if(timeChange>=SampleTime) 19 { 20 /*Compute all the working error variables*/ 21 double error = Setpoint - Input; 22 ITerm+= (ki * error); 23 if(ITerm> outMax) ITerm= outMax; 24 else if(ITerm< outMin) ITerm= outMin; 25 double dInput = (Input - lastInput); 26 27 /*Compute PID Output*/ 28 Output = kp * error + ITerm- kd * dInput; 29 if(Output> outMax) Output = outMax; 30 else if(Output < outMin) Output = outMin; 31 32 /*Remember some variables for next time*/ 33 lastInput = Input; 34 lastTime = now; 35 } 36 } 37 38 void SetTunings(double Kp,double Ki,double Kd) 39 { 40 double SampleTimeInSec = ((double)SampleTime)/1000; 41 kp = Kp; 42 ki = Ki * SampleTimeInSec; 43 kd = Kd / SampleTimeInSec; 44 } 45 46 void SetSampleTime(int NewSampleTime) 47 { 48 if (NewSampleTime > 0) 49 { 50 double ratio = (double)NewSampleTime 51 / (double)SampleTime; 52 ki *= ratio; 53 kd /= ratio; 54 SampleTime = (unsigned long)NewSampleTime; 55 } 56 } 57 58 void SetOutputLimits(double Min,double Max) 59 { 60 if(Min > Max) return; 61 outMin = Min; 62 outMax = Max; 63 64 if(Output > outMax) Output = outMax; 65 else if(Output < outMin) Output = outMin; 66 67 if(ITerm> outMax) ITerm= outMax; 68 else if(ITerm< outMin) ITerm= outMin; 69 } 70 71 void SetMode(int Mode) 72 { 73 bool newAuto = (Mode == AUTOMATIC); 74 if(newAuto && !inAuto) 75 { /*we just went from manual to auto*/ 76 Initialize(); 77 } 78 inAuto = newAuto; 79 } 80 81 void Initialize() 82 { 83 lastInput = Input; 84 ITerm = Output; 85 if(ITerm> outMax) ITerm= outMax; 86 else if(ITerm< outMin) ITerm= outMin; 87 }
我们修改了 SetMode (...) 以检测从手动到自动的转换,并添加了初始化功能。它通过设置“积分项=输出”来处理积分项,“最后输入=输入”以防止微分激增。比例项不依赖于过去的任何信息,因此不需要任何初始化。
4、最终结果
我们从上面的图表中看到,正确的初始化会导致从手动到自动的无扰动切换,这正是我们所追求的。
5、更新:为什么不 ITerm=0?
我最近收到了很多问题,问为什么我没有把 ITerm=0 设置为初始化。作为答案,我请您考虑以下方案:PID是手动的,用户已将输出设置为50。一段时间后,该过程稳定到75.2 的输入。用户将设置点75.2 打开 PID。会发生什么事?
我认为,切换到自动后,输出值应该保持在50。由于 P 和 D 项将为零,因此发生这种情况的唯一方法是将 ITerm项初始化为“输出”值。
如果您处于需要输出初始化为零的情况,则无需更改上面的代码。在将 PID 从“手动”转换为“自动”之前,只需在调用例程中设置Output=0。
欢迎关注: