☰
电机控制中的IIR滤波器:从Z平面到FOC与高频注入实战
2026/10/1 1:50:09 网站建设 项目流程

1. 为什么电机控制工程师绕不开 IIR 滤波器

如果你正在做 FOC 或者准备上手高频注入,大概率已经在某个时刻被滤波器坑过。电流采样里混进了 PWM 开关噪声,速度环反馈抖得跟心电图一样,高频注入的响应信号被噪声淹没到根本提取不出来——这些问题最后都会指向同一个工具:IIR 数字滤波器。

IIR,全称 Infinite Impulse Response,无限冲击响应滤波器。名字听着唬人,但它的本质非常简单:当前的输出,不仅取决于当前和过去的输入,还取决于过去的输出。这个"输出反馈到输入"的结构,就是它和 FIR(有限冲击响应)最根本的区别。FIR 只吃输入,IIR 会"记住"自己之前的输出,所以它能用很低的阶数实现非常陡峭的截止特性。

在电机控制这个场景里,这件事意味着什么?意味着你可以在 STM32F103 这种资源紧张的芯片上,用几个乘加运算就跑出一个效果不错的低通滤波器,而不需要像 FIR 那样动辄几十个抽头。对于 FOC 的电流环,你的控制周期可能只有 50 微秒甚至更短,每个周期能留给滤波器的计算时间极其有限,IIR 的低计算量优势就体现出来了。

但 IIR 也不是没有代价。它的相位非线性、极点可能不稳定、系数量化敏感,这些问题在电机控制里都会被放大。我见过太多人从 MATLAB 里 filterDesigner 导出一组系数,直接塞进代码,结果电机一跑起来就啸叫或者震荡。问题往往不在系数本身,而在于没有理解 IIR 在 Z 平面上的行为,以及它和电机控制环路之间的相互作用。

这篇内容我会从 Z 平面讲起,把 IIR 的传递函数、极点零点分布讲清楚,然后落到低通和带通两种最常用的结构上,再具体到 FOC 电流环和速度环里怎么用,最后讲高频注入里 IIR 带通滤波器怎么帮你把转子位置信号从噪声里捞出来。全程会给出可以直接用的差分方程和 C 代码结构,也会分享一些我在实际项目里踩过的坑。

2. 从 Z 平面理解 IIR:极点、零点与稳定性

2.1 传递函数到底在描述什么

IIR 滤波器的通用传递函数长这样:

H(z) = (b0 + b1*z^-1 + b2*z^-2 + ... + bM*z^-M) / (1 + a1*z^-1 + a2*z^-2 + ... + aN*z^-N)

分子决定零点,分母决定极点。z^-1 在时域里就是"延迟一个采样周期"。所以这个式子在说:输出 y[n] 是当前输入 x[n] 和过去输入的加权和,减去过去输出的加权和。

写成差分方程更直观:

y[n] = b0*x[n] + b1*x[n-1] + ... - a1*y[n-1] - a2*y[n-2] - ...

注意分母的 a 系数前面是负号,因为移项到左边了。这个细节在写代码时特别容易搞错,我后面会专门讲。

2.2 极点在单位圆内意味着什么

Z 平面的单位圆是稳定性的分界线。所有极点必须严格在单位圆内,系统才稳定。极点越靠近单位圆,对应频率的增益越大,滤波器的选择性越强,但响应也越慢、越容易震荡。

举个实际例子。一个简单的一阶低通 IIR:

y[n] = alpha*x[n] + (1-alpha)*y[n-1]

它的极点在 z = 1-alpha。alpha 越小,极点越靠近 1,截止频率越低,滤波越狠,但相位滞后也越大。在 FOC 电流环里,如果你把 alpha 设成 0.01,截止频率可能只有几十赫兹,电流环带宽直接被拖垮,电机动态响应会变得极其迟钝。

我一般建议在电流环里,滤波器的截止频率至少是电流环带宽的 5 到 10 倍。比如电流环带宽 1kHz,滤波器截止频率设在 5kHz 以上。这样相位滞后在电流环带宽处只有几度,不会明显影响环路稳定性。

2.3 零点的作用:不只是"反极点"

零点影响的是滤波器的频率响应形状。在带通滤波器里,零点通常放在直流和奈奎斯特频率处,把这两个频段的增益压下去,只让中间某个频段通过。高频注入里用的带通滤波器,零点就负责把基波和低频噪声挡掉,极点负责在注入频率处形成峰值。

零点的位置也影响相位。一个零点在单位圆内,会提供超前相位;在单位圆外,提供滞后相位。设计时如果只盯着幅频响应看,忽略了相位,在闭环系统里很容易出问题。

2.4 双线性变换:从模拟到数字的桥梁

大部分 IIR 设计都是从模拟滤波器原型(巴特沃斯、切比雪夫等)出发,通过双线性变换映射到 Z 平面。双线性变换的公式是:

s = (2/T) * (1 - z^-1) / (1 + z^-1)

T 是采样周期。这个变换把模拟域的虚轴映射到 Z 平面的单位圆上,所以稳定性得以保持。但频率轴会被"压缩",高频段非线性。实际设计时,截止频率需要做预畸变校正:

Omega_analog = (2/T) * tan(omega_digital * T / 2)

如果你直接用目标数字频率去设计模拟原型,实际截止频率会偏低。这个坑我在早期项目里踩过,设计了一个 1kHz 低通,实测截止频率只有 800Hz 左右,后来才发现是忘了预畸变。

3. 低通与带通:电机控制里最常用的两种 IIR 结构

3.1 一阶低通:简单但够用

一阶低通是电机控制里出现频率最高的 IIR。电流采样滤波、速度反馈滤波、电压输出滤波,到处都有它的影子。差分方程:

y[n] = alpha*x[n] + (1-alpha)*y[n-1]

alpha 和截止频率的关系:

alpha = 1 - exp(-2*pi*fc*T)

T 是采样周期,fc 是截止频率。当 fcT 很小时,可以近似为 alpha ≈ 2pifcT。

在 STM32F103 上,如果电流环 20kHz,想设 2kHz 截止频率:

alpha = 2 * 3.14159 * 2000 / 20000 = 0.628

这个值不算小,滤波效果有限,但相位滞后也小。如果你想要更强的滤波,把 fc 降到 500Hz,alpha 变成 0.157,滤波效果明显,但相位滞后在 1kHz 处大概有 18 度,需要评估对电流环的影响。

注意:一阶低通的 alpha 不要小于 0.05,否则在定点 DSP 上量化误差会很明显,而且相位滞后可能超过 45 度,闭环系统容易震荡。

3.2 二阶低通:更陡的滚降

当一阶低通不够用,比如 PWM 噪声在 10kHz,而你的电流环带宽只有 1kHz,一阶低通在 10kHz 处只能衰减 20dB 左右,可能还不够。这时候用二阶巴特沃斯低通:

H(z) = (b0 + b1*z^-1 + b2*z^-2) / (1 + a1*z^-1 + a2*z^-2)

巴特沃斯的特点是通带最平坦,没有纹波。设计步骤:

  1. 确定截止频率 fc 和采样频率 fs
  2. 计算预畸变后的模拟角频率
  3. 查巴特沃斯二阶原型的系数
  4. 用双线性变换得到数字系数

我通常直接用 Python 的 scipy 算:

from scipy import signal fs = 20000 fc = 2000 b, a = signal.butter(2, fc/(fs/2), 'low') print("b =", b) print("a =", a)

得到的 b 和 a 直接对应差分方程的系数。注意 scipy 的 a[0] 通常是 1,如果代码里要求归一化,需要把 b 和 a 都除以 a[0]。

3.3 带通:高频注入的核心工具

带通滤波器在高频注入里是刚需。注入频率通常是 500Hz 到 2kHz,你需要把基波(几十赫兹)和 PWM 噪声(几十千赫兹)都挡掉,只留下注入频率附近的响应信号。

二阶带通传递函数:

H(z) = (b0 + b1*z^-1 + b2*z^-2) / (1 + a1*z^-1 + a2*z^-2)

设计时指定中心频率 f0 和带宽 BW。品质因数 Q = f0/BW。Q 越高,选择性越强,但响应越慢。高频注入里 Q 一般取 5 到 20,太低选不出来,太高响应延迟大,影响转子位置估计的实时性。

用 scipy 设计:

fs = 20000 f0 = 1000 Q = 10 b, a = signal.iirpeak(f0/(fs/2), Q)

iirpeak设计的是峰值滤波器,在 f0 处增益为 1,适合提取特定频率分量。如果你需要更标准的带通,可以用signal.butter(2, [f_low/(fs/2), f_high/(fs/2)], 'band')。

3.4 系数归一化与定点实现

在 STM32F103 这种没有 FPU 的芯片上,浮点运算很慢。我一般会把系数放大成 Q15 格式的整数,用定点乘加。步骤:

  1. 找到 b 和 a 系数中的最大值
  2. 计算缩放因子 scale = 32767 / max_abs
  3. 所有系数乘以 scale 并取整
  4. 运算时用 int32 累加,最后右移 15 位
// Q15 定点一阶低通 #define ALPHA_Q15 20589 // 0.628 * 32768 int32_t y_fixed = 0; int16_t iir_lpf_q15(int16_t x) { y_fixed = (ALPHA_Q15 * x + (32768 - ALPHA_Q15) * y_fixed) >> 15; return (int16_t)y_fixed; }

这里 y_fixed 用 int32 保存,避免中间结果溢出。右移 15 位相当于除以 32768。注意 (32768 - ALPHA_Q15) 对应 (1-alpha),这个结构比先算 alpha*x 再算 (1-alpha)*y 更省一次乘法。

提示:定点实现时,y_fixed 的初始值建议设为第一次输入的 x 左移 15 位,避免滤波器从 0 开始爬升导致的启动延迟。

4. IIR 在 FOC 电流环和速度环里的具体用法

4.1 电流采样滤波:位置和参数怎么定

FOC 电流环的采样通常在 PWM 中心对齐模式下触发,采样频率等于 PWM 频率,常见 10kHz 到 20kHz。电流采样信号里主要有两类噪声:PWM 开关引入的高频尖峰,以及 ADC 量化噪声。

滤波器的截止频率选择要考虑两点:一是要高于电流环带宽,二是要低于 PWM 频率的一半(奈奎斯特频率)。比如 PWM 20kHz,电流环带宽 1kHz,滤波器截止频率可以设在 3kHz 到 5kHz。

我一般用一阶低通,alpha 取 0.3 到 0.5。这个范围在 STM32F103 上计算量小,相位滞后在 1kHz 处大约 10 到 20 度,对电流环影响可控。如果噪声特别大,可以上二阶,但要注意相位滞后会翻倍。

实际调试时,我会先不加滤波器,用示波器看电流波形,确认噪声幅度和频率。然后逐步降低截止频率,观察电流环的阶跃响应,直到噪声被压到可接受范围,同时阶跃响应没有明显变慢或过冲。

4.2 速度环滤波:慢速环的滤波策略

速度环的带宽通常只有几十赫兹到几百赫兹,采样频率也低得多,可能 1kHz。速度反馈来自编码器差分或者无感观测器,噪声来源不同。

编码器速度计算本身就有量化噪声,尤其是低速时。这时候 IIR 低通很有用,截止频率可以设得比较低,比如 50Hz 到 100Hz。因为速度环带宽低,相位滞后在带宽处不会太大。

但要注意,速度环滤波器的延迟会影响整个系统的动态响应。如果速度环带宽 100Hz,滤波器截止频率 50Hz,在 100Hz 处相位滞后大约 63 度,这已经很大了。我一般会把截止频率设在速度环带宽的 2 到 3 倍,比如 200Hz 到 300Hz,牺牲一点滤波效果换动态性能。

无感 FOC 里,速度估计来自滑模观测器或者龙伯格观测器,观测器本身就有滤波作用。这时候再加 IIR 要小心,两级滤波叠加可能导致相位滞后过大,系统震荡。我的做法是先在观测器内部做好滤波,速度环外面只加很轻的一阶低通,alpha 不小于 0.5。

4.3 滤波器在环路中的位置:前向还是反馈

这个问题很多人忽略,但影响很大。电流采样滤波器放在反馈路径上,会引入相位滞后,降低相位裕度。放在前向路径上,会影响指令跟踪,但不影响稳定性。

我的建议是:电流环滤波器放在反馈路径,因为电流采样噪声必须滤掉,而且电流环带宽高,滤波器截止频率也高,相位滞后可以接受。速度环滤波器也放在反馈路径,但截止频率要仔细选。

如果你发现加了滤波器后系统震荡,可以先尝试把滤波器移到前向路径,看看是否改善。如果改善明显,说明相位裕度不够,需要重新调整滤波器参数或者降低环路增益。

4.4 实测案例:STM32F103 上的电流环滤波

我之前做过一个基于 STM32F103C8T6 的 FOC 项目,PWM 16kHz,电流环 16kHz,电机是 2204 云台电机。原始电流采样噪声峰峰值大约 200mA,电流环输出抖动明显。

加了 alpha=0.4 的一阶低通后,噪声峰峰值降到 50mA 左右,电流波形明显干净。但电机在高速时出现轻微啸叫,频率大约 2kHz。排查后发现是滤波器相位滞后导致电流环相位裕度下降,在某个转速下激发了振荡。

