前言 在刚接触到PID控制器的时候,我对增量式,位置式这些其实也是很懵的,然后又有什么速度环啊,位置环啊,电流环啊…巴拉巴拉一堆的,但是现在理解起来其实也就利用一些简简单单的离散数据运算出期望罢了。 首先假设我们已经知道了什么是Kp、Ki、Kd了(不知道的话那就看看我上一篇讲PID的那个文章)。 关于PID的算法可以分为两大类,一类是位置式,还有一类是增量式,下面就开始说说我对这两种算法的理解。 位置式PID首先是位置式,也是我用得最多的一种算法。位置式PID其实就是当前系统实际位置,与你期望想要达到的位置的偏差而进行的PID控制。那么一个用位置式的系统中是什么样的呢? 看公式来理解: U k = K p ∗ e k + K i ∑ j = 0 k e k + K d ( e k − e k − 1 ) U_k= K_p*e_k+K_i\sum^k_{j=0}e_k+K_d(e_k-e_{k-1})Uk=Kp∗ek+Ki∑j=0kek+Kd(ek−ek−1) 由上式可以看出,PID的每次运算的输出都与过去的状态有关,并且积分项的误差会进行累加。如果偏差一直都是正的或者是负的,位置式PID在积分项就会一直累积,当偏差开始反向变化的时候,位置式PID需要一段时间才能从最大值减下来,会造成位置PID控制输出的滞后。所以我们还需要对积分项进行限幅(做一个最大值max和最小值min),同时也要对输出进行限幅。同时,我们输出的U k U_kUk对应的是执行机构的实际位置,一旦控制输出出错了,也就是说我们控制的对象当前的状态值出现了问题,U k U_kUk的大幅度变化就会影响到系统的大幅度变化。 所以我们如果单单使用位置式PID的时候,一般都是直接使用PD控制的,也正因为这样,位置式PID是用于执行机构不带积分部件的对象,像平衡车的直立控制呀、温控系统呀…等等。 下面是代码实现: #define HAVE_PID_INTEGRAL#define LIMIT(TargetValue, LimitValue) \if (TargetValue > LimitValue)\{\ TargetValue = LimitValue;\}\else if (TargetValue < -LimitValue)\{\ TargetValue = -LimitValue;\}\typedef struct{ float Kp; float Ki; float Kd;#ifdef HAVE_PID_INTEGRAL int index; // 积分分离系数 float Integral; // 积分项 float I_outputMax; // 积分限幅#endif float Last_Err; // 上次误差 float Output; // PID输出 float OutputMax; // 位置式PID输出限幅}Position_PID;void PositionPID_Calculate(Position_PID *pid,const float Target,const float Measure){ if(pid == NULL) return; float Err; Err = Target - Measure; pid->Output = pid->Kp * Err + pid->Kd * (Err - pid->Last_Err) #ifdef HAVE_PID_INTEGRAL /* 积分分离 */ if(abs(pid->Err) > Integraldead_zone) { pid->index=0; }else { pid->index = 1; } pid->Integral += pid->Ki * Err * pid->index; LIMIT(pid->Integral,I_outputMax); pid->Output += pid->Integral;#endif LIMIT(Output,OutputMax); pid->Last_Err = Err; }优点: 位置式是一种非递推式算法,可以直接控制对象,U(k)的值与对象的实际当量是一一对应的,所以在不带积分部件的控制对象中可以很好应用。 缺点: 每次输出都与之前的状态有关,并且还要对误差值err进行累加,计算量大。 增量式PID那么什么是增量式PID呢?增量式PID的输出只是控制量的增量Δ U k \Delta U_kΔUk。当执行机构需要的控制量是增量,那么我们就可以采用增量式PID控制算法进行控制。(增量式PID的计算输出结果是增量,并不是直接作用到执行机构) (增量式PID可以由位置式推导出,感兴趣的可以百度,这里就不浪费篇幅了。) 看公式来理解: Δ U k = K p ( e ( k ) − e ( k − 1 ) ) + K i e ( k ) + K D [ e ( k ) − 2 e ( k − 1 ) + e ( k − 2 ) ] \Delta U_k=K_p(e(k)-e(k-1))+K_i{e(k)}+K_D[e(k)-2e(k-1)+e(k-2)]ΔUk=Kp(e(k)−e(k−1))+Kie(k)+KD[e(k)−2e(k−1)+e(k−2)] 对于增量式PID来说,给定一个输入量,系统反馈回来的量与设定的量的偏差为Err,系统中保存上一次的偏差Last_Err和上上次的偏差Previous_Err,这三个输入量经过增量式PID可以计算得到上述说的控制量增量Δ U k \Delta U_kΔUk。而得出的控制量Δ U ( k ) \Delta U(k)ΔU(k)对应的是近几次位置误差的增量,而不是对应与实际位置的偏差,也就是说没有误差累加。即在上一次的控制量的基础上需要增加控制量。 以下是代码实现: #define LIMIT(TargetValue, LimitValue) \if (TargetValue > LimitValue)\{\ TargetValue = LimitValue;\}\else if (TargetValue < -LimitValue)\{\ TargetValue = -LimitValue;\}\#define Integraldead_zone 100 // 积分死区 根据自己的需求定义typedef struct{ float Kp; float Ki; float Kd; float p_out; float i_out; float d_out; float Err; float Last_Err; // 上次误差 float Previous_Err; // 上上次误差 float Output; float OutputMax; // 增量式式PID输出限幅}Incremental_PID;void IncrementalPID_Calculate(Incremental_PID *pid,const float Target,const float Measure){ if(pid == NULL) return; pid->Err = Target - Measure; pid->p_out = pid->Kp * (Err - Last_Err); pid->i_out = pid->Ki * Err; pid->d_out = pid->Kd * (Err - 2.0f*Last_Err + Previous_Err); pid->Output += p_out + i_out + d_out; LIMIT(pid->Output, pid->OutputMax); // 限幅 pid->Previous_Err = pid->Last_Err; pid->Last_Err = Err; }优点: - 系统在误动作时影响小,并且可以利用逻辑判断来对错误数据进行去除。
- 冲激小,便于实现无扰动切换。
- 不需要累加误差值,并且控制增量的确定只与最近几次采样值有关。
缺点: - 积分的阶段效应大,有稳态误差。
- 溢出的影响大,有的被控对象用增量式不好。
总结(增量式与位置式的区别)- 增量式算法不需要做累加,增量式PID求出来的是系统需要的增量,并且增量的确定仅与最近几次偏差采样值有关,计算的误差对控制量计算的影响比较小。与位置式相比的话,位置式就需要用到偏差的累加值(Ki*err),容易产生累计误差。
- 增量式PID控制输出的是控制量增量,没有积分作用,所以该方法适用于带积分部件的对象。而位置式PID适用于执行机构不带积分部件的对象。
- 上面的代码也很清楚的显示了,位置式PID需要积分限幅和输出限幅,而增量式PID只需要输出限幅。
|