ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高频注入无感FOC代码实现:零低速转子位置估计与PLL观测器

高频注入无感FOC代码实现:零低速转子位置估计与PLL观测器 做无感FOC的都知道零低速状态下的转子位置估计是整个算法里最让人头疼的一环。滑模观测器、龙伯格观测器这类基于反电动势的方案在转速接近零、反电动势几乎为零的时候基本都会失去对转子位置的感知能力。这时候如果直接切到强拉同步或者开环强拖电机轻则抖动重则反转在带载场合甚至会直接把母线电压打出一个大坑。高频注入HFI就是解决这个问题的标准方案之一它不依赖反电动势纯粹利用电机本身的凸极特性在零速和极低速下依然能拿到可靠的转子位置。这篇文章我从代码层面把高频注入无感FOC整个链路拆开讲一遍包括高频电压怎么叠加进电流环、IIR滤波器的差分方程怎么写、位置估计用的PLL观测器怎么搭、以及最后磁极极性判断这一步的处理逻辑。适合正在调试无感FOC但又对HFI一知半解的人也适合那些能把代码跑起来但说不清楚每个模块为什么这样设计的人。文章里所有代码都是C语言风格可以直接移植到STM32平台或者自己的电机控制框架里。1. 为什么零低速非得靠高频注入——先搞清思路再动代码1.1 反电动势法在零低速失效的物理原因永磁同步电机在旋转时转子磁场切割定子绕组会在定子侧感应出反电动势。反电动势的大小跟转速成正比转速为零时反电动势为零转速很低时反电动势也很小小到会被死区效应、电流采样噪声、PWM纹波这些完全淹没掉。滑模观测器也好磁链观测器也好本质上都是在从反电动势里提取转子位置信息。既然反电动势本身都没了那这些观测器自然就失去了信息来源。所以无感FOC在零低速段出现位置估计漂移、震荡、甚至完全发散不是算法参数没调好而是这个方案在这个工况下根本不成立。1.2 凸极效应与高频注入的工作原理高频注入换了一条路。它不依赖反电动势而是往电机里注入一个高频电压信号通常1kHz左右然后去检测高频电流响应。PMSM的转子结构决定了一个关键特性d轴和q轴的电感不相等Ld Lq或Ld Lq取决于内嵌式还是表贴式。这个电感差异就是凸极效应它在定子绕组上等效出一个北极和南极方向不同的磁路阻抗分布。当我们在估算的d轴上注入高频电压时如果估算角度和真实角度存在偏差q轴方向上就会出现对应的高频电流分量。这个电流分量的幅值和相位跟位置误差之间存在特定关系。通过解调这个高频电流信号就能把位置误差提取出来再用锁相环把它收敛到零。注意这里说的位置误差可以是正的和负的高频响应信号在位置误差等于0和等于180度时都会收敛这就是后面讨论磁极极性判断的根本原因。整个HFI算法的核心链路可以概括为在d轴输出电压指令上叠加高频正弦电压采样三相电流并转换到估算dq坐标系用带通滤波器提取高频电流响应分量用同步解调把高频分量转换成低频误差信号用低通滤波器和PLL观测器估计出转子角度和转速做磁极极性判断消除180度模糊2. 注入信号怎么生成频率选择与代码实现2.1 注入频率和幅值怎么定注入频率的选择是HFI方案里第一个关键参数。频率太高了电流环带宽跟不上PWM开关频率也限制了你能注入的最高频率频率太低了又容易跟电流环的控制频率混在一起解调出来的信号也容易被转速相关的频率分量干扰。我常用的取值逻辑是这样的PWM频率低于20kHz时不用想太多直接用1kHz左右PWM频率20kHz以上时可以适当提高。判断标准是注入频率要低于PWM频率的十分之一同时要远高于电流环带宽通常几百Hz这样注入信号和电流环控制信号在频域上是可分的。还要避开机械共振频率否则注入信号会激发出明显的噪声和振动。注入幅值也是个讲究参数。幅值太小高频电流响应信噪比低位置估计噪声大幅值太大会产生明显的转矩脉动和噪音。以标幺值计算我一般先给0.05到0.1之间也就是母线电压的5%到10%。具体值需要在示波器上看高频电流响应幅值来微调目标是让高频电流响应幅值在额定电流的10%左右这时候信噪比和噪声基本平衡。下面是一个注入信号生成的代码片段#define HFI_PI (3.14159265358979f) #define HFI_FREQ_HZ (1000.0f) // 注入频率1kHz #define HFI_AMPLITUDE (0.08f) // 注入电压幅值标幺值 #define HFI_TS (0.0001f) // 电流环周期100us static float hfi_phase 0.0f; float HFI_GetInjectedVoltage(void) { hfi_phase 2.0f * HFI_PI * HFI_FREQ_HZ * HFI_TS; if (hfi_phase 2.0f * HFI_PI) { hfi_phase - 2.0f * HFI_PI; } return HFI_AMPLITUDE * sinf(hfi_phase); }这段代码在电流环的每个周期被调用返回一个高频正弦电压值。如果你的电流环是10kHz那这个函数每个周期相位步进就是0.628弧度一个完整的正弦波需要大约100个周期正好对应10kHz/1001kHz。2.2 在d轴还是q轴注入高频电压可以注入d轴也可以注入q轴两种方案都能做但工程上绝大多数选择在估算d轴注入。原因在于注入d轴后位置误差信息会在q轴电流的高频分量上体现解调出的误差信号对位置误差是正弦关系配合PLL收敛逻辑简单清晰。而在q轴注入误差信号在d轴电流上体现逻辑上也通但d轴电流还包含磁链和弱磁控制相关的分量解耦起来更麻烦。在实际代码里注入电压是在电流环的输出端叠加的也就是在PID控制器输出vd_ref和vq_ref之后再加float vd_ref PI_d_controller(id_ref, id_fb); float vq_ref PI_q_controller(iq_ref, iq_fb); // 在d轴叠加高频注入电压 vd_ref HFI_GetInjectedVoltage();2.3 注入信号叠加进电流环的注意事项这里有个容易被忽视的点注入信号必须叠加在电流环输出上而不是叠加在电流参考值上。如果把高频信号加到电流参考上电流环会把它当作参考信号去跟踪最后输出端也有高频电压但多了电流环的动态响应信号链和相位关系会变得不清晰解调复杂度大增。另外要注意如果你的电流环饱和了也就是占空比已经到限幅值叠加的高频电压会被削掉一部分导致注入波形畸形解调出来的误差信号也会畸变。所以调试HFI时尽量把电流环的限幅值留出至少10%的余量给注入电压留空间。还有一个实操经验注入信号用sinf函数计算在嵌入式里sinf执行周期一般几十微秒电流环10kHz的周期是100微秒占用比例不低。为了优化可以预先算一张正弦波查找表或者用角度累加方式只查表不调用数学库。在STM32F4这类主频不算高的MCU上这个优化能明显降低电流环负载。3. IIR滤波器代码详解系数来源、差分方程与调试要点3.1 信号链路上为什么要用带通和低通两级滤波HFI的信号链路上需要两级滤波。第一级是电流环反馈量上的带通滤波器中心频率对准注入频率比如1kHz用来从电流反馈信号里提取出高频响应分量把基波分量、其他次谐波噪声都过滤掉。第二级是解调后的低通滤波器用来把解调产生的高频成分通常是2倍注入频率的分量滤除只保留低频的位置误差信号。两级滤波器缺一不可。如果省掉带通滤波器直接对电流信号做解调那么基波电流分量会和注入频率分量混叠解调结果里会出现大量与转速相关的交调成分。如果省掉低通滤波器误差信号里会带着2倍注入频率的纹波进了PLL之后会在角度估计上产生明显的脉动。3.2 IIR滤波器的C代码实现IIR滤波器的实现核心就是差分方程。以二阶IIR为例y[n] b0x[n] b1x[n-1] b2x[n-2] - a1y[n-1] - a2*y[n-2]系数b0、b1、b2、a1、a2可以用MATLAB的fdatool直接算出来也可以手工用双线性变换法从模拟滤波器原型转换得到。对工程应用来说直接用工具算出系数然后做成表是最省事的。这里给出一个通用的二阶IIR滤波器实现typedef struct { float b0, b1, b2; float a1, a2; float x1, x2; float y1, y2; } IIR_Filter2; void IIR_Init2(IIR_Filter2 *f, float *coeffs) { f-b0 coeffs[0]; f-b1 coeffs[1]; f-b2 coeffs[2]; f-a1 coeffs[3]; f-a2 coeffs[4]; f-x1 f-x2 0.0f; f-y1 f-y2 0.0f; } float IIR_Process2(IIR_Filter2 *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; }放在HFI信号链里带通滤波器滤电流低通滤波器滤误差信号处理逻辑类似// 电流环周期内执行 float id_bp IIR_Process2(hfi_bp_filter, id_fb); float iq_bp IIR_Process2(hfi_bp_filter, iq_fb);3.3 滤波器参数选择与实际调试中踩过的坑先说带通滤波器。中心频率就是注入频率这个没悬念。带宽的选择直接影响到提取信号的纯度和响应速度一般取中心频率的10%到20%。比如1kHz注入带宽取200Hz左右。带宽太窄滤波器的gruppe延迟会变大信号通过后相位延迟明显影响解调带宽太宽滤不干净基波分量和噪声。再说低通滤波器。解调后的信号包含2倍注入频率的纹波2kHz也有真实的误差信号带宽要求几百Hz。低通滤波器截止频率取100-300Hz比较折中。截止频率再低位置估计环路的响应速度就慢动态性能差截止频率再高纹波滤不干净角度估计抖动大。实际调试中我踩过的坑主要有两个第一个坑是滤波器初值没清零。滤波器状态变量x1、x2、y1、y2在初始化时必须清零否则启动瞬间会出现一段很长的暂态输出位置估计会跟着乱跳。特别是带通滤波器Q值高的滤波器暂态更长如果初始化时不清零启动后的前几百个周期里估计角度会明显偏掉。第二个坑是定点化。如果用定点MCUIIR滤波器的系数通常都是小于1的小数直接乘整型很容易产生截断误差导致滤波器实际响应和设计的不一致。正确做法是把系数统一缩放成Q15或Q31格式然后乘法之后做饱和移位回退。我在平台上调试时发现Q15格式下系数精度对二阶滤波器来说够用但在级联高阶滤波器时误差会累积建议直接上浮点运算或者用Q31。还有一个细节值得提醒滤波器系数的分母a1、a2在浮点计算时没什么问题但如果你把滤波器从MATLAB导出的系数直接放进代码发现输出一直浮到最大或者直接溢出先检查一下是不是差分方程的符号写错了。a系数的符号在差分方程里是减号有些工具导出的格式会带负号这个错位很隐蔽排查起来很费时间。4. 位置估计器外差法锁相环的推导与代码实现4.1 从高频电流响应到误差信号的推导假设我们已经把高频电压叠加到了估算dq坐标系的d轴那么在估算dq坐标系下的高频电流响应可以写成如下形式这个公式是HFI的核心iqh Vh * (Ld - Lq) / (2 * omega_h * Ld * Lq) * sin(2 * theta_err)其中Vh是注入电压幅值omega_h是注入角频率theta_err是真实角度与估算角度的偏差。当theta_err很小的时候sin(2theta_err)近似等于2theta_err所以iqh近似正比于theta_err。这意味着q轴高频电流分量本身就携带了位置误差信息。但直接拿iqh当误差信号是不行的因为iqh也包含了基波分量和噪声我们需要用同步解调去提取它的包络。做法是先用上面说的带通滤波器提取iq的高频分量再乘以一个与注入信号同频率的参考信号然后低通滤波。解调后得到的信号就是epsilon k * sin(2 * theta_err)这个k是一个跟电机参数相关的系数符号和大小取决于Ld-Lq的值对算法来说不需要知道它的精确值只要它非零就行PLL会自动收敛。4.2 PLL观测器代码实现误差信号得到之后角度估计用PLL来完成。PLL的结构是一个二阶观测器。以角度误差作为输入经过PI调节器输出转速估计再积分得到角度估计。这里的积分器可以用梯形积分或简单的欧拉积分。typedef struct { float theta_hat; // 角度估计值单位弧度 float omega_hat; // 转速估计值单位rad/s float integral_acc; // PI积分器累计值 float kp; // PLL比例增益 float ki; // PLL积分增益 } PLL_Observer; void PLL_Init(PLL_Observer *pll, float theta_init, float omega_init, float kp, float ki) { pll-theta_hat theta_init; pll-omega_hat omega_init; pll-integral_acc 0.0f; pll-kp kp; pll-ki ki; } float PLL_Update(PLL_Observer *pll, float theta_err, float ts) { // 转速估计由PI积分输出 pll-integral_acc pll-ki * theta_err * ts; pll-omega_hat pll-integral_acc; // 角度估计 上一拍角度 转速积分 比例修正 pll-theta_hat (pll-omega_hat pll-kp * theta_err) * ts; // 角度归一化到[-pi, pi] if (pll-theta_hat HFI_PI) { pll-theta_hat - 2.0f * HFI_PI; } else if (pll-theta_hat -HFI_PI) { pll-theta_hat 2.0f * HFI_PI; } return pll-theta_hat; }这个PLL结构和传统的同步锁相环没有本质区别。theta_err是靠近零程的误差信号经过适当的缩放PI调节器让它收敛到零。当theta_err为正时说明估算角度滞后于真实角度PI输出增大omega_hat让角度追赶反过来则减小。4.3 观测器带宽和PI参数怎么调PLL的PI参数直接决定了位置估计环路的带宽。一个实用的整定方法是从带宽出发先定一个想要的带宽比如50Hz然后用公式估算PI参数。以典型的二阶PLL系统为例假设开环传递函数为G(s) kp * (s ki/kp) / s^2要让闭环带宽达到f_bw一般可以取kp ≈ 2 * pi * f_bw * sqrt(2) ki ≈ (2 * pi * f_bw)^2实际调试时可以先按这个公式算出一组初值然后根据波形微调。带宽高则响应快但噪声大带宽低则噪声小但动态响应慢需要在示波器上对比观察。我实测下来给一个比较稳妥的经验起始值如果电流环10kHzPLL带宽建议在20Hz到80Hz之间起步。先给50Hz看看效果。如果角度估计噪声大往下调如果动态跟丢或者对负载突变响应慢往上调。有一个容易犯的错误是直接拿电流环PI的参数套到PLL上用导致角度估计震荡。电流环PI的整定带宽在数百Hz以上直接用在PLL上等于让位置环带宽追平电流环整个环路必然不稳定。PLL带宽一定比电流环低一个数量级以上。关于解调增益还有一个问题。误差信号epsilon的幅值k与好多因素有关不同电机差异很大。如果不做归一化就直接把epsilon当成theta_err用那么PLL的kp、ki参数在不同电机上就需要重新整定。为了通用性可以在解调后加一个归一化处理用标定的最大值把epsilon缩放到适当范围内这样PI参数对大多数电机都能适用。不过这个方法需要在电机上做一个自动标定过程代码逻辑会复杂一些我一般是在原型调试时手动测一次量产时把归一化系数固化到参数表里。5. 磁极极性判断解决180度模糊的最后一步5.1 位置收敛到0还是pi的原理用上面的外差法锁相环做位置估计会发现一个绕不开的问题误差信号sin(2*theta_err)在theta_err等于0和等于pi时都等于0。所以PLL可能收敛到真实角度也可能收敛到真实角度加180度。也就是说高频注入本身只能锁定转子磁极轴线方向但无法区分N极和S极。这个180度偏差如果不修正电机通电后实际电流矢量和转子磁场的关系是反的在控制器看来励磁变成了去磁轻则转矩方向和预期相反重则直接把电机转子退磁问题很严重。所以磁极极性判断是HFI方案里不可省略的一步。5.2 极性判断的两种常用方法极性判断的基本原理是磁饱和效应。发电机转子在N极方向和S极方向上的磁路饱和程度不同。在N极方向继续加d轴正电流磁路更饱和等效电感变小电流响应更快、幅值更大在S极方向加同样的d轴电流磁路从不饱和往饱和方向走电流响应相对慢一些、幅值稍小。常用做法一电压短脉冲法。在估算角度theta_hat和theta_hatpi两个方向上分别施加一个短促的d轴正电压脉冲持续几十到几百微秒采样并比较两个方向上的峰值电流或者电流上升斜率。电流响应大的那个方向就是真实N极所在方向。如果发现真实方向和当前估计一致保持角度不动如果不一致把估计角度修正180度同时把转速估计也取反。常用做法二高频电流幅值比较法。更接近HFI的整体架构在完成PLL收敛后分别向估算角的两个方向发射同样幅值的高频电压通过检测峰值电流的差异来判断方向。不过这个方法对滤波和采样的要求比短脉冲法更高现场调试工程量略大。第一种方法更简单直接我用得最多。极性判断代码的核心部分是这样的// 在估算角的两个方向分别施加d轴电压脉冲 // 分别记录两次电流响应的峰值或斜率 // 返回值0表示无需修正1表示需要把估计角度加上PI #define POL_CHECK_VOLTAGE (0.2f) // 极性判断脉冲电压标幺值 #define POL_CHECK_TIME (500.0f) // 脉冲持续时间单位us int HFI_PolarityCheck(float theta_hat, float *theta_corrected) { float id_peak_0 0.0f; float id_peak_1 0.0f; float tmp_theta; // 第一次脉冲方向为 theta_hat tmp_theta theta_hat; // 设置电压角度到 d 轴方向并施加脉冲 vd_pulse POL_CHECK_VOLTAGE; // 在这里等待 POL_CHECK_TIME 时间并记录 d 轴电流峰值 id_peak_0 capture_d_axis_peak_current(); // 第二次脉冲方向为 theta_hat PI tmp_theta theta_hat HFI_PI; if (tmp_theta HFI_PI) tmp_theta - 2.0f * HFI_PI; vd_pulse POL_CHECK_VOLTAGE; id_peak_1 capture_d_axis_peak_current(); // 比较峰值电流峰值大的方向是真实N极 if (id_peak_1 id_peak_0) { *theta_corrected theta_hat HFI_PI; if (*theta_corrected HFI_PI) *theta_corrected - 2.0f * HFI_PI; return 1; // 需要修正 } else { *theta_corrected theta_hat; return 0; // 无需修正 } }实际应用时极性判断一般放在每次上电启动的初始位置检测阶段完成判断完成后就把修正角度写入PLL的初始值然后正常切到HFI的位置估计闭环中。5.3 极性判断与启动流程的完整衔接把这个步骤放到整体启动流程里看更清楚。一个典型的无感FOC启动流程是这样的先做开环定位或者预定位让转子大致停到已知角度如果用的是高频注入法就切入HFI闭环让PLL收敛先不管N/S方向做磁极极性判断修正180度偏差修正完成后HFI位置估计得到的角度就是真实转子角度接着可以做速度闭环让电机平滑加速在转速上升到反电动势足够大时再平滑切换到反电动势观测器在代码层面关键是让PLL的状态量跟着修正一起更新。修正角度后不能只改角度变量转速估计的符号也要跟着变化否则PLL会立刻再收敛到错误的方向上。6. 常见问题与排查技巧实录6.1 调试中常见的5类典型问题与解决方案第一类启动后角度估计一直抖动位置波形有明显的高频纹波。这通常是解调后的低通滤波器性能不够或者注入幅值过大导致磁路饱和异常。优先检查低通滤波器的截止频率是否偏高然后观察注入幅值是否太大了把高频电流响应幅值控制在额定电流的10%附近。第二类PLL无法收敛角度持续偏差甚至积分器饱和。排查顺序是先确认注入信号是否正确叠加到了d轴电压指令上用示波器看相电流里有没有明显的1kHz分量再确认带通滤波器中心频率是否对准注入频率滤波方向是否对应正确最后检查解调参考信号的相位是否正确。第三类位置估计在小负载下正常一加载或者加速就飞。这是PLL带宽和驱动带宽不匹配导致的。要么提高PLL带宽让位置环跟上速度环要么降低速度环的响应速度让位置环有时间收敛。我见过很多人在这个环节死磕电机控制参数实际问题是PLL带宽给的太低只有10Hz速度环带宽却到了100Hz位置信息根本跟不上动态过程。第四类极性判断结果不稳定两次判断结果不一致。多半是电流采样噪声太大或者脉冲时间太短。把采样窗口适当加长或者对峰值电流做多次采样取平均能改善稳定性。另外还要注意脉冲电压方向必须是估算d轴的正方向角度转换宏写错的话两次发出去的脉冲方向就乱了。第五类零速能定位但稍微给一点转速位置估计就明显滞后。这种滞后是滤波器和PLL的固有延迟造成的。可以在转速环里加上位置补偿即用估计转速乘以一个延迟时间常数对角度做前馈修正。补偿系数需要实测标定我给个参考范围不带载时通常需要补偿几十个微秒到几百个微秒对应的角度。6.2 快速排查清单如果你在调HFI过程中遇到问题按照下面顺序逐项排查大部分问题五分钟内能定位注入频率是否符号要求是否正确避开PWM开关频率谐波叠加的高频电压幅值是否能从母线电压波形上看出来带通滤波器的中心频率是否精确等于注入频率解调参考信号的相位是否和注入信号的相位一致低通滤波器的截止频率是否低于注入频率的一半PLL带宽是否远低于电流环带宽极性判断的脉冲宽度和采样窗口是否足够角度修正后PLL的omega_hat是否同步取反6.3 参数整定顺序建议最后说一遍我个人习惯的参数整定顺序无论是新项目还是新电机这套顺序能少走很多弯路。先固化注入参数定注入频率和注入幅值这个一步到位不需要反复调。然后调带通滤波器和低通滤波器参数在示波器上看解调前后的波形确保信号干净。接着调PLL参数用给角速度阶跃的方法去看估计角度的跟随性。最后做极性判断的验证确保连续多次上电启动都能得到正确方向。调PLL参数的时候建议用一个小技巧先把ki设成0只调kp。这时候PLL变成了一个纯比例观测器角度估计会有稳态误差但不会震荡。逐渐增大kp到临界震荡然后再加入ki消除稳态误差。这样的收敛过程比直接两个参数一起试更容易找到可行区间。实测下来kp给到临界震荡值的一半左右ki给到约为kp的五分之一到十分之一是一个比较稳的起点。这几年做无感FOC下来我最大的体会是HFI本身是个成熟的方案原理也不复杂但真正让算法“能干活”的是细节。滤波器的相位延迟、PLL的带宽分配、极性判断的时机任何一环没处理好整个系统都会在启动阶段给你颜色看。如果你正好卡在某个环节别急着堆代码先把信号链路每个节点的波形量一遍定位到具体某级再动手改参数效率会高很多。
返回列表