☰
PMSM无感FOC中EKF观测器设计与STM32自动代码生成实战
2026/10/24 2:52:22 网站建设 项目流程

做电机控制的都知道,无感FOC最难的部分从来不是电流环,而是没有编码器的情况下,怎么把转子位置和转速估准。早些年我搭滑模观测器,抖振处理得头疼,后来转向扩展卡尔曼滤波(EKF)观测器,配合STM32与Simulink自动代码生成,整个方案在永磁同步电机模型上跑得相当稳。这篇文章就把这套方案的完整思路记录下来:从EKF的数学模型怎么搭、Q和R矩阵怎么调,到Simulink里的仿真验证,再到自动代码生成、Keil MDK工程集成,最后是台架实机测试那几天踩过的坑和排查链路。

如果你手头正在做PMSM无位置传感器控制,或者刚把Simulink模型生成了代码但不知道怎么接进STM32工程,又或者EKF调了大半个月还是波形乱飘,这篇文章应该都能给你一些直接能用的东西。

1. 为什么这套方案选择EKF而不是滑模或高频注入

1.1 无位置传感器方案里的三角色

PMSM无位置传感器控制,主流方案大体三种:滑模观测器(SMO)、高频信号注入、扩展卡尔曼滤波(EKF)。

SMO的实现最激进,依赖反电动势提取转子位置,鲁棒性很好,但滑模切换带来的抖振在低速和轻载时特别明显。很多工程做法是后面再接一个低通滤波和一个锁相环,把抖振压下去,代价是相位滞后,转速越高越明显。我拿这个方案做过一次实验,突加负载时角度估计能跳好几度,电流环跟着抖,整个系统给人的感觉就是"虚胖"。

高频注入能在零速和极低速下工作,原理是把一个高频电压信号叠在基波上,靠电机的凸极性或者饱和凸性来辨识转子位置。问题是它对电机参数的凸极性敏感,对噪声和滤波器设计的要求也高,工程上要想稳定得很细的成本。

EKF的思路则是另一条路:承认系统有噪声、建模有不精确、测量有干扰,然后通过统计意义下的最优滤波,把转子位置和转速作为状态量直接估计出来。它不需要额外的锁相环,也不需要复杂的信号注入,只要状态方程建得靠谱、噪声矩阵给得合理,中高速段的表现稳定,尤其在电流采样噪声比较大的情况下,EKF的抗扰能力明显好过SMO。

1.2 项目为什么选中速段优先、以EKF为核心观测器

我这套方案的场景是中高速段无感FOC,目标转速范围大概从额定转速的10%左右开始,往上覆盖到高速区。零速和极低速的启动没有指望纯靠EKF解决,那一块通过预定位加I/F开环拖动,等转速起来了再切到EKF闭环。这个组合逻辑在工程上非常常见,也是比较稳妥的路子。

EKF在PMSM上的优势还有一点:它可以直接估计电角速度,而不需要像SMO那样需要反正切函数加上微分去求转速。转速是位置积分出来的状态量,微分噪声天然被滤波过程压制,放大做转速环反馈时信号干净很多。

Pick一下实现平台:Simulink建模加自动代码生成,目标MCU选STM32,集成环境用Keil MDK。之所以不手写全套C代码,是因为EKF涉及矩阵运算,手写容易出现维度错和不一致的bug;用Simulink里MATLAB Function块搭完EKF,侧重点是代码可读性和迭代效率。模型改一个矩阵参数,重新生成代码重编译,整个流程比手改C文件快得多,而且能保证仿真和实机用的是同一套逻辑。

1.3 整套方案的实现链路

我采用的链路分四段:

  • Simulink中搭建PMSM被控对象模型和EKF观测器,先做纯仿真;
  • 仿真调好Q和R矩阵、确认观测器收敛性和稳态精度;
  • 通过Embedded Coder配置生成嵌入式C代码;
  • 把生成代码导入Keil MDK工程,和HAL库、电机驱动、ADC采样、FOC主中断配合,烧录到STM32样机实测。

下面按这条链路展开讲,每一步都有具体的做法和为什么这么做。

2. 建立可代码生成的EKF观测器模型:状态方程与雅可比矩阵怎么推

2.1 从αβ坐标系下的PMSM电气方程出发

做EKF的第一步是选定状态方程。PMSM在旋转dq坐标系下的电压方程里含有正弦余弦耦合项,位置角度本身就是需要估的东西,不适合直接作为观测模型的坐标。所以标准做法是把方程转换到静止αβ坐标系下。

以表贴式PMSM为例,Ld=Lq=Ls,αβ坐标下的电压方程可以写成:

uα = Rs·iα + Ls·(diα/dt) - ωe·ψf·sinθe
uβ = Rs·iβ + Ls·(diβ/dt) + ωe·ψf·cosθe

把电流导数提出来,状态量选为[iα, iβ, ωe, θe],得到:

diα/dt = (-Rs/Ls)·iα + (ψf/Ls)·ωe·sinθe + uα/Ls
diβ/dt = (-Rs/Ls)·iβ - (ψf/Ls)·ωe·cosθe + uβ/Ls
dωe/dt = 0
dθe/dt = ωe

其中ωe是电角速度,ψf是永磁磁链,Rs是定子电阻,Ls是定子电感。dωe/dt=0这个式子表示在仿真步长内认为转速不变或变化很慢,相当于把负载转矩和机械动态当作过程噪声处理。这种简化是EKF工程应用里的常见技巧:不增加机械方程复杂度,把模型误差交给Q矩阵去兜底。

测量量选[iα, iβ],因为这两个电流可以直接从直流母线采样和Clark变换得到,不需要额外传感器。

2.2 EKF五步更新流程与雅可比矩阵推导

EKF的预测和更新分五步,我来把每一步对应到PMSM模型里:

预测步骤:

状态先验估计: x_pred = x_est + f(x_est, u)·Ts

其中f(x,u)就是上面那组状态方程,Ts是控制周期/步长。实际离散化用前向欧拉就够,因为Ts在50µs到100µs级别,系统极点离单位圆边界有充分裕量。

协方差先验: P_pred = Fd·P_est·Fd^T + Qd

Fd是离散雅可比矩阵,Fd = I + Ts·F。连续雅可比F是状态方程对状态变量的偏导数矩阵,推出来是:

F13 = (ψf/Ls)·sinθe
F14 = (ψf/Ls)·ωe·cosθe
F23 = -(ψf/Ls)·cosθe
F24 = (ψf/Ls)·ωe·sinθe

F矩阵里第一行第二列、第二行第一列均为0,第三行全0,第四行只有第四列的转速微分项。整体看起来就是:

F = [-Rs/Ls, 0, F13, F14;
0, -Rs/Ls, F23, F24;
0, 0, 0, 0;
0, 0, 1, 0]

建议这一步不要手算,直接把偏导公式写到MATLAB Function里自动求导反而容易出错,手推一次然后写成矩阵形式最稳妥。我也见过有人用Symbolic Math Toolbox推导再生成代码,能做但对工程来说有点重。

更新步骤:

卡尔曼增益: K = P_pred·H^T·(H·P_pred·H^T + R)^(-1)

H是测量矩阵,因为测量量直接是iα和iβ,所以H就是2×4的标准选择矩阵:

H = [1, 0, 0, 0;
0, 1, 0, 0]

状态后验更新: x_est = x_pred + K·(y_meas - H·x_pred)

协方差后验: P_est = (I - K·H)·P_pred

整套流程下来,只有4维状态量和2维测量量,矩阵运算规模很小,STM32F407级别的MCU跑起来完全无压力,后面我会给出实测的执行时间。

2.3 Q和R矩阵怎么定,决定观测器性命的接地气经验

EKF调参的重头戏基本都在Q和R。这两个矩阵决定滤波器的"信任感"分配:Q大,说明你觉得模型不可靠,更相信测量,滤波器动态响应快但噪声也大;R大,说明你觉得测量噪声厉害,更相信模型预测,波形平滑但响应滞后。

在PMSM的EKF里我习惯先给这样的初始范围:

参数含义推荐初值范围实测调整方向
Q(1,1) Q(2,2)iα/iβ过程噪声1e-3 ~ 1e-2负载突变时上调
Q(3,3)转速过程噪声1 ~ 10转速跟踪慢时上调
Q(4,4)角度过程噪声0.1 ~ 1低速飘移时上调
R(1,1) R(2,2)电流测量噪声0.01 ~ 0.1采样纹波大时上调

注意:这几个值物理量纲不一样,不用追求数值在同一数量级,关键是相对量级。有的同行刚上手时看到Q(1,1)只有1e-3,Q(3,3)却是1,怀疑是不是差太多了——其实是因为电流用安培、转速用rad/s、角度用rad,单位不同而已。

