☰
HFI无位置传感器控制:BPF、同步解调与LPF信号链拆解
2026/10/27 9:12:54 网站建设 项目流程

HFI(High Frequency Injection,高频注入)是无位置传感器电机控制里绕不开的一块硬骨头。很多人第一次看代码时都会卡在同一个地方:明明已经拿到了高频响应电流,为什么还要先过一个带通滤波器(BPF),再乘一个 sinωht 做同步解调,最后又过一个低通滤波器(LPF)?这三个环节看起来像在反复折腾同一个信号,实际上每一步都在解决一个非常具体的问题。我当初啃这段代码的时候,也是把公式推了三遍、波形画了两遍,才真正把"为什么"想明白。这篇就把 LPF、BPF 和同步解调这条链路从代码层面彻底拆开,讲清楚每个滤波器的设计意图、参数怎么定、代码怎么写,以及实测中容易踩的坑。适合正在做 HFI 无感控制、看得懂 C 代码但被信号处理绕晕的嵌入式工程师和电机控制方向的学生。

1. 先把 HFI 的信号链路在脑子里跑一遍

1.1 高频注入到底往系统里塞了什么

HFI 的核心思路是:电机低速甚至零速时,反电动势太小,没法靠它估转子位置,那就主动往定子电压里注入一个高频信号,靠电机凸极效应(d 轴和 q 轴电感不等)产生的响应电流差异来反推转子角度。最常用的注入方式是旋转高频电压注入,在估计的 d 轴方向叠加一个高频余弦电压:

u_dh = Vh * cos(ωh * t) u_qh = 0

其中 Vh 是注入幅值,ωh 是注入角频率。这个电压进到电机里,会在 dq 轴上产生高频电流响应。因为 d 轴电感 Ld 和 q 轴电感 Lq 不一样,响应电流里就带上了转子位置信息。关键点在于:这个响应电流的频率就是 ωh,它和基波电流(电机的正常工作电流,频率是电频率 ωe)混在一起,同时还夹着 PWM 开关噪声、电流采样噪声。

所以你在代码里拿到的 Iq,其实是一个"大杂烩":低频的基波分量、高频的注入响应分量、还有各种噪声。后面那一串 BPF → 同步解调 → LPF,本质就是从这个大杂烩里把"转子位置信息"这道菜单独端出来。

1.2 为什么不能直接从 Iq 里读角度

新手最容易有的疑问是:既然响应电流里含位置信息,那我直接拿 Iq 去算不就行了?不行,原因有两个。

第一,基波分量太大。电机正常工作时的 q 轴电流可能是几安培甚至几十安培,而高频响应电流往往只有几十到几百毫安。位置信息藏在那个小信号里,直接读会被基波淹没,信噪比低到没法用。

第二,位置信息不是以"幅值"形式直接出现的,而是以"相位/幅值随角度变化"的形式调制在高频载波上的。你得先把这个高频载波单独拎出来,再解调,才能还原出角度误差信号。

这就是 BPF 存在的第一个理由:把 ωh 附近的频率成分单独选出来,把基波(低频)和开关噪声(高频)都挡在外面。

1.3 三个环节各自的分工

把整条链路的分工先摆清楚,后面再逐个展开:

环节作用挡掉什么留下什么
BPF选频基波、低频扰动、高频开关噪声ωh 附近的高频响应电流
同步解调(乘 sinωht)频谱搬移无把 ωh 分量搬到直流和 2ωh
LPF取直流2ωh 分量、残余高频含角度误差的直流量

这三个环节是串联的,缺一不可。BPF 不做,解调时基波会被搬移到 ωh 附近形成干扰;同步解调不做,你没法把相位信息转成直流量;LPF 不做,2ωh 分量会让角度估计抖得没法看。

2. BPF:为什么必须先把 Iq 里的高频分量抠出来

2.1 带通滤波器的中心频率和带宽怎么定

BPF 的设计有两个核心参数:中心频率 f0 和带宽 BW。中心频率毫无疑问就是注入频率 fh = ωh / (2π)。带宽的选择就讲究了。

