前面我們發布了一系列PID控制器相關的文章,包括經典PID控制器以及參數自適應的PID控制器。這一系列PID控制器雖說實現了主要功能,也在實際使用中取得了良好效果,但還有很多的細節部分可以改進以提高性能和靈活性。所以在這篇中我們來討論改進PID控制器以實現動態調整參數的目的。
1、提出問題
在我們一開始開發PID控制器時,我們主要是關注於其算法的實現而沒有過多的關心其使用過程。但在我們的使用過程中發現有些不夠靈活的地方。
在原有的PID控制器中,設定值是通過在外部給PID對象的參數賦值實現的,雖然說並不影響使用,但我們若想對PID控制器中的參數設定值進行某些處理就不是很方便了。而在原有的PID控制器中,輸出值在外部是不可見的,只能通過PID對象查看且不可更改。這些使得對這些參數的操作顯得不夠靈活。
而且在原有的PID控制器中3個調節參數也不能在外部隨時調整,這顯然不符合很多應用的需要,因為PID參數的調整是很常見的工作。所以在這篇中我們來考慮實現這些參數的動態調整。
2、分析設計
為了使得PID控制器使用起來更為靈活,我們需要將PID對象作必要的改動。關於PID對象我們考慮將測量值、設定值、輸出值作為對象的屬性。但我們不是直接將這幾個變量作為對象屬性,因為這樣達不到我們從外部靈活操作的目的,我們將幾個指向浮點變量的指針作為對象的屬性,而初始化后這幾個指針將指向我們的測量值、設定值、輸出值變量。
同樣的三個PID參數我們想要在外部修改它,我們也將其在外部定義為變量,而在PID對象中定義為指向這三個變量的浮點數指針。在對對象進行初始化時,我們將變量地址賦值給這幾個指針。據此我們定義PID對象類型為:
1 /*定義結構體和公用體*/ 2 typedef struct CLASSIC 3 { 4 float *pPV; //測量值指針 5 float *pSV; //設定值指針 6 float *pMV; //輸出值指針 7 float *pKp; //比例系數指針 8 float *pKi; //積分系數指針 9 float *pKd; //微分系數指針 10 11 float setpoint; //設定值 12 float lasterror; //前一拍偏差 13 float preerror; //前兩拍偏差 14 float deadband; //死區 15 float result; //PID控制器計算結果 16 float output; //輸出值0-100% 17 float maximum; //輸出值上限 18 float minimum; //輸出值下限 19 float errorabsmax; //偏差絕對值最大值 20 float errorabsmin; //偏差絕對值最小值 21 float alpha; //不完全微分系數 22 float deltadiff; //微分增量 23 float integralValue; //積分累計量 24 float gama; //微分先行濾波系數 25 float lastPv; //上一拍的過程測量值 26 float lastDeltaPv; //上一拍的過程測量值增量 27 }CLASSICPID;
3、軟件實現
我們計划將PID參數和過程變量改成指向浮點型變量的指針,那么代碼上需要做哪些修改呢?需要修改的主要是兩個函數:PID調節函數和PID對象初始化函數。
首先,我們來看一看PID對象的初始化函數。我們知道將這些變量修改為指向浮點變量法的指針后,我們就必須在初始化時指定具體的變量地址,否則指向的將是不可預知的位置。所以我們修改初始化函數如下:
1 /* PID初始化操作,需在對vPID對象的值進行修改前完成 */ 2 void PIDParaInitialization(CLASSICPID *vPID, //PID控制器對象 3 float *pPV, //測量值指針 4 float *pSV, //設定值指針 5 float *pMV, //輸出值指針 6 float *pKp, //比例系數指針 7 float *pKi, //積分系數指針 8 float *pKd, //微分系數指針 9 float vMax, //控制變量量程 10 float vMin, //控制變量的零點 11 ) 12 { 13 if((vPID==NULL)||(pPV==NULL)||(pSV==NULL)||(pMV==NULL)||(pKp==NULL)||(pKi==NULL)||(pKd==NULL)) 14 { 15 return; 16 } 17 vPID->pPV=pPV; 18 vPID->pSV=pSV; 19 vPID->pMV=pMV; 20 vPID->pKp=pKp; 21 vPID->pKi=pKi; 22 vPID->pKd=pKd; 23 24 vPID->maximum=vMax; /*輸出值上限*/ 25 vPID->minimum=vMin; /*輸出值下限*/ 26 27 vPID->setpoint=*pPV; /*設定值*/ 28 29 vPID->lasterror=0.0; /*前一拍偏差*/ 30 vPID->preerror=0.0; /*前兩拍偏差*/ 31 vPID->result=vMin; /*PID控制器結果*/ 32 vPID->output=0.0; /*輸出值,百分比*/ 33 34 vPID->errorabsmax=(vMax-vMin)*0.8; 35 vPID->errorabsmin=(vMax-vMin)*0.2; 36 37 vPID->deadband=(vMax-vMin)*0.0005; /*死區*/ 38 vPID->alpha=0.2; /*不完全微分系數*/ 39 vPID->deltadiff=0.0; /*微分增量*/ 40 41 vPID->integralValue=0.0; 42 43 vPID->mode=mode; 44 }
其次,我們還需要修改PID調節函數。在原來的PID調節器中過程值是作為函數的參數輸入的,而且PID參數是作為變量存在於對象內部的,所以要針對這兩個方面做相應的修改:
1 /* 通用PID控制器,采用增量型算法,具有變積分,梯形積分和抗積分飽和功能,微分項采用不完全微分,一階濾波,alpha值越大濾波作用越強 */ 2 void PIDRegulator(CLASSICPID *vPID) 3 { 4 float thisError; 5 float result; 6 float factor; 7 float increment; 8 float pError,dError,iError; 9 10 vPID->setpoint=*vPID->pSV; 11 thisError=vPID->setpoint-(*vPID->pPV); //得到偏差值 12 result=vPID->result; 13 if (fabs(thisError)>vPID->deadband) 14 { 15 pError=thisError-vPID->lasterror; 16 iError=(thisError+vPID->lasterror)/2.0; 17 dError=thisError-2*(vPID->lasterror)+vPID->preerror; 18 19 //變積分系數獲取 20 factor=VariableIntegralCoefficient(thisError,vPID->errorabsmax,vPID->errorabsmin); 21 22 //計算微分項增量帶不完全微分 23 vPID->deltadiff=(*vPID->pKd)*(1-vPID->alpha)*dError+vPID->alpha*vPID->deltadiff; 24 25 increment=(*vPID->pKp)*pError+(*vPID->pKi)*factor*iError+vPID->deltadiff; //增量計算 26 } 27 else 28 { 29 if((fabs(vPID->setpoint-vPID->minimum)<vPID->deadband)&&(fabs((*vPID->pPV)-vPID->minimum)<vPID->deadband)) 30 { 31 result=vPID->minimum; 32 } 33 increment=0.0; 34 } 35 36 result=result+increment; 37 38 /*對輸出限值,避免超調和積分飽和問題*/ 39 if(result>=vPID->maximum) 40 { 41 result=vPID->maximum; 42 } 43 if(result<=vPID->minimum) 44 { 45 result=vPID->minimum; 46 } 47 48 vPID->preerror=vPID->lasterror; //存放偏差用於下次運算 49 vPID->lasterror=thisError; 50 vPID->result=result; 51 52 vPID->output=(vPID->result-vPID->minimum)/(vPID->maximum-vPID->minimum)*100.0; 53 54 *vPID->pMV=vPID->output; 55 } 56 }
4、總結
我們將PID參數和過程變量都改為了對象所包含的指針,這樣當我們從上位機或者其他進程修改變量的值時,也同步修改了PID對象中的值。測試的結果比原來的方式操作更為方便。
歡迎關注: