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 知识体系总览

下面这张图把本章的核心逻辑串起来了,建议保存下来对照着看。

微分项抖动优化方案 标准微分项三大缺陷 ① 噪声放大效应 ② 设定值突变冲击 ③ 高频谐振激发 两大解决方案 方案一:微分先行 (D-on-P) 只对反馈求导,消除设定值冲击 方案二:不完全微分 串联低通滤波,抑制高频噪声 实现:dy/dt 替代 de/dt 实现:D(s)=Kd·s/(Tf·s+1) 选型建议:频繁变设定值→微分先行;噪声大→不完全微分 两者可组合使用,效果更佳

这张图从问题出发,引出两种方案,再到实现细节和选型建议。你可以把它当作调试时的快速参考。

调试口诀:先看反馈噪声大不大,再看设定值变不变。噪声大用不完全微分,设定值跳变用微分先行。两个都有问题?那就一起上,但注意参数要重新整定。