把 alpha 提高到 0.6,啸叫消失,噪声峰峰值约 80mA,仍然可接受。这个案例说明,滤波器参数不是越强越好,要在噪声和稳定性之间找平衡。

5. 高频注入里的 IIR 带通:从噪声中提取转子位置

5.1 高频注入的基本原理

无感 FOC 在零速和低速时,反电动势太小,滑模观测器无法准确估计转子位置。高频注入通过向 d 轴注入一个高频电压信号,利用电机的凸极效应(Ld ≠ Lq),在 q 轴电流里产生一个与转子位置相关的响应信号。提取这个响应信号的幅值或相位,就能估计转子位置。

注入频率通常 500Hz 到 2kHz,幅值几十伏。响应信号很微弱,而且混有基波电流和 PWM 噪声。带通滤波器的作用就是把注入频率附近的响应信号提取出来,把其他频率成分挡掉。

5.2 带通滤波器的设计细节

假设注入频率 1kHz,PWM 20kHz,电流环 20kHz。带通滤波器中心频率 1kHz,带宽 200Hz,Q=5。

用 scipy 设计:

fs = 20000 f0 = 1000 Q = 5 b, a = signal.iirpeak(f0/(fs/2), Q)

得到的滤波器在 1kHz 处增益为 1,在 500Hz 和 2kHz 处衰减约 14dB。这个选择性对于高频注入够用了。

但要注意,带通滤波器的群延迟在中心频率处最大。Q=5 时,群延迟大约 Q/(pif0) = 5/(3.141000) ≈ 1.6ms。这个延迟会导致位置估计滞后,在低速时影响不大,但高速时可能造成角度误差。所以高频注入通常只在低速段使用,高速段切换到反电动势观测器。

5.3 同步解调与低通后处理

带通滤波器的输出是注入频率的交流信号,还需要同步解调才能得到位置信息。解调方法是用一个与注入信号同频同相的参考信号相乘,然后低通滤波取出直流分量。

这里的低通滤波器又是一级 IIR。截止频率通常设得比较低,比如 100Hz 到 200Hz,因为位置估计不需要很快的响应。但截止频率太低会导致位置估计延迟大,影响低速性能。

我一般用两级一阶低通级联,截止频率 200Hz,等效二阶巴特沃斯。这样在 1kHz 处衰减约 28dB,解调后的纹波可以压到很小。

5.4 实际调试中的坑

高频注入调试最头疼的是参数耦合。注入幅值、注入频率、带通 Q 值、解调低通截止频率,这四个参数互相影响。我的经验是:

  1. 先固定注入频率为 PWM 频率的 1/10 到 1/20,比如 20kHz PWM 用 1kHz 注入
  2. 注入幅值从额定电压的 10% 开始,逐步增加,直到响应信号在示波器上清晰可见
  3. 带通 Q 值从 5 开始,如果响应信号仍然被噪声淹没,增加到 10
  4. 解调低通截止频率从 200Hz 开始,如果位置估计抖动大,降低到 100Hz

还有一个坑是带通滤波器的中心频率必须和注入频率严格一致。如果注入频率有漂移,或者滤波器系数计算有误差,响应信号会被衰减。我一般会在代码里用同一个频率变量生成注入信号和滤波器系数,确保一致。

6. 代码实现:从差分方程到可运行函数

6.1 二阶 IIR 的通用 C 实现

typedef struct { float b0, b1, b2; float a1, a2; float x1, x2; float y1, y2; } IIR_Biquad; float iir_biquad_process(IIR_Biquad *f, float x) { float y = f->b0 * x + f->b1 * f->x1 + f->b2 * f->x2 - f->a1 * f->y1 - f->a2 * f->y2; f->x2 = f->x1; f->x1 = x; f->y2 = f->y1; f->y1 = y; return y; }

这个结构叫 Direct Form I,最直观,也最不容易出错。每个滤波器实例需要保存 x1、x2、y1、y2 四个状态变量。

注意 a1 和 a2 前面的负号。如果你从 scipy 拿到 a = [1, a1, a2],差分方程里是减去 a1y1 和 a2y2。这个符号搞错的话,滤波器会变成不稳定。

6.2 定点实现的注意事项

在 STM32F103 上用浮点会很慢,建议用 Q15 定点。但二阶 IIR 的定点实现比一阶复杂,因为中间累加可能溢出。

typedef struct { int32_t b0, b1, b2; int32_t a1, a2; int32_t x1, x2; int32_t y1, y2; } IIR_Biquad_Q15; int16_t iir_biquad_q15_process(IIR_Biquad_Q15 *f, int16_t x) { int32_t acc = f->b0 * x + f->b1 * f->x1 + f->b2 * f->x2 - f->a1 * f->y1 - f->a2 * f->y2; int16_t y = (int16_t)(acc >> 15); f->x2 = f->x1; f->x1 = x; f->y2 = f->y1; f->y1 = y; return y; }

系数用 Q15 格式,即实际系数乘以 32768。累加器用 int32,最后右移 15 位。如果系数较大,累加器可能溢出,需要检查最大可能值。一般来说,如果输入是 int16 范围,系数和不超过 4,累加器不会溢出 int32。

6.3 滤波器初始化和状态复位

滤波器上电时,状态变量应该初始化为第一次输入值,而不是 0。否则滤波器会从 0 开始爬升,导致启动时的瞬态。

void iir_biquad_init(IIR_Biquad *f, float init_val) { f->x1 = init_val; f->x2 = init_val; f->y1 = init_val; f->y2 = init_val; }

在电机启动时,用第一个电流采样值初始化滤波器,可以避免启动冲击。

7. 几个容易踩的坑和排查思路

7.1 滤波器震荡:先查符号再查极点

如果加了 IIR 后系统震荡,第一步检查差分方程的符号。a 系数前面的负号是最常见的错误。第二步检查极点是否在单位圆内。可以用 Python 算一下:

import numpy as np roots = np.roots([1, a1, a2]) print(np.abs(roots))

如果任何一个根的模大于等于 1,系统不稳定。正常情况下应该都小于 1。

7.2 相位滞后导致环路不稳

如果滤波器本身稳定,但系统仍然震荡,大概率是相位滞后太大。用 Python 画一下滤波器的相频响应:

from scipy import signal import matplotlib.pyplot as plt w, h = signal.freqz(b, a) plt.plot(w/np.pi*fs/2, np.angle(h, deg=True)) plt.xlabel('Frequency (Hz)') plt.ylabel('Phase (deg)') plt.show()

看电流环带宽处的相位滞后。如果超过 45 度,就需要降低滤波器阶数或者提高截止频率。

7.3 定点溢出的隐蔽表现

定点实现时,如果累加器溢出,结果会突然跳变,表现为电机偶尔抽搐或者电流尖峰。这种问题很难查,因为不是每次都出现。

我的做法是在调试阶段用 int64 累加器,确认没有溢出后再改回 int32。或者在累加后加一个饱和判断:

if (acc > 2147483647) acc = 2147483647; if (acc < -2147483648) acc = -2147483648;

7.4 采样频率变化时的系数重算

如果 PWM 频率动态调整,滤波器的系数需要重新计算。因为 alpha 和采样周期 T 有关。我一般会在频率切换时调用一个系数更新函数,用当前 T 重新算 alpha。

void update_lpf_alpha(float fc, float fs) { alpha = 1.0f - expf(-2.0f * 3.14159f * fc / fs); }

注意 expf 在 STM32F103 上比较慢,如果频率切换不频繁,可以接受。如果频繁切换,建议用查表法。

8. 一些个人经验

IIR 滤波器在电机控制里是个双刃剑。用得好,噪声压得干干净净,环路稳如磐石;用不好,震荡、啸叫、响应迟钝全来了。我的核心经验就一条:永远不要孤立地设计滤波器,要把它放在整个控制环路里评估。

具体来说,设计滤波器时先问自己三个问题:这个滤波器的截止频率和环路带宽是什么关系?相位滞后在环路带宽处有多少度?滤波器的延迟会不会影响系统的动态响应?这三个问题回答清楚了,参数就不会跑偏。

另外,仿真和实测差距很大。MATLAB 里完美的滤波器,到了实际电机上可能完全不是那么回事。因为实际系统里有非线性、有延迟、有量化误差。我一般会在仿真确认基本参数后,直接在硬件上调试,用示波器和电流探头看实际波形,逐步微调。

最后,高频注入里的带通滤波器,Q 值不要设太高。我见过有人设 Q=50,结果响应信号是干净了,但位置估计延迟大到电机根本转不起来。Q=5 到 10 是实践中最稳的范围。

这个内容后续还可以扩展的方向包括:自适应 IIR 滤波器在电机参数变化时的应用、多速率滤波在 FOC 里的实现、以及 IIR 和 FIR 在电机控制里的混合使用策略。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询