我的调试顺序是先固定R,再调Q。启动阶段如果角度估计收敛慢,优先加大Q(3,3)和Q(4,4);稳态带载时如果角度波形毛糙,优先加大R。还有一条很有用的经验:实机上逆变器死区会对反电动势产生畸变,这个畸变在模型里没有体现,等效于过程噪声变大。所以从仿真到实机,Q矩阵通常还要再放大1~2倍才合适。

2.4 离散化和一拍延时补偿

模型里用到的uα和uβ,理想情况下是逆变器输出的实际相电压,但工程上很难实时重构死区效应之后的电压值,所以一般直接用FOC电流环输出的参考电压指令。这里有个坑:电压指令是当前控制周期产生的,要等下一个PWM周期才真正施加到电机端,而电流采样是当前周期已经完成的,两者在时间上差了一拍。如果没有补偿,EKF的输入序列相位是错位的,角度估算误差会随着转速升高而放大。

我做的补偿很简单:在模型输入处给uα、uβ加一个一拍的延迟,让观测器的输入和电机实际承受的电压在时间轴上对齐。实现就是在Simulink里用一个Unit Delay块,代码生成后就是一个状态变量,代价极小,但对高速段的精度提升很明显。

3. Simulink仿真先把Q、R矩阵差不多调明白

3.1 仿真模型的层级划分

Simulink里的调试环境搞成三层:

  • 最外层是完整的FOC控制环路,包含转速PI环、电流PI环、SVPWM生成、坐标变换模块;
  • 中间层是电机与逆变器模型,我用Simscape Electrical搭的PMSM模型和三相全桥,这样仿真结果和台架更接近;
  • 最内层才是EKF观测器模块,输入uα/uβ和iα/iβ,输出估计的转速和角度。

EKF观测器本身我放在MATLAB Function块里实现,原因是可以直接把矩阵运算写成向量化的代码,调试时还能用MATLAB的变量检查功能看中间值。为了兼容代码生成,所有变量类型统一用single,矩阵运算保持固定维度,不动态分配内存。

结构大概是:FOC控制最外层给电机模型发电压,真实角度从电机模型里引出,和EKF估计角度对比,直接在示波器上就能看到误差。

3.2 仿真波形的关键指标看什么

跑仿真的目的不是看"波形能不能转",而是要把几个指标摸清楚:

  • 稳态角度误差。额定转速下,EKF估计角度和模型真实角度之间的误差绝对值,应该稳定在1°~2°电角度以内。超过这个范围,就说明模型参数或者某些矩阵给得有问题。
  • 突加突卸负载时的收敛速度和波动幅度。这个直接反映Q矩阵是否匹配实际工况。我一般在25%额定转速和100%额定转速两个工况各测一次突加50%负载,观察转速估计在1秒内能不能回到设定值误差的5%以内。
  • 低速极限。慢慢把速度指令往下压,找到角度误差开始发散的临界转速,这个值决定了I/F切换到EKF闭环的合理时机。
  • 抗噪能力。在仿真里给电流反馈加白噪声,看角度和转速估计是否还能保持平滑。这能验证R矩阵是否合理,也能提前暴露滤波器结构里的维度问题。

3.3 仿真给实际项目带来的前置收益

有人说仿真和实机差得远,不如直接上台架调。这话对一半。我的感受是,仿真阶段最大的价值是把"模型逻辑正确性"和"参数初始值合理性"先锁死。EKF实现里最大的风险不是参数,而是维度错误、雅可比矩阵符号搞反、坐标变换逆矩阵写错,这些问题到了台架上排查成本极高,在仿真里一眼就能看出来。

我实际碰到过最典型的例子:有一次F矩阵里F13的符号反了,仿真波形在低速段看着还行,转速一拉高角度突然跳180°,这个在台架上根本想不到是符号问题,可能要折腾两天。仿真里直接用模型真实角度一对比,五分钟定位。参数初值也是,仿真把Q、R调到一个能稳定工作的范围,台架实测在此基础上微调,可以节约大量时间。

4. 从Simulink到Keil MDK:自动代码生成的工程细节

4.1 Embedded Coder配置需要注意的开关

Simulink模型搭好、仿真通过之后,就到了代码生成阶段。我用Embedded Coder做目标配置,几个关键项值得单独说:

  • 求解器一定要设成固定步长、离散求解器,优先od4或discrete,不能选连续变步长,否则生成代码里会出现动态分配和复杂求解逻辑,嵌入式端完全不适配。
  • 采样时间要和实际控制环周期一致。我这边固定成10kHz,也就是Ts=1e-4s,对应STM32定时器中断频率。
  • 在Code Generation里把系统目标文件选为ert.tlc,这是Embedded Coder的实时目标文件,生成的代码质量比grt好很多,没有多余文件系统接口。
  • 硬件实现这一栏,设备类型选ARM兼容,字节顺序小端。这会影响生成代码里结构体对齐和位操作的实现方式,选错会出很难查的隐性bug。
  • 代码优化不要上来就选速度最优。第一次生成代码时把优化级别关掉或者选"平衡",保留变量命名清晰度,方便在Keil里打断点查中间量。等整体功能确认了,再回到Embedded Coder里把优化拉上去。

如果想让生成代码挂进Keil工程时更省心,还可以在Code Generation界面里设置"Generate code only"之后导出代码报告。报告里会有每个变量的内存分配和函数调用关系,后面手写外部接口时会非常有用。

4.2 生成代码导入Keil MDK工程的步骤

Embedded Coder生成的文件大致是几个C源文件和头文件,加上rtwtypes.h等基础定义。我一般在项目目录下建一个app_model文件夹,专门放这些生成文件,不跟HAL库混在一起。

导入Keil MDK工程的流程:

  1. 把生成的所有.c和.h文件复制到Keil工程的App/Model目录下;
  2. 在Keil工程里Add Existing Files加入这些C文件;
  3. 模型顶层信号的输入输出在生成代码里会对应到model_initialize和model_step这两个函数:
    • model_initialize()在主函数初始化阶段调用一次,负责状态初始化和协方差矩阵初值设定;
    • model_step()放在PWM下溢中断或定时器中断里,每个控制周期调用一次,内部完成了从信号读取、EKF预测更新到PWM占空比输出的全部流程。
  4. ADC采样值、编码器值(如果有)、母线电压等通过模型输入端口映射到某个全局变量,要么在外部C文件里用extern声明调用,要么配置成接口函数回调。

关键细节是Keil MDK工程的优化等级要和生成代码匹配。如果Keil那边选了高优化而Simulink生成时代码风格是面向可读性的,偶尔会出现变量被optimizer优化掉导致调试时观察不到想要的中间量。我一般在Debug阶段把Keil优化设成-O0或者-O1,等最终发布再拉到-O2。

4.3 中断优先级、采样时刻和执行顺序

代码进入STM32中断的时序对整个系统稳定性影响极大。我的做法是:

  1. PWM定时器中心对齐模式,计数到特定值(通常是下溢)时启动ADC注入组采样,采样完成中断里启动FOC计算和EKF更新;
  2. PWM更新事件把最新计算出来的占空比加载到寄存器;
  3. 整个控制链路的顺序是:ADC采样 → Clark变换 → 电流PI → 反Park变换 → SVPWM → EKF输入更新 → PWM占空比装载。

EKF的输入要特别注意:uα/uβ必须用上一拍电流环计算输出的指令电压,不能直接读当前的参考值,这跟前面说的一拍延时补偿是同一个问题。在中断里实现时,用一个静态变量暂存上一周期的uα/uβ,然后传给model_step的输入端口。

中断优先级设置上,ADC采样完成中断给最高优先级,PWM更新中断次之,UART调试在最低优先级。如果优先级反了,采样抖动会让电流波形产生周期性扰动,EKF的输入噪声会异常增大,角度估计开始飘,这是台架调试中很隐性的一个坑。

5. 台架实测排坑:启动、精度、运行时间的真实情况

5.1 Startup阶段观测器失效的根因与I/F切换策略

EKF对静止状态几乎不可观测:转速为零时,状态方程里根本没有角度信息的激励,反电动势也为零,EKF无法从电流测量中提取转子位置。这个本质上是系统能观性问题,靠调参数解决不了。

所以启动阶段不能直接进EKF闭环。我采用的方案是:

  1. 预定位:给定一个固定方向的电压矢量,持续时间大约200ms,让转子转到已知位置;
  2. I/F开环拖动:以电流幅值和角度积分的方式把转子加速,角度由程序给定,不依赖观测器;
  3. 转速上升到EKF有足够反电动势激励时,切换到EKF角度闭环。

