前面我們發布了一系列PID控制器相關的文章,包括經典PID控制器以及參數自適應的PID控制器。這一系列PID控制器雖說實現了主要功能,也在實際使用中取得了良好效果,但還有很多的細節部分可以改進以提高性能和靈活性。所以在這篇中我們來討論改進PID控制器以實現正反作用轉換。
1、提出問題
到目前為止我們討論的情況都是在增大輸出而控制變量也隨之增加的情況,而實際的情況並非完全如此。有些系統當我們增加輸出時,控制變量會隨之減小,這就是我們在此要討論的問題。在前面的PID控制器開發中我們並未考慮這一問題,為了增強PID控制器使用的適應性和方便性,我們需要添加上控制PID輸出方向的功能,這就是所說的正反作用控制。
事實上,PID控制器分為正作用和反作用兩種,這本身並沒有什么本質上的區別。但需要使用者自己清楚,是希望輸出增加時輸入也增加還是輸出增加時輸入減小,並以此來確定控制器的正反作用。
2、分析設計
我們要添加上正反作用的功能該如何實現呢?首先我們需要明白,所謂正作用實際就是輸出增加輸入值也隨之增加的控制方式;而反作用就是輸出增加輸入值隨之減小的控制方式。一個系統選擇什么樣的作用方式是由系統本身的特性決定的,這包括被控對象的屬性,執行機構的操作特性等。換句話說我們沒辦法要通過對輸入的處理實現作用方式,但可以從輸出的角度來實現我們的要求。
首先,我們需要為PID對象添加一個描述正反作用的屬性。這個屬性的取值決定了控制器對象采用何種作用方式。
1 /*定義正反作用枚舉類型*/ 2 typedef enum ClassicPIDDR { 3 DIRECT, //正作用 4 REVERSE //反作用 5 }ClassicPIDDRType; 6 7 /*定義PID對象類型*/ 8 typedef struct CLASSIC 9 { 10 float *pPV; //測量值指針 11 float *pSV; //設定值指針 12 float *pMV; //輸出值指針 13 float *pKp; //比例系數指針 14 float *pKi; //積分系數指針 15 float *pKd; //微分系數指針 16 uint16_t *pMA; //手自動操作指針 17 18 float setpoint; //設定值 19 float lasterror; //前一拍偏差 20 float preerror; //前兩拍偏差 21 float deadband; //死區 22 float result; //PID控制器計算結果 23 float output; //輸出值0-100% 24 float maximum; //輸出值上限 25 float minimum; //輸出值下限 26 float errorabsmax; //偏差絕對值最大值 27 float errorabsmin; //偏差絕對值最小值 28 float alpha; //不完全微分系數 29 float deltadiff; //微分增量 30 float integralValue; //積分累計量 31 float gama; //微分先行濾波系數 32 float lastPv; //上一拍的過程測量值 33 float lastDeltaPv; //上一拍的過程測量值增量 34 35 ClassicPIDDRType direct; //正反作用 36 }CLASSICPID;
其次,我們要在初始化對象時,對這一屬性進行初始化。至於初始化的值,就需要根據實際使用需求選擇枚舉。
最后還需要在PID控制器中實現這部分的操作。實現這一操作的方式有兩種。一種方法是將PID的三個參數取反變為負值,這樣就可以實現反作用。第二種方法是我們正常使用PID的三個參數計算而將增量部分取反,這樣也可實現反作用。顯然第一種方法便於理解,而第二種方法更便於操作,這里我們使用第二種方法來實現。
3、軟件實現
我們已經設計好使用參數正常計算增量,在正作用時將增量取加,而在反作用時將增量取為減,這樣就實現了正反作用的轉換。這里所說的“加”與“減”是純粹的數學運算,不用考慮增量本身的符號是正是負。正反作用與手自動轉換的性質不同,並不需要在線修改,在系統確定后就已經確定,所以我們需要在初始化中設定它。而在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 if(*vPID->pMA<1) //手動模式 11 { 12 vPID->output=*vPID->pMV; 13 //設置無擾動切換 14 vPID->result=(vPID->maximum-vPID->minimum)*vPID->output/100.0+-vPID->minimum; 15 *vPID->pSV=*vPID->pPV; 16 vPID->setpoint=*vPID->pSV; 17 } 18 else //自動模式 19 { 20 vPID->setpoint=*vPID->pSV; 21 thisError=vPID->setpoint-(*vPID->pPV); //得到偏差值 22 result=vPID->result; 23 if (fabs(thisError)>vPID->deadband) 24 { 25 pError=thisError-vPID->lasterror; 26 iError=(thisError+vPID->lasterror)/2.0; 27 dError=thisError-2*(vPID->lasterror)+vPID->preerror; 28 29 //變積分系數獲取 30 factor=VariableIntegralCoefficient(thisError,vPID->errorabsmax,vPID->errorabsmin); 31 32 //計算微分項增量帶不完全微分 33 vPID->deltadiff=(*vPID->pKd)*(1-vPID->alpha)*dError+vPID->alpha*vPID->deltadiff; 34 35 increment=(*vPID->pKp)*pError+(*vPID->pKi)*factor*iError+vPID->deltadiff; //增量計算 36 } 37 else 38 { 39 if((fabs(vPID->setpoint-vPID->minimum)<vPID->deadband)&&(fabs((*vPID->pPV)-vPID->minimum)<vPID->deadband)) 40 { 41 result=vPID->minimum; 42 } 43 increment=0.0; 44 } 45 46 //正反作用設定 47 if(vPID->direct==DIRECT) 48 { 49 result=result+increment; 50 } 51 else 52 { 53 result=result-increment; 54 } 55 56 /*對輸出限值,避免超調和積分飽和問題*/ 57 if(result>=vPID->maximum) 58 { 59 result=vPID->maximum; 60 } 61 if(result<=vPID->minimum) 62 { 63 result=vPID->minimum; 64 } 65 66 vPID->preerror=vPID->lasterror; //存放偏差用於下次運算 67 vPID->lasterror=thisError; 68 vPID->result=result; 69 70 vPID->output=(vPID->result-vPID->minimum)/(vPID->maximum-vPID->minimum)*100.0; 71 *vPID->pMV=vPID->output; 72 } 73 }
4、總結
我們在PID控制器中增加了正反作用的處理,經驗證與我們預期的結果是一致的。其實正反作用並不復雜,就是要在我們期望輸入增加時,如何控制輸出的方向。如果輸入輸出的變化趨勢一致,我們就需要采用正作用;如果輸入輸出的變化趨勢相反,則我們就需要選擇反作用。
關於如何確定一個控制器的正反作用,這里我們不妨舉一個例子。我們設想我們需要控制一個水槽的液位,怎么樣確定這個控制器的正反作用呢?我們說過,確定控制器的正反作用需要考慮被控對象的特性和執行機構的特性。對於這個例子,被控對象我們要考慮是入水口可控還是出水口可控,執行機構是常閉還是常開,所以有四種情況:

所謂常閉型和常開型是指在沒有信號輸入的時候,執行機構所處的狀態。所以常開型給的信號越大開度越小,常閉型給的信號越大開度越大。當然,這只是一個簡單的例子,實際使用中會更復雜,但都可從被控對象和執行機構的特性入手來選取控制器的正反作用。
歡迎關注:

