4、增量式PID实现

增量式PID,这个名字听起来挺唬人。其实说白了,它跟位置式PID是亲兄弟,只是算账的方式不一样。我个人习惯在电机控制、步进驱动这类场景里用增量式,因为它算得快,而且不容易出大乱子。

4.1 增量式PID公式推导

我们先回忆一下位置式PID的公式:

u(k) = Kp * e(k) + Ki * Σe(i) + Kd * [e(k) - e(k-1)]

这个公式里有个累加项 Σe(i),它记录了从启动到现在的所有误差。你想想看,如果系统跑了一天,这个累加值会变得非常大。万一哪天积分项溢出了,输出直接飞掉,电机就乱转了。

增量式PID的思路很简单——我不算总输出,我只算这次比上次多了多少

推导过程是这样的:

u(k) = Kp * e(k) + Ki * Σe(i) + Kd * [e(k) - e(k-1)]
u(k-1) = Kp * e(k-1) + Ki * Σe(i-1) + Kd * [e(k-1) - e(k-2)]

Δu(k) = u(k) - u(k-1)
     = Kp * [e(k) - e(k-1)] + Ki * e(k) + Kd * [e(k) - 2*e(k-1) + e(k-2)]

嗯,这里要注意:增量式PID的输出是Δu(k),不是u(k)。最终的控制量是 u(k) = u(k-1) + Δu(k)。

核心要点:增量式PID只需要保存最近三次的误差值 e(k)、e(k-1)、e(k-2),不需要累加历史误差。这意味着它天然没有积分饱和的问题。

4.2 与位置式PID对比

我在项目里经常被问到:「到底该用增量式还是位置式?」我的回答是:看你的执行机构。

对比项 位置式PID 增量式PID
输出形式 直接输出控制量 u(k) 输出增量 Δu(k)
积分处理 需要防积分饱和 天然无积分饱和
存储需求 需保存所有历史误差 只需保存3个误差值
故障影响 输出突变,可能损坏设备 输出平滑变化
适用场景 伺服电机、温度控制 步进电机、阀门控制

举个例子。我曾经调试一个阀门开度控制系统,用的是位置式PID。结果有一次传感器突然跳变,积分项瞬间累积了一个巨大的值,阀门直接全开,差点把管道冲爆。后来换成增量式,同样的故障发生时,输出只变化了一点点,系统稳稳的。

避坑指南:我曾经在伺服电机位置环里硬套增量式PID,结果发现电机永远到不了目标位置。为什么?因为增量式没有积分项的直接累加,稳态误差消不掉。后来我加了个死区处理,才解决了这个问题。

4.3 C语言代码实现

直接上代码。这是我个人常用的增量式PID结构体,简洁实用:

typedef struct {
    float Kp;           // 比例系数
    float Ki;           // 积分系数
    float Kd;           // 微分系数
    float e_k;          // e(k)
    float e_k_1;        // e(k-1)
    float e_k_2;        // e(k-2)
    float u_k_1;        // u(k-1)
    float out_max;      // 输出限幅
    float out_min;      // 输出下限
} IncrementalPID_t;

float IncrementalPID_Calc(IncrementalPID_t *pid, float target, float feedback) {
    float delta_u;
    
    // 更新误差
    pid->e_k_2 = pid->e_k_1;
    pid->e_k_1 = pid->e_k;
    pid->e_k = target - feedback;
    
    // 计算增量
    delta_u = pid->Kp * (pid->e_k - pid->e_k_1)
            + pid->Ki * pid->e_k
            + pid->Kd * (pid->e_k - 2*pid->e_k_1 + pid->e_k_2);
    
    // 累加得到当前输出
    pid->u_k_1 += delta_u;
    
    // 输出限幅
    if(pid->u_k_1 > pid->out_max) pid->u_k_1 = pid->out_max;
    if(pid->u_k_1 < pid->out_min) pid->u_k_1 = pid->out_min;
    
    return pid->u_k_1;
}

小技巧:初始化时记得把 e_k、e_k_1、e_k_2 都清零,u_k_1 设为0。我见过有人忘了初始化,结果第一次调用时微分项算出一个巨大的值,系统直接震荡。

4.4 应用场景分析

增量式PID最适合以下场景:

  • 步进电机控制:每次只发一个脉冲增量,不会因为积分饱和导致电机丢步
  • 阀门/舵机控制:执行机构有记忆效应,断电后保持当前位置
  • 需要平滑启动的场景:增量式天然有软启动效果,不会一开机就猛冲

不适合的场景:

  • 位置伺服控制:需要精确到达目标位置,增量式缺少积分累加,会有静差
  • 积分作用要求强的系统:比如温度控制,增量式的积分效果偏弱

我做过一个项目,用增量式PID控制直流电机转速。刚开始效果不错,但负载变化时转速波动很大。后来我加了个前馈补偿,把负载变化量直接加到输出上,效果立竿见影。

下面这张图展示了增量式PID的核心逻辑:

目标值 r(k) - 反馈值 y(k) e(k) Δu(k) = Kp*Δe + Ki*e(k) + Kd*Δ²e u(k) = u(k-1) + Δu 输出 u(k) 增量式PID控制框图

总结一下:增量式PID不是万能的,但在合适的场景下,它比位置式PID更稳定、更安全。我个人建议,凡是执行机构有记忆效应(比如步进电机、阀门),优先考虑增量式。凡是需要精确位置控制(比如伺服电机),还是老老实实用位置式。

嗯,这一章就到这里。代码可以直接拿去用,但记得根据你的实际系统调参。


公众号:蓝海资料掘金营,微信deep3321