一、前言:为什么大家都在用 PID?
在工业控制和嵌入式领域,只要你做自动控制,几乎绕不开一个词:PID 控制器。
从简单的温度控制、电机转速控制,到无人机姿态、自动驾驶方向盘转角,PID 都大显身手。
PID 有三个特点,让它成为“通吃”方案:
-
原理不复杂:数学模型相对简单,工程上好理解、好上手。
-
通用性强:只要能测量“误差”(目标 - 实际),就基本能上 PID。
-
实现成本低:不需要复杂的矩阵运算。
二、什么是 PID?
PID 控制器:根据当前误差、误差的历史累积、误差变化趋势,来计算控制量。
-
P:Proportional,比例
-
I:Integral,积分
-
D:Derivative,微分
如果用一句“人话”来类比:
你在开车追前面的车:
离太远了(误差大)你就多踩油门 —— 比例
长时间都离太远(误差一直存在)你会更着急,多踩一点 —— 积分
发现距离正在快速缩小(误差变化很快)你会提前松油门或者踩刹车 —— 微分
PID 的目标是:快速追上目标,又尽量不要来回震荡。
三、三大组成部分:P、I、D 各管啥?
1. P(比例):主力军 —— 快速响应
作用:
-
误差越大,控制量越大
-
提高 KpK_pKp 可以加快系统响应速度,使输出更快接近目标
缺点:
-
如果只有 P(纯比例控制),系统往往存在稳态误差(例如电机转速总是比设定值低一点)
-
KpK_pKp 太大,会导致系统振荡、甚至不稳定
2. I(积分):强迫逼近目标 —— 消除稳态误差
作用:
-
误差长期存在,就不断积累,推动控制量一直增大
-
可以消除稳态误差(最终能精准逼近目标),非常适合温度、液位这类慢系统
缺点:
-
引入超调:积累太多,一旦误差变小,控制量来不及“刹车”,会冲过头
-
可能导致响应变慢:积分过强,系统“粘粘糊糊”的
-
会出现 积分饱和 / 积分风up:
-
比如控制电机占空比,已经打满 100%,但误差还在,积分还在累,之后误差变号,会导致系统长时间反向过冲
-
工程上通常要做 “积分限幅 / 抗积分饱和”:
-
限制积分项的最大最小值
-
当输出已经到极限(比如 0% 或 100% 占空比)时,暂停积分
3. D(微分):提前预判 —— 减少超调&振荡
作用:
-
看的是误差变化的“速度”
-
如果误差在迅速减小(说明快到目标了),D 项会提前减小控制量,起到**“刹车”**的效果
-
可以减少超调,改善系统稳定性,使响应更“干脆”
缺点:
-
对噪声极其敏感:
-
传感器信号里只要有一点抖动,微分都会放大这种高频噪声
-
-
物理系统不可能真是“连续微分”,所以实现时一般会加滤波
常见工程写法:带滤波的微分
离散实现上就是给微分信号加一个一阶低通滤波器。
四、PID 参数对系统动态性能的影响
经典控制里经常看几个指标:
-
上升时间(Rise time)
-
超调量(Overshoot)
-
调节时间 / 建立时间(Settling time)
-
稳态误差(Steady-state error)
大致影响关系可以用一个表来记:
| 参数 | 增大后的典型效果 |
|---|---|
| KpK_pKp | 上升更快;超调变大;振荡可能变强;稳态误差减小但不一定为 0 |
| KiK_iKi | 消除稳态误差;超调变大;响应变慢;容易振荡、可能不稳定 |
| KdK_dKd | 减少超调;改善稳定性;有时让响应更快;但太大容易放大噪声 |
调参时的常用“经验顺序”:
-
先调 P:从小到大加,直到系统能跟,稍有超调/振荡
-
再加 I:慢慢增加,刚好消除稳态误差即可
-
最后补一点 D:用来压超调、减振荡、小心不要太大
五、工程实现:位置式 vs 增量式 PID
1. 位置式 PID(直接输出目标值)
typedef struct {
float Kp;
float Ki;
float Kd;
float set; // 目标值
float fdb; // 反馈值
float err; // 当前误差
float err_last;// 上一次误差
float err_sum; // 误差积分
float out; // 输出
} PID_t;
void PID_PositionCalc(PID_t *pid)
{
pid->err = pid->set - pid->fdb;
pid->err_sum += pid->err;
// 积分限幅,防止积分饱和
if (pid->err_sum > 1000) pid->err_sum = 1000;
if (pid->err_sum < -1000) pid->err_sum = -1000;
float P = pid->Kp * pid->err;
float I = pid->Ki * pid->err_sum;
float D = pid->Kd * (pid->err - pid->err_last);
pid->out = P + I + D;
// 输出限幅,例如 0~100% 占空比
if (pid->out > 100) pid->out = 100;
if (pid->out < 0) pid->out = 0;
pid->err_last = pid->err;
}
2. 增量式 PID(算“这次多加多少”)
思想:不直接算输出的绝对值,而是算相对上一次要加多少:
typedef struct {
float Kp;
float Ki;
float Kd;
float set;
float fdb;
float err;
float err_last;
float err_llast; // e[k-2]
float out;
} PID_Inc_t;
void PID_IncrementCalc(PID_Inc_t *pid)
{
pid->err = pid->set - pid->fdb;
float delta_u = 0;
delta_u += pid->Kp * (pid->err - pid->err_last);
delta_u += pid->Ki * pid->err;
delta_u += pid->Kd * (pid->err - 2 * pid->err_last + pid->err_llast);
pid->out += delta_u;
// 输出限幅
if (pid->out > 100) pid->out = 100;
if (pid->out < 0) pid->out = 0;
pid->err_llast = pid->err_last;
pid->err_last = pid->err;
}
六、总结
PID 控制虽然只由简单的三个参数组成,但背后包含了对系统当前状态、历史误差以及未来趋势的综合考量。
对工程师来说,掌握 PID 的本质,比死记公式更重要:
-
P 决定系统“敢不敢快”;
-
I 决定系统“能不能到”;
-
D 决定系统“稳不稳当”。
理解了这三者的作用,再结合具体对象做一些实验,就能在项目中设计出既稳定又灵敏的控制系统。
1万+

被折叠的 条评论
为什么被折叠?



