第四节:一阶系统建模——一阶低通滤波器
说实话,一阶低通滤波器是我在工程中用得最多的环节之一。不管是传感器信号去噪,还是PWM波形的平滑处理,它都像个老朋友一样可靠。今天咱们就把它彻底聊透。
4.1 一阶低通滤波器的传递函数
先看连续域的传递函数。一阶低通滤波器,说白了就是一个惯性环节。它的标准形式是:
G(s) = 1 / (τs + 1)
其中 τ 是时间常数,单位是秒。这个公式看着简单,但背后藏着不少门道。
我当年刚入行时,总觉得这公式太简单,没啥好研究的。直到有一次做电机电流环的滤波,发现实际响应总比仿真慢一拍。查了半天,原来是时间常数算错了。嗯,这里要注意——时间常数决定了系统的响应速度,不是随便拍脑袋定的。
4.2 时间常数与截止频率
时间常数 τ 和截止频率 fc 之间有个简单关系:
fc = 1 / (2πτ)
这个公式怎么来的?其实从幅频特性就能推导出来。当 ω = 1/τ 时,幅值下降到 -3dB,也就是原来的 0.707 倍。这个点就是截止频率。
我个人习惯用截止频率来设计滤波器,因为更直观。比如我要滤除 50Hz 的工频干扰,那就把截止频率设在 5Hz 左右,留点余量。
关键参数速查表:
| 参数 | 符号 | 单位 | 物理意义 |
|---|---|---|---|
| 时间常数 | τ | 秒 (s) | 系统响应到63.2%所需时间 |
| 截止频率 | fc | 赫兹 (Hz) | 幅值衰减-3dB的频率点 |
| 角频率 | ωc | 弧度/秒 | ωc = 2πfc = 1/τ |
4.3 离散化方法
连续域的东西再好,到了数字系统里也得离散化。常用的方法有三种:
- 前向欧拉法:简单但容易不稳定,我一般不推荐
- 后向欧拉法:稳定,适合大多数场景
- 双线性变换(Tustin):精度最高,但计算量稍大
我个人最常用后向欧拉法。为什么?因为它在工程实践中足够稳定,而且实现起来代码量最少。你想想看,在嵌入式系统里,每节省一个时钟周期都是好事。
后向欧拉法的离散公式推导如下:
s = (1 - z⁻¹) / Ts
代入 G(s) = 1 / (τs + 1) 得:
G(z) = 1 / (τ * (1 - z⁻¹) / Ts + 1)
= Ts / (τ + Ts - τ * z⁻¹)
= (Ts/(τ+Ts)) / (1 - (τ/(τ+Ts)) * z⁻¹)
写成差分方程就是:
y[n] = a * x[n] + (1 - a) * y[n-1]
其中 a = Ts / (τ + Ts)
这个公式我闭着眼睛都能写出来。当年做无人机飞控的时候,每个传感器通道都要过这么一道,写了几十遍。
4.4 Python实现
光说不练假把式。咱们直接上代码:
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
class FirstOrderLowPass:
"""一阶低通滤波器类"""
def __init__(self, fc, Ts):
"""
fc: 截止频率 (Hz)
Ts: 采样周期 (s)
"""
self.fc = fc
self.Ts = Ts
self.tau = 1 / (2 * np.pi * fc)
self.alpha = Ts / (self.tau + Ts)
self.y_prev = 0
def filter(self, x):
"""单步滤波"""
y = self.alpha * x + (1 - self.alpha) * self.y_prev
self.y_prev = y
return y
def filter_batch(self, x_array):
"""批量滤波"""
y_array = np.zeros_like(x_array)
for i, x in enumerate(x_array):
y_array[i] = self.filter(x)
return y_array
# 使用示例
Ts = 0.001 # 1ms采样周期
fc = 10 # 10Hz截止频率
lpf = FirstOrderLowPass(fc, Ts)
# 生成测试信号:10Hz正弦波 + 100Hz噪声
t = np.arange(0, 1, Ts)
clean = np.sin(2 * np.pi * 10 * t)
noise = 0.5 * np.sin(2 * np.pi * 100 * t)
signal_noisy = clean + noise
# 滤波
signal_filtered = lpf.filter_batch(signal_noisy)
小技巧:实际项目中,我习惯把 α 值限制在 0 到 1 之间。如果算出来 α > 1,说明采样周期比时间常数还大,这时候滤波器基本失效了。我曾经在调试一个低速数据采集系统时踩过这个坑,数据全乱了。
4.5 频率响应分析
滤波器的好坏,频率响应说了算。咱们用 Python 画一下幅频和相频特性:
# 连续域频率响应
w = np.logspace(0, 3, 1000) # 频率范围 1~1000 rad/s
s = 1j * w
G_cont = 1 / (lpf.tau * s + 1)
# 离散域频率响应
z = np.exp(1j * w * Ts)
G_disc = lpf.alpha / (1 - (1 - lpf.alpha) * z**(-1))
# 绘制幅频特性
plt.figure(figsize=(10, 6))
plt.subplot(2, 1, 1)
plt.semilogx(w/(2*np.pi), 20*np.log10(np.abs(G_cont)), 'b-', label='连续域')
plt.semilogx(w/(2*np.pi), 20*np.log10(np.abs(G_disc)), 'r--', label='离散域')
plt.axvline(fc, color='gray', linestyle=':', label=f'fc={fc}Hz')
plt.grid(True)
plt.ylabel('幅值 (dB)')
plt.legend()
plt.title('一阶低通滤波器频率响应')
# 绘制相频特性
plt.subplot(2, 1, 2)
plt.semilogx(w/(2*np.pi), np.angle(G_cont, deg=True), 'b-', label='连续域')
plt.semilogx(w/(2*np.pi), np.angle(G_disc, deg=True), 'r--', label='离散域')
plt.grid(True)
plt.xlabel('频率 (Hz)')
plt.ylabel('相位 (度)')
plt.legend()
plt.tight_layout()
plt.show()
从频率响应图上能看出几个关键点:
- 在截止频率以下,幅值基本不变(0dB附近)
- 过了截止频率,幅值以 -20dB/十倍频 的斜率下降
- 相位滞后最大到 -90°,在截止频率处正好 -45°
离散域和连续域的响应在低频段几乎重合,但到了高频段(接近奈奎斯特频率)会有偏差。这就是采样效应带来的影响。
避坑指南:我曾经在一个项目中把截止频率设得太接近奈奎斯特频率,结果滤波器在高频段出现了混叠。记住一条经验法则:截止频率不要超过采样频率的 1/10。比如采样频率 1000Hz,截止频率最好设在 100Hz 以下。
4.6 知识体系总览
下面这张图把一阶低通滤波器的核心逻辑串起来了:
这张图把整个知识脉络理清楚了。从连续域的传递函数出发,经过离散化变成可编程的差分方程,再用频率响应来验证设计是否达标,最后落到具体的工程场景中。
一阶低通滤波器看似简单,但它是数字信号处理的基石。把这块吃透了,后面学高阶滤波器、状态观测器都会轻松很多。我在实际项目中,经常把多个一阶低通串联起来用,效果比直接用一个高阶滤波器更稳定。
公众号:蓝海资料掘金营,微信deep3321