带宽太窄,滤波器 Q 值高,选频特性好,但有两个问题:一是对频率漂移敏感,注入频率稍有偏差(比如时钟误差、参数变化)就会衰减有用信号;二是群延迟大,相位滞后严重,影响角度估计的动态响应。

带宽太宽,选频特性差,基波和噪声漏进来,解调后干扰大。

工程上的经验值:带宽取注入频率的 10% 到 30% 比较稳妥。比如注入频率 1kHz,带宽取 100~300Hz。我实测下来,200Hz 左右是个比较平衡的点,既能容忍一定的频率偏差,又不会让基波漏太多。

2.2 二阶巴特沃斯 BPF 的代码实现

工程里最常用的是二阶巴特沃斯带通,因为它通带平坦、相位特性相对温和。用双线性变换离散化后,差分方程形式是:

// 二阶带通滤波器结构体 typedef struct { float b0, b1, b2; // 分子系数 float a1, a2; // 分母系数 float x1, x2; // 输入历史 float y1, y2; // 输出历史 } BiquadBPF; // 直接II型转置结构,数值稳定性好 float bpf_process(BiquadBPF *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; }

系数怎么算?给定中心频率 f0、带宽 BW、采样率 fs:

void bpf_init(BiquadBPF *f, float f0, float BW, float fs) { float w0 = 2.0f * PI * f0 / fs; float alpha = sinf(w0) * sinhf(logf(2.0f) / 2.0f * BW / f0 * w0 / sinf(w0)); float cosw0 = cosf(w0); float a0 = 1.0f + alpha; f->b0 = alpha / a0; f->b1 = 0.0f; f->b2 = -alpha / a0; f->a1 = -2.0f * cosw0 / a0; f->a2 = (1.0f - alpha) / a0; f->x1 = f->x2 = f->y1 = f->y2 = 0.0f; }

这段代码是标准的 RBJ Audio EQ Cookbook 里的带通公式。注意这里用的是"恒定 0dB 峰值增益"的带通形式,b0 = alpha/a0,b1 = 0,b2 = -alpha/a0。如果你用的是"恒定 skirt 增益"版本,系数会不一样,别抄错。

2.3 采样率与注入频率的匹配关系

这里有个很容易被忽略的坑:采样率必须远高于注入频率,否则滤波器根本没法工作。根据奈奎斯特,fs > 2fh 是底线,但工程上至少要 fs > 10fh 才够用。

假设你的电流环跑在 10kHz,注入频率 1kHz,那 fs/fh = 10,勉强够。如果注入频率提到 2kHz,fs/fh = 5,滤波器过渡带会非常陡峭,对系数精度要求极高,定点实现基本没法做。所以注入频率一般不会超过采样率的 1/10。

我见过有人为了追求更好的凸极辨识效果,把注入频率往高了调,结果 BPF 输出全是噪声,还以为是算法问题,其实是采样率不够。这个坑一定要先排掉。

3. 同步解调:乘 sinωht 到底在做什么

3.1 从积化和差看频谱搬移

同步解调这一步,代码上就是一行乘法:

float demod = iq_bpf * sinf(omega_h * t);

但这一行背后的数学是积化和差公式。假设 BPF 输出的高频电流可以写成:

i_h = A * sin(ωh*t + φ)

其中 A 是幅值,φ 是相位(含转子位置信息)。乘以 sin(ωh*t):

i_h * sin(ωh*t) = A * sin(ωh*t + φ) * sin(ωh*t) = A/2 * [cos(φ) - cos(2ωh*t + φ)]

看到了吗?乘积结果里出现了两项:一项是 A/2 * cos(φ),这是直流分量,只和相位 φ 有关;另一项是 A/2 * cos(2ωh*t + φ),是两倍注入频率的高频分量。

这就是同步解调的精髓:把"藏在载波相位里的信息"搬移到直流上。直流分量好处理啊,后面一个 LPF 就能把它干净地取出来,而 2ωh 分量被 LPF 挡掉。

