该项目旨在使用Simulink来完成某些计算和仿真,计算工具采用自己的求解器,因此Simulink在这里的作用主要为模拟输入输出接口和触发信号,后续可能会引入优化器来对参数进行优化,本篇作为开头,主要介绍如何使用Simulink调用外部工具。
创建m文件
首先打开Matlab,指定工作路径,在命令行窗口输入edit sfun_callXXX
从而在当前路径下创建sfun_callXXX.m文件,输入下述代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| function sfun_callXXX(block)
setup(block);
end
function setup(block) block.NumInputPorts = 1; block.SetPreCompInpPortInfoToDynamic; block.InputPort(1).Dimensions = 1; block.InputPort(1).DatatypeID = 0; block.InputPort(1).Complexity = 'Real'; block.InputPort(1).DirectFeedthrough = true;
block.NumOutputPorts = 1; block.SetPreCompOutPortInfoToDynamic; block.OutputPort(1).Dimensions = 1; block.OutputPort(1).DatatypeID = 0; block.OutputPort(1).Complexity = 'Real';
block.NumDworks = 1; block.Dwork(1).Name = 'prevInput'; block.Dwork(1).Dimensions = 1; block.Dwork(1).DatatypeID = 0; block.Dwork(1).Complexity = 'Real'; block.Dwork(1).UsedAsDiscState = true;
block.SampleTimes = [1.0 0.0];
block.SimStateCompliance = 'DefaultSimState';
block.InitializeConditions = @InitConditions; block.Outputs = @Output; end
function InitConditions(block) block.Dwork(1).Data = 0; end
function Output(block) u = block.InputPort(1).Data; prev = block.Dwork(1).Data;
if u == 1 && prev == 0 exePath = 'I:\XXX.exe'; paramPath = 'C:\Users\pc\Desktop\XXX';
if ~isfile(exePath) warning('[XXX] EXE 路径不存在: %s', exePath); block.OutputPort(1).Data = -2; block.Dwork(1).Data = u; return; end
if ~isfolder(paramPath) warning('[XXX] 参数路径不存在: %s', paramPath); block.OutputPort(1).Data = -3; block.Dwork(1).Data = u; return; end
exePathQuoted = ['"' exePath '"']; paramPathQuoted = ['"' paramPath '"']; args = ['1|1|' paramPathQuoted]; cmd = [exePathQuoted ' ' args];
disp(['[XXX] 正在执行: ' cmd]);
status = system(cmd);
if status == 0 block.OutputPort(1).Data = 1; else block.OutputPort(1).Data = -1; end else block.OutputPort(1).Data = 0; end
block.Dwork(1).Data = u; end
|
该函数的主要作用是在 Simulink 中通过输入触发器调用外部程序 XXX.exe,实现如下特性:
- 支持多次触发执行,但只在输入信号从
0
到 1
的上升沿执行
- 执行结果通过输出端口返回状态码:
1
→ 执行成功;
-1
→ 执行失败;
-2
→ EXE 路径不存在;
-3
→ 参数路径不存在;
0
→ 没有触发。
主函数部分
1 2 3 4
| function sfun_callXXX(block)
setup(block); end
|
这是主入口函数,Simulink 在加载该 S-Function 时会调用这个函数。block
是代表这个 S-Function 块的对象,包含输入输出、状态等所有接口
1 2 3 4 5 6
| block.NumInputPorts = 1; block.SetPreCompInpPortInfoToDynamic; block.InputPort(1).Dimensions = 1; block.InputPort(1).DatatypeID = 0; block.InputPort(1).Complexity = 'Real'; block.InputPort(1).DirectFeedthrough = true;
|
设置只有 1 个输入端口,类型为 double
实数,输入数据是一个触发信号,为 1
表示要执行 XXX.exe
1 2 3 4 5
| block.NumOutputPorts = 1; block.SetPreCompOutPortInfoToDynamic; block.OutputPort(1).Dimensions = 1; block.OutputPort(1).DatatypeID = 0; block.OutputPort(1).Complexity = 'Real';
|
也只有 1 个输出端口,用于输出执行状态
1 2 3 4 5 6
| block.NumDworks = 1; block.Dwork(1).Name = 'prevInput'; block.Dwork(1).Dimensions = 1; block.Dwork(1).DatatypeID = 0; block.Dwork(1).Complexity = 'Real'; block.Dwork(1).UsedAsDiscState = true;
|
Dwork
是 S-Function 中的离散状态变量,在仿真期间保留,这里用来保存上一时刻的输入值,从而判断是否出现了 0→1
的跳变(上升沿)
1 2 3 4
| block.SampleTimes = [1.0 0.0]; block.SimStateCompliance = 'DefaultSimState'; block.InitializeConditions = @InitConditions; block.Outputs = @Output;
|
SampleTimes = [1.0 0.0]
→ 每 1 秒调用一次 Output
函数,注册初始化函数和主输出函数
初始化函数 InitConditions
1 2 3
| function InitConditions(block) block.Dwork(1).Data = 0; end
|
初始化 Dwork 状态变量,表示开始时没有任何触发信号
主执行函数 Output
1 2
| u = block.InputPort(1).Data; prev = block.Dwork(1).Data;
|
u
是当前输入值,prev
是上一个采样时刻的输入值,用于检测跳变
- 只有从
0 → 1
的跳变,才执行外部命令
- 如果持续为
1
,不会重复执行
1 2
| exePath = '...XXX.exe'; paramPath = '...指定输入';
|
exePath
是要调用的外部可执行文件路径,paramPath
是输入数据或配置文件的路径
1 2 3 4 5 6 7 8 9 10 11 12 13
| if ~isfile(exePath) warning('[XXX] EXE 路径不存在: %s', exePath); block.OutputPort(1).Data = -2; block.Dwork(1).Data = u; return; end
if ~isfolder(paramPath) warning('[XXX] 参数路径不存在: %s', paramPath); block.OutputPort(1).Data = -3; block.Dwork(1).Data = u; return; end
|
- 如果 EXE 不存在 → 返回
-2
- 如果参数目录不存在 → 返回
-3
1 2 3 4 5
| exePathQuoted = ['"' exePath '"']; paramPathQuoted = ['"' paramPath '"']; args = ['1|1|' paramPathQuoted]; cmd = [exePathQuoted ' ' args]; disp(['[XXX] 正在执行: ' cmd]);
|
1 2 3 4 5 6 7
| status = system(cmd);
if status == 0 block.OutputPort(1).Data = 1; else block.OutputPort(1).Data = -1; end
|
system(cmd)
用于调用系统命令
- 如果返回值为
0
,表示执行成功 → 输出 1
- 否则 → 输出
-1
1 2 3
| else block.OutputPort(1).Data = 0; end
|
1
| block.Dwork(1).Data = u;
|
- 用当前输入值覆盖
Dwork
,供下一次仿真调用使用
输出值 |
含义 |
1 |
成功执行 XXX.exe |
-1 |
执行失败 |
-2 |
找不到 EXE |
-3 |
参数目录不存在 |
0 |
无触发(未上升沿) |
代码流程概览
阶段 |
Simulink 触发 |
函数 |
功能描述 |
模块加载时 |
Simulink 加载模型 |
sfun_callXXX() → setup() |
初始化输入输出端口、采样时间、Dwork 状态变量等 |
仿真开始时 |
点击仿真按钮 Run |
InitializeConditions() |
将 prevInput 初始化为 0 |
每个仿真步 |
每秒调用一次 |
Output() |
检测输入信号是否跳变为 1,如果是就执行 XXX.exe |
在Simulink中使用S Function
在命令行窗口输入simulink
或直接点击菜单面板“Simulink”来打开Simulink仿真工具,新建空白模型

将该模型保存到当前目录下(与上述.m文件相同),在库浏览器中依次将Constant、Level-2 MATLAB S-Function(不能直接选S-Function,因为我们的代码是针对Level 2的)、Display模块拖入,并连接如下

双击S-Function模块,将 S-function name 设为 sfun_callXXX
,确认之后提示出错。
这里的主要原因为MATLAB Level-2 S-Function 的 Dwork
区只能在 PostPropagationSetup()
阶段设置,而目前把 NumDworks
和 Dwork 配置写在了 setup()
里,这是不允许的,修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| function sfun_callXXX(block)
setup(block); end
function setup(block)
block.NumInputPorts = 1; block.NumOutputPorts = 1;
block.SetPreCompInpPortInfoToDynamic; block.SetPreCompOutPortInfoToDynamic;
block.InputPort(1).Dimensions = 1; block.InputPort(1).DatatypeID = 0; block.InputPort(1).Complexity = 'Real'; block.InputPort(1).DirectFeedthrough = true;
block.OutputPort(1).Dimensions = 1; block.OutputPort(1).DatatypeID = 0; block.OutputPort(1).Complexity = 'Real';
block.SampleTimes = [1 0];
block.SimStateCompliance = 'DefaultSimState';
block.RegBlockMethod('PostPropagationSetup', @PostPropSetup); block.RegBlockMethod('InitializeConditions', @InitConditions); block.RegBlockMethod('Outputs', @Output); end
function PostPropSetup(block)
block.NumDworks = 1; block.Dwork(1).Name = 'prevInput'; block.Dwork(1).Dimensions = 1; block.Dwork(1).DatatypeID = 0; block.Dwork(1).Complexity = 'Real'; block.Dwork(1).UsedAsDiscState = true; end
function InitConditions(block)
block.Dwork(1).Data = 0; end
function Output(block)
u = block.InputPort(1).Data; prev = block.Dwork(1).Data;
if u == 1 && prev == 0 exePath = 'I:\XXX.exe'; paramPath = 'C:\Users\pc\Desktop\XXX';
if exist(exePath,'file') ~= 2 warning('[XXX] EXE 不存在: %s', exePath); block.OutputPort(1).Data = -2; elseif exist(paramPath,'dir') ~= 7 warning('[XXX] 参数路径不存在: %s', paramPath); block.OutputPort(1).Data = -3; else cmd = ['"' exePath '" 1|1|"' paramPath '"']; disp(['[XXX] 执行: ' cmd]);
status = system(cmd); block.OutputPort(1).Data = double(status == 0) * 2 - 1; end else block.OutputPort(1).Data = 0; end
block.Dwork(1).Data = u; end
|
运行仿真,观察到Display显示为1表明正确调用执行,为了方便截图,我把Display换为Scope,观察如下

第一秒,输入从0→1
调用exe成功并输出1
,第二秒没有跳变发生,输出为0
Simulink 对该 S-Function 模块的调用时序如下
时间 |
执行 |
来自 S-Function 的函数调用 |
说明 |
0s |
初始化 |
setup() → InitializeConditions() |
初始化端口、状态变量(prevInput=0) |
每秒 |
仿真步 |
Output() 被调用一次 |
判断输入信号是否从 0→1 变化,若是就调用 EXE |
假设输入信号如下
步数 |
输入 u |
prevInput |
是否执行 EXE? |
输出值 |
1 |
0 |
0 |
否 |
0 |
2 |
1 |
0 |
是(跳变) |
1 / -1 |
3 |
1 |
1 |
否 |
0 |
4 |
0 |
1 |
否 |
0 |
5 |
1 |
0 |
是(跳变) |
1 / -1 |