4. PID 控制算法:位置式 PID 与增量式 PID、数字 PID 的实现、积分饱和与抗饱和策略

PID 控制,说白了就是数字电源的“大脑”。你想想看,输出电压高了要降,低了要升,怎么降、怎么升、降多快、升多稳——全靠这个算法在背后算账。我做了这么多年电源,见过太多“调参调到头秃”的案例,其实很多问题不是参数不对,而是对 PID 的本质理解不到位。

这一章,咱们就把 PID 的底裤扒干净。从数学公式到代码实现,从位置式到增量式,再聊聊那个让人头疼的积分饱和问题。嗯,都是实战中绕不过的坎。

4.1 从模拟到数字:PID 的离散化

模拟 PID 大家都很熟悉,公式长这样:

u(t) = Kp * e(t) + Ki * ∫e(τ)dτ + Kd * de(t)/dt

但在数字电源里,我们面对的是离散的采样点。每隔一个固定的时间 ΔT(也就是开关周期),DSP 或者 MCU 才去读一次 ADC,算一次占空比。所以必须把连续的积分和微分,变成离散的累加和差分。

我习惯用后向差分法来做离散化,简单直接,工程上最常用:

  • 积分项:∫e(τ)dτ ≈ Σ e(k) * ΔT
  • 微分项:de(t)/dt ≈ [e(k) - e(k-1)] / ΔT

把这两个近似代回原式,就得到了数字 PID 的雏形。这里有个细节要注意——采样周期 ΔT 必须足够小,至少要比系统时间常数快 5~10 倍,否则离散化误差会让你调参调到怀疑人生。

4.2 位置式 PID:最直观的实现

位置式 PID 直接计算当前时刻的控制量 u(k),也就是占空比的绝对值。公式如下:

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

代码实现也很直白:

// 位置式 PID 实现
float pid_positional(PID_t *pid, float setpoint, float feedback) {
    float error = setpoint - feedback;
    float output;

    // 比例项
    pid->proportional = pid->Kp * error;

    // 积分项(累加)
    pid->integral += error * pid->Ki * pid->dt;

    // 微分项
    pid->derivative = pid->Kd * (error - pid->prev_error) / pid->dt;
    pid->prev_error = error;

    // 总输出
    output = pid->proportional + pid->integral + pid->derivative;

    return output;
}

位置式 PID 的特点:

  • 输出 u(k) 是完整的控制量,物理意义明确
  • 积分项需要累加所有历史误差,容易出问题
  • 一旦积分项失控,输出会直接“飞掉”

我在项目中遇到过一件事:一个 48V 转 12V 的 DC-DC,启动时输出电压爬坡太慢,我就把 Ki 调大了一点。结果启动瞬间积分项疯狂累加,输出占空比直接冲到 100%,电感啸叫、输出过冲——嗯,那就是积分饱和的典型症状。

4.3 增量式 PID:更安全的方案

增量式 PID 不计算控制量的绝对值,而是计算当前时刻相对于上一时刻的变化量 Δu(k)。

推导过程很简单:用 u(k) 减去 u(k-1),消掉积分累加项,得到:

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

实际输出:

u(k) = u(k-1) + Δu(k)

代码实现:

// 增量式 PID 实现
float pid_incremental(PID_t *pid, float setpoint, float feedback) {
    float error = setpoint - feedback;
    float delta_u;

    // 计算增量
    delta_u = pid->Kp * (error - pid->e1)
            + pid->Ki * error * pid->dt
            + pid->Kd * (error - 2*pid->e1 + pid->e2) / pid->dt;

    // 更新历史误差
    pid->e2 = pid->e1;
    pid->e1 = error;

    // 累加输出
    pid->output += delta_u;

    return pid->output;
}

实战建议:我个人习惯在数字电源里优先用增量式 PID。原因有三:

  1. 没有积分累加项,不会出现积分饱和
  2. 输出变化平缓,手动/自动切换时无冲击
  3. 计算量小,适合高频开关电源

4.4 位置式 vs 增量式:怎么选?

很多初学者会纠结这个问题。我直接给结论:

对比维度 位置式 PID 增量式 PID
输出形式 控制量绝对值 控制量变化量
积分处理 需要累加所有误差 无累加,靠增量累积
积分饱和风险 高 低
手动/自动切换 需做输出跟踪 天然无冲击
适用场景 执行器有记忆(如电机位置) 执行器无记忆(如 PWM 占空比)

数字电源的 PWM 占空比,每次上电都是从 0 开始,没有记忆。所以增量式 PID 天然适合。但如果你做的是伺服电机的位置环,位置式 PID 可能更直观。

4.5 积分饱和:数字电源的头号杀手

积分饱和,我愿称之为“PID 界的鬼压床”。

现象是这样的:系统输出已经达到极限(比如占空比 100%),但误差仍然存在。积分项还在继续累加,越积越大。等到误差反向时,积分项需要很长时间才能“消化”掉之前积累的数值——这段时间里,系统完全失控。

为什么会这样?你想想看,积分项的本质是“历史欠账”。当执行器已经饱和,你还在记账,这笔账迟早要还的。

积分饱和的典型场景:

  • 启动阶段:输出电压远低于设定值,积分项疯狂累加
  • 负载突变:输出跌落后恢复,积分项反应过慢
  • 限流保护:输出被硬件限流,但 PID 还在算

4.6 抗饱和策略:实战中的三板斧

我曾经在一个 2kW 的通信电源项目上被积分饱和坑惨了。启动时输出电压过冲 30%,直接把后级负载打坏。从那以后,我养成了“不管用不用,先把抗饱和加上”的习惯。

这里分享三种最实用的方法:

4.6.1 积分限幅法

最简单粗暴。给积分项设一个上限 Imax,超过就截断:

if (pid->integral > pid->Imax)
    pid->integral = pid->Imax;
if (pid->integral < -pid->Imax)
    pid->integral = -pid->Imax;

Imax 怎么取?我一般取输出限幅值的 20%~30%。太小了影响稳态精度,太大了防不住饱和。

4.6.2 积分冻结法

当输出饱和时,停止积分累加。等输出退出饱和,再恢复积分:

if (output >= output_max || output <= output_min) {
    // 输出饱和,冻结积分
    // 不更新 pid->integral
} else {
    // 正常累加
    pid->integral += error * pid->Ki * pid->dt;
}

这个方法效果很好,但要注意一个细节:退出饱和的瞬间,积分项可能和当前误差方向相反,导致输出抖动。我一般会加一个小的滞回区间来平滑过渡。

4.6.3 反向计算法(Back-Calculation)

这是最“优雅”的方案。当输出饱和时,把实际输出和理想输出的差值,反馈回积分项:

// 计算理想输出
float u_ideal = proportional + integral + derivative;

// 限幅得到实际输出
float u_actual = clamp(u_ideal, output_min, output_max);

// 如果饱和,用差值修正积分
if (u_ideal != u_actual) {
    integral += Kb * (u_actual - u_ideal) * dt;
}

其中 Kb 是反向增益系数,一般取 1/Kp 或者 2/Kp。这个方法的好处是积分项能“自动”收敛到合适的值,不会过冲也不会欠调。

4.7 知识体系总览

说了这么多,咱们用一张图把这一章的核心逻辑串起来:

PID 控制算法知识体系 PID 控制算法知识体系 模拟 PID → 数字 PID 离散化 位置式 PID 增量式 PID 输出 = 比例 + 积分累加 + 微分 积分饱和风险高 输出 = 上一时刻 + 增量 无积分累加,饱和风险低 积分饱和问题 积分限幅法 积分冻结法 反向计算法

4.8 小结

这一章的内容,说白了就是三件事:

  • 怎么算:位置式 vs 增量式,选对公式
  • 怎么写:代码实现要干净,注意数据类型和溢出
  • 怎么防:积分饱和是数字电源的常见病,抗饱和策略必须上

我个人建议,新手先从增量式 PID 入手,配合积分冻结法。等跑通了,再回头研究位置式和反向计算法。别一上来就想搞“最优方案”,工程上先跑起来比什么都重要。

一个小技巧:调试 PID 时,先把 Ki 和 Kd 设为 0,只调 Kp。等系统不振荡了,再加一点 Ki 消除静差。最后加 Kd 改善动态响应。这个顺序我用了十几年,从来没翻过车。