3.2 为什么是乘 sin 而不是乘 cos

这个问题问的人特别多。答案取决于你注入的是什么信号,以及你想提取什么。

如果你注入的是 cos(ωht),响应电流是 Asin(ωht + φ),那乘 sin(ωht) 得到的是 cos(φ) 项。如果你乘 cos(ωh*t),得到的是:

A * sin(ωh*t + φ) * cos(ωh*t) = A/2 * [sin(φ) + sin(2ωh*t + φ)]

直流项变成 A/2 * sin(φ)。cos(φ) 和 sin(φ) 都能用,区别在于小角度近似时哪个更线性。当 φ 很小时,sin(φ) ≈ φ,cos(φ) ≈ 1,所以用 sin 解调得到的直流项在小误差下更接近线性,更适合做角度误差信号。

实际工程里,注入信号和本地载波的相位关系要严格对齐。如果你注入用 cos,解调用 sin,那解调出来的直流项就是 cos(φ),在 φ=0 附近是极大值,不敏感;反过来才对。所以注入和解调的相位必须配套设计,不能随便换。

3.3 相位误差对解调结果的影响

本地载波 sin(ωh*t) 和实际注入载波之间如果有相位偏差 Δθ,解调结果会变成:

i_h * sin(ωh*t + Δθ) = A/2 * [cos(φ - Δθ) - cos(2ωh*t + φ + Δθ)]

直流项变成 A/2 * cos(φ - Δθ)。这意味着相位偏差会直接叠加到角度估计误差上。如果 Δθ 是固定的,那还好,可以在角度输出端做个补偿;如果 Δθ 随温度、频率漂移,那就麻烦了。

所以同步解调时,本地载波的相位必须和注入载波严格同步。工程上通常用同一个角度累加器生成注入信号和解调信号,保证相位一致:

// 角度累加器,注入和解调共用 theta_h += omega_h * Ts; if (theta_h > 2*PI) theta_h -= 2*PI; // 注入用 cos u_dh = Vh * cosf(theta_h); // 解调用 sin demod_carrier = sinf(theta_h);

这样注入和解调天然同相,不会引入额外相位误差。

4. LPF:把 2ωh 分量和残余噪声清干净

4.1 截止频率的选取逻辑

LPF 的任务很明确:让直流分量通过,把 2ωh 分量挡掉。所以截止频率 fc 必须远小于 2ωh,同时又要足够高,保证角度估计的动态响应。

经验公式:fc 取注入频率的 1/10 到 1/5。比如注入 1kHz,2ωh 对应 2kHz,fc 取 100~200Hz 比较合适。这样对 2kHz 的衰减能到 -20dB 以上,同时角度环带宽还能做到几十赫兹。

截止频率定得太低,角度估计滞后大,电机动态响应差;定得太高,2ωh 漏进来,角度估计会有高频抖动,表现为转速波动、电流啸叫。

4.2 一阶还是二阶:延迟与衰减的权衡

LPF 用一阶还是二阶,是个典型的权衡问题。

一阶 LPF 相位滞后小,动态响应快,但对 2ωh 的衰减只有 -20dB/dec。如果 fc=100Hz,2ωh=2kHz,衰减约 -26dB,勉强够用。

二阶 LPF 对 2ωh 的衰减是 -40dB/dec,同样 fc=100Hz,衰减约 -52dB,干净得多。但相位滞后大,在 fc 附近约 -90°,动态响应慢。

我的经验是:如果角度环带宽要求不高(比如低速运行、负载变化慢),用二阶,信号干净;如果要求快速动态响应,用一阶,接受一点残余纹波。很多工程代码里用的是二阶巴特沃斯,因为角度估计对纹波更敏感。

