很久没做电机控制类的项目,突然接触还是有点手生。以前做了许多PID控制类项目,但是都没有正经八百的记录下来。还是自己没有良好的记录习惯,最近公司接到一个小项目,算是有机会重拾PID算法,还是有点小兴奋。早些年参加很多小车竞赛,多用PID对电机的控速,也算是对PID算法有较多的感触。对于常规使用场合,PID算法是最实用的控制算法。结构简单、易于操作实现。具体算法原理不做赘述,就基于STM32的算法实现,笔者在此做一个稍微的总结。
PID控制系统原理框图如下:
基本控制器PID算法公式为:
式中:
u(t)-控制器输出控制量;
e(t)-系统偏差,当前设定值与反馈值之差;
KP-比例系数,放大增益(P);
Ti-积分时间常数,(对应参数是I);
Td-微分时间常数,(对应参数D);
数字PID控制算法通常分为位置式PID和增量式PID控制算法。
位置式PID算法
设U(K)为第K次采样时刻控制器的输出值,于是离散的PID算式如下:
e(k):用户设定值(目标值)- 控制对象的当前的状态值
比例P:
积分I:∑e(i) 误差的累加
微分(D):e(k) -e(k-1) 当前误差 - 上次误差
注意事项:由于积分项的不断累加,也就是说前控制输出与过去的所有状态均有关系,容易产生较大的累加误差,如果控制输出的某一个状态出错就会导致当前系统的大幅度变化,同时在积分项达到饱和时,误差在积分的作用下继续累加,一旦误差开始反向变化,系统需要较长时间退饱和,因此为了防止这种情况的发生,通常对积分和输出限幅。在一定的情况下停止积分的作用。
/*************************************
Uout=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]
*************************************/
typedef struct
{
float Set; //目标值
float fdb; //反馈值
float kp; //比例系数
float ki; //积分系数
float kd; //微分系数
float err_Integral_Lim;//积分限幅值
float errNow; //当前偏差e(k)
float err_old_Last; //上一次偏差e(k-1)
float err_old_LLast; //上上次偏差e(k-2)
float dCtrOut; //控制增量输出
float ctrOut; //控制输出
float KPout; //比例输出
float KIout; //积分输出
float KDout; //微分输出
float KP_OUTMAX; //比例限幅
float KP_OUTMIN;
float KI_OUTMAX; //积分限幅
float KI_OUTMIN;
float KD_OUTMAX; //微分限幅
float KD_OUTMIN;
float OutMax; //输出最大值
float OutMin; //输出最小值
}PID;
PID P_PID; //定义一个位置式的PID结构体变量
void PID_PositionalMode()
{
float P_errP = 0.0,P_errI = 0.0,P_errD = 0.0;
P_PID.fdb = 0.0; //反馈值
P_PID.Set = 0.0; //目标值
P_PID.errNow = P_PID.Set - P_PID.fdb; //当前偏差
P_errP = P_PID.errNow;
P_errI += P_PID.errNow;
P_errD = P_PID.errNow-P_PID.err_old_Last;
P_PID.err_old_Last = P_PID.errNow;
if(P_PID.err_Integral_Lim!=0) //积分限幅
{
if(P_errI> P_PID.err_Integral_Lim) P_errI = P_PID.err_Integral_Lim;
else if(P_errI<-P_PID.err_Integral_Lim)P_errI = -P_PID.err_Integral_Lim;
}
P_PID.KPout = P_PID.kp * P_errP; //
if(P_PID.KPout>P_PID.KP_OUTMAX) P_PID.KPout = P_PID.KP_OUTMAX;
if(P_PID.KPout<-P_PID.KP_OUTMIN)P_PID.KPout = P_PID.KP_OUTMIN;
P_PID.KIout = P_PID.ki * P_errI;
if(P_PID.KIout>P_PID.KI_OUTMAX) P_PID.KIout =P_PID.KI_OUTMAX; //
if(P_PID.KIout<-P_PID.KI_OUTMIN)P_PID.KIout =-P_PID.KI_OUTMIN;
P_PID.KDout = P_PID.kd * P_errD;
if(P_PID.KDout>P_PID.KD_OUTMAX)P_PID.KDout = P_PID.KD_OUTMAX; //
if(P_PID.KDout<-P_PID.KD_OUTMIN)P_PID.KDout = P_PID.KD_OUTMIN;
//位置式PID计算
P_PID.ctrOut = P_PID.KPout +P_PID.KIout+P_PID.KDout;
if(P_PID.ctrOut>P_PID.OutMax) P_PID.ctrOut = P_PID.OutMax;
if(P_PID.ctrOut<-P_PID.OutMin)P_PID.ctrOut = P_PID.OutMin;
}
增量式PID算法
设U(K)为第K次采样时刻控制器的输出值,于是离散的PID算式如下:
比例P:e(k)-e(k-1) 当前误差-上次误差
积分I:e(i) 当前误差
微分D:e(k)-2e(k-1)-e(k-2) 当前误差-2*上次误差+上上次误差
增量式PID与位置式相比,最终求出的是控制输出的增量,而且只使用了前后三次的测量值的偏差。
注意事项:
由于位置式 PID的控制输出与整个过去的状态都有关,增量式PID只与当前三次状态有关,没有积分的累加误差,因此相对来说,增量式的累计误差相对较小。但是增量式PID的缺点也是很明显的,积分截断效应大,有稳态误差,溢出的影响大,需要对输出进行限幅。
/********************************************************
//根据增量式离散PID公式
//Uout+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
//e(k)代表本次偏差
//e(k-1)代表上一次的偏差
//e(k-2)代表上上次的偏差
********************************************************/
typedef struct
{
float Set; //目标值
float fdb; //反馈值
float kp; //比例系数
float ki; //积分系数
float kd; //微分系数
float err_Integral_Lim;//积分限幅值
float errNow; //当前偏差e(k)
float err_old_Last; //上一次偏差e(k-1)
float err_old_LLast; //上上次偏差e(k-2)
float dCtrOut; //控制增量输出
float ctrOut; //控制输出
float KPout; //比例输出
float KIout; //积分输出
float KDout; //微分输出
float KP_OUTMAX; //比例限幅
float KP_OUTMIN;
float KI_OUTMAX; //积分限幅
float KI_OUTMIN;
float KD_OUTMAX; //微分限幅
float KD_OUTMIN;
float OutMax; //输出最大值
float OutMin; //输出最小值
}PID;
PID P_PID; //定义一个增量式的PID结构体变量
void PID_IncrementalMode(PID* PID)
{
float dErrP = 0,dErrI = 0.0,dErrD = 0.0;
P_PID.fdb = 0.0; //反馈值
P_PID.Set = 0.0; //目标值
P_PID.errNow = P_PID.Set - P_PID.fdb; //当前偏差
dErrP = P_PID.errNow - P_PID.err_old_Last;
dErrI = P_PID.errNow;
dErrD = P_PID.errNow-P_PID.err_old_Last+P_PID.err_old_LLast - P_PID.err_old_Last;
P_PID.err_old_LLast = P_PID.err_old_Last;
P_PID.err_old_Last = P_PID.errNow;
if(P_PID.err_Integral_Lim!=0)
{
if(dErrI>P_PID.err_Integral_Lim)
dErrI = P_PID.err_Integral_Lim;
else if(dErrI < -P_PID.err_Integral_Lim)
dErrI = -P_PID.err_Integral_Lim;
}
P_PID.dCtrOut = dErrP*P_PID.kp +dErrI*P_PID.ki +dErrD*P_PID.kd; //输出增量
P_PID.ctrOut += P_PID.dCtrOut; //控制输出
if(P_PID.ctrOut>P_PID.OutMax) P_PID.ctrOut=P_PID.OutMax;
}