最近看到了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輸出中。
歡迎關注: