3. MIL环境搭建与模型规范
MIL(Model-in-the-Loop)阶段的核心产物是“可被后续 SIL/HIL 直接复用的模型资产”。如果 MIL 阶段只追求“能跑出曲线”,而工具链、求解器、命名、单位、版本各自为政,那么进入 SIL 时往往要重做接口,进入 HIL 时又要重做标定与代码生成配置,返工成本极高。因此本节的重点不是“把模型搭出来”,而是把模型搭成可移植、可追溯、可自动构建的工程资产。
一、建模工具链:先定“主干”,再定“插件”
工具链的第一原则是单一主干。建议以 MATLAB/Simulink 作为模型主干,Stateflow 承担状态逻辑,Simulink Test 承担 MIL 测试,Embedded Coder 承担后续 SIL/HIL 的代码生成。外围工具只做“插件”,例如用 Git 做版本管理,用 Jenkins 或 GitLab CI 做自动构建,用 Excel/JSON 做参数标定表。主干一旦确定,模型库、回调脚本、代码生成配置都围绕它组织,避免出现“一半模型在 Simulink、一半逻辑在 Python 脚本”的割裂状态。
工具链还要固定目录骨架。一个可直接复用的 MIL 工程建议采用如下结构:
project_root/
├─ model/ # .slx 模型与库
├─ data/ # 参数 .m / .mat / .json
├─ test/ # MIL 测试用例与基线
├─ script/ # 初始化、构建、检查脚本
├─ config/ # 代码生成与求解器配置
└─ doc/ # 接口表、单位表、变更记录
目录骨架的意义在于:SIL 阶段只需替换“被测对象”的编译产物,HIL 阶段只需替换 I/O 驱动,而模型、参数、测试用例的路径不变,脚本无需重写。
二、求解器设置:定步长是 SIL/HIL 复用的前提
MIL 阶段常有人用变步长求解器(如 ode45)追求速度,但变步长在 SIL/HIL 中会带来两个问题:一是代码生成后步长不确定,二是与 HIL 实时步长不匹配。因此,凡是要进入 SIL/HIL 的模型,MIL 阶段就应使用定步长离散求解器,推荐 Fixed-step discrete,步长与目标 ECU 任务周期一致。
以某电机控制器为例,电流环任务周期为 100 μs,则 MIL 模型求解器设置为:
Solver: Fixed-step
Solver type: Discrete (no continuous states)
Fixed-step: 1e-4 % 100 μs
Sample time: 1e-4 % 与任务周期一致
同时要检查模型中是否存在连续状态(如积分器未离散化)。若存在,应改用离散积分器或前向欧拉,否则代码生成会报“连续状态不支持定步长离散”的错误。求解器设置还应写入 config/ 下的配置脚本,由初始化脚本统一加载,避免每次手工点选。
三、信号命名与单位约定:接口表的“法律效力”
信号命名和单位约定是 SIL/HIL 复用中最容易被忽视、却最容易出错的环节。建议采用“方向_对象_物理量_单位”的命名规则,例如 In_MotorSpd_rpm、Out_IdqRef_A、In_TrqCmd_Nm。方向前缀区分输入输出,对象段说明信号归属,物理量段说明含义,单位后缀直接写在名字里,避免“看名字猜单位”。
单位约定要形成一张接口表,至少包含:信号名、方向、数据类型、单位、量程、默认值、采样周期。下表为示例片段:
| 信号名 | 方向 | 类型 | 单位 | 量程 | 周期 |
|---|---|---|---|---|---|
| In_MotorSpd_rpm | 输入 | single | rpm | -12000~12000 | 100 μs |
| In_TrqCmd_Nm | 输入 | single | Nm | -200~200 | 100 μs |
| Out_IdqRef_A | 输出 | single | A | -400~400 | 100 μs |
| Out_PwmDuty_pu | 输出 | uint16 | pu | 0~1 | 100 μs |
接口表一旦冻结,SIL 的测试用例、HIL 的 I/O 映射都直接引用它,不再各自定义。若后续需要修改,必须走变更流程并同步更新接口表版本号。
四、模型版本管理:让“可复用”可追溯
模型版本管理不是简单地把 .slx 丢进 Git。二进制模型文件无法 diff,因此要配合模型导出为 XML 或使用 Simulink 的模型比较工具,并在提交时附带变更说明。建议每次提交包含:模型文件、接口表、参数文件、测试基线、变更记录。版本号采用 主版本.次版本.修订号,例如 v1.2.0,主版本对应接口变更,次版本对应功能变更,修订号对应参数或缺陷修复。
此外,模型应设置只读保护与签出机制,避免多人同时编辑同一模型。对于要进入 HIL 的模型,还应打上 release 标签,并记录对应的代码生成配置哈希,确保“模型版本—代码版本—标定版本”三者一致。
五、把规范落到脚本与检查项
规范若只写在文档里,执行时必然走样。建议把上述约定固化为初始化脚本 + 模型检查脚本。初始化脚本负责加载求解器配置、参数文件和接口表;模型检查脚本在每次提交前自动运行,检查项包括:求解器是否为定步长离散、是否存在未离散的连续状态、信号名是否符合命名规则、单位后缀是否与接口表一致、模型版本号是否更新。
例如,一个简化的检查脚本可以这样组织:
% check_model.m
mdl = 'MotorCtrl_MIL';
load_system(mdl);
cs = getActiveConfigSet(mdl);
assert(strcmp(get_param(cs,'SolverType'),'Fixed-step'), '求解器必须为定步长');
assert(strcmp(get_param(cs,'Solver'),'FixedStepDiscrete'), '必须使用离散求解器');
assert(strcmp(get_param(cs,'FixedStep'),'1e-4'), '步长必须为 100 μs');
% 信号名检查
sigNames = find_system(mdl,'FindAll','on','Type','line');
for i = 1:numel(sigNames)
nm = get_param(sigNames(i),'Name');
assert(~isempty(regexp(nm,'^(In|Out)_[A-Za-z]+_[A-Za-z]+_(rpm|Nm|A|pu|V)$','once')), ...
['信号命名不合规: ' nm]);
end
这类脚本一旦接入 CI,任何不合规的模型都无法合入主干,SIL/HIL 阶段拿到的就是“干净”的模型资产。实际项目中,检查项可根据团队约定增减,但求解器、命名、单位、版本这四项建议作为强制项保留。
最后需要强调,MIL 环境搭建不是一次性任务,而是贯穿模型生命周期的约束。工具链主干决定模型能否被自动构建,求解器设置决定模型能否被代码生成,命名与单位决定接口能否被 SIL/HIL 直接映射,版本管理决定问题能否被追溯。把这四件事在 MIL 阶段做扎实,后续 SIL 和 HIL 的工作量会显著下降,模型资产才真正具备“一次建模、多阶段复用”的价值。