所谓PID,就是指比例、积分、微分控制器,是一种非常经典的控制算法。它的用途很多,是不需要知道系统的模型,仅仅根据期望与现状的偏差调节,使之能够到达期望的一种线性控制器。
其用途广泛,在温度控制和电机控制中被广泛运用。在刚刚结束的智能车竞赛中,我们所用的电机控制就是典型的PID控制。
所谓PID,就是三个参数P、I、D对控制对象进行调节。分增量式和位置式两种,在电机的控制方案上我们采取的是增量式PID进行控制,因为位置式PID控制的输出与整个过去的状态有关,用到了误差的累加值;而增量式PID的输出只与当前拍和前两拍的误差有关,因此位置式PID控制的累积误差相对更大;
增量式PID的公式:pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
在PID调节中,各个参数起着不同的作用,但是这些参数又不能通过具体的公式推导出合适值,我们称之为超参数
P参数的作用是加快调节速度,使之更快到达期望位置,但是只有P参数调节时会存在静态误差,所谓静态误差就是说与系统输入的期望值总是差一个值,这个值称为静态误差。
I参数的作用是减小静态误差使曲线达到期望值,但是I参数的加入会增加调节时间,可能还会引入超调量。
D参数的相当于是一个反馈的作用,它可以减少调节时间,在控制量低于目标值的时候会使曲线加速生长,在控制量高于目标值的时候会抑制曲线增长,减少超调。但D参数容易受到干扰,在干扰比较强的系统中,引入D值会使系统更加不稳定。
对于不同的系统可以根据系统的特征使用不同的参数组合,PD,PI,PID。
在伺服电机如舵机的控制中我们可以采取PD控制,因为舵机用来打角,需要很快的响应速度,引入I值会增加响应时间,所以我们采取PD控制。
在步进电机如电机的控制中我们采取PI控制,因为电机控制系统干扰强,而且电机不需要很高的响应速度,所以我们采取PI控制。
/**************************************************************************
函数功能:增量式电机PID
入口参数:电机控制结构体参数,PID结构体参数
返回 值:无
**************************************************************************/
void SpeedCtrl(motorL_param_t *p_stMotorL,motorR_param_t *p_stMotorR)
{
static int32 LastPidOutL=0,LastPidOutR=0;
int32 PidOutL=0,PidOutR=0;
p_stMotorL->PID.Error[2] = p_stMotorL->PID.Error[1];
p_stMotorL->PID.Error[1] = p_stMotorL->PID.Error[0];
p_stMotorL->PID.Error[0] = p_stMotorL->IdealSpeedL - p_stMotorL->RealSpeedL;
p_stMotorR->PID.Error[2] = p_stMotorR->PID.Error[1];
p_stMotorR->PID.Error[1] = p_stMotorR->PID.Error[0];
p_stMotorR->PID.Error[0] = p_stMotorR->IdealSpeedR - p_stMotorR->RealSpeedR;
/*左电机增量式PID*/
PidOutL = (int32)(p_stMotorL->PID.KI * p_stMotorL->PID.Error[0] +
p_stMotorL->PID.KP * ((int64)(p_stMotorL->PID.Error[0] - p_stMotorL->PID.Error[1])) +
p_stMotorL->PID.KD * ((int64)p_stMotorL->PID.Error[0] - 2*(int64)p_stMotorL->PID.Error[1] + p_stMotorL->PID.Error[2]));
/*右电机增量式PID*/
PidOutL = (int32)(p_stMotorR->PID.KI * p_stMotorR->PID.Error[0] +
p_stMotorR->PID.KP * ((int64)(p_stMotorR->PID.Error[0] - p_stMotorR->PID.Error[1])) +
p_stMotorR->PID.KD * ((int64)p_stMotorR->PID.Error[0] - 2*(int64)p_stMotorR->PID.Error[1] + p_stMotorR->PID.Error[2]));
/*左电机抗积分饱和*/
if (LastPidOutL >= p_stMotorL->Max)
{
if (PidOutL > 0)
{PidOutL = 0;}
}
if (LastPidOutL <= p_stMotorL->Max)
{
if (PidOutL < 0)
{PidOutL = 0;}
}
/*右电机抗积分饱和*/
if (LastPidOutR >= p_stMotorR->Max)
{
if (PidOutR > 0)
{PidOutR = 0;}
}
if (LastPidOutR <= p_stMotorR->Max)
{
if (PidOutR < 0)
{PidOutR = 0;}
}
/*增量输出*/
p_stMotorL->PIDOutL += PidOutL;
p_stMotorR->PIDOutR += PidOutR;
/*限幅保护*/
if(p_stMotorL->PIDOutL >= p_stMotorL->Max)
{p_stMotorL->PIDOutL = p_stMotorL->Max;}
if(p_stMotorR->PIDOutR >= p_stMotorR->Max)
{p_stMotorR->PIDOutR = p_stMotorR->Max;}
LastPidOutL = PidOutL;
LastPidOutR = PidOutR;
/*输出给电机执行*/
MotorCtrlPwmOut((int16)p_stMotorL->PIDOutL,(int16)p_stMotorR->PIDOutR);
}
我的PID算法中引入了抗积分饱和,所谓积分饱和,是指控制器的输出由于积点的作用不断累加,导致电机的控制量达到一个极限值,若输出继续增加,那么控制量就会超过正常范围进入一个饱和区。若此时系统受到一个反向的偏差量,那么系统的输出就要从饱和区花一定时间退出,这时电机不能马上根据偏差进行调节,这就会导致系统的控制性能大打折扣。
而抗积分饱和是在系统计算输出时,先判断上一个输出量是否超出限制的范围,如果上一个输出量大于最大值,那么系统只累加负偏差;如果上一个输出量小于最大值,那么系统只累加正偏差。这就有效抑制系统出现积分饱和的现象。
其实PID还有许多其他的算法,如积分分离,微分先行,这些我会在后面继续更新。
关于PID超参数的调参,我的方案是:在三个参数都是0的情况下调节P值增大,使响应时间变快,直到系统震荡,再降低些许P值,再给些许I值,直到系统的响应时间和稳定性能达到一个最佳值。
关于硬件驱动,我的建议是采用HIP4082和DRV8701,我实际调过的有HIP4082和一款比较蠢的芯片RZ7889。我的评价是,HIP4082的线性性比较好,PID调节相对轻松,但是PID调到很硬的情况下电机堵转容易炸管;RZ7889,多的也就不说了,真的rz,不建议在智能车上使用。
第一次写博文,有写的不好的点多多谅解。