☰
Simulink FMU导出实战指南:从模型配置到外部验证的完整流程
2026/9/29 19:30:55 网站建设 项目流程

搞仿真的人,迟早会被一个问题找上门:怎么把这个模型交给同事、交给客户,又不至于把整个 Simulink 环境、工具箱版本、许可证一并交出去。我最早遇到这个需求是做控制算法交付的时候,对方用的一套自成体系的多学科仿真平台,根本打不开 .slx,唯一能接的就是 FMU。那次摸爬滚打之后,我才真正把 Simulink 里 FMU 导出的整套流程吃透。

FMU,全称 Functional Mock-up Unit,是 FMI(Functional Mock-up Interface)标准下的打包产物。简单理解,它就是把一个仿真模型编译成一个独立单元,里面包含模型描述文件、二进制库和必要资源,外部工具通过标准接口就能加载并调用它。Simulink 中导出 FMU 这件事,本质上就是把 MATLAB/Simulink 环境里的模型“翻译”成行业通用的标准件,配置得好,一次生成到处运行;配置不好,光踩坑就能浪费一整天。这篇实战指南我从模型配置到生成验证完整走一遍,把我实际踩过的坑一并写出来,适合所有要把 Simulink 模型交付到外部仿真环境、第三方工具或客户现场的朋友。

1. FMI/FMU 到底是什么,为什么值得从 Simulink 导出

1.1 FMI 标准的背景与 FMU 的内部结构

FMI 标准最早由 Modelica 协会推动,现在已经是工业仿真领域事实上的互操作标准,汽车、航空航天、能源、机器人等行业都在用。它的目标很朴素:让不同厂商的仿真工具能交换模型。实现方式就是把模型打包成 FMU,FMU 其实是一个 zip 压缩包,标准结构大致如下:

  • modelDescription.xml:描述模型接口的关键文件,包括输入输出变量列表、变量数据类型、模型类型、求解器能力等;
  • binaries:编译好的平台库,Windows 下是 .dll,Linux 下是 .so,macOS 下是 .dylib;
  • sources:可选的 C 源码,有些 FMU 允许你拿源码重新编译,适合跨平台场景;
  • resources:额外资源,比如数据文件、图片说明等。

你拿到一个 FMU,其实不需要关心它内部是怎么实现的,只要按照 modelDescription.xml 里的描述连好输入输出,就能调用它。这正是 FMU 最有价值的地方——跨工具、跨团队交付模型时,对方不需要安装 MATLAB,也不需要了解模型细节,只需要一个能加载 FMU 的宿主环境。

1.2 Model Exchange 和 Co-Simulation 怎么选

FMI 标准把 FMU 分成了两类,很多人一开始都搞混。

Model Exchange(ME)模型交换型,FMU 内部只提供微分方程和代数方程,宿主环境自己选择求解器来求解。它对宿主环境要求高,宿主必须内置可靠的求解器,但自由度也大。Co-Simulation(CS)联合仿真型,FMU 内部自带求解器,宿主环境只负责协调时间推进和数据交换。CS 用起来更省心,我实际项目中绝大多数情况用的都是 CS。

从 Simulink 导出的习惯,我推荐优先选 Co-Simulation,原因有三个:第一,外部工具集成 CS 的兼容性最好,几乎没有额外的求解器依赖;第二,Simulink 模型通常已经用某一种求解器验证过了,导出 CS 之后内部行为最接近原模型;第三,CS 模式对仿真步长的控制更直观,你在外面能看到通信步长,出了问题容易定位。

1.3 Simulink 对 FMU 导出的支持概况

Simulink 从 R2019a 开始,把 FMU 导出功能集成到了 Simulink Compiler 产品线里。需要说明的是,这个功能不是基础版本自带的能力,依赖 Simulink Compiler 授权,同时底层会调用 Simulink Coder 做代码生成。导出时你不需要手动配置复杂的代码生成模板,工具会自动把模型转换成 C 代码,再编译成平台二进制。

一个很重要的限制是,Simulink 直接把模型编译成当前平台的二进制 FMU。你在 Windows 64 位上用 MATLAB 导出的 FMU,加载到 Linux 的工具里,基本是不可能成功的。跨平台交付要么用源代码型 FMU,要么在目标平台上重新导出。Simulink 的 FMU 导出在这方面比较保守,我用下来的经验是,平台和位数必须一一对应,导出前一定提前确认目标环境。

2. 导出前必做的模型配置,别急着点生成

2.1 接口设计:用 Inport/Outport 定义 FMU 的边界

FMU 对外暴露的接口来自模型里的 Inport 和 Outport 模块。导出的时候,每个 Inport 会对应 FMU 的一个输入变量,每个 Outport 对应一个输出变量。这一步听起来简单,但实际操作中经常出问题。

接口命名很关键。FMU 卫生导出后,变量名会带上前缀和端口号,比如 in_1、out_2 这种,别人用起来阅读性很差。我习惯在模型里直接把端口信号命名成有意义的名字,比如 throttle_cmd、vehicle_speed、battery_soc,这样 modelDescription.xml 里看到名字就知道含义。关于信号类型,建议全部显式指定,不要用 inherit,避免生成后变量类型模糊。能用 double 就不要用 single,能用标量或向量就不要用总线对象,结构体信号在 FMU 接口映射时处理繁琐,外部工具支持也参差不齐,能避免就避免。

端口初始值也要检查。CS 模式下,FMU 首次接收输入前的内部状态由模型初始条件决定,如果初始值设置不合理,外部工具里的仿真可能一开始就发散。可以在模型里给状态和端口做好初始化,再导出。

2.2 求解器与采样时间:这是 Co-Simulation FMU 的核心命门

很多人第一次导出 FMU 失败,问题都出在求解器配置上。Simulink 导出 CS 型 FMU 时,模型必须配置为定步长求解器。原因很好理解:FMU 内部自己推进时间,外部工具按照通信步长同步时间,内部必须按固定时间步迭代,才能保证与外部时钟对齐。

实际配置路径是:模型设置 → Solver → Solver options,把 Type 改成 Fixed-step。如果模型里全是离散模块,直接用 discrete 求解器;如果模型有连续状态,可以选 ode3、ode4 这类定步长连续求解器,步长不宜太大,一般取系统最小时间常数的 1/10 到 1/50 比较稳妥。

采样时间配置同样容易踩坑。模型里所有模块的采样时间必须兼容。如果一个离散模块采样时间是 1 秒,另一个是 0.1 秒,外部工具用 0.05 秒通信步长加载,FMU 内部容易产生各种怪异行为。我的做法是,导出一个专门用于 FMU 交付的模型副本,把所有采样时间统一成同一个基础步长,尽量减少多速率混合。

2.3 编译工具链:先解决 C 编译器,再谈生成

Simulink 导出 FMU 的最后一层是调用编译器把生成的 C 代码编译成动态库。没有可用的编译器,一切配置都白搭。在 MATLAB 命令窗口运行:

mex -setup

会列出当前机器上可用的编译器。Windows 上优先选 Microsoft Visual Studio,版本要和 MATLAB 版本匹配。新版本 MATLAB 对较老的 VS 版本兼容性不好,这个坑我踩过很多次。

配置完编译器之后,我建议先做一个快速验证:用一个 hello world 级别的简单模型(比如一个 Inport 连一个 Outport),直接导出一遍 FMU。如果这一步能顺利生成,说明编译器链路没问题;如果这里就失败,问题一定在编译工具链层面,先去解决编译器,而不是去改模型。这一步能帮你节省大量排查时间。

2.4 模型内容检查:哪些模块能导出,哪些不能

不是所有 Simulink 模型都能一键导出 FMU。代码生成是底层的硬约束,模型里如果包含不适合代码生成的模块,导出时会报错或生成后行为异常。

  • Stateflow 状态机:一般情况下支持,但要注意状态事件、图表间通信等高级特性可能有限制;
  • Simscape 物理模型:支持不好,涉及多个物理域解算逻辑的模型,导出后行为可能与原始模型有偏差;
  • 直接访问工作空间的 MATLAB Function 模块:有限制,尽量少用或改成纯 Simulink 模块;
  • 外部驱动器、To Workspace 等与 MATLAB 环境强绑定的模块:必须删除或用等价方式替代。

我一般会在导出前把模型复制一份,命名为 model_export_backup,在这个副本里清理所有日志输出模块、显示模块和 MATLAB 工作空间交互模块,只保留从 Inport 到 Outport 的纯功能链路。宁可多花十分钟收拾,也别让奇怪模块在导出时报错。

3. FMU 生成全流程实操

3.1 打开导出工具:两种入口都顺手

先说 App 入口。打开 Simulink 模型后,在工具条的 Apps 页卡里找到 FMU Export 应用,点击后会出现一个导出配置界面。这个界面是图形化的,适合新手,每个选项都有说明,操作过程比较直观。

命令行入口对频繁操作的人更友好。MATLAB 里可以调用:

exportToFMU('my_model')

这个函数支持通过参数对象控制导出细节,比如:

opts = fmuexport.ExportOptions; opts.FMUType = 'cosimulation'; opts.FMIVersion = '2.0'; opts.TargetPlatform = 'win64'; exportToFMU('my_model', opts)

不同版本函数名称可能有差异,建议先用 help exportToFMU 确认当前版本支持情况,或者直接用 App 界面最稳妥。

3.2 导出选项怎么填,每一步的含义

无论是 App 界面还是命令行,导出选项都差不多,我列一下核心参数:

FMI 版本(FMIVersion):选 2.0 是主流。FMI 3.0 版本新功能多,但大多数第三方工具还没完全跟上,2.0 兼容性最好。除非你确定接收方支持 3.0,否则不要追求新版本。

FMU 类型(FMUType):优先 Co-Simulation,具体原因前面已经说过。如果接收方希望自己主导求解过程,再考虑 Model Exchange。

目标平台(TargetPlatform):就是前面强调的位数和系统,默认是当前平台。如果接收方用的是 32 位软件,需要在 32 位 MATLAB 环境里导出,这个选项一般不能跨位数生成。

输出目录(OutputFolder):建议单独建一个文件夹,不要把 FMU 生成到模型目录里,不然后续模型文件多了容易混淆。

版本信息:可以填写 FMU 的描述信息、作者、版本号,交付给客户时这些信息非常有用。modelDescription.xml 会记录这些,接收方加载 FMU 时能直接看到。

3.3 从点击生成到拿到 FMU,中间到底发生了什么

点击生成之后,后台大概会经历这几个阶段:

第一步,模型代码生成。Simulink Coder 会为模型生成 C 代码,生成方式是自动化的,不需要你额外配置系统目标文件,导出工具内置好了。如果模型里有不支持代码生成的模块,这一步就会报错,错误信息会指向具体模块。

第二步,编译成动态库。生成的 C 代码会被编译器编译成对应平台的动态库文件,放在 FMU 包内的 binaries 目录下,同时自动生成 modelDescription.xml。这一步耗时最长,取决于模型复杂度和机器配置。

第三步,打包。工具把所有文件压缩成 .fmu 文件。实际上 FMU 文件就是一个标准 zip 包,你可以把后缀改成 .zip 后用解压工具打开查看内部结构。我第一次用这个方式查看 FMU 内部文件的时候,对 FMU 的理解一下子通透了。

生成成功后,建议立即做一件事:把生成的 FMU 文件重命名成有意义的名字。Simulink 默认用模型名字命名,如果模型名是 untitled.slx,生成的就是 untitled.fmu。交付外部之前,改成项目相关名字,加上版本号,比如 VehicleModel_v1.2.fmu,对方拿到手一目了然。

3.4 用重新导入做一次闭环验证

这一点强烈建议做。把刚导出的 FMU 再导入回 Simulink,用同样的输入信号同时驱动原始模型和 FMU 模型,对比两个模型的输出信号。仿真是可以设置人为误差的,例如故意加一个噪声输入,观察两条曲线。

如果开环输出曲线偏差在一定范围内(比如千分之一以内),说明导出过程没有引入异常;如果偏差很大,就要检查求解器配置和数据类型问题。这个闭环验证是我每次必做的环节,能拦住大部分低级错误。

4. 外部工具加载验证:用 FMPy 快速测试 FMU

4.1 为什么要在 Simulink 之外再验证一次

Simulink 环境里验证通过,只代表在 Simulink 自己的 FMU 加载器里没问题。但端交付场景中,对方用的可能是其他工具,加载方式、时间推进方式、变量映射方式都有差异。不同工具对 FMU 标准的实现严格程度不一样,有的宽松,有的严格。我在 Simulink 里加载流畅的 FMU,换到另一个工具就加载失败的情况,出现过不止一次。所以,建议你在一个 Simulink 之外的环境里再测一遍。

FMPy 是 Python 生态里最常用的 FMI 验证工具,免费开源,跨平台,支持 FMI 1.0 和 2.0。用它能模拟第三方软件加载 FMU 的过程,几分钟就能验证 FMU 基本可用性。

4.2 FMPy 验证步骤与示例代码

第一步,安装 FMPy:

pip install fmpy

第二步,写一个简单的 Python 脚本,读取 FMU 的模型描述并运行一次仿真:

import fmpy filename = 'VehicleModel_v1.2.fmu' # 读取模型描述,输出所有变量 model_description = fmpy.read_model_description(filename) for var in model_description.modelVariables: print(var.name, var.causality, var.variability, var.type) # 运行仿真 result = fmpy.simulate_fmu( filename, start_time=0.0, stop_time=10.0, step_size=0.01, output=['out1', 'out2'] ) print(result.keys()) # 可视化 import matplotlib.pyplot as plt plt.plot(result['time'], result['out1']) plt.xlabel('time (s)') plt.ylabel('out1') plt.grid(True) plt.show()

如果 FMU 有多个输入变量,simulate_fmu 函数还支持传递 start_values 参数,在加载前给输入变量赋初值。这一步能直接验证 modelDescription.xml 里的变量声明是否和 Simulink 模型里的端口一致。我实测下来,80% 的问题在这一步就能暴露出来,比如变量列表缺失、类型不匹配、FMU 库文件位数不对等。

4.3 测试中常见的外部加载现象

用 FMPy 加载时,有些 FMU 会报出一些看起来和 Simulink 无关的错误。举两个实际遇到过的:如果生成 FMU 时目标平台选错,比如在 64 位 MATLAB 生成了 win64 的库,但 Python 本身是 32 位进程,FMPy 加载会直接报“can’t load library”之类的错误。另一个常见问题是 FMU 内部有非零初值或持久化内部状态,仿真时外部没有正确初始化变量,输出一开始就跳变。这些问题在 Simulink 内部闭环验证时不容易暴露,但 FMPy 一测就现原形。

5. 踩坑记录与排查速查表

5.1 六个高频问题,照着这个表查

问题现象可能原因解决方案
导出时报错,提示找不到编译器编译器未配置或版本不兼容运行 mex -setup 重新选择编译器,升级或降级 VS 版本
导出成功,但外部工具加载失败目标平台位数不匹配用 32 位 MATLAB 导出 32 位 FMU,或在目标平台重新导出
外部工具运行结果和 Simulink 差异大求解器配置不正确,内部步长过大改用定步长求解器,减小步长或改用离散求解器
modelDescription.xml 里找不到某个变量端口未命名,信号类型为 inherit显式命名所有 Inport/Outport,指定明确数据类型
FMU 加载成功后没有输出初始化不完整,或输入端口未连接到外部信号在外部工具里检查输入映射,给所有输入提供初值
生成 FMU 文件很大,超出预期内部包含大量资源文件或日志数据清理模型中的日志模块,只保留功能链路

5.2 我反复踩过的三个坑,单独拿出来说

第一个坑是编译器版本问题。曾有一次用 R2023b 导出模型,mex 配置里还残留着老版本的 VS,导出时直接生成失败。报错信息不太直观,我一度以为是模型有问题,花了大半天排查,最后发现只是编译器路径配错了。后来我的习惯是先跑一次最简单的模型验证工具链,确认无误后再碰复杂模型。

第二个坑和采样时间有关。有个项目里模型是纯离散系统,基础步长设成 0.01 秒,但内部某个触发子系统用了 inherit 采样时间,导出 FMU 后,外部工具用固定步长驱动时,内部计算出现了多次重复计算的情况,导致输出波形上出现了奇怪的毛刺。排查方法是对比了 Simulink 内部仿真和 FMPy 仿真的输出,终于定位到那个触发模块。最后我把所有 inherit 采样时间改成显式的 0.01 秒,问题才消失。

第三个坑是参数调节。Simulink 默认会把模型工作区里的参数变成生成代码里的常量,也就是生成后的 FMU 外部是不能调参数的。如果你希望接收方能在外部调某些参数,比如 PID 增益、控制器限幅值,最好把这些参数改成输入端口。这样虽然模型接口会变得复杂,但灵活性大大提升。千万别以为模型里的 tunable 参数会自动出现在 FMU 参数表里,实测下来很多版本不会这么做,最可靠的办法还是端口传入。

5.3 一条实用的排查思路

遇到 FMU 导出或加载问题,优先跑这条链路定位:先确认编译工具链,再确认求解器配置,再检查接口变量,最后检查平台位数。不要一上来就在模型里大改。按这个顺序排查,能覆盖 90% 的常见问题。

如果模型里用了很多自定义模块,提供一个辅助方法:在导出前,先在模型配置里手动执行一次 Code Generation(代码生成),生成成功之后再点击 FMU 导出。手动代码生成报错时,错误信息往往能直接定位到具体模块,比 FMU 导出时报的笼统错误更容易排查。手动生成没问题了,再走 FMU 导出流程,成功率会高很多。

6. 延伸建议:把 FMU 交付这件事做得更顺

6.1 建立一套导出模板模型

我现在的习惯是维护一个模板模型,里面把所有导出相关配置都提前设好,包括定步长求解器、编译器适配、端口命名规范、初始值约定,等等。每次开始新项目,从模板复制一份,只需要改模型功能逻辑,导出相关的格式校验和硬件配置基本不用碰。这样大量减少重复劳动,也减少遗漏。

6.2 版本管理意识

FMU 是交付物,一定要纳入版本管理。我在实际项目中实行一套命名规则:模型名 + 版本号 + 导出日期,比如 BMS_Controller_v2.1_20250612.fmu。同时保留生成该 FMU 的模型源文件、MATLAB 版本、编译器和所用工具箱版本记录。这样一旦对方用了旧版本,或者某个 FMU 行为不对,你能快速回溯是哪个环境生成的,而不是对着一个孤零零的 fmu 文件发呆。

6.3 交接时写一份简短说明

FMU 虽然自带 modelDescription.xml,但接收方往往没空细看。我通常会在交付 FMU 时附带一个一页纸的说明,写清楚:这个 FMU 是干什么的、输入输出是什么单位、正常数值范围是多少、建议通信步长是多少、有没有特殊的初始化要求。极其简单,但能省掉大量沟通成本。我做过的交付里,这种方式让对接方评价都非常高,他们不再需要猜测模型的含义和边界条件,拿来就能集成到自己环境里。

好的,以上就是我在 Simulink 里从配置到生成 FMU 的完整流程。这套方式我用过很多次,从最初踩坑到后来形成规范,每一步都值得仔细推敲。这里再最后分享一个小技巧:每次导出结束,我都会在 FMPy 里用相同的输入信号跑一遍,和 Simulink 内部仿真波形做对比,记录最大绝对误差。这个数值不仅是验证手段,也是后续排查问题时的参考基准。如果以后某个版本的行为忽然变了,翻一翻对比记录,往往能迅速定位是哪次配置变化引起的。希望能帮你在 FMU 导出这条路上少走弯路。

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

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

立即咨询