☰
Simulink软件在环仿真(SIL)实战:从原理到配置与测试
2026/10/17 17:14:02 网站建设 项目流程

1. 软件在环仿真(SIL)是什么,为什么需要它

在基于模型的设计(MBD)开发流程中,从算法模型到产品代码,中间有一个关键过渡点:你写的 Simulink 模型最终要变成 C 代码,烧录到控制器里。这个过渡往往最怕两件事:一是模型能跑但代码行为不对,二是生成代码与模型仿真结果不一致。软件在环仿真(Software-in-the-Loop,SIL)就是专门用来验证“模型到代码”这个环节可靠性的手段。

通俗理解,SIL 就是把“控制器算法”从 Simulink 模型形态,替换成“已经编译好的软件代码形态”,然后在普通电脑上继续跑仿真。原来的被控对象模型可以保留在 Simulink 中,不需要真实的硬件。通过这种方式,我们可以在没有 ECU、没有单片机的情况下,提前验证目标代码的行为是否符合算法设计。

与大家熟悉的 MIL(Model-in-the-Loop,模型在环)相比,SIL 最大的变化是被测对象发生了变化。MIL 阶段测的是模型本身,算法是否正确;SIL 阶段测的是代码本身,代码是否忠实地实现了模型逻辑。如果只做 MIL 不做 SIL,很多代码生成时引入的问题要到 PIL 或者 HIL 阶段才能暴露,那时候排错代价会明显增加。

SIL 的常见应用场景包括:

  • 验证自动生成的代码与原始模型行为是否一致。
  • 在没有目标硬件的情况下,提前开展集成测试和回归测试。
  • 验证手写代码与模型代码混合集成后的正确性。
  • 进行大批量自动化测试,例如参数扫描、随机输入测试、边界条件测试。
  • 在 V 型开发流程中,作为 MIL 与 PIL/HIL 之间的过渡验证环节。

在汽车电子、航空航天、轨道交通等长周期控制器开发项目中,SIL 几乎是“标配”。因为控制器软件复杂度很高,如果直接跳到硬件测试,很多软件逻辑问题不好定位。而 SIL 可以在普通电脑上快速跑完大量测试用例,帮助开发者在进入硬件在环测试之前,先把软件级问题清干净。

要注意的是,SIL 不能完全替代 HIL。SIL 只验证软件代码逻辑,不验证目标芯片时钟、外设、中断优先级、驱动接口等硬件相关问题。它更像是“软件层面的第一道关口”,后续还需要通过 PIL 或 HIL 解决硬件相关风险。

2. MIL、SIL、PIL、HIL 的区别与联系

很多刚开始学习软件在环仿真的人,容易把 SIL、PIL、HIL 混为一谈。这里用一个表格帮大家梳理清楚。

仿真模式控制器算法形态被控对象运行环境控制器运行平台主要验证目标
MIL(模型在环)Simulink 模型Simulink 模型开发电脑算法逻辑正确性
SIL(软件在环)编译后的软件代码Simulink 模型或外部模型开发电脑通用处理器代码与模型一致性、软件逻辑
PIL(处理器在环)编译后的软件代码Simulink 模型或外部模型目标处理器/开发板代码在目标处理器上的正确性
HIL(硬件在环)真实控制器(ECU/MCU)实时仿真机真实控制器控制器软硬件整体行为、时序、故障注入

从中可以看出,SIL 和 PIL 的区别不在于代码,而在于代码运行在哪里:

  • SIL 的代码运行在开发电脑的处理器上,速度较快,方便调试。
  • PIL 的代码运行在目标处理器上,虽然平台更接近实际,但编译下载和部署成本更高。

所以在流程上,多数团队会先做 SIL 再做 PIL。SIL 能提前发现算法实现问题,PIL 再确认目标平台兼容性问题,HIL 最后做闭环系统级验证。

这里也顺带解释一个容易混淆的概念:SIL 中的“软件”并不一定专指 Simulink 自动生成的代码。如果你的项目有手写 C 代码,同样可以把手写代码编译后接入 Simulink 仿真环境,通过 SIL 模式验证它与模型算法、被控对象模型之间的协同是否正确。本文后面主要以 Simulink 自动生成代码的场景为例展开。

3. SIL 仿真的核心工作流程与原理

在深入实操之前,先了解 SIL 在 Simulink 内部是怎么工作的,这样遇到问题时更容易定位。

3.1 SIL 的基本流程

