前面我們發布了一系列PID控制器相關的文章,包括經典PID控制器以及參數自適應的PID控制器。這一系列PID控制器雖說實現了主要功能,也在實際使用中取得了良好效果,但還有很多的細節部分可以改進以提高性能和靈活性。這篇中我們來討論改進PID控制器參數設置的問題。
1、問題提出
在前面的文章中我們曾推導過PID控制器的公式,並且對其進行了離散化以適用於程序實現,具體的離散化公式如下:
在編寫程序時,我們將比例項的系數設定為Kp、積分項的系數設定為Ki、微分項的系數設定為Kd,其中:
這其中T是采樣周期,Ti是積分時間,Td是微分時間。所以在設置參數的時候我們需要先去頂比例系數Kp,然后在根據采樣周期和積分微分時間來計算Ki和Kd。這么做雖然是公式變得簡單了,但與我們傳統的參數設置相比就顯得不那么直觀,所以有些使用者希望還是以傳統的比例帶PB、積分時間Ti、微分時間Td來配置相應的參數,這一篇中就來分析並解決這個問題。
2、分析設計
對於上述這個問題,我們需要搞清楚Kp、Ki、Kd與PB、Ti、Td之間的關系。事實上,它們之間的關系並不復雜。首先比例系數Kp與比例帶之間是互為倒數的關系,所以我們知道了其中一個就可以得到另一個。而Ti和Ki的關系以及Td和Kd的關系我們前面已經給出了。
接下來我們需要做的事,實際上就是讓我們的PID控制器在不同的應用需求下呈現出不同的參數設置就可以設置不同的參數形式了。
3、軟件實現
我們已經分析了需要實現的內容,接下來我們就來考慮怎么實現。關於這一點,我們考慮我們的PID控制器的設計形式,需要修改的主要是三個方面的內容。第一個需要修改的地方就是PID控制器對象的定義。我們定義一個宏來實現條件編譯,以實現在不同的需求下實現不同的參數定義,所以我們實現PID控制器的對象類型定義如下:
/*定義PID對象類型*/
typedef struct CLASSIC
{
float *pPV; //測量值指針
float *pSV; //設定值指針
float *pMV; //輸出值指針
uint16_t *pMA; //手自動操作指針
\#if PID_PARAMETER_STYLE > (0)
float *pKp; //比例系數指針
float *pKi; //積分系數指針
float *pKd; //微分系數指針
\#else
float *pPb; //比例帶
float *pTi; //積分時間,單位為秒
float *pTd; //微分時間,單位為秒
float ts; //采樣周期,單位為秒
\#endif
float setpoint; //設定值
float lasterror; //前一拍偏差
float preerror; //前兩拍偏差
float deadband; //死區
float result; //PID控制器計算結果
float output; //輸出值0-100%
float maximum; //輸出值上限
float minimum; //輸出值下限
float errorabsmax; //偏差絕對值最大值
float errorabsmin; //偏差絕對值最小值
float alpha; //不完全微分系數
float deltadiff; //微分增量
float integralValue; //積分累計量
float gama; //微分先行濾波系數
float lastPv; //上一拍的過程測量值
float lastDeltaPv; //上一拍的過程測量值增量
ClassicPIDDRType direct; //正反作用
ClassicPIDSMType sm; //設定值平滑
ClassicPIDCSType cas; //串級設定
}CLASSICPID;
我們定義了對象類型,可以得到我們需要的對象變量,但這個對象變量需要初始化才能使用。所以第二個需要修改的地方就是PID控制器對象初始化函數。我們使用條件編譯,在不同的應用需求下我們初始化不同的對象參數,具體實現如下:
/* PID初始化操作,需在對vPID對象的值進行修改前完成 */
/* CLASSICPID vPID,普通PID對象變量,實現數據交換與保存 */
/* float vMax,float vMin,過程變量的最大最小值(量程范圍) */
void PIDParaInitialization(CLASSICPID *vPID, //PID控制器對象
float *pPV, //測量值指針
float *pSV, //設定值指針
float *pMV, //輸出值指針
\#if PID_PARAMETER_STYLE > (0)
float *pKp, //比例系數指針
float *pKi, //積分系數指針
float *pKd, //微分系數指針
\#else
float *pPb; //比例帶
float *pTi; //積分時間
float *pTd; //微分時間
float ts, //采樣周期,單位為秒
\#endif
uint16_t *pMA, //手自動操作指針
float vMax, //控制變量量程
float vMin, //控制變量的零點
ClassicPIDDRType direct, //正反作用
ClassicPIDSMType sm, //設定值平滑
ClassicPIDCSType cas //串級設定
)
{
if((vPID==NULL)||(pPV==NULL)||(pSV==NULL)||(pMV==NULL)||(pMA==NULL))
{
return;
}
vPID->pPV=pPV;
vPID->pSV=pSV;
vPID->pMV=pMV;
vPID->pMA=pMA;
\#if PID_PARAMETER_STYLE > (0)
if((pKp==NULL)||(pKi==NULL)||(pKd==NULL))
{
return;
}
vPID->pKp=pKp;
vPID->pKi=pKi;
vPID->pKd=pKd;
if(*vPID->pKp<=0.00001)
{
*vPID->pKp=1.0; //比例系數
*vPID->pKi=0.01; //積分系數
*vPID->pKd=0.01; //微分系數
}
\#else
if((pPb==NULL)||(pTi==NULL)||(pTd==NULL))
{
return;
}
vPID->pPb=pPb;
vPID->pTi=pTi;
vPID->pTd=pTd;
vPID->ts=ts;
if(*vPID->pPb<=0.00001)
{
*vPID->pPb=1.0; //比例帶
*vPID->pTi=1.0; //積分時間,單位為秒
*vPID->pTd=0.0001; //微分時間,單位為秒
}
\#endif
vPID->maximum=vMax; //控制變量的量程
vPID->minimum=vMin; //控制變量的零點
*vPID->pSV=*pPV; //設定值
vPID->setpoint=*pPV; //設定值
*vPID->pMA=1; //初始化為自動模式
vPID->direct=direct; //設定PID對象的正反作用
vPID->cas=cas; //設定是否啟用串級
vPID->sm=sm; //設定是否啟用設定值平滑
if(vPID->cas==CASCADE)
{
vPID->sm=SMOOTH_DISABLE;
}
vPID->lasterror=0.0; //前一拍偏差
vPID->preerror=0.0; //前兩拍偏差
vPID->result=vMin; //PID控制器結果
vPID->output=0.0; //輸出值,百分比
*vPID->pMV=vPID->output; //輸出值,百分比
vPID->errorabsmax=(vMax-vMin)*0.9;
vPID->errorabsmin=(vMax-vMin)*0.1;
vPID->deadband=(vMax-vMin)*0.001; //死區
vPID->alpha=0.2; //不完全微分系數
vPID->deltadiff=0.0; //微分增量
vPID->integralValue=0.0;
}
第三個需要修改的是PID控制器對象的實現。在前面我們已經描述PB、Ti、Td與Kp、Ki、Kd之間的數學關系。為了方便處理,我們通過條件編譯在不同應用需求下將參數均轉化為統一的Kp、Ki、Kd來進行計算。具體的實現方式如下:
/* 通用PID控制器,采用增量型算法,具有變積分,梯形積分和抗積分飽和功能 */
/* 微分項采用不完全微分,一階濾波,alpha值越大濾波作用越強 */
/* CLASSICPID vPID,PID對象變量,實現數據交換與保存 */
/* float pv,過程測量值,對象響應的測量數據,用於控制反饋 */
void PIDRegulator(CLASSICPID *vPID)
{
float thisError;
float result;
float factor;
float increment;
float pError,dError,iError;
float kp,ki,kd;
\#if PID_PARAMETER_STYLE > (0)
kp=*vPID->pKp;
ki=*vPID->pKi;
kd=*vPID->pKd;
\#else
if((*vPID->pTi)<vPID->ts)
{
*vPID->pTi=vPID->ts;
}
kp=1/(*vPID->pPb);
ki=kp*(vPID->ts/(*vPID->pTi));
kd=kp*((*vPID->pTd)/vPID->ts);
\#endif
if(*vPID->pMA<1) //手動模式
{
vPID->output=*vPID->pMV;
//設置無擾動切換
vPID->result=(vPID->maximum-vPID->minimum)*vPID->output/100.0+vPID->minimum;
*vPID->pSV=*vPID->pPV;
vPID->setpoint=*vPID->pSV;
}
else //自動模式
{
if(vPID->sm==SMOOTH_ENABLE) //設定值平滑變化
{
SmoothSetpoint(vPID);
}
else
{
if(vPID->cas==CASCADE) //串級處理
{
vPID->setpoint=(vPID->maximum-vPID->minimum)*(*vPID->pSV)/100.0+vPID->minimum;
}
else
{
vPID->setpoint=*vPID->pSV;
}
}
thisError=vPID->setpoint-(*vPID->pPV); //得到偏差值
result=vPID->result;
if (fabs(thisError)>vPID->deadband)
{
pError=thisError-vPID->lasterror;
iError=(thisError+vPID->lasterror)/2.0;
dError=thisError-2*(vPID->lasterror)+vPID->preerror;
//變積分系數獲取
factor=VariableIntegralCoefficient(thisError,vPID->errorabsmax,vPID->errorabsmin);
//計算微分項增量帶不完全微分
vPID->deltadiff=kd*(1-vPID->alpha)*dError+vPID->alpha*vPID->deltadiff;
increment=kp*pError+ki*factor*iError+vPID->deltadiff; //增量計算
}
else
{
if((fabs(vPID->setpoint-vPID->minimum)<vPID->deadband)&&(fabs((*vPID->pPV)-vPID->minimum)<vPID->deadband))
{
result=vPID->minimum;
}
increment=0.0;
}
//正反作用設定
if(vPID->direct==DIRECT)
{
result=result+increment;
}
else
{
result=result-increment;
}
/*對輸出限值,避免超調和積分飽和問題*/
if(result>=vPID->maximum)
{
result=vPID->maximum;
}
if(result<=vPID->minimum)
{
result=vPID->minimum;
}
vPID->preerror=vPID->lasterror; //存放偏差用於下次運算
vPID->lasterror=thisError;
vPID->result=result;
vPID->output=(vPID->result-vPID->minimum)/(vPID->maximum-vPID->minimum)*100.0;
*vPID->pMV=vPID->output;
}
}
4、總結
在這一篇中,我們只是為了實現不同使用者的需求,將PID控制器的參數定義做了相應的修改,而控制器本身的功能並沒有什么變化。這樣既保證原有的應用不會受到影響,新的應用也可以根據需要定義參數,使用Kp、Ki、Kd或是PB、Ti、Td由應用設計的需要選擇。
這里需要說一下,不論我們如何定義參數,采樣周期的選擇都需要認真考慮。即使我們采用相同的參數整定,當采樣周期不同時,效果可能會有較大差異,所以在整定參數前應根據系統的特性采用合適的采樣周期。