什么是 PID?
我们有上篇文章内容的基础,现在从下面三个方面去进行扩展
P(Proportional)比例项
:对“当前误差”做出反应;
I(Integral)积分项
:对“历史误差”做累积,消除稳态误差;
D(Derivative)微分项
:预测“误差变化趋势”,防止系统超调、振荡。
控制器的输出由这三部分叠加而成:
e(t) 是当前误差(设定值 - 实际值)
Kp, Ki, Kd 是比例、积分、微分系数
比例项(P):反应越快,误差越小?
比例项是最直观的一项,它的输出和误差成正比
优点:
快速响应,提升系统速度;
简单易调。
缺点:
无法消除稳态误差
Kp 过大易导致系统震荡甚至失稳。
稳态误差的计算(阶跃输入):
当系统稳定后,输出值 y∞ 和设定值 r 之间的差就是 稳态误差:
说明:即使 Kp 很大,稳态误差仍然存在,想完全消除?那就需要积分项!
积分项(I):消除稳态误差的关键
积分项可以让误差“累加起来”,逼近目标值
作用:
消除稳态误差;
增加系统类型,提升精度。
风险:
积分过快会导致过冲,甚至“积分风暴”(windup);
响应变慢。
如何防止积分风up?
积分限幅
:限制积分项累加的最大值;
反积分(Anti-Windup)
:控制器输出饱和时,停止或回退积分。
微分项(D):抑制超调、让系统更稳
微分项相当于“预测未来”,对误差变化进行响应
作用:
快速响应误差变化;
抑制系统震荡和超调。
风险:
对噪声非常敏感,必须加滤波。
实验验证(你可能看到这样的曲线):
这说明微分项对系统稳定性有显著提升。
PID 调参的推荐流程(手动调优)
调 PID 就像调音一样,需要“一步一步来”:
初始设定:
Kp = 初始值;Ki = 0;Kd = 0;
运行本项目
先调 P:
缓慢增加 Kp;
直到系统反应不再迟钝,但振荡不明显;
记录此时的稳态误差。
加入 I:
缓慢增加 Ki(或减小积分时间 Ti);
直到系统可以消除稳态误差;
注意观察是否“过冲”过大。
最后加 D:
缓慢增加 Kd,抑制震荡和超调;
注意不要放大噪声。
微调:
在响应速度、稳态误差和系统稳定性之间找到平衡。
离散 PID 控制器
/* pid.c - 简单且实用的 PID 控制器实现
浮点版本,适合 STM32/Arduino 等有浮点的 MCU。
*/
#include <stdint.h>
typedef struct {
float Kp;
float Ki; // 注意:Ki 已包含采样时间缩放(或在 update 中乘 Ts)
float Kd;
float Ts; // 采样周期(秒)
float out_min; // 输出限幅下界
float out_max; // 输出限幅上界
// 内部状态
float integrator;
float prev_error;
float prev_measurement;
float differentiator; // 用于滤波的微分项状态
float tau; // 微分滤波时间常数(tau >= 0)
} PID_t;
/* 初始化 PID */
void pid_init(PID_t *pid, float Kp, float Ki, float Kd, float Ts, float out_min, float out_max, float tau){
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->Ts = Ts;
pid->out_min = out_min;
pid->out_max = out_max;
pid->integrator = 0.0f;
pid->prev_error = 0.0f;
pid->prev_measurement = 0.0f;
pid->differentiator = 0.0f;
pid->tau = tau; // 推荐 tau 在 0.01*Ts 到 10*Ts 之间尝试
}
/* 限幅辅助 */
static float clampf(float v, float lo, float hi){
if(v < lo) return lo;
if(v > hi) return hi;
return v;
}
/* PID 核心更新:传入设定值和测量值,返回控制量 */
float pid_update(PID_t *pid, float setpoint, float measurement){
float error = setpoint - measurement;
// 比例项
float P = pid->Kp * error;
// 积分项(矩形积分)
pid->integrator += 0.5f * pid->Ki * pid->Ts * (error + pid->prev_error);
// 积分防风(限制积分值,避免积分累积过大)
// 可将积分范围设为输出范围的一部分,或单独配置
float integ_min = pid->out_min;
float integ_max = pid->out_max;
pid->integrator = clampf(pid->integrator, integ_min, integ_max);
float I = pid->integrator;
// 微分项(使用测量值微分 + 一阶低通滤波,减少噪声放大)
// differentiator 状态使用滤波器: D = ( -Kd * (measurement - prev_measurement) * (1/Ts) ) filtered
// 使用标准形式: differentiator = (2*tau - Ts)/(2*tau + Ts) * differentiator_prev
// - 2*Kd/(2*tau + Ts) * (measurement - prev_measurement)
if(pid->tau <= 0.0f){
// 没有滤波,简单差分
pid->differentiator = pid->Kd * (error - pid->prev_error) / pid->Ts;
} else {
float alpha = (2.0f * pid->tau - pid->Ts) / (2.0f * pid->tau + pid->Ts);
float diff_measure = (measurement - pid->prev_measurement) / pid->Ts;
// 用 measurement 上的微分能减少 setpoint step 导致的 D 爆炸(常见做法)
pid->differentiator = alpha * pid->differentiator - (2.0f * pid->Kd / (2.0f * pid->tau + pid->Ts)) * diff_measure;
}
float D = pid->differentiator;
// 合并输出并限幅
float output = P + I + D;
float output_clamped = clampf(output, pid->out_min, pid->out_max);
// 抗积分风(simple back-calculation):如果输出被限幅,减小积分(选项)
// 若输出 被限幅 且 与未限幅输出有差异,则按差异调整积分(更稳定)
// 下面是一个简单的实现:当限幅时,撤销刚才的积分累加量
if(output != output_clamped){
// 取消本次积分(另一种方法是使用反向补偿 gain)
pid->integrator -= 0.5f * pid->Ki * pid->Ts * (error + pid->prev_error);
I = pid->integrator;
// 可考虑更复杂的反向补偿法: integrator += (output_clamped - output) * K_aw
}
// 更新历史值
pid->prev_error = error;
pid->prev_measurement = measurement;
return output_clamped;
}
运行本项目
总结:记住这几点就能用好 PID
P 控制当前误差
,太大易振荡;
I 消除长期误差
,但可能带来超调;
D 抑制振荡
,但要防噪声;
调参顺序:P → I → D;
实验评估:看响应曲线和稳态误差;
实现时注意 限幅、防风up 和滤波。
————————————————
版权声明:本文为CSDN博主「平凡灵感码头」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Cha3043445754/article/details/151583357
|
|