Simulink 中做软件在环仿真,本质上分为三个步骤:

  1. 根据模型生成 C/C++ 代码。
  2. 将生成的代码编译成可执行模块,通常以 S-Function 或共享库的形式加载到 Simulink 中。
  3. 运行仿真。此时模型中原来的控制器算法被替换成这段编译好的代码,被控对象模型仍由 Simulink 正常计算。

从用户角度看,仿真还是那个仿真,示波器还能看波形,数据还能导出到工作区。但真正参与计算的核心控制器部分,已经从“模型解释执行”变成了“目标代码执行”。

3.2 SIL 与普通仿真的差异

普通仿真模式下,Simulink 以解释执行的方式运行模型,重点在于验证算法逻辑,并不会生成可供嵌入式控制器使用的代码。

加速器模式和快速加速器模式虽然也会生成代码,但它们的目的是“加速计算”,生成代码的路径和产品级代码不是一回事。例如,加速器生成的代码不追求运行效率,也不会按照目标芯片架构做优化。

SIL 模式则不同。使用 SIL 时,代码生成配置会走产品代码生成路径,例如使用ert.tlc系统目标文件,生成适合嵌入式系统的代码。然后这段代码以软件在环的形式参与仿真。正因为代码生成路径与真实产品一致,SIL 的验证结果才更有说服力。

3.3 SIL 的三种常见接入方式

在实际项目中,SIL 可以通过三种方式接入:

接入方式操作位置适用场景
顶层模型 SIL模型设置 → 代码生成 → 验证 → 仿真模式对整个模型做验证,适合快速验证完整算法
模型引用块 SIL被引用模型对应的 Model 块 → 块参数 → 仿真模式对控制器子系统做 SIL,被控对象仍保持模型仿真
外部代码 SIL将手写 C 代码通过 S-Function 或其他接口集成验证手写代码与模型的协同逻辑

其中第二种“模型引用块 SIL”是最符合实际工程习惯的。因为一个完整系统里面,往往包含控制器、被控对象、传感器、执行器等模块。真正需要生成代码的是控制器部分,被控对象在仿真中通常只是一个被模拟的环境。因此,把控制器单独建到一个模型里,再以模型引用的方式接入顶层系统,然后在模型引用块上打开 SIL,是最清晰的验证结构。

4. 软件在环仿真环境准备

做 SIL 仿真前,需要先确认环境和工具箱是否齐全。

4.1 必需工具箱

  • Simulink:建模和仿真主环境。
  • Simulink Coder:负责从模型生成 C/C++ 代码。
  • Embedded Coder:生成适合嵌入式控制器的优化代码,属于进阶选项。如果只是学习 SIL,有 Simulink Coder 基本够用;如果目标是做产品级代码生成,建议安装 Embedded Coder。
  • MATLAB Coder:如果你的算法通过 MATLAB Function 块编写,可能需要用到它。

部分 SIL 测试还会用到 Simulink Test、Simulink Coverage,前者用于自动化测试管理,后者用于代码覆盖率分析。初学者第一阶段不强制安装,后面做项目规范化时可以再补充。

4.2 C/C++ 编译器

SIL 模式需要把生成的 C 代码编译成可执行模块,所以电脑上必须安装可用的 C/C++ 编译器。在 Windows 环境中,常用的编译器是 Microsoft Visual C++ 或者 MinGW-w64。建议尽量安装官方支持的编译器版本。

打开 MATLAB,在命令行窗口执行以下命令确认编译器可用:

mex -setup

如果返回结果列出了可用的编译器,说明 C 编译器已经配置好;如果提示找不到编译器,需要先安装对应版本的 Visual Studio 或者 MinGW-w64。

4.3 版本说明

不同 MATLAB 版本的菜单项和默认配置可能略有差异。本文示例以较新的 MATLAB 版本为主,例如 R2021b 及以后版本。如果你使用的是旧版本,菜单名称可能稍有不同,但核心逻辑一致:找到“代码生成”配置页,将“仿真模式”设置为“软件在环”,再生成代码运行仿真即可。

5. 完整实战:从模型搭建到 SIL 仿真

下面用一个简单但完整的闭环控制系统,演示如何从零开始完成软件在环仿真。这个例子包含一个离散 PID 控制器和一个一阶被控对象,在工程中非常有代表性。

5.1 搭建系统模型

假设我们的控制目标是一个简单被控对象,传递函数为:

G(s) = 1 / (s + 1)

控制器采用离散 PID 算法,采样时间为 0.01 秒。

打开 Simulink,新建一个模型,命名为sil_demo_top。在模型中添加以下模块:

模块模块库说明
StepSimulink/Sources输入阶跃信号,例如幅值为 1
SumSimulink/Math Operations计算误差 e = r - y
Discrete PID ControllerSimulink/Continuous 或按名称搜索控制器,采样时间设为 0.01
Transfer FcnSimulink/Continuous被控对象,设为 1/(s+1)
ScopeSimulink/Sinks看输出波形
To WorkspaceSimulink/Sinks导出仿真数据到工作区,用于 MIL/SIL 对比

为了更像工程结构,建议把控制器单独封装成子系统。操作方式:选中 Discrete PID Controller 模块,右键 → Create Subsystem from Selection。这样生成一个名为Subsystem的子系统,后续可以把它单独抽出来作为控制器模型。

模型连接关系如下:

Step ──► Sum(+) ──► PID Controller ──► Transfer Fcn ──► Scope ▲ │ └─────────────────────────────────┘

在 To Workspace 模块中,设置变量名为yout,保存格式选择Timeseries,这样后续方便用脚本分析。

5.2 设置求解器

由于控制器是离散的,而被控对象是连续系统,仿真需要采用固定步长求解器。打开“模型设置”,进入“求解器”页面,配置如下:

类型:固定步长 求解器:ode4(Runge-Kutta) 固定步长:0.01 停止时间:10

为什么必须用固定步长?因为代码生成之后,目标代码的采样周期必须是确定的。如果使用变步长求解器,Simulink 会按误差自动调整仿真步长,生成代码后这种行为无法在目标控制器上复现,导致 SIL 结果失去参考意义。

5.3 将控制器封装成模型引用

为了让 SIL 验证更符合工程习惯,我们需要把控制器独立成一个模型,然后在顶层模型中通过“模型引用”方式调用它。

步骤一:右键控制器子系统,选择“Cut”。然后在新建模型中粘贴,并给这个新建模型添加两个端口:

  • Inport 表示误差输入。
  • Outport 表示控制量输出。

将新建模型保存为sil_demo_controller.slx。

步骤二:回到sil_demo_top顶层模型,把原来的子系统删除,从模块库中拖入一个 Model 块:

Simulink / Ports & Subsystems / Model

双击 Model 块,选择sil_demo_controller。此时顶层模型会根据模型引用的端口自动生成输入输出连接。如果连接不对,重新连线即可。

最后模型结构如下:

sim_demo_top ├── Model: sil_demo_controller └── Plant: Transfer Fcn + Step + Scope + To Workspace

5.4 配置控制器模型的代码生成

在生成 SIL 之前,需要给控制器模型配置代码生成环境。

打开sil_demo_controller.slx,进入“模型设置”,找到“代码生成”页面:

代码生成 └─ 系统目标文件:ert.tlc

这里选择ert.tlc是因为我们要模拟嵌入式控制器代码。如果只是验证功能,grt.tlc也可以,但嵌入式目标更贴近真实产品。

然后进入“代码生成 → 验证”页面,将“仿真模式”设置为“软件在环”:

代码生成 └─ 验证 └─ 仿真模式:Software-in-the-loop

部分版本中,还需要勾选“生成代码报告”,这样代码生成完成后会自动打开 HTML 报告,方便查看代码结构和变量映射。

5.5 在模型引用块上启用 SIL

回到sil_demo_top.slx,右键点击控制器对应的 Model 块,选择“块参数”,找到“仿真模式”下拉框:

仿真模式:软件在环(Software-in-the-loop)

如果没有这个选项,说明代码生成配置尚未生效,请先回到第 5.4 步确认系统目标文件和验证选项已经设置。

这里也给出命令行方式,便于后续脚本化自动化测试:

% 切换控制器模型引用块为 MIL 模式 set_param('sil_demo_top/Controller', 'SimulationMode', 'normal'); % 切换控制器模型引用块为 SIL 模式 set_param('sil_demo_top/Controller', 'SimulationMode', 'software-in-the-loop');

需要注意,sil_demo_top/Controller是模型引用块在顶层模型中的路径,实际名称以你的模型为准。

5.6 运行 MIL 仿真并保存结果

为了对比,先运行一次 MIL 仿真,也就是让控制器以普通模型模式运行。

设置好模型引用块为normal后,在命令行执行:

simOut_mil = sim('sil_demo_top', 'StopTime', '10'); y_mil = simOut_mil.yout{1}.Values.Data; t_mil = simOut_mil.tout;

仿真结束后,把 MIL 结果保存下来:

save('mil_result.mat', 't_mil', 'y_mil');

此时可以打开 Scope 查看波形,正常的闭环阶跃响应应该先上升再稳定到 1,超调量不大。

5.7 运行 SIL 仿真并对比结果

接下来把控制器模型引用块切换为 SIL 模式:

set_param('sil_demo_top/Controller', 'SimulationMode', 'software-in-the-loop');

然后再次运行仿真:

simOut_sil = sim('sil_demo_top', 'StopTime', '10'); y_sil = simOut_sil.yout{1}.Values.Data; t_sil = simOut_sil.tout;

首次运行 SIL 时,Simulink 会先生成代码,再调用编译器编译。命令行会输出类似下面的构建信息:

### Starting build procedure for model: sil_demo_controller ### Successful completion of build procedure for model: sil_demo_controller

构建完成后,仿真会自动继续。SIL 仿真通常比普通仿真慢一些,因为每个采样步长都要通过 S-Function 调用生成的代码。

对比两种模式的结果:

figure; plot(t_mil, y_mil, 'b-', 'LineWidth', 1.5); hold on; plot(t_sil, y_sil, 'r--', 'LineWidth', 1.5); grid on; legend('MIL', 'SIL'); xlabel('时间 t/s'); ylabel('输出 y'); title('MIL 与 SIL 仿真结果对比');

正常情况下,两条曲线会基本重合。如果两条曲线有明显差异,说明控制器代码生成过程中存在数据精度、采样周期或初始化问题,需要进一步排查。

5.8 在构建日志中定位生成代码

SIL 运行完成后,工作目录下会生成slprj文件夹,里面存放代码生成中间产物。如果项目配置了代码生成报告,也可以直接在报告里查看控制器代码。对于简单的 PID 控制器,代码量不大,可以重点看以下几个文件:

sil_demo_controller.c sil_demo_controller.h sil_demo_controller_private.h

对比生成代码里的 PID 系数和 Simulink 模型中的离散 PID 参数,确认系数是否一致。这一步是 SIL 验证中非常实用的细节,很多项目都是在这里发现参数定标或变量名映射问题。

6. 常见问题与排查思路

SIL 仿真在第一次使用时,容易遇到一些配置和编译问题。这里整理最常见的几种情况。

问题现象常见原因解决思路
模型引用块仿真模式里没有“软件在环”选项控制器模型未正确配置代码生成目标打开控制器模型,设置系统目标文件ert.tlc,保存后再回到顶层模型
构建时报错找不到 System Target File未安装 Simulink Coder 或 Embedded Coder检查工具箱列表,确认两个工具箱已安装且许可有效
编译报错找不到 C 编译器未安装编译器或 MATLAB 未识别运行mex -setup,选择正确的编译环境
SIL 仿真结果与 MIL 差异较大数据类型不一致、求解器步长不合适、PID 初始化状态不一致检查模型数据类型,设置相同固定步长,对比初始化条件
仿真运行非常慢SIL 每次调用都要经过代码生成、编译和外部接口调用避免在模型内部频繁使用不必要的数据转换;大批量测试用脚本批处理
生成代码文件特别多,找不到关键文件项目目录未整理,代码生成中间文件混杂使用 Simulink 项目管理,将代码生成路径独立到slprj或指定工作文件夹
切换 SIL 后模型报 “Simulation mode cannot be changed”模型正在运行或代码生成未完成先停止仿真,清理生成代码,重新构建
信号日志丢失,无法获取输出信号没有标记保存,或优化将信号优化掉了使用 Outport 导出信号,或者在模型设置中开启信号日志

如果你遇到的是比较模糊的编译错误,建议优先清理以下内容:

% 关闭当前模型 bdclose('sil_demo_top'); % 删除代码生成中间目录 if exist('slprj', 'dir') rmdir('slprj', 's'); end % 重新打开模型 open_system('sil_demo_top');

这种“清缓存、重新生成代码、重新运行”的操作,能解决大部分奇怪的构建问题。

7. 软件在环仿真的最佳实践与工程建议

SIL 不是一个简单的开关,用好了能显著提升代码质量,用不好会变成重复劳动。下面这些建议来自实际项目经验,供参考。

7.1 为不同验证阶段建立独立配置集

在一个模型中,可能同时存在 MIL、SIL、PIL 三种模式的配置需求。如果每次手动改求解器和代码生成参数,很容易出错。建议使用 Simulink 的“配置引用”功能,为不同仿真阶段创建独立配置集。

在模型设置中:

Simulation → Model Configuration Parameters → Active configuration

或者使用脚本:

% 创建配置引用 configRef = Simulink.ConfigSetRef; configRef.Name = 'SIL_Config';

实际项目中,通常会有MIL_Config、SIL_Config、PIL_Config三套配置。这样切换验证模式时,只需激活对应配置集,不需要逐项核对参数。

7.2 用数据字典管理参数

SIL 验证过程中,经常需要修改控制器参数、采样时间、运行时长。如果这些参数分散在模型内部,维护成本很高。推荐把参数集中到 Simulink 数据字典中,例如controllerParams.sldd。

在数据字典中定义:

Ts = 0.01; Kp = 1.2; Ki = 0.5; Kd = 0.1;

模型中的 PID 控制器参数直接引用这些变量。这样在自动化测试时,只需要修改字典中的数值,就能批量跑不同参数组合下的 SIL 仿真。

7.3 用脚本实现 SIL 自动化测试

SIL 适合大批量测试,但手工点击“运行”显然太低效。可以用脚本驱动仿真和结果对比。

这里给出一个简单的自动化框架思路:

% 定义测试参数 paramList = [1.0, 0.2, 0.05; 1.2, 0.5, 0.10; 0.8, 0.1, 0.01]; for i = 1:size(paramList, 1) % 更新控制器参数 Kp = paramList(i, 1); Ki = paramList(i, 2); Kd = paramList(i, 3); % 运行 MIL set_param('sil_demo_top/Controller', 'SimulationMode', 'normal'); simOutMIL = sim('sil_demo_top', 'StopTime', '10'); % 运行 SIL set_param('sil_demo_top/Controller', 'SimulationMode', 'software-in-the-loop'); simOutSIL = sim('sil_demo_top', 'StopTime', '10'); % 对比 yMIL = simOutMIL.yout{1}.Values.Data; ySIL = simOutSIL.yout{1}.Values.Data; diffMax = max(abs(yMIL - ySIL)); fprintf('测试用例 %d:最大误差 = %.6f\n', i, diffMax); end

这种方式虽然简单,但已经具备仿真测试自动化雏形。如果项目规模更大,建议使用 Simulink Test 管理测试用例、预期结果和覆盖率数据。

7.4 数据类型与步长一致性是重点

SIL 结果和 MIL 结果不一致,最常见的根因就是数据类型和采样步长。

  • 确认控制器模型输入输出都是double,如果使用定点数,要检查定标方式。
  • 确认仿真求解器是固定步长,且步长与控制器采样时间存在合理关系。
  • 确认模型中的单位延迟、PID 初始状态在两次仿真中一致。
  • 如果模型中存在 MATLAB Function 块,建议用codegen提前检查类型推导结果。

7.5 SIL 不是越早越好,也不是越多越好

SIL 验证是有成本的,尤其是代码生成和编译阶段比较耗时。建议在以下时间点做 SIL:

  • 算法模型稳定、MIL 测试通过之后。
  • 控制器接口定义完成之后。
  • 每次修改控制器代码生成相关配置时。
  • 每次修改控制器算法逻辑时。

不建议在算法还在频繁调整的阶段就反复跑 SIL,那样只会增加编译等待时间。建议先在 MIL 阶段把算法迭代成熟,再进入 SIL 阶段做代码级验证。

7.6 保留构建日志和测试记录

在 V 型开发流程中,SIL 测试结果往往需要作为交付物提交给质量团队或配合安全标准评审。因此建议每次 SIL 测试保留以下信息:

  • MATALB 和 Simulink 版本号。
  • 系统目标文件名称。
  • 求解器和步长设置。
  • 测试用例列表。
  • MIL 与 SIL 对比误差。
  • 生成的代码报告。

这些记录不仅方便追溯问题,也是很多行业标准评审时的必要材料。

8. 总结与下一步学习建议

本文围绕 Simulink 软件在环仿真,讲解了 SIL 的基本概念、与 MIL/PIL/HIL 的关系、环境准备、核心配置、完整实战流程以及常见问题排查方法。通过一个离散 PID 控制器的闭环系统,演示了如何从普通模型仿真切换到 SIL 仿真,并对比验证结果。

掌握 SIL 之后,下一步可以继续学习:

  • PIL 处理器在环仿真,看看代码在目标处理器上的行为差异。
  • HIL 硬件在环仿真,结合实时仿真机验证真实控制器。
  • Simulink Test 自动化测试,把 SIL 变成可持续的回归测试体系。
  • 代码覆盖率分析,了解测试用例对代码的覆盖情况。

建议你先找一个小模型,比如本文的 PID 闭环系统,把 MIL 到 SIL 的流程完整跑一遍。第一次跑通可能花一些时间,但跑通之后,后续再做复杂控制器的软件验证,就会顺很多。如果你在实际操作中遇到其他问题,欢迎在评论区留言交流。

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

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

立即咨询