PID 控制¶
PID 是工业界使用最广泛的控制算法,没有之一。超过 90% 的工业控制回路都在用 PID 或其变体。
一、PID 是什么¶
PID 控制器根据误差 \(e(t) = r(t) - y(t)\)(目标值 − 实际值)来计算控制量,由三项组成:
在 \(s\) 域中:
graph LR
R["目标值 r"] --> SUM((+/-))
SUM -->|误差 e| P["P: 比例<br>Kp × e"]
SUM -->|误差 e| I["I: 积分<br>Ki × ∫e dt"]
SUM -->|误差 e| D["D: 微分<br>Kd × de/dt"]
P --> ADD((Σ))
I --> ADD
D --> ADD
ADD -->|控制量 u| PLANT["被控对象"]
PLANT -->|输出 y| OUT["输出"]
OUT --> SUM
二、P / I / D 各项的物理意义¶
2.1 比例项(P)¶
- 做什么:误差越大,输出越大。就像弹簧——你拉得越远,回复力越大。
- 效果:提高响应速度,减小稳态误差。
- 问题:单纯的 P 控制无法消除稳态误差。
为什么 P 控制有稳态误差?
假设控制一个水箱水位。水位低于目标 → 打开阀门进水。水位越接近目标 → 阀门开度越小 → 进水流量越小。
当进水流量 = 出水流量时,系统达到平衡,但此时水位并没有完全到达目标——因为如果误差 = 0,控制量也 = 0,没有动力维持水位。
这个残余误差就是 \(e_{ss} = \frac{1}{1 + K_p}\)(对于一个 0 型系统)。\(K_p\) 越大,误差越小,但永远不为零。
2.2 积分项(I)¶
- 做什么:累积历史误差。即使当前误差很小,只要一直存在,积分就会持续增长,产生越来越大的控制输出。
- 效果:消除稳态误差(这是 I 项存在的核心意义)。
- 问题:引入相位滞后,可能导致超调和振荡。
积分消除稳态误差的直觉
还是水箱例子。加了 I 项后,即使水位差只有一点点,积分项也会随时间持续累积,不断增大阀门开度,直到水位完全到达目标值。此时误差 = 0,积分不再增长,输出维持在一个恒定值——恰好补偿出水流量。
2.3 微分项(D)¶
- 做什么:关注误差的变化速率。误差在快速减小 → 提前减小控制量;误差在增大 → 立刻加大控制量。
- 效果:预测未来趋势,抑制超调,增加阻尼。
- 问题:对噪声极其敏感(噪声的导数很大)。
D 项的直觉
类比开车刹车:你不会等到距前车 0 距离才踩刹车,而是看到距离在快速减小时就提前减速。D 项就是这个"提前量"。
三、P / I / D 参数效果总结¶
| 增大参数 | 响应速度 | 超调量 | 稳态误差 | 稳定性 |
|---|---|---|---|---|
| \(K_p\) ↑ | ⬆ 加快 | ⬆ 增大 | ⬇ 减小(但不消除) | ⬇ 变差 |
| \(K_i\) ↑ | ⬆ 加快 | ⬆ 增大 | 消除 | ⬇ 变差 |
| \(K_d\) ↑ | — 轻微影响 | ⬇ 减小 | — 无影响 | ⬆ 变好(适度时) |
不是所有情况都要用完整 PID
- P 控制:简单场景,允许一定稳态误差
- PI 控制:最常见组合,消除稳态误差(温度控制、流量控制)
- PD 控制:需要快速响应,不关心稳态误差(某些位置伺服)
- PID 控制:需要兼顾精度和动态性能
四、离散化 PID¶
在嵌入式系统中,PID 必须用离散形式实现。采样周期为 \(T\):
4.1 位置式 PID¶
特点:输出是控制量的绝对值。积分项需要保存所有历史误差的累积。
4.2 增量式 PID¶
特点:输出是控制量的增量。不需要累积历史,更适合嵌入式实现。
位置式 vs 增量式
| 位置式 | 增量式 | |
|---|---|---|
| 输出 | 绝对控制量 | 控制量增量 |
| 积分饱和 | 容易出现 | 天然避免 |
| 无扰切换 | 困难 | 容易 |
| 适用场景 | 阀门开度等绝对量 | 电机 PWM 等增量式执行器 |
五、PID 调参方法¶
5.1 手动调参经验法¶
这是最常用的工程方法,按以下顺序:
调参三步法
第一步:只调 P
- 令 \(K_i = 0\),\(K_d = 0\)
- 从小到大增大 \(K_p\)
- 观察阶跃响应:响应快但开始出现明显振荡时,记下此时的 \(K_p\)
- 取 \(K_p\) 为该值的 60%~70%
第二步:加入 I
- 保持 \(K_p\) 不变,从小到大增大 \(K_i\)
- 观察稳态误差是否被消除
- 如果超调变大、振荡加剧,减小 \(K_i\)
- 找到一个消除稳态误差但超调可接受的值
第三步:加入 D
- 如果超调仍然太大,从小到大增大 \(K_d\)
- \(K_d\) 能抑制超调,但太大会导致高频振荡(噪声放大)
- 通常 \(K_d\) 远小于 \(K_p\)
5.2 Ziegler-Nichols 整定法¶
一种经典的半经验方法:
临界比例法:
- 令 \(K_i = 0\), \(K_d = 0\)
- 增大 \(K_p\) 直到系统出现等幅持续振荡
- 记录此时的 \(K_p = K_u\)(临界增益)和振荡周期 \(T_u\)
| 控制器类型 | \(K_p\) | \(K_i\) | \(K_d\) |
|---|---|---|---|
| P | \(0.5 K_u\) | — | — |
| PI | \(0.45 K_u\) | \(\frac{0.54 K_u}{T_u}\) | — |
| PID | \(0.6 K_u\) | \(\frac{1.2 K_u}{T_u}\) | \(\frac{0.075 K_u \cdot T_u}{1}\) |
Ziegler-Nichols 的局限
- 需要让系统达到临界振荡,在实际系统中可能有风险
- 调出的参数通常超调较大(约 25%),需要后续微调
- 更适合作为起点,而非最终参数
5.3 软件辅助调参¶
在嵌入式开发中,常用方法:
- 串口/CAN 实时调参:运行时通过上位机动态修改 PID 参数
- 示波器观察:用 DAC 或串口输出目标值和实际值的曲线
- MATLAB/Simulink 仿真:建立电机模型,先在仿真中整定参数
六、工程中的 PID 改进¶
6.1 积分抗饱和(Anti-Windup)¶
问题:当执行器输出饱和(如 PWM 已达 100%),误差仍在累积积分项,导致积分项膨胀到很大的值。当误差方向反转时,需要很长时间才能消化掉积分量——造成严重超调。
解决方案:
6.2 微分滤波¶
D 项对噪声敏感,实践中几乎不会直接用纯微分。常用方法:
一阶低通滤波:
其中 \(N\) 是滤波系数,通常取 8~20。这等价于在微分后加一个截止频率为 \(\frac{N}{K_d}\) 的低通滤波器。
// 离散实现
derivative = (error - prev_error) / dt;
filtered_derivative = alpha * filtered_derivative + (1 - alpha) * derivative;
// alpha = tau / (tau + dt), tau 为滤波时间常数
6.3 微分先行(Derivative on Measurement)¶
问题:当目标值突变(阶跃输入)时,误差的导数会出现脉冲,导致控制量瞬间冲击。
解决方案:对测量值而非误差做微分:
目标值的突变不会引起微分冲击,但仍然能对输出的变化做出反应。
6.4 前馈补偿¶
PID 是反馈控制——必须先产生误差,才能修正。前馈在误差产生之前就施加补偿。
前馈的例子
控制机械臂的关节角度。已知关节受重力影响,可以用逆动力学模型直接计算需要的力矩作为前馈量 \(u_{\text{FF}} = \tau_{\text{gravity}}\),PID 只负责补偿剩余的小误差。
七、PID 参数整定的完整思维导图¶
graph TB
START["开始调参"] --> P["1. 只用 P 控制"]
P --> P_Q{"响应够快?"}
P_Q -->|慢| P_UP["增大 Kp"]
P_UP --> P
P_Q -->|振荡| P_DOWN["减小 Kp 到 60%~70%"]
P_DOWN --> I["2. 加入 I"]
P_Q -->|合适| I
I --> I_Q{"稳态误差消除?"}
I_Q -->|有余差| I_UP["增大 Ki"]
I_UP --> I
I_Q -->|超调大| I_DOWN["减小 Ki"]
I_DOWN --> D
I_Q -->|合适| D["3. 加入 D"]
D --> D_Q{"超调可接受?"}
D_Q -->|超调大| D_UP["增大 Kd"]
D_UP --> D
D_Q -->|高频振荡| D_DOWN["减小 Kd,加滤波"]
D_DOWN --> DONE["调参完成 ✅"]
D_Q -->|合适| DONE
八、一段完整的嵌入式 PID 代码¶
typedef struct {
float Kp, Ki, Kd;
float integral;
float prev_error;
float prev_derivative; // 用于微分滤波
float output_min, output_max;
float integral_min, integral_max;
float dt;
float alpha; // 微分滤波系数
} PID_Controller;
float PID_Update(PID_Controller *pid, float setpoint, float measurement) {
// 1. 计算误差
float error = setpoint - measurement;
// 2. 比例项
float P = pid->Kp * error;
// 3. 积分项(带抗饱和)
pid->integral += error * pid->dt;
if (pid->integral > pid->integral_max) pid->integral = pid->integral_max;
if (pid->integral < pid->integral_min) pid->integral = pid->integral_min;
float I = pid->Ki * pid->integral;
// 4. 微分项(对测量值微分 + 低通滤波)
float raw_derivative = -(measurement - pid->prev_error) / pid->dt; // 微分先行
float D_filtered = pid->alpha * pid->prev_derivative
+ (1.0f - pid->alpha) * raw_derivative;
float D = pid->Kd * D_filtered;
// 5. 输出限幅
float output = P + I + D;
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;
// 6. 保存状态
pid->prev_error = measurement; // 注意:微分先行时保存的是测量值
pid->prev_derivative = D_filtered;
return output;
}
代码要点
- 积分限幅防止 windup
- 微分先行避免目标值突变引起冲击
- 低通滤波抑制噪声
- 输出限幅保护执行器