// 二阶巴特沃斯 LPF typedef struct { float b0, b1, b2, a1, a2; float x1, x2, y1, y2; } BiquadLPF; void lpf_init(BiquadLPF *f, float fc, float fs) { float w0 = 2.0f * PI * fc / fs; float cosw0 = cosf(w0); float sinw0 = sinf(w0); float alpha = sinw0 / (2.0f * 0.7071f); // Q = 0.7071 float a0 = 1.0f + alpha; f->b0 = (1.0f - cosw0) / 2.0f / a0; f->b1 = (1.0f - cosw0) / a0; f->b2 = f->b0; f->a1 = -2.0f * cosw0 / a0; f->a2 = (1.0f - alpha) / a0; f->x1 = f->x2 = f->y1 = f->y2 = 0.0f; }

4.3 解调后直流项的物理含义

LPF 输出的直流项,就是 A/2 * cos(φ)。这个 φ 是什么?它是高频响应电流相对于注入电压的相位差,而这个相位差和转子位置误差 Δθ = θ_real - θ_est 直接相关。

在旋转高频注入里,当估计角度和真实角度有偏差时,q 轴高频电流的幅值会正比于 sin(2Δθ)。经过同步解调后,这个关系被搬到直流项上。所以 LPF 输出本质上是一个"角度误差信号",把它送进一个 PI 调节器(通常叫锁相环 PLL 或角度跟踪观测器),就能把 Δθ 拉回零,实现角度跟踪。

这就是整条链路的最终目的:BPF 选频 → 同步解调搬移 → LPF 取直流 → 得到角度误差 → PLL 收敛。

5. 把三个环节串起来:完整代码与参数实例

5.1 一条完整的数据流代码

把前面几段拼起来,一个完整的 HFI 信号处理链路大概长这样:

// 全局状态 BiquadBPF bpf_iq; BiquadLPF lpf_demod; float theta_h = 0.0f; float omega_h = 2.0f * PI * 1000.0f; // 1kHz 注入 float Ts = 1.0f / 10000.0f; // 10kHz 采样 void hfi_init(void) { bpf_init(&bpf_iq, 1000.0f, 200.0f, 10000.0f); // f0=1k, BW=200 lpf_init(&lpf_demod, 150.0f, 10000.0f); // fc=150 theta_h = 0.0f; } // 每个电流环周期调用一次 float hfi_process(float iq_measured) { // 1. 带通滤波,提取高频响应 float iq_hf = bpf_process(&bpf_iq, iq_measured); // 2. 更新载波相位 theta_h += omega_h * Ts; if (theta_h > 2.0f * PI) theta_h -= 2.0f * PI; // 3. 同步解调 float demod = iq_hf * sinf(theta_h); // 4. 低通滤波,提取角度误差信号 float error = lpf_process(&lpf_demod, demod); return error; // 送给 PLL }

这段代码就是整条链路的最小实现。注意几个细节:BPF 和 LPF 都用直接II型转置结构,数值稳定性好;载波相位用累加器生成,保证注入和解调同相;error 输出直接送 PLL,不需要额外处理。

5.2 参数整定的实测顺序

参数不能一起调,得按顺序来。我的实测顺序是:

  1. 先定注入频率 fh。一般取电流环带宽的 1/5 到 1/10,比如电流环 1kHz,注入取 100~200Hz;电流环 2kHz,注入取 200~500Hz。注入频率太高,BPF 难做;太低,和基波分不开。

  2. 再定 BPF 带宽。从 fh 的 20% 开始试,观察解调后直流项的纹波。纹波大就收窄带宽,但别低于 10%,否则动态响应会变差。

  3. 然后定 LPF 截止频率。从 fh 的 1/10 开始,观察角度估计的抖动和动态响应。抖动大就降低 fc,响应慢就提高 fc。

  4. 最后调 PLL 的 PI 参数。这时候前面的滤波器都定好了,PLL 就是个标准二阶系统,按带宽和阻尼比设计就行。

这个顺序很重要,因为前面的滤波器会直接影响后面 PLL 看到的信号质量。如果先调 PLL,等滤波器一改,PLL 参数又得重调。

5.3 定点实现的注意事项

很多工程代码跑在定点 DSP 上,这时候滤波器系数和中间变量的定标就很关键。

