4. 微分先行与不完全微分:根治微分抖动
各位工程师朋友,咱们继续聊运动控制里的抖动问题。上一章我们讲了积分限幅和抗积分饱和,今天要啃的这块骨头,是微分项带来的高频抖动。
说实话,标准PID里的微分项,是个让人又爱又恨的角色。爱它,是因为它能预测误差趋势,提前给出修正力;恨它,是因为它对噪声极其敏感,稍微有点干扰,它就能把系统抖成筛子。我在做伺服驱动器项目时,就吃过这个亏——电机空载跑得挺好,一加上负载,速度反馈里带点高频毛刺,电机就开始“吱吱”响,电流波形跟心电图似的。
后来怎么解决的?就是靠今天要讲的两种方案:微分先行和不完全微分。
4.1 标准PID微分项的三大缺陷
先看看标准PID的微分项长什么样:
u(t) = Kp * e(t) + Ki * ∫e(t)dt + Kd * de(t)/dt
其中微分项是 Kd * de(t)/dt。问题就出在这个 de(t)/dt 上。
缺陷一:噪声放大效应
微分运算本质是高通滤波。你想想看,如果误差信号里混入了高频噪声,比如编码器反馈的量化噪声,微分之后噪声幅度会被放大。频率越高,放大倍数越大。我见过一个案例,工程师把Kd调大了一点,结果电机在静止状态下就开始抖动,示波器一看,微分项的输出已经淹没了比例项。
缺陷二:设定值突变冲击
当目标位置或速度发生阶跃变化时,误差e(t)会瞬间跳变。这个跳变的微分值理论上是个脉冲——无限大。虽然数字系统里不会真的无限大,但一个巨大的尖峰足以让执行器饱和,甚至触发过流保护。我记得第一次调试龙门架时,一给阶跃指令,电机直接“哐”一声撞到限位,就是微分项惹的祸。
缺陷三:高频谐振激发
机械系统都有固有频率。微分项引入的高频分量,如果恰好落在机械谐振频段,就会激发振荡。这种振荡不是控制参数能压住的,必须从源头上滤掉。
核心结论:标准微分项的问题根源在于——它对误差信号直接求导,而误差信号里既有有用信息,也有噪声和跳变。我们需要把微分对象换一换,或者给微分加个“口罩”。
4.2 微分先行(D-on-P):只对反馈求导
微分先行的思路很简单:不让微分项看到设定值的变化。
标准PID的微分是对误差 e(t) = r(t) - y(t) 求导。微分先行改为只对反馈值 y(t) 求导:
u(t) = Kp * e(t) + Ki * ∫e(t)dt - Kd * dy(t)/dt
注意这里有个负号。因为 de/dt = dr/dt - dy/dt,如果设定值不变(dr/dt = 0),两种形式等价。但设定值变化时,微分先行不会产生冲击。
为什么有效?
- 设定值阶跃时,
dy/dt是连续的(机械系统有惯性),不会出现脉冲 - 反馈信号通常经过滤波,高频噪声比误差信号少
- 保留了微分对反馈变化的预测能力,抑制超调
我在一个包装机械项目里用过这个方案。切刀需要快速定位,标准PID每次启动都过冲,加了微分先行后,定位精度从±0.5mm提升到±0.1mm,而且没有冲击声了。
实现技巧:如果控制器只支持标准PID结构,可以用一个变通方法——把设定值通过一阶低通滤波后再送入PID。效果类似微分先行,但参数调整更直观。
4.3 不完全微分:给微分戴上口罩
不完全微分的思路更直接:在微分通道里串联一个低通滤波器。
标准微分的传递函数是:
D(s) = Kd * s
不完全微分变成:
D(s) = Kd * s / (Tf * s + 1)
其中 Tf 是滤波时间常数。这个传递函数本质上是一个高通滤波器+低通滤波器,只让中低频的微分通过,高频噪声被滤掉。
频率特性分析:
| 频率范围 | 标准微分 | 不完全微分 |
|---|---|---|
| 低频(ω < 1/Tf) | 增益随频率线性增加 | 增益随频率线性增加(同标准微分) |
| 高频(ω > 1/Tf) | 增益无限增加 | 增益趋于常数 Kd/Tf |
你看,高频段被压住了。这就是“不完全”的含义——高频微分作用被削弱了。
4.4 代码实现:两种方案手把手写
理论讲完了,上代码。我习惯用C语言写运动控制,结构清晰,移植方便。
4.4.1 微分先行实现
typedef struct {
float Kp;
float Ki;
float Kd;
float integral;
float last_feedback; // 上一拍的反馈值
float dt;
} PID_DOnP;
float PID_DOnP_Calc(PID_DOnP *pid, float setpoint, float feedback) {
float error = setpoint - feedback;
float derivative = -(feedback - pid->last_feedback) / pid->dt; // 只对反馈求导
pid->integral += error * pid->dt;
float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
pid->last_feedback = feedback;
return output;
}
注意看第7行,微分项用的是 - (feedback - last_feedback),而不是 (error - last_error)。这就是核心区别。
4.4.2 不完全微分实现
不完全微分有两种实现方式:串联型和并联型。我推荐并联型,参数物理意义更清晰。
typedef struct {
float Kp;
float Ki;
float Kd;
float Tf; // 滤波时间常数
float integral;
float last_error;
float last_derivative; // 上一拍的微分输出
float dt;
} PID_IncompleteD;
float PID_IncompleteD_Calc(PID_IncompleteD *pid, float setpoint, float feedback) {
float error = setpoint - feedback;
// 比例项和积分项不变
pid->integral += error * pid->dt;
float p_out = pid->Kp * error;
float i_out = pid->Ki * pid->integral;
// 不完全微分:一阶低通滤波
float raw_derivative = (error - pid->last_error) / pid->dt;
float alpha = pid->dt / (pid->Tf + pid->dt);
float d_out = pid->Kd * (alpha * raw_derivative + (1 - alpha) * pid->last_derivative);
pid->last_error = error;
pid->last_derivative = d_out / pid->Kd; // 保存滤波后的微分值
return p_out + i_out + d_out;
}
这里 alpha 是滤波系数,范围0~1。alpha越小,滤波越强,微分响应越慢。我一般从0.2开始调。
曾经踩过的坑:有一次我把Tf设得太大(0.1秒),结果微分项几乎不起作用,系统响应变慢,超调反而增加了。后来才意识到,不完全微分不是把微分完全干掉,而是把高频噪声滤掉。Tf一般取采样周期的5~10倍比较合适。
4.5 两种方案的对比与选型
| 对比项 | 微分先行 | 不完全微分 |
|---|---|---|
| 抑制设定值冲击 | 优秀 | 一般(仍对误差求导) |
| 抑制反馈噪声 | 一般(依赖反馈质量) | 优秀(可调滤波强度) |
| 参数调整 | 无新增参数 | 需调整Tf |
| 适用场景 | 频繁改变设定值的系统 | 反馈噪声较大的系统 |
| 实现复杂度 | 低 | 中 |
我的选型建议:
- 如果系统经常做阶跃定位(比如点胶机、贴片机),优先用微分先行
- 如果反馈信号噪声大(比如低成本编码器、电位计),优先用不完全微分
- 如果两者问题都有,可以组合使用:微分先行 + 不完全微分
4.6 知识体系总览
下面这张图把本章的核心逻辑串起来了,建议保存下来对照着看。
这张图从问题出发,引出两种方案,再到实现细节和选型建议。你可以把它当作调试时的快速参考。
调试口诀:先看反馈噪声大不大,再看设定值变不变。噪声大用不完全微分,设定值跳变用微分先行。两个都有问题?那就一起上,但注意参数要重新整定。