搞电机控制的朋友,十有八九都被“滤波器”这三个字缠过。电机电流里的PWM开关毛刺、采样瞬间的混叠分量、速度计算出来的锯齿波、无感FOC里滑模观测器吐出来的高频噪声,哪个不是滤波器该管的活?而说到具体用哪种滤波器,绕不开的就是IIR数字滤波器。这篇文章就把IIR在电机控制里的来龙去脉讲透,从Z平面上的极点位置,到低通/带通怎么设计,再到FOC电流环、速度环以及高频注入转子初始位置检测里的实际用法,最后给出一套能在定点MCU上直接跑的C代码。无论是刚入门FOC的嵌入式工程师,还是已经在调无感算法的老手,都能从这里拿到能落地的经验。
1. 电机控制里为什么绕不开IIR滤波器
1.1 噪声在电机控制链路里的来源清单
先说个很反直觉的事实:电机控制环路本身并不需要很高的采样率,电流环通常20kHz以内,速度环1kHz左右就够了。但PWM开关动作、死区效应、电流传感器带宽、ADC采样保持电路,每一环都会把宽频噪声注入到采样值里。我测过一台普通的低压永磁同步电机,在PWM开关瞬间去采样电流,波形上能看见幅度将近2%额定值的尖刺,这个尖刺如果不处理,电流环PI控制器会把它当成真实电流误差来响应,轻则电流波形难看,重则直接触发过流保护。
噪声的来源大致可以分成几类:
- PWM开关噪声:IGBT或MOSFET开关瞬间的dV/dt耦合进采样回路,表现为时间上极窄的尖峰。
- ADC采样混叠:采样频率低于信号频率时,高频分量折叠回低频频段,表现为随机波动。
- 速度计算噪声:编码器或霍尔信号经过M法/T法测速,相邻周期测量值抖动很明显,转速反馈因此变得毛糙。
- 观测器噪声:滑模观测器、龙伯格观测器输出的反电动势估计里混合了大量开关频率分量,需要提取基波。
这几类噪声的共同特点是:有效信号要么集中在低频,要么集中在一个已知的窄带频率附近。所以处理手段就归拢成两种——低通滤波和带通滤波。而IIR数字滤波器,正好能以很小的计算量搞定这两件事。这也是为什么市面上几乎所有电机控制库,从STM32的Motor Control SDK到TI的InstaSPIN,内部都塞着各种IIR模块。
1.2 FIR和IIR怎么选:为什么说IIR更“能打”
每次聊到数字滤波,一定会有人问:为什么不用FIR?FIR线性相位、绝对稳定,看起来更斯文。但放在电机控制这个场景里,有一个致命伤——阶数。电机控制系统里MCU的算力是挤出来的,电流环计算时间通常要求在10微秒以内完成,你要是在电流反馈路径上放一个128抽头的FIR,光是乘加运算就够喝一壶了,更别提FIR带来的群延迟在闭环里往往比IIR更麻烦。
IIR的核心优势就是“用少量状态变量换陡峭的滚降特性”。一个二阶IIR低通,滚降斜率是12dB/oct,算上零点还能做得更陡;四阶IIR就能达到24dB/oct,这在绝大多数电机控制场景里已经绰绰有余。计算量呢?四阶IIR也就是4次乘法和4次加法,在Cortex-M4上单精度浮点一次算完不会超过200ns。电机控制是典型的周期性实时任务,控制频率越高,留给滤波器的计算预算就越少,IIR几乎是必然选择。
当然IIR也有代价:相位非线性,极点靠近单位圆时对系数量化敏感。这些坑后面会专门讲。但总的结论很明确:在电机控制这种“低阶数、低延迟、高实时性”要求面前,IIR的性价比明显高于FIR。
1.3 低通和带通:两种最常用的滤波器类型各自解决什么
低通滤波器在电机控制里承担的是“提纯”工作。比如电流采样值从ADC出来后,你想看的是基波电流,PWM开关噪声是高频的,直接一个截止频率在载波频率五分之一左右的IIR低通就能压掉。速度环前面的低通更典型,测速值抖动得很厉害,取个截频10~50Hz的低通,转速反馈立刻顺滑。
带通滤波器承担的是“选频”工作。最典型的场景就是高频注入法检测转子初始位置:注入的高频电压信号是已知频率的,比如1kHz或10kHz,我要从电流响应里把这个频率的分量单独抠出来,周围基波和其它噪声全部滤掉。这时候低通或者高通都干不了这活,只有带通滤波器能在指定频率附近形成通带,把信号提取出来。
所以理解IIR在电机控制里的角色,可以简化成一句话:低通留着有用的低频,带通挑出特定频段的有用分量。后面讲到的Z平面设计,也都是围绕这两种类型展开的。
2. 从Z平面看IIR的本质,以及怎么设计低通/带通
2.1 传递函数和Z平面:极点决定一切
IIR数字滤波器的系统函数长这样:
[ H(z)=\frac{b_0+b_1z^{-1}+b_2z^{-2}+\cdots}{1+a_1z^{-1}+a_2z^{-2}+\cdots} ]
在Z平面上看,零点让某些频率的信号被抑制,极点让某些频率的信号被增强。一个稳定的IIR滤波器,所有极点必须落在单位圆内部。极点越靠近单位圆,滤波器的选择性越强,也就是共振峰越尖锐,但同时也越脆弱——只要系数一不小心让极点跑出单位圆,滤波器立刻变成振荡器,输出直接飞掉。
我见过很多新手用MATLAB的fdatool设计完滤波器后直接导出浮点系数,然后复制到STM32工程里。MCU里如果写的是浮点代码,那多半没问题;但一旦换成定点Q格式,系数量化误差就会导致极点稍微偏移。在设计时让极点离单位圆有个安全距离(比如半径不超过0.95),比事后加各种保护都管用。
2.2 设计一个Butterworth低通:从模拟原型到Z变换
最实用的IIR设计方法是双线性变换法。拿二阶Butterworth低通举例,模拟域的原型传递函数是:
[ H(s)=\frac{\omega_c^2}{s^2+\sqrt{2}\omega_c s+\omega_c^2} ]
代入双线性变换 (s=\frac{2}{T_s}\cdot\frac{1-z^{-1}}{1+z^{-1}}),再整理成Z传递函数的标准形式。这里说明一下,用双线性变换有个频率预畸变问题:数字域截止频率 (\omega_d) 和模拟域截止频率 (\omega_a) 的关系是 (\omega_a=\frac{2}{T_s}\tan(\frac{\omega_d T_s}{2})),设计时必须先做预畸变,否则实际截止频率会偏。
实际工程里没人手算这个,用Python的scipy可以直接算:
from scipy.signal import butter, bilinear, freqs_zpk import numpy as np fs = 10000 # 采样率 10kHz fc = 1000 # 截止频率 1kHz b, a = butter(2, fc, btype='low', fs=fs) print("b:", b) print("a:", a)输出就是一组可以直接用的二阶系数。这里要留个心:scipy的butter用的是数字滤波器设计,已经处理好了预畸变和归一化,但不同版本的系数排列顺序略有差异,使用时先看看输出,再对应到C代码里的结构体。
2.3 设计一个IIR带通:中心频率、Q值与带宽的关系
带通滤波器比低通多两个参数:中心频率 (f_0) 和品质因数 (Q)。带宽 (BW=f_0/Q)。Q值越大,通带越窄,选择性越好,但也意味着极点越靠近单位圆,系统对系数误差越敏感。高频注入场景里,注入频率如果选在1kHz,载波20kHz,基波频率在几十Hz到几百Hz之间变化,那么带通的Q值通常选在5~15之间比较合适。太低了,基波电流会漏进来;太高了,极点离单位圆太近,动态响应慢,启动瞬间还容易振荡。
还是用Python算一组二阶带通系数:
from scipy.signal import iirpeak fs = 10000 f0 = 1000 # 中心频率 Q = 8 b, a = iirpeak(f0, Q, fs) print("b:", b) print("a:", a)iirpeak设计的带通增益在中心频率处是1,适合提取信号。如果需要在某个频段带通增益平坦一些,要使用butter配合btype='bandpass',指定两个截止频率。
2.4 采样率的牵扯:同一个设计参数在不同fs下的表现
设计IIR滤波器时,所有系数本质上都跟采样率绑定。同样一个截止频率1kHz的低通,采样率10kHz和20kHz设计出来的系数完全不同,这一点看似简单,却在联调时经常坑人。我就见过有人配置文件里只改了采样率,滤波器系数还是旧的,结果电流波形莫名震荡,查了半天才发现滤波器实际截止频率变成了2kHz,相位裕度被吃掉一大截。
所以一个负责任的做法是把滤波器设计工具直接集成到工程配置阶段,用脚本根据当前采样率实时算系数,而不是把系数硬编码进C文件。哪怕只是用Python脚本生成一个头文件,也比手动复制粘贴靠谱得多。
3. IIR在FOC控制里的典型应用与参数整定
3.1 电流采样滤波:放在哪里最合理?PWM采样时刻配合
FOC电流环的采样和PWM是强相关的。目前的通用做法是在PWM载波的中心点触发ADC采样,因为此时三相电流处于续流或恒通的稳定区,开关噪声最小。但即便在这个时刻采样,由于采样保持电路和传感器响应的非理想特性,ADC读到的值里依然有毛刺,特别是IGBT关断瞬间的dv/dt会通过寄生电容串进采样电路。
在FOC库里,电流采样滤波通常有两种位置可放:一是对ADC原始值做IIR低通后再参与Clarke/Park变换;二是经过Clark变换后,对Iα、Iβ分别滤波。从理论上说,后者更优雅,因为滤波环节跟随信号旋转坐标系,但做起来要注意滤波延迟在d/q轴之间引入交叉耦合。实际工程里,大部分做的是对相电流先滤波,把载波频率噪声抑制掉,然后再进变换。我当时在STM32F405上做FOC,ADC触发放在PWM定时器UPDATE事件中心点,滤波截止频率设为开关频率的十分之一左右,也就是开关频率20kHz时截止2kHz,这个组合测下来电流波形既干净,相位延迟也不至于拖垮电流环。
注意一个细节:电流环带宽通常设计在1kHz左右,滤波器的截止频率不能比带宽低太多,否则电流环高频段的相位裕度会被严重侵蚀。经验值是截止频率取电流环带宽的3~5倍比较稳。
3.2 速度环滤波和观测器信号调理
速度环是另一个IIR低通的重灾区。M法测速在低速时会遇到“量化噪声”,一个码盘脉冲对应一个转速台阶,转速反馈信号看起来像锯齿波。如果不滤波,速度环PI会跟着锯齿反复调节,电机低速会一抖一抖的。
速度环滤波器有两个选择:一是放在编码器速度计算之后,二是放在速度环PI输出之后。前者更常见,因为速度反馈信号直接进PI,滤波越早越干净。但滤波器延迟会让速度环带宽下降,所以截止频率不能选得太低。如果速度环采样率1kHz,我一般选50~100Hz截止,具体看负载惯量。惯量大、系统刚性强的,可以适当放宽截止频率;惯量小、容易震荡的,把截止频率压低一些。
无感FOC里,滑模观测器输出的反电动势估计通常带着很重的开关噪声。处理办法是在观测器输出和速度计算之间加一个二阶IIR低通,把高频抖毛滤掉,但反电动势基波的相位延迟又会转成位置误差,所以很多人会再用一个角度补偿。这里有个经验做法:不直接对角度做低通,而是对转速做低通,把滤波后的转速积分得到位置,这样相位延迟对位置的拖累会小一些。
3.3 一套可复用的参数整定流程
FOC里滤波参数整定,我总结出一个比较顺手的流程。先把滤波器从控制环里摘出去,调出能稳定工作的基础环路;然后逐级加入滤波,每加一级都做阶跃响应测试,观察电流/速度的响应曲线是否出现次谐波振荡。
第一步,确定采样率和开关频率,滤波截止频率初步取开关频率的1/10到1/5。 第二步,用仿真或脚本算出IIR系数,直接代入工程。 第三步,用电流探头或ADC内部数据看滤波后的信号,确认载波频率的毛刺已经压掉,同时观察基波幅度是否衰减过大。如果基波幅度下降了3dB以上,就把截止频率往上调。 第四步,调整速度环或位置环的带宽,在实测输入端给一个小幅阶跃,观察响应是否有振荡。如果振荡频率落在滤波器截止频率附近,基本可以断定是滤波器相位裕度不够,需要提高截止频率或降低滤波阶数。
这套流程听上去很简单,但每步都要用数据说话,不能靠感觉。滤波器系数、截止频率、采样率这些参数,最好全部做成上位机可调的变量,调试的效率会高非常多。
4. 高频注入、带通滤波器与转子初始位置检测
4.1 为什么无感FOC启动要用高频注入
无感FOC在电机静止或低速时,反电动势太小,滑模观测器那一套基本失效。这时候要启动,必须知道转子初始位置。最简单的方法是预定位,通电让转子转到已知角度,但如果有负载或负载转矩比较大,预定位可能会失败,也会造成电机反转或抖动。所以需要一种能在零速附近估计转子位置的方法,高频注入法就是其中之一。
高频注入的核心是利用永磁同步电机的凸极效应:转子所在位置不同,d轴和q轴的电感不同,呈现出一个随转子位置变化的高频阻抗。往定子绕组里注入一个已知频率的高频电压信号,电流响应里就会携带转子位置信息。通常做法是在估计的d轴方向上叠加高频余弦电压,然后从高频电流响应里解调出位置误差,再交给跟踪环。
4.2 高频注入的信号路径:BPF负责提取,LPF负责解调
高频注入的信号处理链路是IIR滤波器最标准的应用场景。整个链路大致如下:
- 发送端:在D轴电压指令上叠加高频余弦信号,频率通常在500Hz到2kHz之间,幅度是额定电压的5%~10%。注入频率必须避开电流环带宽和机械共振点,同时又要低于PWM采样频率的1/5,才能保证波形能被还原。
- 接收端:电流采样后,先经过一个中心频率等于注入频率的IIR带通滤波器,把高频电流响应从基波电流和噪声里抠出来。
- 解调端:带通输出和同步解调参考信号相乘,再经过一个低通滤波器,就能得到含有转子位置误差信息的直流分量,送进位置跟踪观测器。
这里有一个常见的认知误区:很多人觉得带通滤波器带宽越窄越好,实际不是。带通带宽太窄会导致高频响应信号的群延迟变大,而解调出来的相位误差跟滤波器群延迟直接相关,延迟大了,位置估计就会滞后,启动过程就容易顿挫。我实测下来,注入频率1kHz时,带通Q值取8~12比较合适,既能把800Hz到1.2kHz之外的基波拒之门外,又不会带来太夸张的延迟。
4.3 IIR带通实现实例与注意事项
用二阶IIR实现一个小Q值带通,可以用上一节的iirpeak直接算系数。在代码里落地时,要把这个带通滤波器放在电流采样回调函数里,和FOC主环路同步执行。注意,带通的中心频率要精确对应注入频率,如果MCU的时钟基准有偏差,带通的中心频率也会跟着飘,解调出来的信号质量会明显下降。
另一个容易忽略的细节是,注入信号幅度要能适应不同电机。电感小、电阻大的电机,同样的注入电压产生的电流偏大,容易触发过流保护;电感大、电阻小的电机,注入电流又可能太小,解调后的信噪比不够。所以注入电压幅值最好做成启动前自动标定的参数,在电机静止时测一版注入电流的基波幅度,然后按目标电流反过来推注入电压。
高频注入解调链路里,第二级低通滤波器的截止频率也没那么难选。因为位置跟踪环带宽通常只有几十赫兹,低通截止频率取跟踪环带宽的5~10倍就够了。比如跟踪环带宽20Hz,低通截止100Hz,能滤掉解调产生的2倍注入频率分量,同时不会给位置环带来太多滞后。
5. 定点MCU上的IIR实现:C语言、Q格式与防溢出
5.1 直接I型和直接II型:数值精度与内存取舍
IIR滤波器的实现结构主要分直接I型、直接II型和转置直接II型。直接I型把输入输出分别做延迟链,结构直观但要用两倍的存储;直接II型把状态变量合并,节省内存,但数值敏感度稍高。电机控制的MCU内存不太紧张,我更推荐直接I型,调试的时候状态变量看得清楚,实现也简单。
二阶直接I型的差分方程:
[ y[n]=b_0x[n]+b_1x[n-1]+b_2x[n-2]-a_1y[n-1]-a_2y[n-2] ]
这个公式就是所有二阶IIR的骨架,更高阶的IIR由多个二阶节级联或并联而成。级联的好处是每一节的极点都独立可控,即使某一节出现数值问题,也只影响那个频段,不会拖垮整体稳定性。
5.2 Q格式定点化:从浮点到整数
很多电机控制MCU不带FPU,或者FPU被其他任务占用,IIR滤波器就得上定点实现。Q格式的思路是把浮点系数放大到整数域做运算,结果再缩回来。系数和状态变量放大倍数要选得合适:放大倍数太小,精度不够;太大,中间累加容易溢出。
以二阶低通为例,输入电流采样值ADC结果通常是12位或16位整数,取有效值在±2048左右。滤波器系数如b0、b1、b2可能落在0.1~0.9之间,a1、a2可能是负数且绝对值可能大于1。如果统一用Q15格式(即放大32768倍),那么a1、a2可能会超出正负32767的范围,所以不能直接把所有系数都塞进Q15。做法是用Q14(放大16384倍)或Q13存放系数,状态变量用高精度类型。
关键中间量用32位整数来承载。假设输入是16位int,系数是Q14,那么一次乘法的中间结果需要32位存储,而累加时最多会叠加5个数,需要再留几位余量。总体建议:状态变量用int32,乘法的中间结果用int64或分两步缩位,避免一次乘法后再加另一个乘法时溢出。
5.3 可直接参考的C代码实现
下面给一个通用的二阶IIR低通滤波函数,浮点版和定点版分开写。浮点版本适合有FPU的Cortex-M4F/M7,定点版本适合无FPU的F103级别MCU。代码结构可以直接移植到TMS320C6416这类DSP上,DSP上需要注意的只是把乘加循环用内部的MAC指令替代。
浮点版本:
typedef struct { float b0, b1, b2; float a1, a2; float x1, x2; // x[n-1], x[n-2] float y1, y2; // y[n-1], y[n-2] } iir_biquad_f32_t; float iir_biquad_f32_run(iir_biquad_f32_t *f, float x0) { float y0 = f->b0 * x0 + f->b1 * f->x1 + f->b2 * f->x2 - f->a1 * f->y1 - f->a2 * f->y2; // 状态变量更新 f->x2 = f->x1; f->x1 = x0; f->y2 = f->y1; f->y1 = y0; return y0; }定点版本(Q14系数+Q15状态变量):
typedef struct { int16_t b0, b1, b2; // Q14格式系数 int16_t a1, a2; // Q14格式系数 int32_t x1, x2; // Q15格式状态 int32_t y1, y2; // Q15格式状态 int32_t shift; } iir_biquad_q15_t; int16_t iir_biquad_q15_run(iir_biquad_q15_t *f, int16_t x0) { // 输入统一为Q15 int32_t acc; acc = (int32_t)f->b0 * x0; acc += (int32_t)f->b1 * f->x1; acc += (int32_t)f->b2 * f->x2; acc -= (int32_t)f->a1 * f->y1; acc -= (int32_t)f->a2 * f->y2; // 系数是Q14,状态是Q15,乘完得到Q29,缩到Q15需要 >>14 acc >>= 14; // 饱和:防止极端输入导致输出超范围 if (acc > 32767) acc = 32767; if (acc < -32768) acc = -32768; int16_t y0 = (int16_t)acc; f->x2 = f->x1; f->x1 = x0; f->y2 = f->y1; f->y1 = y0; return y0; }这个定点代码要注意的是,acc用int32,在极端输入下可能溢出,尤其是高阶IIR级联时前一节的输出可能被放得很大。稳妥做法是每一级输出之后做饱和,不要让上一级的异常数据传到下一级。
5.4 在TMS320C6416这类DSP上的优化思路
DSP和MCU不一样,它缺的不是算力,而是你写代码的方式。TMS320C6416是定点DSP,有VLIW结构,天然适合做乘加运算。把IIR滤波器写成循环里的一堆独立乘加,C编译器通常能自动并行化。如果手写汇编,要注意延迟槽的问题,C64x DSP的乘法结果要几个周期后才可用,所以连续多个滤波器级联时,最好让它们之间不要产生立即互相关,或者把这几个滤波器交错执行,流水线就能跑满。
另外,DSP上通常有环形缓冲和位反转寻址,但对二阶IIR这种少量状态变量来说,位反转寻址用不上,主要优化手段是手动展开和软件流水。举个小例子,如果同时有3个独立的IIR滤波器需要跑,与其逐个跑完,不如把3个滤波器的当前采样值先算好,再一起更新状态,这样CPU流水线的停顿会少很多。
6. 实战里常踩的坑和调试技巧
6.1 相位滞后把电流环搞到失稳
这是最典型的IIR坑。你在电流反馈路径上加了个二阶低通,截止频率1kHz,采样率10kHz,看起来一切正常。但在1kHz附近,这个滤波器的相位滞后已经达到-90°左右,而电流环PI控制器在这个频段通常还有几十度的相位贡献。两者叠加,很容易把整个电流环的相位裕度吃没了,于是系统高速时开始啸叫,甚至特定转速下电流振荡。
解决思路有两种。第一种是提高滤波器截止频率到电流环带宽的5倍以上,牺牲一点滤波效果换稳定性。第二种是用高阶Butterworth低通配合相位补偿,比如在PI控制器后加一个超前校正网络,把滤波器造成的那部分相位损失拉回来。第二种实现稍复杂,但效果更好,尤其在需要抑制PWM噪声又要保证高带宽的场合。
6.2 滤波曲线看着正常但启动就振荡:留意启动瞬态
滤波器稳态特性是正弦稳态下的表现,但电机启动是瞬态过程。你给滤波器输入一个阶跃电流,输出会先冲一下,然后慢慢收敛到稳态。如果IIR极点离单位圆很近,这个“冲一下”的幅度可能远超稳态值,于是启动瞬间电流保护误触发,或者速度环出现明显超调。
我处理过一辆无人车永磁同步电机驱动的案子,位置环在零点附近抖个不停。检查半天,问题出在滤波器的初始状态没有清零——上一轮下电时状态变量里残留了很大的历史值,下次上电一启动,滤波器输出直接饱和。解决方式很简单:每次电机控制系统复位或使能前,把结构体里的x1、x2、y1、y2全部清零。这个动作必须写在控制环执行之前,而且最好用宏一次性清,不要漏。
另外,为了削弱启动瞬态,可以让滤波器的状态变量在启动前通过一个软使能过程缓慢追上真实值,或者干脆在前几百微秒把滤波器旁路掉,等电流稳定了再切入滤波输出。这个细节在量产中很重要。
6.3 用滤波曲线和示波器来验证滤波效果
很多工程师调完滤波器只看输出波形顺不顺畅,这远远不够。要验证IIR滤波器的行为,最好做三件事。
第一,用示波器同时观察原始信号和滤波后信号,给一个阶跃输入,测量滤波输出的上升时间和超调量,并与理论计算的群延迟对比。如果阶跃响应上升时间太慢,说明滤波器截止频率实际比设计值低。
第二,用频率扫描(线性调频或扫频激励)看滤波曲线。在电机静止时给电流环注入一个扫频正弦指令,记录反馈电流幅值,用上位机画Bode图,峰值和-3dB点能直接暴露系数算错、采样率配错这些问题。这个方法我基本每次调新板子都会跑一遍,比肉眼盯波形高效得多。
第三,检查定点版本的数值噪声。把定点滤波器和浮点滤波器跑同一段数据,两者输出之差如果超过2%~3%,就说明定点格式的动态范围不够。常见的补救方法是把输入信号先按比例缩小再进滤波器,或者在滤波器内部做中间量缩放,这些都是常规操作,但现场能想到的人不多。
关于IIR滤波器的最后一点经验
我做无感FOC也踩过不少滤波器的坑,这里分享一个印象最深的教训。有次做高频注入起动,转子位置在静止时估计得很准,但一加转矩,位置就开始漂。查到最后,问题居然出在带通滤波器的群延迟上——注入频率1kHz,带通Q值选了20,位置解调信号被延迟了将近两个采样周期,转矩一变,跟踪环来不及跟上,位置就从收敛点滑出去了。后来把Q值降到10,群延迟缩短,再配合一个简单的相位补偿,同样工况下位置误差从十几度降到两三度。
IIR滤波器在电机控制里从来不是孤立存在的,它和采样时刻、控制带宽、观测器结构、执行频率统统纠缠在一起。参数不能照抄别人的,一定要结合自己系统的采样率、开关频率和机械特性去现场调。有条件的话,把所有滤波器系数都做成参数,用上位机在线修改,你会少走很多弯路。这篇文章如果能帮你少踩一个滤波器的坑,就算值了。