切换时机一般选在额定转速的5%到10%之间,具体根据EKF仿真中角度误差发散的临界转速来定。切换时要做角度平滑衔接,如果I/F给定角度和EKF估计角度差得太多,直接切会导致电流突变。我在切换点加了两个周期的斜坡过渡,效果比硬切好很多。

5.2 运算时间和浮点精度之间的权衡

EKF的4状态矩阵运算复杂度很低,实测下来单次运行时间:STM32F407主频168MHz,代码优化等级-O2,EKF单次执行时间约12到15µs。完整FOC电流环大概8到10µs,ADC采样和坐标变换约3µs,整个控制中断总耗时在25µs左右,相对10kHz(100µs周期)的控制频率还有大约75%的裕量。

系统默认生成的EKF矩阵运算如果全用double类型,浮点运算器处理时间会翻倍到30µs以上,虽然也能跑但中断占用明显偏高。我在Simulink里把所有数据都强制设成single后,执行时间直接降下来,角度估计精度在16位ADC电流采样条件下并没有肉眼可见的下降。如果用单精度时发现角度噪声变大,建议先把电流采样延迟和时序处理干净,而不是急着换回double。

5.3 针对实机测试中的几个典型坑

我在实测中遇到的比较值得记录的坑:

问题现象可能原因解决思路
转速一旦超过某个阈值角度突然跳变一拍延时未补偿在模型输入端对uα/uβ加一拍延迟
带载后角度误差随电流增加而增大死区时间、管压降导致模型电压和实际电压偏差增加Q值,或在低速段做死区补偿
协方差矩阵P跑飞,出现数值溢出长时间运行后P失去对称正定性每次更新后强制对称化,并设置P对角线最小值下限
电流采样毛刺大,EKF角度抖动ADC采样点不在PWM中心改为中心对齐采样,采样时刻对齐下溢
切换I/F到EKF瞬间电流冲击角度给定不连续加斜坡过渡,用切换标志做角度融合

协方差矩阵跑飞这个问题最隐蔽。仿真中因为步数少,很少出现,但实机跑几十分钟后P矩阵的对称性可能因为舍入误差慢慢被破坏,表现出来就是估计值偶尔跳变然后发散。我的做法是在每次更新之后补一行: P = (P + P^T) / 2 再把P的对角线元素clip到一个下限值,比如1e-8。代码量极小,但能彻底规避这一类数值问题。

还有一条经验:R矩阵在实机上需要调得比仿真大。因为电流采样的实际噪声包含采样保持电容误差、开关管动作的共模干扰,这些在仿真模型里不容易完全复现。我在仿真里R设0.01,实机调到0.05才达到同等平滑程度。光这一条就能解释很多人"仿真好好的,上机就飘"的现象。

5.4 一套可复用的调参流程

总结一套我自己常用的实机调参顺序,供参考:

  1. 先在10%额定转速空载运行,把R矩阵固定,Q(3,3)和Q(4,4)从最小值开始往上调,直到转速估计无静差、角度波形平滑;
  2. 加到100%额定转速,观察角度误差,若是误差呈现随转速增长的趋势,优先检查一拍延迟补偿是否正确;
  3. 做一次突加负载测试,如果转速恢复时间过长,适度加大Q(1,1)和Q(2,2),让滤波器更快跟随电流变化;
  4. 做低速极限测试,找到角度误差的可接受转速下限,这个转速作为I/F切换点的参考;
  5. 连续运行2小时,监控角度估计和协方差矩阵对角线值是否有缓慢漂移,验证数值稳定性。

这套流程不需要每台样机都从头调一遍,同一系列电机换型号以后,主要重新匹配Q(3,3)和R值,其他参数基本沿用。如果你刚开始接触EKF,建议不要一上来就追求全速域无感,先把中高速段做稳,把I/F启动到EKF的切换做顺,再往下扩展低速和零速工况,成功的概率会高很多。

我在这套方案上最深的体会是:EKF本质上是把模型误差、测量噪声和参数不确定性都塞进两个矩阵里,调参看起来玄学,但每一步背后都有明确的物理含义。仿真和台架各验证一半,相互印对着来,这套观测器才能真正落到量产级别的稳定性上。后续如果再有空,我计划在同一个框架上加上在线定子电阻辨识,把低速域的观测性能再往前推一截。

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

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

立即咨询