数字控制基础:采样定理、Z变换、差分方程、数字PID实现
说实话,很多做模拟控制的工程师,一转到数字控制就懵了。我当年也是这样。总觉得数字控制不就是把模拟PID抄过来吗?结果第一次调数字PID,系统直接震荡到飞起。后来才明白——数字控制有它自己的脾气,你得顺着它来。
这一章,咱们就聊聊数字控制最核心的几个概念。采样定理、Z变换、差分方程,还有数字PID的实现。这些东西,说白了就是数字控制的“语法”。语法不通,写出来的程序肯定跑不顺。
3.1 采样定理:别让你的信号“说谎”
先问一个问题:你用一个数字控制器去控制一个电机,采样频率设多少合适?
很多人随口就说:“越快越好呗。” 嗯,我以前也这么想。直到有一次,我采样频率设得特别高,结果系统反而更不稳定了。为什么?因为高频噪声被放大了。
采样定理告诉我们:采样频率必须大于信号最高频率的两倍。这个“两倍”就是奈奎斯特频率。低于这个值,信号就会发生混叠——高频信号伪装成低频信号,控制器就会被骗。
核心公式:
fs > 2 × fmax
其中 fs 是采样频率,fmax 是信号最高频率。
我在项目中遇到过一件事。一个温度控制系统,温度变化很慢,按理说采样频率不用太高。但工程师把采样频率设成了100Hz,结果系统一直在震荡。后来发现,是电源的50Hz工频干扰被采样进来了。加了低通滤波器之后,问题才解决。
我的经验:
实际工程中,采样频率通常取信号最高频率的5-10倍。别卡着2倍去设,太危险。另外,采样前一定要加抗混叠滤波器——这是硬件上的事,但作为算法工程师,你得知道这个坑。
3.2 Z变换:连续世界的“翻译官”
模拟控制里我们用拉普拉斯变换,数字控制里我们用Z变换。Z变换的本质是什么?就是把连续时间信号变成离散时间信号的一种数学工具。
你想想看,一个连续信号 s(t),我们每隔 T 秒采一个点,得到一串数字 s[0], s[1], s[2]... Z变换就是把这串数字映射到复平面上,方便我们分析系统的稳定性、频率响应。
常用的Z变换对,我建议你记住这几个:
| 连续信号 | 拉普拉斯变换 | Z变换 |
|---|---|---|
| 单位脉冲 δ(t) | 1 | 1 |
| 单位阶跃 u(t) | 1/s | z/(z-1) |
| 指数衰减 e-at | 1/(s+a) | z/(z - e-aT) |
我记得刚学Z变换的时候,总觉得这东西太抽象。后来做电机控制,用Z变换分析系统的稳定性,才发现它有多好用。说白了,Z变换就是帮你把连续世界的微分方程,变成离散世界的差分方程。
3.3 差分方程:数字控制器的“骨架”
差分方程,就是数字控制器的数学表达。模拟控制器用微分方程,数字控制器用差分方程。两者之间怎么转换?最常用的方法是双线性变换(Tustin变换)。
举个例子。一个模拟积分器:
y(t) = ∫ e(t) dt
用双线性变换离散化后,变成:
y[n] = y[n-1] + (T/2) * (e[n] + e[n-1])
其中 T 是采样周期。你看,这就是一个差分方程。当前输出 y[n] 由上一时刻的输出 y[n-1] 和当前、上一时刻的输入共同决定。
注意:
差分方程的实现顺序很重要。我曾经犯过一个低级错误——先更新了 y[n-1] 的值,再用它去算 y[n],结果整个系统都乱了。正确的做法是:先计算当前输出,再更新历史值。
差分方程写出来之后,就可以直接写成C代码了。比如上面的积分器:
float integrator(float e, float *y_prev, float T) {
float y = *y_prev + (T / 2.0f) * (e + e_prev);
e_prev = e;
*y_prev = y;
return y;
}
3.4 数字PID实现:从理论到代码
好了,终于到了最实用的部分——数字PID。模拟PID大家都会,但数字PID有几个坑,我一个个说。
3.4.1 位置式PID vs 增量式PID
数字PID有两种常见形式:位置式和增量式。
- 位置式PID:直接计算控制量 u[n] = Kp*e[n] + Ki*Σe[k] + Kd*(e[n] - e[n-1])
- 增量式PID:计算控制量的增量 Δu[n] = Kp*(e[n]-e[n-1]) + Ki*e[n] + Kd*(e[n]-2e[n-1]+e[n-2])
我个人习惯用增量式PID。为什么?因为增量式只输出变化量,即使积分项饱和了,也不会一下子输出一个很大的值。而且增量式天然支持手动/自动切换——切换时不会产生冲击。
增量式PID的C代码实现:
typedef struct {
float Kp, Ki, Kd;
float e_prev, e_prev2;
float out_prev;
float T; // 采样周期
} PID_Inc;
float PID_Inc_Calc(PID_Inc *pid, float setpoint, float feedback) {
float e = setpoint - feedback;
float delta_u = pid->Kp * (e - pid->e_prev)
+ pid->Ki * e * pid->T
+ pid->Kd * (e - 2*pid->e_prev + pid->e_prev2) / pid->T;
// 更新历史值
pid->e_prev2 = pid->e_prev;
pid->e_prev = e;
// 输出限幅
float out = pid->out_prev + delta_u;
if (out > OUT_MAX) out = OUT_MAX;
if (out < OUT_MIN) out = OUT_MIN;
pid->out_prev = out;
return out;
}
3.4.2 积分饱和与抗饱和措施
积分饱和是数字PID最常见的坑。当执行器达到极限时,积分项还在继续累加,等误差反向时,积分项需要很长时间才能退回来——这就是“饱和”。
我曾经在一个伺服电机项目上吃过这个亏。电机位置偏差很大,PID积分项一直累加,等电机到位了,积分项还没退回来,结果电机直接冲过了头。后来加了抗饱和措施,问题才解决。
常用的抗饱和方法:
- 积分限幅:给积分项设一个上限,别让它无限累加
- 积分分离:误差大的时候,停止积分
- 反计算法:输出饱和时,把多余的积分量减掉
我的建议:
做数字PID,一定要加抗饱和。别嫌麻烦。我见过太多工程师,PID调了半天调不好,最后发现是积分饱和的问题。加一行代码就能解决的事,别折腾一整天。
3.4.3 微分项的陷阱
微分项对噪声特别敏感。你想想看,微分就是求误差的变化率,噪声一进来,变化率就乱跳。所以数字PID的微分项,一定要加滤波。
常用的做法是:对微分项做一阶低通滤波。或者直接用“微分先行”——只对反馈信号做微分,不对设定值做微分。这样设定值突变时,不会产生微分冲击。
// 微分先行实现
float d_term = pid->Kd * (feedback - 2*feedback_prev + feedback_prev2) / pid->T;
// 再加一阶低通滤波
d_term = alpha * d_term + (1 - alpha) * d_term_prev;
嗯,这里要注意:滤波系数 alpha 不能太大,否则微分项就失效了。一般取0.1到0.3之间。
3.5 小结
数字控制的基础,说白了就是三件事:采样要够快、模型要准确、实现要稳健。采样定理告诉你多快算够,Z变换和差分方程帮你建立模型,数字PID实现就是最后的落地。
我做了这么多年控制算法,最大的体会是:理论要懂,但别死磕。遇到问题,先想想是不是采样频率设低了,是不是积分饱和了,是不是微分项没滤波。这些坑,踩过一次就记住了。
下一章,咱们聊聊更高级的控制方法——状态空间和现代控制理论。到时候你会发现,数字控制的基础打好了,后面学什么都快。