1. 从一个反直觉的工程现象说起:为什么Iq不能直接拿来用
如果你做过永磁同步电机的无感控制,大概率在第一次接触高频注入(HFI)时会有这样的困惑:明明电流采样已经拿到了三相电流,经过Clarke/Park变换后也得到了Iq,为什么不能直接把这个Iq丢进锁相环去估算角度,非要再折腾一圈——先带通滤波、再乘一个sinωht、最后又低通滤波?
我当初也是这么想的。第一次看HFI的框图,看到信号链路上串了BPF、乘法器、LPF三个环节,第一反应是"这不是脱裤子放屁吗"。直到自己动手把每一级的波形用示波器和上位机抓出来对比,才真正明白:Iq里混着的东西太多了,直接拿来估角度,等于在一堆噪声里找一根针。
先把结论摆在这里,方便你带着目标往下看:HFI的核心思路是"用高频信号去激励电机,再从响应里把转子位置信息解出来"。而Iq这个量,是基波电流、高频响应电流、PWM开关噪声、采样噪声、逆变器非线性导致的谐波全部叠加在一起的结果。你要的只是其中那一小撮和转子位置相关的高频成分,所以必须经过"选频—搬移—再选频"三步,才能把它干净地提取出来。
这篇文章我会从代码层面把这三步拆开讲透。不是泛泛地说"带通滤波提取高频",而是具体到:BPF的带宽怎么定、中心频率为什么必须等于注入频率、乘sinωht这一步在数学上到底做了什么、LPF的截止频率为什么不能随便设。看完之后你应该能自己推导出每一级的参数,而不是照抄别人的代码。
适合的读者:做过FOC、知道Park变换、但对HFI信号链还停留在"照着框图连线"阶段的工程师。如果你连Clarke/Park都还没搞明白,建议先把基础补上再回来。
2. Iq里到底混了哪些东西:把信号成分一层层剥开
2.1 基波分量:最大的那块"干扰"
电机正常运行时,Iq的主要成分是基波电流对应的转矩分量。在稳态下,这个量基本是直流量(在dq坐标系下),或者变化很慢的低频量。它的幅值可能是几安培到几十安培,而HFI要提取的高频响应电流幅值往往只有几十到几百毫安。信噪比差了两三个数量级,这就是为什么不能直接用的第一个原因。
你可能会想:那我用一个高通滤波器把直流滤掉不就行了?理论上可以,但实际不行。因为基波不是纯直流,它随着负载变化、转速波动会有低频分量,而且逆变器死区、管压降这些非线性因素会在基波上叠加低次谐波(5次、7次等)。高通滤波器的过渡带如果不够陡,这些低频成分会漏进来。
2.2 高频响应电流:我们真正要的东西
当你在d轴(或估计的d轴)注入一个高频电压信号,比如Vh·cos(ωh·t),电机因为凸极效应(IPMSM的Ld≠Lq),会在电流里产生一个包含转子位置信息的高频响应。这个响应的频率就是注入频率ωh,幅值和相位与转子位置误差相关。
关键点:这个有用信号只存在于ωh附近的一个窄带内。它不在直流,也不在开关频率那么高的地方,而是卡在中间——通常注入频率选在500Hz到2kHz之间(对应电频率),具体取决于电机基波频率和PWM频率的折中。
2.3 开关噪声与采样噪声:高频段的"垃圾"
PWM开关频率通常在10kHz到20kHz,开关瞬间产生的电流纹波、采样保持电路的孔径抖动、ADC量化噪声,这些都会在Iq上表现为高频杂散。虽然它们的频率比ωh高,但如果BPF的高频侧衰减不够,照样会混进来。
2.4 逆变器非线性导致的谐波:最阴险的一类
死区时间、开关管导通压降、寄生电容,这些会让实际输出的电压和指令电压之间有偏差。这个偏差不是随机的,它和电流方向相关,会在电流里产生6次、12次等谐波(对应电频率的倍数)。这些谐波的频率可能刚好落在ωh附近,BPF滤不掉,最后就会在估算角度上表现为周期性误差。
把这几类成分画在一张频谱图上,你会看到:低频段是基波和它的谐波,中间某个位置是ωh处的有用信号,高频段是开关噪声。BPF要做的,就是在中间那个位置开一扇窄窗,把有用信号放进来,把两边都挡在外面。
3. BPF这一级:中心频率、带宽与Q值的工程取舍
3.1 为什么中心频率必须精确等于注入频率
这是最容易被忽略的一点。很多人设计BPF时习惯性地把中心频率设成"大概差不多",比如注入1kHz,BPF中心设900Hz或1.1kHz。这样做的问题是:有用信号的幅值和相位都会失真。
BPF在中心频率处的相移是0(对于理想带通),但偏离中心频率后相移会迅速变化。HFI解调对相位非常敏感,因为转子位置信息就藏在响应电流的相位里。如果BPF在ωh处引入了额外的相移,解调出来的角度就会有固定偏差。
我在实测中对比过:中心频率偏差5%,估算角度的稳态误差会增加3到5度电角度。对于需要精确转矩控制的应用,这个误差是不能接受的。
所以代码里BPF的中心频率必须和注入频率严格一致。如果你用的是数字滤波器(比如IIR biquad),系数计算时要把ωh准确代入。
3.2 带宽怎么定:窄了响应慢,宽了滤不干净
BPF的带宽决定了它能"容忍"多大的频率偏差,也决定了它的群延迟。带宽越窄,选频特性越好,但群延迟越大,动态响应越慢。
工程上的经验值:带宽取注入频率的10%到20%。比如注入1kHz,带宽取100到200Hz。这个范围能覆盖转速变化导致的频率偏移(因为ωh是电频率,转速变化时实际注入频率在定子坐标系下会变),同时群延迟还在可接受范围内。
如果你用的是跟踪滤波器(中心频率随转速调整),带宽可以取窄一些,比如5%到10%。但跟踪滤波器实现复杂,需要额外的频率检测环节,一般应用用固定中心频率就够了。
3.3 用biquad实现BPF的代码细节
数字BPF最常用的实现是双二阶(biquad)结构。下面是一个标准的带通biquad系数计算(直接II型转置):
import math def design_bpf(fs, f0, Q): """ fs: 采样频率 f0: 中心频率 Q: 品质因数 = f0 / 带宽 """ w0 = 2 * math.pi * f0 / fs alpha = math.sin(w0) / (2 * Q) cos_w0 = math.cos(w0) b0 = alpha b1 = 0 b2 = -alpha a0 = 1 + alpha a1 = -2 * cos_w0 a2 = 1 - alpha # 归一化 b0 /= a0 b1 /= a0 b2 /= a0 a1 /= a0 a2 /= a0 return b0, b1, b2, a1, a2这里Q = f0 / 带宽。如果注入1kHz、带宽200Hz,Q = 5。注意alpha的计算里用了sin(w0),当f0远小于fs时,alpha近似等于w0/(2Q),但精确计算还是要用sin。
实际跑的时候,biquad的差分方程是:
def bpf_filter(x, state, coeffs): b0, b1, b2, a1, a2 = coeffs w = x - a1 * state['w1'] - a2 * state['w2'] y = b0 * w + b1 * state['w1'] + b2 * state['w2'] state['w2'] = state['w1'] state['w1'] = w return y注意:这里用的是转置直接II型,数值稳定性比直接I型好。在定点DSP上实现时,要注意系数的Q格式定标,避免溢出。
3.4 一个容易踩的坑:滤波器初始状态
BPF刚上电时,内部状态是0,输出会有一个暂态过程。如果这时候就拿去解调,估算角度会乱跳。解决办法是:上电后先让滤波器空跑几十个周期,等状态稳定了再启用解调。或者用前几个周期的数据去初始化滤波器状态,让它从稳态开始。
我在代码里通常加一个计数器,BPF运行超过N个采样周期后才把解调结果送去锁相环。N取3到5倍群延迟对应的周期数就够了。
4. 乘sinωht这一步:同步解调的本质是频谱搬移
4.1 从数学上看,乘法器做了什么
假设BPF输出的信号是:
i_h = A·cos(ωh·t + φ)
其中A是幅值,φ是包含转子位置信息的相位。现在把它乘以sin(ωh·t):
i_h · sin(ωh·t) = A·cos(ωh·t + φ)·sin(ωh·t)
用积化和差公式展开:
= (A/2)·[sin(2ωh·t + φ) + sin(-φ)] = (A/2)·sin(2ωh·t + φ) - (A/2)·sin(φ)
看到没有?乘积里出现了两个成分:一个是两倍频(2ωh)的高频项,一个是与频率无关的直流项。这个直流项就是-(A/2)·sin(φ),它正比于位置误差的正弦值。
这就是同步解调的核心:用乘法把"载波"上的相位信息搬移到直流。原来藏在ωh处的相位φ,现在变成了一个直流量。接下来只要用低通滤波器把2ωh的高频项滤掉,剩下的直流就是我们要的位置误差信号。
4.2 为什么乘的是sin而不是cos
这取决于注入信号的类型和响应信号的相位关系。如果注入的是cos(ωh·t),响应也是cos(ωh·t + φ),那么乘sin(ωh·t)得到的是sin(φ)项;乘cos(ωh·t)得到的是cos(φ)项。
用sin解调得到的是sin(φ),在φ接近0时,sin(φ)≈φ,线性度好,适合做锁相环的误差输入。用cos解调得到cos(φ),在φ=0附近是平坦的,对误差不敏感,不适合做鉴相。
所以解调用的参考信号必须和注入信号正交。注入cos,解调就用sin;注入sin,解调就用cos。这一点在代码里如果搞反了,锁相环根本锁不住。
4.3 数字实现:乘法器就是一行代码
在DSP里,同步解调就是一次乘法:
// theta_h 是注入信号的相位累加器 // i_bpf 是BPF的输出 float demod = i_bpf * sinf(theta_h);但这里有个细节:sinf的精度和速度。在定点DSP上,sinf可能很慢,通常用查表法或者CORDIC。查表法要注意表长和插值方式,表太短会有量化误差,表现为估算角度的周期性抖动。
我一般用256点的正弦表加线性插值,精度足够,速度也快。如果DSP有硬件三角函数单元,直接用硬件指令。
4.4 相位对齐:一个隐蔽的误差源
注入信号的相位累加器和解调用的相位累加器必须是同一个,或者至少有确定的相位关系。如果注入用了一个累加器,解调用另一个独立的累加器,两者之间的初始相位差会导致解调结果里混入一个固定的直流偏置。
这个偏置在锁相环里会被积分器慢慢吃掉,但会拖慢收敛速度,严重时导致锁相环在错误的角度上稳定下来。最稳妥的做法是:注入和解调共用同一个相位累加器,解调时直接取它的sin值。
5. LPF这一级:截止频率决定了整个环路的带宽
5.1 为什么截止频率要远小于ωh
乘法器的输出里有直流项和2ωh项。LPF的任务是把2ωh项衰减掉,同时让直流项无失真通过。所以截止频率必须远小于2ωh,一般取ωh的1/5到1/10。
比如注入1kHz,2ωh对应2kHz,LPF截止取100到200Hz。这样2kHz处的衰减至少有20到30dB,残余的纹波对锁相环影响很小。
但截止频率也不能太低。太低的话,LPF的群延迟大,整个解调环路的带宽就窄,转速变化时角度跟踪跟不上。LPF的截止频率实际上决定了HFI环路的带宽。
5.2 环路带宽与动态响应的权衡
HFI环路的带宽需求取决于应用场景:
- 如果是低速大扭矩启动,转速变化慢,带宽可以低一些,100Hz左右够用。
- 如果是需要快速响应的伺服应用,带宽要几百Hz甚至更高。
但带宽越高,LPF截止频率越高,2ωh的残余纹波就越大,估算角度的抖动也越大。这是一个硬性的权衡,没有两全其美的方案。
我的经验是:先按动态需求定环路带宽,再反推LPF截止频率,最后检查2ωh处的衰减是否足够。如果衰减不够,要么提高注入频率(把2ωh推得更远),要么降低环路带宽要求。
5.3 用一阶还是二阶LPF
一阶LPF的衰减斜率是-20dB/dec,二阶是-40dB/dec。如果2ωh离截止频率只有10倍频程,一阶LPF在2ωh处的衰减是20dB,二阶是40dB。
对于大多数应用,一阶LPF就够了,因为注入频率和截止频率的比值通常大于10,20dB的衰减已经能把纹波压到可接受范围。二阶LPF会引入更多的相位滞后,对环路稳定性不利。
但如果你的注入频率选得比较低(比如500Hz),2ωh只有1kHz,和截止频率的比值可能只有5,这时候一阶LPF的衰减不够,需要考虑二阶。
5.4 代码实现与参数计算
一阶LPF的差分方程:
def lpf(x, state, alpha): """ alpha = dt / (RC + dt) dt = 1/fs RC = 1/(2*pi*fc) """ state['y'] = state['y'] + alpha * (x - state['y']) return state['y']alpha的计算:
def calc_lpf_alpha(fs, fc): dt = 1.0 / fs RC = 1.0 / (2 * math.pi * fc) return dt / (RC + dt)注意这个alpha是"平滑系数",不是滤波器系数。alpha越小,截止频率越低,平滑效果越强。
提示:在定点实现时,alpha通常用Q15格式表示,state['y']也要用足够的位宽,避免累积误差。
6. 把三级串起来:完整信号链的代码走读
6.1 从Iq采样到角度输出的完整流程
把前面几级串起来,一个完整的HFI解调周期是这样的:
- 采样三相电流,Clarke/Park变换得到Iq。
- Iq送入BPF,滤出ωh附近的高频成分。
- BPF输出乘以sin(ωh·t),得到含直流和2ωh的混合信号。
- 送入LPF,滤掉2ωh,得到与位置误差相关的直流量。
- 这个直流量作为锁相环的误差输入,经过PI调节器得到估算转速,积分得到估算角度。
代码框架:
typedef struct { float bpf_state[2]; float bpf_coeffs[5]; float lpf_state; float lpf_alpha; float pll_integral; float pll_kp; float pll_ki; float theta_est; float omega_est; float theta_h; float omega_h; } HFI_State; float hfi_update(HFI_State *s, float iq, float dt) { // 1. BPF float i_bpf = bpf_filter(iq, s->bpf_state, s->bpf_coeffs); // 2. 同步解调 float demod = i_bpf * sinf(s->theta_h); // 3. LPF s->lpf_state += s->lpf_alpha * (demod - s->lpf_state); float error = s->lpf_state; // 4. PLL s->pll_integral += s->pll_ki * error * dt; float omega_correction = s->pll_kp * error + s->pll_integral; s->omega_est = omega_correction; s->theta_est += s->omega_est * dt; // 5. 更新注入相位 s->theta_h += s->omega_h * dt; if (s->theta_h > 2 * M_PI) s->theta_h -= 2 * M_PI; return s->theta_est; }6.2 每一级引入的延迟与相位补偿
信号链上的每一级都会引入延迟:BPF有群延迟,LPF有相位滞后,PLL的PI调节器也有响应时间。这些延迟累加起来,会导致估算角度滞后于真实角度。
在低速时,这个滞后对应的角度误差可能不大;但在高速时,同样的时间延迟对应的角度就很大了。所以HFI通常只用在低速段(比如额定转速的10%以下),高速段切换到反电动势观测器。
如果一定要在较宽的速度范围内用HFI,需要做相位补偿。补偿量等于总延迟乘以估算转速。总延迟可以通过测量或仿真得到,一般在代码里加一个补偿项:
float delay_compensation = total_delay * s->omega_est; s->theta_est += delay_compensation;6.3 实测波形的对比:有BPF和没BPF的差别
我用上位机抓过Iq的原始波形和BPF之后的波形。原始Iq上,基波占了绝大部分,高频响应几乎被淹没。BPF之后,基波被压下去40dB以上,高频响应清晰可见。
再做解调,LPF之前的波形是"直流+2ωh纹波",LPF之后纹波基本消失,剩下干净的直流。这个直流送入PLL,估算角度能稳定跟踪真实角度,稳态误差在2度电角度以内。
如果去掉BPF,直接把Iq送去解调,LPF输出里会混入基波和低频谐波,PLL的误差输入不干净,估算角度会有明显的低频抖动,严重时根本锁不住。
7. 参数整定与常见问题排查
7.1 注入频率怎么选:三个约束条件
注入频率的选择要同时满足三个条件:
- 远高于基波频率:保证BPF能把基波和有用信号分开。一般要求ωh > 5倍基波频率。
- 远低于PWM开关频率:保证开关噪声不会混入有用信号。一般要求ωh < 1/5开关频率。
- 在BPF和LPF的过渡带之外:保证滤波器有足够的衰减。
这三个条件往往不能同时满足。比如基波频率50Hz、开关频率10kHz,那么ωh要在250Hz到2kHz之间。如果选500Hz,2ωh=1kHz,LPF截止取50到100Hz,衰减够;如果选1kHz,2ωh=2kHz,LPF截止取100到200Hz,也够。
我的经验是:优先选1kHz左右,这个频率在大多数应用里都能满足条件,而且BPF和LPF的设计都不算困难。
7.2 估算角度抖动大:从哪几级找原因
角度抖动是最常见的问题。排查顺序:
- 先看LPF输出:如果LPF输出本身就有纹波,说明LPF截止频率太高,或者BPF没滤干净。用示波器看LPF输出的峰峰值,正常应该在误差信号的1%以内。
- 再看BPF输出:如果BPF输出里能看到明显的基波成分,说明BPF带宽太宽或中心频率偏了。用FFT看BPF输出的频谱,ωh处的峰值应该比基波处高20dB以上。
- 最后看解调参考信号:如果注入和解调的相位没对齐,解调输出里会有直流偏置,表现为角度有固定偏差。检查两个相位累加器是否同步。
7.3 低速时锁不住:可能是BPF群延迟太大
低速时,有用信号的幅值本来就小,如果BPF群延迟太大,PLL的响应跟不上,就会锁不住。解决办法:
- 降低BPF的Q值,增大带宽,减小群延迟。
- 提高注入电压幅值,增大有用信号的幅值。
- 降低PLL的带宽,让它对噪声更不敏感。
这三个措施要配合使用。单独提高注入电压会增加损耗和噪声,单独降低PLL带宽会拖慢响应。
7.4 一个实用的调试技巧:注入已知角度
调试HFI时,最有效的方法是让电机静止,手动设定一个已知的转子角度,然后看估算角度能不能收敛到这个值。如果能收敛但有小偏差,说明有固定相位误差;如果根本不收敛,说明信号链有问题。
我通常会在代码里加一个调试模式,允许手动设定转子角度,然后用上位机记录估算角度的收敛过程。这个模式下能快速定位是BPF的问题、解调的问题还是PLL的问题。
8. 几个容易被忽略的实现细节
8.1 定点运算的溢出与精度
如果DSP是定点的,BPF的中间状态可能会溢出。因为BPF在中心频率处有增益(对于单位增益的biquad,中心频率处增益为1,但内部状态w可能比输入大),如果输入信号幅值接近满量程,内部状态就会溢出。
解决办法:在BPF前面加一个衰减,后面再加一个对应的增益。或者用更高位宽的累加器。我一般用32位累加器,输入用Q15,中间状态用Q31,输出再截断回Q15。
8.2 滤波器系数的在线更新
如果注入频率需要在线调整(比如根据转速改变注入频率),BPF的系数也要在线更新。但系数更新时,滤波器的内部状态不能突变,否则会产生冲击。
正确的做法是:系数更新时保持内部状态不变,只更新系数。这样滤波器的输出会有短暂的暂态,但不会产生大的冲击。如果暂态太大,可以在更新系数后让滤波器空跑几个周期再启用输出。
8.3 多采样率处理
如果PWM频率很高(比如20kHz),而HFI只需要1kHz的注入频率,可以在高采样率下做BPF,然后降采样再做解调和LPF。这样能减少计算量。
但降采样会引入混叠,降采样之前必须做抗混叠滤波。如果BPF已经滤掉了高频成分,它本身就可以充当抗混叠滤波器。降采样因子取4或8,解调和LPF在低采样率下做,计算量能降一个数量级。
8.4 与其它观测器的切换
HFI通常只用在低速段,高速段要切换到反电动势观测器。切换时要注意:
- 角度要平滑过渡:不能直接切换,否则会有转矩冲击。通常用一个渐变因子,在几十毫秒内从HFI角度过渡到反电动势角度。
- 转速也要过渡:两个观测器估算的转速可能不同,切换时要做平滑。
- 切换点要选在两者都可靠的区域:一般在额定转速的5%到10%之间,这个区域HFI还有足够的信噪比,反电动势也已经有足够的幅值。
我在实际项目里踩过的最大的坑就是切换没做好,电机在切换点附近来回振荡。后来加了一个滞环,切换点以上用反电动势,切换点以下用HFI,中间有一个过渡带,问题就解决了。
9. 从代码反推设计:为什么是这个顺序
回到标题的问题:为什么Iq要先带通、乘sinωht、再低通?
现在你应该能自己回答了:
- 先带通:因为Iq里混着基波、低频谐波、开关噪声,有用信号只占很小一部分。BPF的作用是选频,把有用信号从噪声里分离出来。
- 乘sinωht:因为有用信号是载在ωh上的,直接低通滤不掉。乘法器的作用是频谱搬移,把ωh处的相位信息搬到直流。
- 再低通:因为乘法输出里有2ωh的高频项,必须滤掉才能得到干净的直流误差信号。
这个顺序不能变。如果先低通再带通,低通会把有用信号也滤掉;如果先乘sin再带通,乘法产生的2ωh项和原来的ωh项混在一起,带通没法分开。
每一级的存在都有明确的数学理由,不是拍脑袋加的。理解了这一点,你就能自己推导参数,而不是照抄别人的代码。
我在调试HFI时最大的体会是:不要把它当成一个黑盒。每一级的输入输出都用上位机抓出来看,对照数学推导验证,很快就能建立起直觉。一旦有了直觉,参数整定和问题排查都会变得简单。
最后分享一个我常用的调试顺序:先单独验证BPF,注入一个已知频率的正弦信号,看BPF输出是不是只剩这个频率;再单独验证解调,用信号发生器给一个已知相位的信号,看解调输出是不是对应的直流;最后把整个环路闭合,看角度收敛。分步验证比一次性调整个环路要高效得多。