4、MIL环境搭建:Simulink模型配置、测试平台搭建、信号注入与采集

好,咱们进入正题。MIL环境搭建,说白了就是给算法模型搭个“试验台”。你想想看,代码还没写,硬件还没影,怎么验证控制逻辑对不对?就得靠MIL。我个人习惯,这一步宁可多花点时间,把环境搭得规规矩矩,后面SIL、HIL阶段能省下大把的调试时间。

4.1 Simulink模型配置——别小看这一步

很多人觉得模型配置就是点几下鼠标,没什么技术含量。我在项目中遇到过太多次,因为配置不对,仿真跑出来的结果跟实际差十万八千里。嗯,这里要注意几个关键点。

4.1.1 求解器设置

连续系统还是离散系统?这决定了你选哪种求解器。

  • 连续系统:用ode45(变步长)或ode4(定步长)。我建议新手先用ode45,自适应步长,不容易发散。
  • 离散系统:必须用离散求解器(discrete)。固定步长,比如0.001秒。为什么?因为你的控制器就是按固定周期运行的。

重要:如果你做的是嵌入式控制器的MIL,99%的情况用离散求解器。连续求解器会掩盖采样时间带来的问题。

4.1.2 数据类型与信号属性

Simulink默认是double类型,但实际嵌入式代码里,谁用double啊?都是single、int16、uint8这些。我建议从一开始就设置好。

% 在模型工作空间中设置
Simulink.Bus.createObject(...
    'SignalObject', ...
    'BusName', 'ControlBus');

% 定义信号属性
sig = Simulink.Signal;
sig.DataType = 'single';
sig.InitialValue = '0';
sig.StorageClass = 'ExportedGlobal';

这样做的好处是,后面生成代码时,数据类型自动匹配,不用再手动改。我曾经因为偷懒没设数据类型,结果生成代码后,发现所有变量都是double,硬生生把MCU的RAM撑爆了……

4.1.3 采样时间设置

多速率系统很常见。比如控制周期1ms,通信周期10ms。怎么配?

  • 每个模块单独设置采样时间
  • 或者用Rate Transition模块做速率转换

小技巧:用颜色区分不同采样时间。在Simulink菜单里选 Display → Sample Time → Colors。一眼就能看出哪些模块跑得快,哪些跑得慢。

4.2 测试平台搭建——搭积木的思路

测试平台,我习惯叫它“Testbench”。说白了,就是把被测模型(DUT)包起来,外面接上信号源、观测器、评估模块。

4.2.1 标准测试平台结构

我常用的结构是这样的:

Testbench/
├── 信号生成模块 (Signal Generator)
│   ├── 阶跃信号
│   ├── 正弦波
│   ├── 自定义波形 (From Workspace)
│   └── 噪声注入
├── 被测模型 (DUT)
│   └── 你的控制算法
├── 参考模型 (Reference Model)
│   └── 理想行为模型
├── 比较与评估模块
│   ├── 误差计算
│   ├── 性能指标 (超调量、稳态误差等)
│   └── 断言检查 (Assertion)
└── 数据记录模块
    └── To Workspace / To File

为什么要加参考模型?因为你要知道“正确答案”是什么。比如你测一个PID控制器,参考模型就是理想PID响应。实际输出跟参考一对比,偏差一目了然。

4.2.2 信号生成——别只会用Step模块

实际工况哪有那么理想?我建议至少准备这几种信号:

信号类型 用途 Simulink模块
阶跃信号 测试响应速度、超调量 Step
斜坡信号 测试跟踪能力 Ramp
正弦波 测试频率响应 Sine Wave
方波 测试切换响应 Signal Generator
自定义波形 模拟真实工况 From Workspace + 时间序列
噪声 测试鲁棒性 Band-Limited White Noise

注意:From Workspace模块的数据格式必须是 [时间, 数值] 的二维数组。我见过有人把维度搞反了,仿真直接报错。

4.3 信号注入与采集——把“探针”插对地方

信号注入,就是把测试信号送进模型。信号采集,就是把模型输出抓出来分析。这两步做不好,前面全白搭。

4.3.1 信号注入的三种方式

  1. 直接连接:信号源模块直接连到DUT输入端口。最简单,但灵活性差。
  2. 通过Inport模块:在DUT外面加Inport,测试信号从工作空间或MATLAB脚本注入。适合批量测试。
  3. 通过Signal Builder:Simulink自带的信号编辑器,可以图形化编辑波形。我比较喜欢这个,直观。

我个人习惯用第二种。为什么?因为可以写脚本批量跑测试用例。

% 批量测试脚本示例
testCases = {...
    struct('input', [0 1; 1 1], 'expected', 0.5), ...
    struct('input', [0 0; 1 2], 'expected', 1.0)};

for i = 1:length(testCases)
    assignin('base', 'testInput', testCases{i}.input);
    sim('myModel');
    % 检查结果
    if abs(yout(end) - testCases{i}.expected) > 1e-3
        warning('Test case %d failed', i);
    end
end

4.3.2 信号采集——别漏掉关键数据

采集信号,我建议用To Workspace模块,把数据存到MATLAB工作空间。然后画图、算指标、做报告,一条龙。

关键要采集哪些信号?

  • 输入信号:确认注入正确
  • 输出信号:DUT的实际响应
  • 内部状态:比如积分器的值、状态机的当前状态
  • 误差信号:参考值与实际值的差

经验之谈:我习惯把采集频率设成仿真步长的10倍。比如步长0.001s,采集间隔0.0001s。这样画出来的曲线平滑,不会漏掉瞬态细节。

4.3.3 数据后处理——从数据里看出门道

仿真跑完了,数据也采了,然后呢?你得分析啊。

我常用的几个分析指标:

指标 计算公式 说明
超调量 (峰值 - 稳态值) / 稳态值 × 100% 越小越好
上升时间 从10%到90%稳态值的时间 越快越好
稳态误差 稳态时参考值与实际值的差 越小越好
均方根误差 sqrt(mean((ref - actual).^2)) 综合指标

用MATLAB代码算一下:

% 假设 yout 是输出信号,ref 是参考信号
overshoot = (max(yout) - yout(end)) / yout(end) * 100;
riseTime = find(yout >= 0.9*yout(end), 1) * Ts - ...
           find(yout >= 0.1*yout(end), 1) * Ts;
steadyError = abs(yout(end) - ref(end));
rmse = sqrt(mean((ref - yout).^2));

fprintf('超调量: %.2f%%\n', overshoot);
fprintf('上升时间: %.3f s\n', riseTime);
fprintf('稳态误差: %.4f\n', steadyError);
fprintf('RMSE: %.4f\n', rmse);

4.4 避坑指南——我踩过的坑,你别再踩

做MIL环境搭建这几年,我踩过的坑真不少。挑几个典型的说说。

坑1:代数环

我曾经搭了一个模型,一仿真就报“Algebraic Loop”。查了半天,发现是输出直接反馈到了输入,中间没有延迟。解决办法:加一个Memory模块或者Unit Delay模块,打破直接反馈。

坑2:仿真时间太长

有一次仿真一个10秒的场景,跑了整整20分钟。后来发现是求解器步长设得太小了。对于离散系统,步长设成采样时间的一半就够了,没必要用连续求解器。

坑3:数据丢失

用To Workspace模块时,默认只保存最后一个时间步的数据。我一开始没注意,结果分析时发现数据只有一行。解决办法:把Save format设成Array,Limit data points to last设成inf。

4.5 小结

MIL环境搭建,说白了就是三件事:配好模型、搭好平台、管好信号。每一步都不难,但细节决定成败。我建议你从一个小例子开始,比如一个简单的PID控制器,把整个流程走一遍。等熟练了,再上复杂的多速率系统。

下一章,咱们聊SIL环境搭建。到时候你会看到,MIL这一步做得扎实,SIL就是水到渠成的事。

一句话总结:MIL环境搭建,是V流程的起点。起点稳了,后面才跑得快。