BPF 的系数 b0 通常很小(比如 0.06 左右),如果定标不当,直接截断成整数会损失大量精度。建议系数用 Q15 或 Q30 格式,中间变量用 32 位累加,最后再移位。

还有一个坑:直接II型转置结构里,y1、y2 的定标要和输出一致,x1、x2 和输入一致。如果输入是 Q12 的电流值,输出也是 Q12,那中间乘法要用 Q24 累加,移位后再存回 Q12。这个定标关系搞错了,滤波器要么溢出要么精度全丢。

我踩过一次坑:BPF 系数用 Q15,输入用 Q12,结果中间乘积是 Q27,用 32 位累加没问题,但移位时移错了位数,输出直接变成噪声。后来用 MATLAB 定点仿真对了一遍才找到问题。所以定点实现一定要先做仿真验证。

6. 实测中那些文档不会告诉你的坑

6.1 滤波器初始瞬态导致的启动抖动

BPF 和 LPF 都有历史状态,上电时这些状态是零,但实际信号不是零,所以滤波器输出会有一个瞬态过程。这个瞬态可能持续几十毫秒,期间角度估计是乱的。

解决办法有两个:一是启动时先让滤波器"预热",前 N 个周期不输出角度,只让滤波器跑;二是给滤波器状态赋初值,比如把 x1、x2 赋成第一个采样值。我一般用第一种,简单可靠。

6.2 注入频率和 PWM 频率的拍频问题

如果注入频率和 PWM 开关频率有整数倍关系,可能会产生拍频,表现为角度估计的低频抖动。比如 PWM 10kHz,注入 1kHz,正好是 10 倍关系,某些情况下会有拍频。

解决办法是让注入频率避开 PWM 频率的整数分频点,比如注入取 1.1kHz 而不是 1kHz。这个细节很小,但实测中确实能改善抖动。

6.3 电感饱和导致的高频响应非线性

HFI 依赖凸极效应,但如果注入幅值太大,电感会饱和,凸极效应减弱甚至反转,角度估计直接失效。所以注入幅值不能贪大,一般取额定电压的 5%~10% 就够了。

判断是否饱和的方法:逐渐增大注入幅值,观察解调后直流项的幅值。如果幅值随注入幅值线性增长,说明没饱和;如果增长变缓甚至下降,说明饱和了,得减小注入幅值。

6.4 多个滤波器级联的相位累积

BPF 和 LPF 都有相位滞后,级联后总滞后是两者之和。在注入频率处,BPF 的相位滞后可能有好几十度,LPF 在直流附近滞后小,但整体链路还是有延迟。

这个延迟会影响 PLL 的相位裕度。如果 PLL 带宽设得高,加上链路延迟,可能就振荡了。所以 PLL 带宽不能超过链路延迟对应频率的 1/5 左右。这个约束在调 PLL 时一定要考虑进去。

7. 从代码回到物理:这套链路为什么长这样

把整条链路再抽象一层看,它其实是一个标准的"锁相放大"结构。锁相放大器的核心思想就是:用参考信号(本地载波)去乘被测信号,把被测信号中与参考同频同相的分量搬移到直流,再用低通滤波提取。这个结构在微弱信号检测里用了几十年,HFI 只是把它搬到了电机控制里。

BPF 对应锁相放大器前端的预滤波,用来抑制带外噪声;同步解调对应相敏检波;LPF 对应积分器。三个环节各司其职,共同完成"从强噪声背景中提取微弱同频信号"的任务。

理解了这一层,你就不会再觉得这三个滤波器是在"反复折腾"了。它们是在用最经典、最可靠的方式,把转子位置信息从一堆干扰里捞出来。代码里的每一行乘法、每一个滤波器系数,背后都是这套信号处理逻辑的直接体现。

我在实际项目里调这套链路时,最大的体会是:不要急着改算法结构,先把每个滤波器的频响曲线画出来,把每个节点的波形抓出来看。很多时候问题不在算法,而在某个滤波器的参数没配对,或者某个定标搞错了。把信号链路的每一段都看清楚,问题自然就浮出来了。

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

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

立即咨询