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。原因有三:
- 没有积分累加项,不会出现积分饱和
- 输出变化平缓,手动/自动切换时无冲击
- 计算量小,适合高频开关电源
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 知识体系总览
说了这么多,咱们用一张图把这一章的核心逻辑串起来:
4.8 小结
这一章的内容,说白了就是三件事:
- 怎么算:位置式 vs 增量式,选对公式
- 怎么写:代码实现要干净,注意数据类型和溢出
- 怎么防:积分饱和是数字电源的常见病,抗饱和策略必须上
我个人建议,新手先从增量式 PID 入手,配合积分冻结法。等跑通了,再回头研究位置式和反向计算法。别一上来就想搞“最优方案”,工程上先跑起来比什么都重要。
一个小技巧:调试 PID 时,先把 Ki 和 Kd 设为 0,只调 Kp。等系统不振荡了,再加一点 Ki 消除静差。最后加 Kd 改善动态响应。这个顺序我用了十几年,从来没翻过车。