尧图精选

高频注入无感FOC实战:从零速启动到观测器切换代码解析

🕒 发布时间:2026/10/1 9:05:43 📁 来源:尧图网络
前阵子有个做电机驱动的朋友问我低速无感FOC是不是都在用高频注入我说会用的说容易真正从代码上看明白的没几个。高频注入HFI看起来不过是在d轴电压上叠一个正弦、再把电流过几个滤波器可一旦想让它从零速平稳转起来IIR滤波、位置估计、磁极极性判断全都牵连在一起任何一个环节不对电机就会原地抖给你看。这篇文章我打算用实际调过的C代码整理一条完整链路权当一次项目复盘。这篇内容会有一定门槛建议至少跑过基础FOC电流环再往下看。如果你接触过STM32上实现FOC、看过PWM波形和ADC触发那基本能无缝对接如果只是刚接触无感控制也可以先把整体流程看明白后面代码部分再慢慢啃。1. 高频注入无感FOC的整体思路拆解1.1 为什么低速不能只靠反电动势无感FOC最常见的方案就是滑膜观测器、龙贝格观测器这一类基于反电动势的估算方法。反电动势的幅值和电角速度成正比电机在低速甚至零速时反电动势只剩下几十毫伏甚至更低早就淹没在PWM开关噪声、死区影响和ADC量化误差里了。这时候观测器输出的角度会带着很大的畸变电流环一闭环就相当于对着一个错误角度干活轻则电流波形难看重则直接失步。所以无感FOC在低速段必须换思路。高频注入HFI不是等反电动势出来再估算而是主动在某个方向上施加一个高频电压激励通过测量电流对电感的响应来反推转子位置。转子位置信息本质上被“编码”在电感上高频信号只是把这段编码读出来的载波。这也是为什么HFI能覆盖到零速的原因——你一开机就注入位置信息马上就有了不需要电机先转起来。1.2 凸极效应与高频注入的基本盘很多人一提到凸极就以为是内置式PMSMIPM表贴式PMSMSPM没有凸极性所以不能用HFI。这是最常见的误解。表贴式电机在结构上确实是隐极但磁路在饱和之后会出现交叉饱和凸极效应。简单理解当转子磁极方向和注入磁场方向一致时磁路更容易饱和等效电感变小方向垂直时磁路相对不饱和电感变大。这个电感差异虽然只有百分之几但足以让高频电流的响应带上位置信息。高频注入的具体形式有旋转电压注入和脉振电压注入两类。旋转注入通常注射在静止坐标系上适合内置式电机脉振注入则在估算d轴方向施加高频电压对表贴式电机更常用因为它可以利用饱和凸极而且不需要知道准确的初始位置。下面我的代码示例就是用脉振注入因为这套方案在低速启动场景下最普适调试起来也相对直观。1.3 从零速到高速的完整无感流程HFI不是无感FOC的全部它只管低速和零速这一亩三分地。实际项目里完整的启动流程通常是这样的开机后先做初始位置检测用HFI或者定频脉冲估算一次转子角度再做磁极极性判断确认N极方向然后切到HFI闭环让电流环用估算角度跑起来带着电机从零低速加速当转速升高到反电动势信噪比足够高的阈值后再切到滑膜或龙贝格观测器。整个过程就是一个低速用高频注入观察、中高速用反电动势观察的接力赛。所以你现在知道为什么标题里把“位置估计”和“磁极极性判断”并列了。前者解决角度是多少后者解决角度是加0°还是180°两个问题缺一个电机转不起来。2. 从代码看高频信号注入与IIR滤波的实现2.1 注入高频电压信号藏在电流环输出里的正弦脉振高频注入的信号很简单在估算d轴的电压指令上叠加一个固定频率、固定幅值的高频余弦波。大部分代码会放在电流控制器输出之后、逆Park变换之前因为注入信号跟随估算角度旋转省去额外坐标变换。// HFI信号生成 float hfi_theta 0.0f; // 高频信号相位 float hfi_freq_rad 2.0f * PI * HFI_FREQ_Hz; float hfi_voltage_V 0.08f * VBUS_V; // 幅值取8%母线电压具体后面讲 // 在电流环每个PWM周期调用一次 void HFI_InjectDaxis(float* vd_ref, float* vq_ref, float dt) { hfi_theta hfi_freq_rad * dt; if (hfi_theta 2.0f * PI) hfi_theta - 2.0f * PI; *vd_ref hfi_voltage_V * cosf(hfi_theta); }这里有几个关键细节。第一注入的余弦频率要远高于基波电频率但又要远低于PWM开关频率。一般采样率10kHz时注入频率放在1kHz左右比较合适采样率20kHz时可以用2kHz。第二注入幅值不要一上来就拉满我习惯从5%母线电压起步看到高频电流响应后再慢慢加加到纹波电流在额定电流5%以内为止。第三这个函数必须在电流环输出后、最终电压限幅前调用否则注入信号会被限幅逻辑削平。有些方案会在q轴也加一个正交信号目的是避免交叉耦合影响。实际项目里表贴式电机用单d轴脉振注入就够q轴注入反而会增大转矩波动得不偿失。2.2 坐标变换从三相电流里取出i_d和i_q高频注入之后电流环采到的相电流里会有基波、PWM谐波和高频响应电流三层信息。我们需要先把三相电流变到估算dq坐标方便后面分离高频分量。typedef struct { float i_alpha; float i_beta; float i_d; float i_q; } CurrentFrame; // Clarke变换 void ClarkeTransform(float ia, float ib, float ic, CurrentFrame* cur) { cur-i_alpha ia; cur-i_beta (ia 2.0f * ib) * 0.57735027f; // 1/sqrt(3) } // Park变换theta为当前估算电角度 void ParkTransform(CurrentFrame* cur, float theta) { float sin_t sinf(theta); float cos_t cosf(theta); float id cur-i_alpha * cos_t cur-i_beta * sin_t; float iq -cur-i_alpha * sin_t cur-i_beta * cos_t; cur-i_d id; cur-i_q iq; }这里有个容易踩的坑坐标变换用的角度必须是估算角度而不是实际角度实际角度咱们正在估呢。所以Park变换要和HFI注入共用同一个theta_est不能自己另搞一个角度。有些新手先把电流变到静止坐标系再过滤波器最后再用外差法解调也可以但计算量稍微大一点。2.3 IIR带通滤波器为什么是它系数怎么来从变换后的i_d、i_q里提取高频分量典型做法是带通滤波器。用FIR带宽直群延迟线性但阶数动不动就是几十阶单片机跑起来很肉。工程上几乎都用二阶IIR带通两个SCL存储跑一次只有五次乘加运算算力开销基本可以忽略。IIR带通的标准差分方程是y[n] b0*x[n] b1*x[n-1] b2*x[n-2] - a1*y[n-1] - a2*y[n-2]具体系数可以通过双线性变换生成。我在调试中常用一个参数初值中心频率1kHz、采样率10kHz、品质因数Q取0.75~1.2然后系数固定死放常量表。下面是一个可以直接用的二阶带通滤波器实现typedef struct { float b0, b1, b2; float a1, a2; float z1, z2; } Biquad; float Biquad_Process(Biquad* 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; }为了防止初始化瞬间产生冲击滤波器系数先清零z1、z2也要清零。IIR滤波器存在相位延迟而且中心频率附近的延迟尤其大后面解调时要做角度补偿这个我会在第4章详细说。为什么不用陷波器因为我们要保留高频信号本身不能把注入频率“挖”掉。如果只是想滤掉高频PWM噪声用陷波器可以但在HFI链路里高频信号是你要的信息所以要用带通把1kHz周围搂出来同时把基波和低频甩开。有些方案先用高通滤波丢掉直流量再做同步解调也可以但带通信噪比更好。2.4 解调出位置误差信号提取到d/q轴的高频电流分量之后还需要做解调才能变成位置误差信号。解调思路是高频电流i_qh本身的包络携带了角度误差信息但它的频率是高频信号频率直接用积分会得到零均值所以要先乘上一个同频参考信号再用低通滤波器把高频载波滤掉。// 全局变量 static Biquad g_bpf_id; static Biquad g_bpf_iq; static Biquad g_lpf_eps; // 每周期调用一次theta_hfi 与注入正弦同相 float HFI_ExtractError(float id_hfi_in, float iq_hfi_in) { float id_h Biquad_Process(g_bpf_id, id_hfi_in); float iq_h Biquad_Process(g_bpf_iq, iq_hfi_in); // 同步解调乘高频余弦再用低通取直流分量 float demod iq_h * 2.0f * cosf(hfi_theta); float epsilon Biquad_Process(g_lpf_eps, demod); return epsilon; }系数2.0是解调增益用来补偿相乘之后产生的0.5倍因子。这个值不是必须的但把信号尺度放大到和真实角度误差相似的量级后面调PLL带宽会舒服很多。低通滤波器带宽我一般取50~200Hz太小会拖慢响应太大会残留高频纹波导致估算角度抖动。3. 位置估计与磁极极性判断的完整代码逻辑3.1 外差法把高频dq电流变成角度差脉振注入法的核心结论是当估算角度和实际角度之间存在误差Δθ时q轴高频电流幅值会近似正比于sin(2Δθ)。所以解调出的ε就是一个比例系数乘以sin(2Δθ)。在小角度时sin(2Δθ)≈2Δθ这就是一个天然的角度误差检测器。如果你把动态坐标系推导完整会看到同时存在一个与位置无关的偏置项来自d、q轴电感差异的不对称。实际工程里这个偏置会造成角度估计带固定偏移所以很多人会在解调之后加一个自适应补偿项或者直接在做极性判断之后进行角度偏移标定。我第一次调的时候没管这个偏置结果锁出来的角度差了大概十几度电角度电流倒是能转但效率奇低。后来我在调试界面里同时看HFI输出角度和手动顺转到角度记下偏差在观测器输出端减去这个常数偏移问题就解决了。如果需要更精确可以再加上一个正交解调通道float demod_sin iq_h * 2.0f * sinf(hfi_theta); float demod_cos iq_h * 2.0f * cosf(hfi_theta); // demod_sin 经过LPF得到幅值demod_cos经过LPF得到零偏 // 用幅值归一化后求atan2(demod_cos, demod_sin)得到的小量就是2Δθ相位偏移不过现场调试中大多数工程师不会真做完整atan2而是直接把同步解调后的信号当作误差值扔给PLL。速度角度来自PLL积分稳态误差很小没必要做那么精细。3.2 PLL观测器让角度自己转起来位置误差信号ε进入锁相环PLLPLL输出电角速度和电角度。PLL的好处是天然对噪声有积分平滑作用而且不会像直接积分那样漂移。typedef struct { float kp; float ki; float integral; float theta_est; float speed_est; float max_speed; } PllObserver; void Pll_Update(PllObserver* pll, float angle_error, float dt) { pll-integral pll-ki * angle_error * dt; float speed pll-kp * angle_error pll-integral; if (speed pll-max_speed) speed pll-max_speed; if (speed -pll-max_speed) speed -pll-max_speed; pll-theta_est speed * dt; pll-speed_est speed; // 角度归一化到 (-pi, pi] if (pll-theta_est PI) pll-theta_est - 2.0f * PI; if (pll-theta_est -PI) pll-theta_est 2.0f * PI; }PLL带宽选择是个平衡。带宽太窄对负载突变的响应跟不上启动时会听见电机闷哼一下才动带宽太宽会把解调出来的高频纹波直接当速度用转速估计上全是毛刺。我通常先把PLL的转折频率设在100Hz左右然后观察稳态角度波形再慢慢往上推。结合传递函数估算kp 2*带宽ki 带宽²如果带宽100Hz对应角频率约628rad/s那么kp≈1256ki≈394000左右。这些值不绝对但量级是对的。3.3 磁极极性判断差180°时怎么办脉振注入的位置观测器天然存在180°模糊。因为sin(2Δθ)在Δθ0和Δθπ时都等于0PLL可能锁在真实角度也可能锁在真实角度加180°。如果带着错180°的角度直接闭环电机要么反转要么卡死不转。所以极性判断是开工前必须做的一步。工程上最常用的是电压脉冲法。思路是转子磁极饱和程度和磁链方向有关N极方向上磁场叠加互感更强等效电感更小电流上升更快反方向则等效电感更大电流上升慢。所以我们在估算角度方向上施加正、负两个相同的短电压脉冲比较两次电流变化斜率。变化快的那次对应的方向就是N极方向。#define HFI_POL_PULSES 6 bool HFI_PolarityCheck(float theta_est, float* corrected_theta) { float slope_pos 0.0f; float slope_neg 0.0f; float v_pulse 0.15f * VBUS_V; // 脉冲电压幅值 float t_on 0.0005f; // 脉冲宽度 500us for (int i 0; i HFI_POL_PULSES; i) { // 先施加正方向脉冲实测电流斜率并累加 slope_pos ApplyVoltagePulse(theta_est, v_pulse, t_on); // 再施加负方向脉冲实测电流斜率 slope_neg ApplyVoltagePulse(theta_est, -v_pulse, t_on); // 两次脉冲之间留一段零电压时间让电流衰减回零 WaitCurrentDecay(); } slope_pos / HFI_POL_PULSES; slope_neg / HFI_POL_PULSES; if (slope_pos slope_neg * 1.1f) { *corrected_theta theta_est; return true; } else if (slope_neg slope_pos * 1.1f) { *corrected_theta theta_est PI; // 翻180度 return true; } return false; }注意脉冲宽度不能太长否则电机可能被推动也不能太短否则ADC采不到足够的斜率样本。5%额定电压、500微秒宽度在大多数小功率PMSM上是个可靠的起点。极性判断不成功时不能盲目闭环要重新注入或者报错。我碰到过一个情况负载锁在电机轴上正负脉冲电流都差不多判断阈值又严结果一直报错。后来我把判断次数从3次提到6次稍微放宽10%判据才稳定。但也别放宽太多否则真的会反转。3.4 HFI状态机从开机到正常运行的顺序HFI不是随便在电流环里加个函数就行它需要按顺序推进。我常用的状态机分四段IDLE - START_ESTIMATION - HFI_RUN - OBSERVER_SWITCHIDLE等待启动命令滤波器复位。START_ESTIMATION注入高频信号约几十到几百毫秒同时做带通和解调得到初始估算角度。POLARITY_CHECK执行脉冲极性判断修正初始角度。HFI_RUN角度修正后直接进入HFI闭环启动电机从零加速。OBSERVER_SWITCH运行到设定转速以上且反电动势观测器角度和HFI角度误差小于阈值切换到滑膜观测器。typedef enum { HFI_IDLE, HFI_START_EST, HFI_POLARITY, HFI_RUN, HFI_SWITCH, } HfiState; void HFI_StateMachine(HfiState* state, float* theta, float* speed, float rpm, float dt) { switch (*state) { case HFI_IDLE: if (start_cmd) { HfiResetFilters(); *state HFI_START_EST; } break; case HFI_START_EST: HFI_InjectDaxis(vd_ref, vq_ref, dt); HFI_RunPll(); if (est_time 200e-3f) { HFI_PolarityCheck(*theta, theta); *state HFI_RUN; } break; case HFI_RUN: HFI_InjectDaxis(vd_ref, vq_ref, dt); HFI_RunPll(); if (rpm switch_speed) *state HFI_SWITCH; break; case HFI_SWITCH: // 交给反电动势观测器 break; } }这个状态机的好处是每个阶段的进入、退出条件非常清楚调试时加断点、看日志都很方便。很多量产项目里还会在START_EST阶段同时估计相电阻用高频电流幅值推算初始电感进一步校准极性判断阈值。4. 高频注入参数怎么调计算过程与调试实录4.1 四个关键频率的配合关系HFI整套系统里有四个频率需要布置PWM采样频率、高频注入频率、带通中心频率、低通截止频率。它们必须拉开梯度否则互相打架。我以10kHz采样率为例做过一组常用参数配置频率量数值为什么这么选PWM/采样频率10kHz单片机FOC控制基频高频注入频率1kHz采样率的1/10既能分离基波又不会过采样带通中心频率1kHz和注入频率一致低通截止频率100Hz只保留角度误差直流包络如果把注入频率提高到2kHz采样率还是10kHz不是不行但ADC需要保证在注入高频一个周期内采至少5到10个点解调分辨率才够。如果PWM和ADC同步10kHz采样率下2kHz也够但留给电流环执行的时间就少了容易在中断里超时。所以我个人保守一点先上1kHz。注入频率和基波频率之间最好也保持至少5倍以上。如果电机额定转速很高基波频率上千赫兹那HFI频段设计就要重新评估可能得提高PWM频率。4.2 一个二阶IIR带通系数的完整计算很多代码直接塞一堆系数让人仿真看得一头雾水。这里演示一次完整的计算过程。假设采样率fs10000Hz中心频率f01000HzQ1。先算归一化角频率w0 2 * PI * f0 / fs 2 * PI * 0.1 0.62832 rad alpha sin(w0) / (2Q) sin(0.62832) / 2 0.58779 / 2 0.29389 cos_w0 cos(0.62832) 0.80902然后按RBJ双二阶带通公式b0 alpha 0.29389 b1 0 b2 -alpha -0.29389 a0 1 alpha 1.29389 a1 -2 * cos_w0 -1.61803 a2 1 - alpha 0.70611代码里实际使用的是归一化后的系数要用a0除以各项b0 b0 / a0 0.22719 b1 0 b2 -0.22719 a1 a1 / a0 -1.25050 a2 a2 / a0 0.54574把这五个数填进2.3节的Biquad结构体里就跑起来了。如果你用Q0.75带宽会稍微宽一点阶跃响应更灵敏Q1.2带宽窄选择性好但相位延迟更大。我通常先Q1再根据启动响应微调。4.3 调试波形的三个观察点调HFI千万别上来就调参数先把波形抓对。我习惯同时观察三个点第一i_q高频带通滤波后的波形。正常情况应该是比较干净的正弦包络频率和注入频率一致。如果这里已经乱七八糟后面解调根本无从谈起。第二解调后的ε信号。它应该是一条随转子位置缓慢变化的曲线而不是等幅高频正弦。如果在静止状态ε应该近似为0实际有偏置用手转动转子ε会跟着角度变化。第三PLL输出的估算角度用手慢慢转动电机角度应该平滑跟随没有突跳。如果跳了180°极性判断还没做或者做反了。波形不对时优先怀疑坐标系问题。我见过一个案例Park变换用的theta是编码器反馈不是估算角度HFI基线就一直在抖电流环还发疯。这类问题在代码评审时盯着算两分钟就能发现但现场调的时候特别容易绕进去。4.4 常见问题排查速查表我把调HFI过程中最常遇到的问题整理成一张表都是现场踩过的现象可能原因排查/解决启动后电机只震动不转位置误差信号过小或极性错180°调大注入幅值确认极性判断结果估算角度有固定偏移滤波器相位延迟、dq轴耦合偏置标定角度补偿或查解调相位HFI频段听到尖锐噪音注入幅值偏大、频率接近谐振降低幅值或微调频率启动加速到某个转速就失步高频注入带宽不够基波进入带通提高注入频率或提前切滑模极性判断偶尔失败负载突变、脉冲次数不足、阈值过严增加脉冲平均次数扩宽判据这些坑在仿真里看不出来因为仿真模型用的是线性电感没有饱和、没有死区噪声也理想。真机调试才是HFI最残酷的考场。5. 从HFI到反电动势切换几个容易忽略的工程细节5.1 切换前的角度同步和速度平滑HFI在低速段好用但在中高速段要继续用它就显得笨重。所以完整代码里还要有观测器切换逻辑。切换最忌讳生切也就是把HFI角度瞬间替换成反电动势观测器角度。两个角度源从不同机理估出来总会存在几度偏差生切会给电流环来一记“急刹车”轻则转速波动重则过流保护。我通常在HFI_RUN阶段同时运行滑膜观测器但它的输出不参与控制只和一个经过低通处理的切换标志比较。当转速和角度差都满足条件时给切换角度做一阶低通过渡比如一个采样周期内从HFI角度线性过渡到滑膜角度过渡时间控制在10~50ms。这样转矩波动会小很多。5.2 死区PWM对高频观测的影响PWM死区是高频注入的隐形杀手。死区时间会在线电压上叠加误差这个误差的频谱很宽包含高频分量和注入频段重叠后会产生额外偏置。电机相电流越小死区影响越明显而HFI恰恰在低速小电流阶段工作最多所以必须做死区补偿至少要用符号函数补偿开关换相时间。如果你的平台上没有死区补偿一个变通办法是提高注入电压幅值到明显超过死区误差的程度让高频电流响应盖过死区扰动。但幅值不能无限加噪音和IGBT发热摆在那里。所以最好还是在FOC底层把死区补偿这块补上。5.3 我踩过的一个坑滤波器相位延迟补偿最后讲一个最容易被忽视的细节。IIR带通滤波器在中心频率处不改变幅值但会引入一明显相位延迟。如果解调参考信号还是用注入时的余弦函数相当于用不完全同相的载波去乘输出误差会带一个常数偏置导致PLL锁定的角度偏移。解决办法有两种。第一种是在解调参考信号里人为补偿滤波器相位比如提前量设为滤波器的相位角第二种是直接改用“双路同步解调atan2”方案把相位信息变成幅值信息不依赖参考相位。我在量产代码里用的是第一种因为计算量小。具体做法是在调试界面里把滤波器在中心频率处的相位求出来然后对hfi_theta加一个补偿角再用它做cos解调。补偿计量好后写死即可。另一个更实用的技巧是在启动阶段连续保存解调误差的平均值标定一次零偏之后在解调输出里减去这个零偏。因为很多偏置是固定方向的这样的标定能很快让位置误差对中比反复调滤波器相位更省事。这套HFI代码链看起来不复杂真正落地的坑在频率配合、极性判断和切换细节。如果你在项目里调到了类似问题希望这份复盘能让你少走两趟弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →