做无感FOC的工程师,大概率都被低速场景折磨过。转速一低、负载一重,反电动势观测器就失效,滑模、龙伯格这些中高速手段全都不顶用,这时候高频注入HFI几乎是唯一能在零速和极低速下还能维持位置估计的方案。这篇文章打算换一个讲法:不堆公式,直接从代码层面把高频注入无感FOC的整条链路拆开,包括HFI注入信号怎么生成、IIR滤波怎么解调、位置估计环路怎么收敛、磁极极性怎么判断,以及每一环在工程上容易踩的坑。适合正在调试无感FOC低速性能的嵌入式工程师、学生,还有想从原理走向落地的入门朋友。
1. HFI高频注入的整体框架与信号链路
1.1 为什么零低速必须换打法
中高速段大家都习惯用滑模观测器或龙伯格观测器,靠反电动势来估转子位置。但有个物理现实绕不过去:反电动势幅值和转速成正比,转速趋近于零时反电动势也趋近于零,信噪比直接崩掉。你就算把观测器增益调到最大,从淹没在噪声里的电流采样里也抠不出有效的位置信息。
解决办法就是主动制造一个“可观测”的激励。高频注入的原理其实很简单:电机转子在结构上存在凸极性,d轴和q轴电感不相等,或者至少磁饱和效应会让等效电感随转子位置周期变化。这时候在估算的d轴上注入一个高频电压,高频电流响应里就会携带转子位置信息。这相当于我们不依赖反电动势这个“被动信号”,而是主动往电机里打一个“雷达波”,再通过解调拿回位置,绕开了零速死区。
1.2 高频注入系统链路是怎么搭起来的
先看一条完整的信号链,心里有个整体地图,后面看代码才不会乱:
- 估算坐标系里向d轴注入高频电压,q轴不注入
- 电流环采样三相电流,经过Clarke和Park变换,得到估算dq坐标下的id和iq
- 对id、iq各做一次带通滤波,只留注入频率附近的分量,滤掉基波和开关噪声
- 用注入角生成的正弦/余弦参考波做乘法解调
- 解调结果再做一次低通滤波,提取包络
- 包络信号经过外差法或者直接atan2得到位置误差相关量
- 送入PLL观测器,输出估计角度和转速
- 启动前或收敛后做一次磁极极性判断,避免N/S极颠倒
这条链路里,每一个模块都不是多余的。高频注入电压生成决定了信噪比,IIR滤波器组决定了信号干净程度,PLL决定了动态响应,极性判断决定了启动方向是否正确。任何一个环节参数不对,整个系统都会出问题。
1.3 注入信号生成代码:先让电机“听到”高频信号
注入信号本身不需要太复杂,最常用的是单频正弦波。代码层面直接按电流环计数器生成旋转角度,然后叠加到输出电压命令上:
#define HFI_FREQ 1000.0f #define HFI_VOLTAGE 8.0f #define PWM_FREQ 16000.0f float hfi_angle = 0.0f; // 每个PWM中断或者每个电流环周期执行 void hfi_signal_update(uint16_t cnt) { hfi_angle = 2.0f * PI * HFI_FREQ * (float)cnt / PWM_FREQ; hfi_u_d = HFI_VOLTAGE * arm_cos_f32(hfi_angle); hfi_u_q = 0.0f; }注入频率的选择是我调试时比较看重的一步。一般取几百赫兹到两三千赫兹,要求是明显高于电流环带宽,但又要低于开关频率的1/4到1/5。比如16kHz开关频率、10kHz电流环周期的时候,注入1kHz比较合适。频率太低会和电流环动态耦合,影响端口电流环响应;频率太高又会被开关噪声淹没,带通滤波器也难做尖锐。
注入幅度的选择同样重要。我在一套48V电机上最开始只给了2V幅值,解调出来的包络完全埋在噪声里。逐步加到8V以后才看到干净的误差信号。这里有个平衡:幅值太低信噪比差,幅值太高会产生 audible noise、增加损耗,还可能励磁过大导致过流。经验做法是从母线电压的5%开始试,然后看解调包络幅值调。如果PMSM表贴式电机凸极率很低,还要考虑是不是需要加偏置磁链或额外磁饱和设计,这个后文再谈。
2. IIR滤波在HFI解调中的设计与实现
2.1 为什么这个场景不用FIR
滤除高频电流里的干扰、提取解调包络,主流选择是IIR,而不是FIR。虽然FIR线性相位好、不存在相位失真,但相同过渡带宽下阶数太高,16kHz采样率下每路电流各做一个上百阶的FIR,计算量直接把DSP或MCU的周期吃掉一大块。
IIR刚好反过来了,二阶或四阶就能达到不错的频率选择性,计算量很小,每个采样周期只做几次乘加。代价是相位是非线性的,尤其滤波器和注入频率有相位偏置。我们会在解调环节做相位对齐,所以这个代价可以接受。
2.2 二阶IIR的核心代码
实际工程里我习惯自己封装一个biquad结构体,不去频繁调DSP库的重型接口,因为代码可读性更好,调式也方便:
typedef struct { float b0, b1, b2; float a1, a2; float z1, z2; } biquad_t; float biquad_process(biquad_t *f, float x) { float y = f->b0 * x + f->z1; f->z1 = f->b1 * x - f->a1 * y + f->z2; f->z2 = f->b2 * x - f->a2 * y; return y; }初始化时要根据目标滤波器类型代入系数。带通我需要提取注入频率附近的电流分量时,使用经典的RBJ公式:
static void biquad_bandpass_init(biquad_t *f, float fc, float fs, float q) { float w = 2.0f * PI * fc / fs; float cw = cosf(w); float sw = sinf(w); float alpha = sw / (2.0f * q); float a0 = 1.0f + alpha; f->b0 = alpha / a0; f->b1 = 0.0f; f->b2 = -alpha / a0; f->a1 = -2.0f * cw / a0; f->a2 = (1.0f - alpha) / a0; }低通滤波器提取包络时用另一个初始化函数,系数是标准二阶低通公式,不再展开。
static void biquad_lowpass_init(biquad_t *f, float fc, float fs, float q) { // 对应RBJ公式,把截止频率、采样率、品质因数填进去 }2.3 滤波器参数怎么算,相位怎么对齐
带通滤波器的中心频率直接设成HFI_FREQ,Q值可以先取0.7到1.2。Q越高选择性越强,但相位滞后也越大、瞬态响应越慢。我习惯先用Q=1,看解调波形的毛刺程度再往下调。如果噪声大,把Q往1.2调;如果动态响应差,把Q往0.7调。
低通滤波器的截止频率选择要结合控制周期。注入1kHz时,解调后出现的位置误差信号带宽一般只需要几十Hz到一两百Hz,所以低通截止频率取50~200Hz比较常见。太高会把2倍注入频率的残差漏进去,太低会把位置动态吃掉。这里我遇到过最典型的现象是:低通截止频率设到100Hz以后,位置响应变慢,高速带载时容易跟丢;调到300Hz又会出现明显的载波纹波,后来折中到150Hz配合速度环带宽才稳定下来。
关于相位对齐,我要特别提醒:IIR带通在注入频率附近一定有相位偏移,而且不同频率、不同Q值下偏移量不同。用示波器或ADC采样对比输入输出后,在解调参考角上补一个固定角度偏移,否则解调出的包络会从同相变成正交,表现就是位置误差信号幅度变小甚至消失。工程上有个土办法,直接用录波功能对比带通滤波前后的波形,数采样点个数来估算延迟,再换算成电角度补偿。
2.4 滤波链路顺序和实测曲线经验
我在驱动板上实际跑的滤波链路顺序是:原始电流 → 带通滤波 → 乘法解调 → 低通滤波。注意不能在解调之后再做带通,那样会把基波干扰混进来。调试时我用DAC直接输出中间变量,把低通前后的包络曲线拉出来看:
- 带通滤波前:信号具慢是电流叠加,看不清包络
- 带通滤波后:能看到明显的1000Hz高频分量,幅值随转子位置变化
- 乘法解调后:出现一个近直流分量加一个2000Hz分量
- 低通滤波后:近直流分量变成平滑的位置误差包络
如果低通出来还是一团抖动的曲线,先查是不是带通Q太低、低通截止频率太高,或者解调参考角和实际相位没对齐。这几个问题按顺序排查,基本能覆盖90%的解调异常。
3. 位置估计:从解调包络到角度与转速
3.1 外差法把二维包络转成一维误差
解调后得到的包络信号通常同时包含d轴和q轴高频电流响应,直接atan2也可以算角度,但工程上更喜欢用外差法,因为输出的是一个有明确物理含义的位置误差项,方便接入PLL。
外差法的代码可以这样理解:
float err_signal = 0.0f; void hfi_polarity_and_pos_update(void) { // env_d和env_q是解调低通滤波器输出 float cos2d = arm_cos_f32(2.0f * theta_est); float sin2d = arm_sin_f32(2.0f * theta_est); err_signal = env_d * cos2d - env_q * sin2d; }当估计角度和真实电角度偏差不大时,这个err_signal的值近似正比于位置误差的二倍角正弦。也就是说,把它当作PLL的输入误差项,负反馈收敛即可。
这里有个容易绕晕的细节:公式里的符号和比例系数跟电机电感模型、注入位置有关,不同文献写法不一样。实际调试时不要死记公式到底该加还是该减,先跑起来看err_signal在给定角度偏置下是正还是负,如果收敛方向反了,把符号取反就行。这是工程里最实用的一招。
3.2 PLL位置观测器代码实现
PLL是整个位置估计环路的闭环部分,我用比例积分形式的观测器,更新公式如下:
void pll_observer_update(float dt) { // err_signal已经由外差法得到 speed_est += KI_PLL * err_signal * dt; theta_est += (speed_est + KP_PLL * err_signal) * dt; }这里KP相当于比例项,把误差折算成角度补偿;KI负责把稳态误差积分消除成转速。带宽设置逻辑:位置环带宽要比速度环高,一般设速度环带宽的2~3倍。调试时先从低带宽开始,观察角度收敛稳定后,再逐步加KP、KI。
需要注意,位置估计出来的角度带有一个固定相位偏置。因为整个HFI链路经过带通滤波、低通滤波、解调和外差,会产生综合滞回,最终theta_est可能和真实转子角度差一个常数。处理方式是在角度送入电流环前加一个角度补偿量,或者直接在标定阶段做离线修正。每套硬件滤波参数改了,这个补偿量都必须重新标一次。
3.3 收敛过程与初值处理
HFI位置观测器能不能快速收敛,启动策略起了决定性作用。我的做法是分段:先让PLL只工作在“吸收误差”状态,屏蔽速度积分输出,只用比例项拉角度;等err_signal进入一个死区范围后,再逐渐放开速度积分。否则零速启动瞬间,速度环、电流环、位置环三个环同时蹿动,一次堵转测试就能把观测器打到崩。
收敛时还有一个判断技巧:把err_signal的绝对值做滑动平均,连续多少个周期低于阈值,再认定位置收敛。不要单周期判断,因为电流采样噪声和开关死区会造成单点毛刺,很容易误判。单位周期判断误判后,后续启动电流方向会直接反冲,轻则抖动,重则过流。
4. 磁极极性判断与启动策略
4.1 为什么必须单独做极性判断
PMSM的位置观测器通常只能收敛到转子磁链方向,而这个方向有180度模糊,也就是N极和S极无法区分。原因在于电角度以2倍关系出现在高频电流响应里,观测器看到的是电周期信号而不是机械的磁极极性。
如果不做极性判断,可能进入这样的状态:估计角度和真实角度相差180度,位置误差信号却显示收敛。这时候给一个正向电流指令,实际输出转矩是反的,堵转时电机直接反转或抖动。对于风机、水泵这类轻负载启动还能靠转起来纠正,但伺服或者带重载启动的场合,反转一步就可能撞机或者报过流。所以磁极极性判断是HFI方案里不可或缺的一步。
4.2 利用磁饱和效应判断N/S
极性判断的核心是磁饱和效应。转子永磁体的N极方向和S极方向对d轴电感的影响不一样。向两者分别注入相同的电流脉冲,正向励磁使磁路更饱和、电感更小,电流上升更快;反向励磁则电流上升更慢。通过比较响应电流大小,就能知道估计方向到底是在真N极上还是反了180度。
具体到代码实现,我在估算d轴方向上先注入一个正向脉冲电压,再注入一个反向脉冲电压,记录两个方向的高频电流幅值或电流上升率。比较之后如果判断极性反了,就直接修正:
float id_pos_pulse = get_daxis_pulse_response(1.0f); float id_neg_pulse = get_daxis_pulse_response(-1.0f); if (id_pos_pulse < id_neg_pulse * 0.9f) { // 正向脉冲电流小说明d轴电感大,原方向对应反极性 theta_est += PI; }这里的阈值0.9不是拍脑袋定的,而是取决于励磁电流大小和电机磁路的饱和程度。脉宽太短、电流幅值太小,饱和差异不明显;脉宽太长又会产生实际转矩导致转子微动。我在一套电机上标定出来的最优脉宽是几个毫秒,电流峰值给到额定电流的10%~20%,这个甜点区间需要实际扫参数。
4.3 极性判断过程如何抗干扰
极性判断最容易翻车的是电流采样毛刺和电机微动干扰。为了防止误判,我在连续多个脉冲周期做平均,并用电流上升率而非峰值电流来判断,因为上升率对采样偏置更不敏感。
另外一个值得注意的点,判断必须在空载或者负载稳定的情况下进行,最好在启动之前的静止状态完成。如果带载过程中做极性判断,负载转矩变化会干扰判断结果。实际工程里我也见过用单次脉冲判断失败的案例,改成三脉冲平均后,重复性明显提升。
除此之外,极性判断之后我习惯加一个软保护逻辑:判断结果生效前,锁定速度环输出,并让电流环使用修正后的角度空跑一小段时间,确认err_signal没有突变。这样做的好处是避免修正瞬间,电流环反Park变换的角度突变导致电流尖峰。
4.4 和整个无感启动流程的配合
完整的无感启动流程在代码层面通常是:初始角度开环强拉或静止预定位 → HFI位置收敛 → 极性判断修正 → 切入速度环和电流环闭环 → 转速上升后切换到反电动势或者滑模观测器。
HFI并不是在所有速度段都一直开着。中高速段反电动势信号质量已经很好,继续开高频注入只会增加损耗和噪音。我当前的实现里,转速超过额定转速的5%~10%后,会经过一个带速度滞回的切换逻辑,慢慢关掉注入电压、切换位置源。切换过程最怕角度阶跃,所以两个观测器的角度输出要通过一个加权过渡,或者让速度环在此期间保持松弛状态,避免转矩突变。
5. 常见问题排查与工程化调参建议
5.1 高频噪声大、解调信号畸变
现象是电流波形已经能看到明显的高频纹波,但解调包络依然毛刺很大,甚至位置估计会周期性跳变。我先检查三个地方:带通滤波Q值是否太低、低通截止频率是否太高、采样点是否和PWM中心对齐。
很多MCU在PWM中心处采样噪声最小,高频注入信号本身会在开关瞬间造成尖峰。如果采样点没有对齐PWM中心,需要在电流环采样前加一个极短延时,或者修改PWM同步配置。这个细节我在不同硬件平台上反复踩过,稍微调整采样时刻,包络信噪比能有质的提升。
5.2 滤波器相位滞后造成估计不准
表现为估计角度在静态时基本稳定,但动态过程中会持续先冲过头再纠正,并且和真实位置成比例偏差。原因基本是外差之后的低通滤波器、带通滤波器以及解调参考角之间的相位不对齐。
排查办法:给电流环故意注入一个固定角度阶跃偏置,看err_signal的变化,如果发现符号或幅度异常,先补偿解调参考角。再通过录波确认带通输出和原始电流的相位差,把补偿角度写进一个常量。每次修改滤波器参数后,这个补偿角都必须重新标定,不能沿用旧值。
5.3 启动时堵转、反转或者反复抖动
这种情况往往不是某一个模块单独的问题,而是启动流程的时序问题。首查极性判断是否真的执行了,再查位置收敛死区判断阈值是否合理。常见坑是死区阈值设得过小,还没等PLL真正稳定就退出收敛状态;或者收敛判断使用瞬时值,被毛刺误导。
另一个坑是速度环在位置未收敛时就投入了积分,导致启动瞬间速度积分饱和,电机先往错误方向猛拽。我现在的做法是:位置收敛和极性判断完成前,速度环只允许输出限制在小范围;等状态机切入运行态后,再逐步放开速度环输出上限。
5.4 关键参数速查和调参顺序
| 参数 | 推荐范围 | 主要作用 | 调参经验 |
|---|---|---|---|
| 注入频率 | 0.5~2kHz | 决定信号频点 | 低于电流环带宽会和电流环打架,太高带通做不干净 |
| 注入电压幅值 | 3%~8%母线电压 | 决定信噪比 | 从小到大慢慢加,以解调包络目视干净为准 |
| 带通Q值 | 0.7~1.2 | 平衡选择性和相位 | Q越高噪声越小但响应越慢 |
| 低通截止频率 | 50~200Hz | 平滑解调包络 | 服从速度环带宽,别为滤噪牺牲动态 |
| PLL带宽 | 速度环带宽2~3倍 | 收敛速度和稳定性 | 先给低带宽验证方向,再往上调 |
| 极性脉冲宽度 | 2~5ms | 判断磁极极性 | 必须实测扫参数,以小电流能分辨为原则 |
调参顺序我给一个比较稳的路线:先把注入频率和幅值固定,用开环方式让电机处于一个已知位置,观察解调包络是否正常;然后让PLL闭环收敛,确认位置误差收敛;再做极性判断的成功率测试;最后才把速度环和电流环整个接通跑启动流程。每一步验证通过再进入下一步,能避免问题堆在一起后完全分不清是谁的锅。
最后分享一点个人经验
我自己在从矩阵式算法迁移到HFI方案时,花时间最多的地方不是高频注入本身,而是滤波器相位对齐和启动状态机。很多代码看起来公式都对,但实际硬件上一跑,相位偏了10度、极性判断延迟了一个周期,整个系统就判若两人。建议调试HFI时手边一定要有能观察中间变量的手段,哪怕只是DAC输出或者串口低速log,也比黑盒调参高效得多。想快速验证极性判断逻辑的话,可以先用手转动电机到一个已知机械位置,用绝对位置编码器做参考基准,把真实角度和估计角度同时打印出来对比,这一步能帮你省下大把排查时间。