最近看到了Brett Beauregard发表的有关PID的系列文章,感受对于理解PID算法颇有帮助,因而将系列文章翻译过来!在自我提升的过程当中,也但愿对同道中人有所帮助。做者Brett Beauregard的原文网址:http://brettbeauregard.com/blog/2017/06/proportional-on-measurement-the-code/算法
在上一篇文章中,我把全部的时间都花在解释了比例测量的好处上。在这篇文章中,我将解释代码。人们彷佛很欣赏我上次一步一步地解释事情的方式,因此在此我也将采起这样的方式。下面的3个步骤详细介绍了我是如何将 PonM 添加到 PID 库的。ui
第一阶段–初始输入和比例模式选择spa
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 #define DIRECT 0 14 #define REVERSE 1 15 int controllerDirection = DIRECT; 16 17 #define P_ON_M 0 18 #define P_ON_E 1 19 bool PonE = true; 20 double initInput; 21 22 void Compute() 23 { 24 if(!inAuto) return; 25 unsigned long now = millis(); 26 int timeChange = (now - lastTime); 27 if(timeChange>=SampleTime) 28 { 29 /*Compute all the working error variables*/ 30 double error = Setpoint - Input; 31 ITerm+= (ki * error); 32 if(ITerm > outMax) ITerm= outMax; 33 else if(ITerm < outMin) ITerm= outMin; 34 double dInput = (Input - lastInput); 35 36 /*Compute P-Term*/ 37 if(PonE) Output = kp * error; 38 else Output = -kp * (Input-initInput); 39 40 /*Compute Rest of PID Output*/ 41 Output += ITerm - kd * dInput; 42 if(Output > outMax) Output = outMax; 43 else if(Output < outMin) Output = outMin; 44 45 /*Remember some variables for next time*/ 46 lastInput = Input; 47 lastTime = now; 48 } 49 } 50 51 void SetTunings(double Kp,double Ki,double Kd,int pOn) 52 { 53 if (Kp<0 || Ki<0|| Kd<0) return; 54 55 PonE = pOn == P_ON_E; 56 57 double SampleTimeInSec = ((double)SampleTime)/1000; 58 kp = Kp; 59 ki = Ki * SampleTimeInSec; 60 kd = Kd / SampleTimeInSec; 61 62 if(controllerDirection ==REVERSE) 63 { 64 kp = (0 - kp); 65 ki = (0 - ki); 66 kd = (0 - kd); 67 } 68 } 69 70 void SetSampleTime(int NewSampleTime) 71 { 72 if (NewSampleTime > 0) 73 { 74 double ratio = (double)NewSampleTime 75 / (double)SampleTime; 76 ki *= ratio; 77 kd /= ratio; 78 SampleTime = (unsigned long)NewSampleTime; 79 } 80 } 81 82 void SetOutputLimits(double Min,double Max) 83 { 84 if(Min > Max) return; 85 outMin = Min; 86 outMax = Max; 87 88 if(Output > outMax) Output = outMax; 89 else if(Output < outMin) Output = outMin; 90 91 if(ITerm > outMax) ITerm= outMax; 92 else if(ITerm < outMin) ITerm= outMin; 93 } 94 95 void SetMode(int Mode) 96 { 97 bool newAuto = (Mode == AUTOMATIC); 98 if(newAuto == !inAuto) 99 { /*we just went from manual to auto*/ 100 Initialize(); 101 } 102 inAuto = newAuto; 103 } 104 105 void Initialize() 106 { 107 lastInput = Input; 108 initInput = Input; 109 ITerm = Output; 110 if(ITerm > outMax) ITerm= outMax; 111 else if(ITerm < outMin) ITerm= outMin; 112 } 113 114 void SetControllerDirection(int Direction) 115 { 116 controllerDirection = Direction; 117 }
随着输入的变化,测量的比例提供了愈来愈大的阻力,但若是没有参照系,咱们的表现会有些不稳定。若是咱们第一次打开控制器时的PID输入是10000,咱们真的想从Kp*10000开始抵制吗?不,咱们但愿使用咱们的初始输入做为参考点 (第108行),从那里开始随着输入的变化增长或减小阻力 (第38行)。翻译
咱们须要作的另外一件事是容许用户选择是要在误差上作比例或在测量上作比例。在最后一个帖子以后,它看起来像 PonE 是无用的,但重要的是要记住,对于许多回路,它工做的很好。所以,咱们须要让用户选择他们想要的模式,而后在计算中相应地操做。code
第二阶段–动态更改整定参数blog
虽然上面的代码确实有效,但它有一个咱们之前看到的问题。当整定参数在运行时发生更改,咱们会获得一个不但愿出现的信号。ci
为何会这样?rem
上次咱们看到这一点时,是积分项被一个新的Ki 从新调整。而这一次,是比例项(输入-初始输入) 被新的Kp所更改。我选择的解决方案相似于咱们为 Ki 所作的:咱们再也不是将(输入-初始输入)做为一个总体乘以当前 Kp,而是我把它分解成单独的步骤乘以当时的Kp:get
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 #define DIRECT 0 14 #define REVERSE 1 15 int controllerDirection = DIRECT; 16 17 #define P_ON_M 0 18 #define P_ON_E 1 19 bool PonE = true; 20 double PTerm; 21 22 void Compute() 23 { 24 if(!inAuto) return; 25 unsigned long now = millis(); 26 int timeChange = (now - lastTime); 27 if(timeChange>=SampleTime) 28 { 29 /*Compute all the working error variables*/ 30 double error = Setpoint - Input; 31 ITerm+= (ki * error); 32 if(ITerm > outMax) ITerm= outMax; 33 else if(ITerm < outMin) ITerm= outMin; 34 double dInput = (Input - lastInput); 35 36 /*Compute P-Term*/ 37 if(PonE) Output = kp * error; 38 else 39 { 40 PTerm -= kp * dInput; 41 Output = PTerm; 42 } 43 44 /*Compute Rest of PID Output*/ 45 Output += ITerm - kd * dInput; 46 47 if(Output > outMax) Output = outMax; 48 else if(Output < outMin) Output = outMin; 49 50 /*Remember some variables for next time*/ 51 lastInput = Input; 52 lastTime = now; 53 } 54 } 55 56 void SetTunings(double Kp,double Ki,double Kd,int pOn) 57 { 58 if (Kp<0 || Ki<0|| Kd<0) return; 59 60 PonE = pOn == P_ON_E; 61 62 double SampleTimeInSec = ((double)SampleTime)/1000; 63 kp = Kp; 64 ki = Ki * SampleTimeInSec; 65 kd = Kd / SampleTimeInSec; 66 67 if(controllerDirection ==REVERSE) 68 { 69 kp = (0 - kp); 70 ki = (0 - ki); 71 kd = (0 - kd); 72 } 73 } 74 75 void SetSampleTime(int NewSampleTime) 76 { 77 if (NewSampleTime > 0) 78 { 79 double ratio = (double)NewSampleTime 80 / (double)SampleTime; 81 ki *= ratio; 82 kd /= ratio; 83 SampleTime = (unsigned long)NewSampleTime; 84 } 85 } 86 87 void SetOutputLimits(double Min,double Max) 88 { 89 if(Min > Max) return; 90 outMin = Min; 91 outMax = Max; 92 93 if(Output > outMax) Output = outMax; 94 else if(Output < outMin) Output = outMin; 95 96 if(ITerm > outMax) ITerm= outMax; 97 else if(ITerm < outMin) ITerm= outMin; 98 } 99 100 void SetMode(int Mode) 101 { 102 bool newAuto = (Mode == AUTOMATIC); 103 if(newAuto == !inAuto) 104 { /*we just went from manual to auto*/ 105 Initialize(); 106 } 107 inAuto = newAuto; 108 } 109 110 void Initialize() 111 { 112 lastInput = Input; 113 PTerm = 0; 114 ITerm = Output; 115 if(ITerm > outMax) ITerm= outMax; 116 else if(ITerm < outMin) ITerm= outMin; 117 } 118 119 void SetControllerDirection(int Direction) 120 { 121 controllerDirection = Direction; 122 }
咱们如今保留一个有效的和组成的P项,而不是将输入-初始输入做为一个总体乘以Kp。在每一步中,咱们只需将当前输入变化乘以当时的Kp,并从P项(第41行) 中减去它。在这里,咱们能够看到变化的影响:it
由于旧的Kp是已经存储,调整参数的变化只会影响咱们后续的过程。
最终阶段–求和问题。
我不会进入完整的细节 (花哨的趋势等) 以及上述代码有什么问题。这至关不错,但仍有重大问题。例如:
类式积分饱和:虽然最终的输出限制在OUTmin和OUTmax之间。当PTerm不该该增加时,它有可能增加。它不会像积分饱和那样糟糕,但仍然是不可接受的。
动态更改:在运行时,若是用户想从P _ On _ M 更改成P_ ON _E,并在一段时间后返回,那么P项将不会被初始化,这会致使输出振荡。
还有更多,但仅仅这些就足以让人看到真正的问题是什么。早在咱们建立I项的时候,咱们已经处理过全部这些问题。我没有对P项从新执行相同的解决方案,而是选择了一个更美观的解决方案。
经过将P项和I项合并到一个名为“outputSum”的变量中,P _ ON _ M 代码将受益于已存在的全部上下文修补程序,而且因为代码中没有两个总和,所以不会出现没必要要的冗余。
1 /*working variables*/ 2 unsigned long lastTime; 3 double Input,Output,Setpoint; 4 double outputSum,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 #define DIRECT 0 14 #define REVERSE 1 15 int controllerDirection = DIRECT; 16 17 #define P_ON_M 0 18 #define P_ON_E 1 19 bool PonE = true; 20 21 22 void Compute() 23 { 24 if(!inAuto) return; 25 unsigned long now = millis(); 26 int timeChange = (now - lastTime); 27 if(timeChange>=SampleTime) 28 { 29 30 /*Compute all the working error variables*/ 31 double error = Setpoint - Input; 32 double dInput = (Input - lastInput); 33 outputSum+= (ki * error); 34 35 /*Add Proportional on Measurement,if P_ON_M is specified*/ 36 if(!PonE) outputSum-= kp * dInput 37 38 if(outputSum > outMax) outputSum= outMax; 39 else if(outputSum < outMin) outputSum= outMin; 40 41 /*Add Proportional on Error,if P_ON_E is specified*/ 42 if(PonE) Output = kp * error; 43 else Output = 0; 44 45 /*Compute Rest of PID Output*/ 46 Output += outputSum - kd * dInput; 47 48 if(Output > outMax) Output = outMax; 49 else if(Output < outMin) Output = outMin; 50 51 /*Remember some variables for next time*/ 52 lastInput = Input; 53 lastTime = now; 54 } 55 } 56 57 void SetTunings(double Kp,double Ki,double Kd,int pOn) 58 { 59 if (Kp<0 || Ki<0|| Kd<0) return; 60 61 PonE = pOn == P_ON_E; 62 63 double SampleTimeInSec = ((double)SampleTime)/1000; 64 kp = Kp; 65 ki = Ki * SampleTimeInSec; 66 kd = Kd / SampleTimeInSec; 67 68 if(controllerDirection ==REVERSE) 69 { 70 kp = (0 - kp); 71 ki = (0 - ki); 72 kd = (0 - kd); 73 } 74 } 75 76 void SetSampleTime(int NewSampleTime) 77 { 78 if (NewSampleTime > 0) 79 { 80 double ratio = (double)NewSampleTime 81 / (double)SampleTime; 82 ki *= ratio; 83 kd /= ratio; 84 SampleTime = (unsigned long)NewSampleTime; 85 } 86 } 87 88 void SetOutputLimits(double Min,double Max) 89 { 90 if(Min > Max) return; 91 outMin = Min; 92 outMax = Max; 93 94 if(Output > outMax) Output = outMax; 95 else if(Output < outMin) Output = outMin; 96 97 if(outputSum > outMax) outputSum= outMax; 98 else if(outputSum < outMin) outputSum= outMin; 99 } 100 101 void SetMode(int Mode) 102 { 103 bool newAuto = (Mode == AUTOMATIC); 104 if(newAuto == !inAuto) 105 { /*we just went from manual to auto*/ 106 Initialize(); 107 } 108 inAuto = newAuto; 109 } 110 111 void Initialize() 112 { 113 lastInput = Input; 114 115 outputSum = Output; 116 if(outputSum > outMax) outputSum= outMax; 117 else if(outputSum < outMin) outputSum= outMin; 118 } 119 120 void SetControllerDirection(int Direction) 121 { 122 controllerDirection = Direction; 123 }
如今你能够得到它。由于上述功能如今已存在于 V1.2.0版的Arduino PID库中。
但等等,还有更多:设定点加权。
我没有将下面的代码添加到Arduino库代码中,可是若是您想滚动本身的代码,这个特性可能会颇有趣。设置点权重的核心是同时拥有PonE和PonM。经过指定0和1之间的比值,能够获得100% PonM、100% PonE(分别)或介于二者之间的某个比值。若是您的流程不是彻底集成的(好比回流炉),而且但愿解释这一点,那么这将很是有用。
最终,我决定不在这个时候将它添加到库中,由于它最终会成为另外一个须要调整/解释的参数,并且我不认为这样作的好处是值得的。不管如何,若是你想修改代码,使其具备设定值权重,而不只仅是纯PonM/PonE选择,下面是代码:
1 /*working variables*/ 2 unsigned long lastTime; 3 double Input,Output,Setpoint; 4 double outputSum,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 #define DIRECT 0 14 #define REVERSE 1 15 int controllerDirection = DIRECT; 16 17 #define P_ON_M 0 18 #define P_ON_E 1 19 bool PonE = true,pOnM = false; 20 double PonEKp,pOnMKp; 21 22 23 void Compute() 24 { 25 if(!inAuto) return; 26 unsigned long now = millis(); 27 int timeChange = (now - lastTime); 28 if(timeChange>=SampleTime) 29 { 30 31 /*Compute all the working error variables*/ 32 double error = Setpoint - Input; 33 double dInput = (Input - lastInput); 34 outputSum+= (ki * error); 35 36 /*Add Proportional on Measurement,if P_ON_M is specified*/ 37 if(pOnM) outputSum-= pOnMKp * dInput 38 39 if(outputSum > outMax) outputSum= outMax; 40 else if(outputSum < outMin) outputSum= outMin; 41 42 /*Add Proportional on Error,if P_ON_E is specified*/ 43 if(PonE) Output = PonEKp * error; 44 else Output = 0; 45 46 /*Compute Rest of PID Output*/ 47 Output += outputSum - kd * dInput; 48 49 if(Output > outMax) Output = outMax; 50 else if(Output < outMin) Output = outMin; 51 52 /*Remember some variables for next time*/ 53 lastInput = Input; 54 lastTime = now; 55 } 56 } 57 58 void SetTunings(double Kp,double Ki,double Kd,double pOn) 59 { 60 if (Kp<0 || Ki<0|| Kd<0 || pOn<0 || pOn>1) return; 61 62 PonE = pOn>0; //some p on error is desired; 63 pOnM = pOn<1; //some p on measurement is desired; 64 65 double SampleTimeInSec = ((double)SampleTime)/1000; 66 kp = Kp; 67 ki = Ki * SampleTimeInSec; 68 kd = Kd / SampleTimeInSec; 69 70 if(controllerDirection ==REVERSE) 71 { 72 kp = (0 - kp); 73 ki = (0 - ki); 74 kd = (0 - kd); 75 } 76 77 PonEKp = pOn * kp; 78 pOnMKp = (1 - pOn) * kp; 79 } 80 81 void SetSampleTime(int NewSampleTime) 82 { 83 if (NewSampleTime > 0) 84 { 85 double ratio = (double)NewSampleTime 86 / (double)SampleTime; 87 ki *= ratio; 88 kd /= ratio; 89 SampleTime = (unsigned long)NewSampleTime; 90 } 91 } 92 93 void SetOutputLimits(double Min,double Max) 94 { 95 if(Min > Max) return; 96 outMin = Min; 97 outMax = Max; 98 99 if(Output > outMax) Output = outMax; 100 else if(Output < outMin) Output = outMin; 101 102 if(outputSum > outMax) outputSum= outMax; 103 else if(outputSum < outMin) outputSum= outMin; 104 } 105 106 void SetMode(int Mode) 107 { 108 bool newAuto = (Mode == AUTOMATIC); 109 if(newAuto == !inAuto) 110 { /*we just went from manual to auto*/ 111 Initialize(); 112 } 113 inAuto = newAuto; 114 } 115 116 void Initialize() 117 { 118 lastInput = Input; 119 outputSum = Output; 120 if(outputSum > outMax) outputSum= outMax; 121 else if(outputSum < outMin) outputSum= outMin; 122 } 123 124 void SetControllerDirection(int Direction) 125 { 126 controllerDirection = Direction; 127 }
没有将pOn设置为整数,而是以双精度输入,容许使用一个比率(第58行)。除了一些标记(第62行和第63行)外,第77-78行计算了加权Kp项。而后在第37行和第43行,将加权后的PonM和PonE贡献添加到整个PID输出中。
欢迎关注: