该项目旨在使用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)
% Level-2 MATLAB S-Function: 可多次触发执行 XXX.exe,并做路径检查

setup(block);

end

function setup(block)
% 一个输入端口:触发信号(1 表示触发执行)
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';

% 一个 Dwork 变量:用于保存上一个输入(用于边沿检测)
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;

% 每 1 秒调用一次
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;

% 只在输入从 0 → 1 的跳变时执行
if u == 1 && prev == 0
% 配置路径
exePath = 'I:\XXX.exe';
paramPath = 'C:\Users\pc\Desktop\XXX';

% 检查 EXE 是否存在
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

% 更新 Dwork 状态
block.Dwork(1).Data = u;
end

该函数的主要作用是在 Simulink 中通过输入触发器调用外部程序 XXX.exe,实现如下特性:

  • 支持多次触发执行,但只在输入信号从 01 的上升沿执行
  • 执行结果通过输出端口返回状态码:
    • 1 → 执行成功;
    • -1 → 执行失败;
    • -2 → EXE 路径不存在;
    • -3 → 参数路径不存在;
    • 0 → 没有触发。

主函数部分

1
2
3
4
function sfun_callXXX(block)
% Level-2 MATLAB S-Function: 可多次触发执行 XXX.exe,并做路径检查
setup(block);
end

这是主入口函数,Simulink 在加载该 S-Function 时会调用这个函数。block 是代表这个 S-Function 块的对象,包含输入输出、状态等所有接口

1
function setup(block)
1
2
3
4
5
6
block.NumInputPorts = 1;
block.SetPreCompInpPortInfoToDynamic;
block.InputPort(1).Dimensions = 1;
block.InputPort(1).DatatypeID = 0; % double
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; % 初始值设为0,表示“未触发”
end

初始化 Dwork 状态变量,表示开始时没有任何触发信号

主执行函数 Output

1
function Output(block)
1
2
u = block.InputPort(1).Data;
prev = block.Dwork(1).Data;

u 是当前输入值,prev 是上一个采样时刻的输入值,用于检测跳变

1
if u == 1 && prev == 0
  • 只有从 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|1|是参数需要)

    1
    "I:\...\XXX.exe" 1|1|"C:\Users\...\指定输入"
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
  • 输入无变化或不是上升沿时,输出 0
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仿真工具,新建空白模型

image-20250708211249478

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

image-20250708213434637

双击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)
% Level-2 MATLAB S-Function:检测 0→1 边沿并调用 XXX.exe
setup(block);
end

%-----------------------------------------------------------%
function setup(block)
%% 1. 端口
block.NumInputPorts = 1;
block.NumOutputPorts = 1;

block.SetPreCompInpPortInfoToDynamic;
block.SetPreCompOutPortInfoToDynamic;

block.InputPort(1).Dimensions = 1;
block.InputPort(1).DatatypeID = 0; % double
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';

%% 2. 采样时间(每 1 s 执行一次)
block.SampleTimes = [1 0];

%% 3. S-Function 状态机合规
block.SimStateCompliance = 'DefaultSimState';

%% 4. 注册回调 —— 用 RegBlockMethod(关键)
block.RegBlockMethod('PostPropagationSetup', @PostPropSetup);
block.RegBlockMethod('InitializeConditions', @InitConditions);
block.RegBlockMethod('Outputs', @Output);
end
%-----------------------------------------------------------%
function PostPropSetup(block)
%% Dwork 在此阶段声明
block.NumDworks = 1;
block.Dwork(1).Name = 'prevInput';
block.Dwork(1).Dimensions = 1;
block.Dwork(1).DatatypeID = 0; % double
block.Dwork(1).Complexity = 'Real';
block.Dwork(1).UsedAsDiscState = true;
end
%-----------------------------------------------------------%
function InitConditions(block)
%% 仿真开始时:prevInput 置零
block.Dwork(1).Data = 0;
end
%-----------------------------------------------------------%
function Output(block)
%% 边沿检测 + 执行外部 EXE
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';

% -------- 路径检查(兼容旧版 MATLAB:用 exist) --------
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; % 1 成功 /-1 失败
end
else
block.OutputPort(1).Data = 0; % 未触发
end

% 更新上周期输入
block.Dwork(1).Data = u;
end

运行仿真,观察到Display显示为1表明正确调用执行,为了方便截图,我把Display换为Scope,观察如下

image-20250708221912975

第一秒,输入从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