MATLAB S-Function 用法详解
MATLAB S-Function 详解
什么是 S-Function?
S-Function(系统函数)为扩展 Simulink®环境的功能提供了强大的机制。 S-Function 是用 MATLAB®、C、C++ 或 Fortran 编写的 Simulink 模块的计算机语言描述。C、C++ 和 Fortran S-Function使用 mex 实用程序编译为 MEX 文件(请参见 Build C MEX S-Function )。与其他 MEX 文件一样,S-Function是动态链接的子例程,MATLAB 执行引擎可以自动加载和执行这些子例程。
S-Function使用称为 S-Function API 的特殊调用语法,使您能够与 Simulink 引擎进行交互。这种交互与引擎和内置 Simulink 模块之间发生的交互非常相似。
S-Function遵循一般形式,可以适应连续、离散和混合系统。通过遵循一组简单的规则,您可以在 S-Function中实现算法,并使用 S-Function模块将其添加到 Simulink 模型中。编写 S-Function并将其名称放在 S-Function模块。
如果您有 Simulink Coder™,则可以在模型中使用 S-Function并生成代码。您还可以通过编写目标语言编译器(TLC)文件来自定义为 S-Function生成的代码。
S-Function的工作原理
S-Function定义了模模块在仿真的不同部分(如初始化、更新、派生、输出和终止)中的工作方式。在仿真的每个步骤中,仿真引擎都会调用一种方法来完成特定任务。S-Function基础知识需要具备模块输入、状态和输出之间数学关系的基础知识。要了解 S-Function的工作原理,首先您需要了解 Simulink 如何仿真模型的数学原理,即仿真的各个阶段。
Simulink Blocks的数学过程
Simulink 模模块由一组输入、一组状态、一组参数和一组输出组成,其中输出是仿真时间、输入、参数和状态的函数。
以下等式表示输入、输出、参数、状态和仿真时间之间的数学关系。
y = f 0( t , x , u ) (Outputs) ˙x = fd ( t , x , u ) (Derivatives) xdk +1= fu ( t , xc , xdk , u ), (Update)
其中 x =[ xc ; xd ]。
仿真阶段
Simulink 模型的执行分阶段进行。在 初始化 阶段,Simulink 引擎将库模块合并到模型中,传播信号宽度、数据类型和采样时间,评估模块参数,确定模块执行顺序,并分配内存。然后,引擎进入一个 仿真循环 ,其中每次通过循环被称为仿真 步骤 。在每个仿真步骤中,引擎按照初始化期间确定的顺序执行模型中的每个模块。对于每个模块,引擎调用计算当前采样时间的模块状态、导数和输出的函数。然后,整个仿真循环将继续,直到仿真完成。
模型初始化 - 为仿真准备模型。在此阶段,将评估块参数,确定块执行顺序,并为每个操作分配内存。在此阶段之后,块将经历一个模拟循环。
连续状态和时间的更新 - 仅当模型具有连续状态时才发生。您可以修改次要步进方法(如 mdlOutputs、mdlDerivatives 和 mdlZeroCrossing)来计算输出。
S-Function回调方法
S-Function包含一组 S-Function回调方法 ,用于执行每个仿真阶段所需的任务。在模型仿真期间,在每个仿真阶段,Simulink 引擎都会为模型中的每个 S-Function 模块调用适当的方法。由 S-Function回调方法执行的任务包括:
- 编译— 在此阶段,Simulink 引擎初始化 S-Function。任务包括:
- 将库模块合并到模型中,并传播信号宽度、数据类型和采样时间
- 设置输入和输出端口的数量和尺寸
- 评估区块参数,并确定区块执行顺序
- 分配内存和存储区域。
- 计算输出— 在此状态下,将计算输出,直到所有模块输出端口对当前时间步长有效,即所有输出值都处于特定误差范围。
- 更新离散状态— 在此调用中,模块执行每时间步长一次的活动,例如更新离散状态。
- 初始化和终止方法— 这些可选方法仅执行一次 S-function 所需的初始化和终止活动。初始化活动可能包括设置用户数据,或在 S-Function中初始化状态向量。终止方法执行任何操作,例如在仿真终止或从模型中删除 S-Function模块时释放所需的内存。
- 积分— 这适用于具有连续状态和/或非采样零交叉的模型。如果 S-Function具有连续状态,则引擎将以较小的时间步长调用 S-Function的输出和派生部分。这样,求解器就可以计算 S-Function的状态。如果 S-Function具有非采样的零交叉,则引擎还会以较小的时间步长调用 S-Function的输出和过零部分,以便可以找到零交叉点。
在模型中使用 S-Function
- 将参数传递给 S-Function
- 何时使用 S-Function
- 要将 C MEX S-Function合并到模型中,请从 Simulink 库浏览器 中拖动 S-Function模块。同样,要将 2 级 MATLAB S-Function合并到模型中,请将 2 级 MATLAB S-Function模块拖到模型中。
- 打开“ 模块参数 ”对话框,并在“S-Function名称”字段中指定 S-Function名称 ,以便为 S-Function模块提供功能。例如,键入 timestwo 并点击应用以添加一个 C MEX S-Function,该函数将传入信号乘以 2。
注意
如果 MATLAB 路径包括一个 C MEX 文件和一个具有 S-Function 模块引用的相同名称的 MATLAB 文件,则 S-Function 模块将使用 C MEX 文件。
将参数传递给 S-Function
在 S-Function模块和级别 2 MATLAB S-Function 模块“参数 ”窗口中,您都可以指定要传递给相应 S-Function的参数值。要使用这些字段,您必须知道 S-Function所需的参数以及函数需要这些参数的顺序。(如果您不知道,请查阅 S-Function的作者、文档或源代码。按 S-Function所需的顺序输入参数(以逗号分隔)。参数值可以是常量、在 MATLAB 或模型工作区中定义的变量的名称,也可以是 MATLAB 表达式。
以下示例说明了“ 参数” 字段的用法,用于为 2 级 MATLAB S-Function输入用户定义的参数。
此示例中的模型msfcndemo_limintm包含示例 S-Function msfcn_limintm.m:
function msfcn_limintm(block)
% Level-2 MATLAB file S-Function for limited integrator demo.
% Copyright 1990-2009 The MathWorks, Inc.
setup(block);
%endfunction
function setup(block)
%% Register number of dialog parameters
block.NumDialogPrms = 3;
%% Register number of input and output ports
block.NumInputPorts = 1;
block.NumOutputPorts = 1;
%% Setup functional port properties to dynamically
%% inherited.
block.SetPreCompInpPortInfoToDynamic;
block.SetPreCompOutPortInfoToDynamic;
block.InputPort(1).Dimensions = 1;
block.InputPort(1).DirectFeedthrough = false;
block.OutputPort(1).Dimensions = 1;
%% Set block sample time to continuous
block.SampleTimes = [0 0];
%% Setup Dwork
block.NumContStates = 1;
%% Set the block simStateCompliance to default (i.e., same as a built-in block)
block.SimStateCompliance = 'DefaultSimState';
%% Register methods
block.RegBlockMethod('InitializeConditions', @InitConditions);
block.RegBlockMethod('Outputs', @Output);
block.RegBlockMethod('Derivatives', @Derivative);
%endfunction
function InitConditions(block)
%% Initialize Dwork
block.ContStates.Data(1) = block.DialogPrm(3).Data;
%endfunction
function Output(block)
block.OutputPort(1).Data = block.ContStates.Data;
%endfunction
function Derivative(block)
lb = block.DialogPrm(1).Data;
ub = block.DialogPrm(2).Data;
u = block.InputPort(1).Data;
if (block.ContStates.Data <= lb && u < 0) || (block.ContStates.Data >= ub && u > 0)
block.Derivatives.Data = 0;
else
block.Derivatives.Data = u;
end
%endfunction
msfcn_limintm.m S-Function接受三个参数:下限、上限和初始条件。如果时间积分介于下限和上限之间,则 S-Function输出输入信号的时间积分;如果时间积分小于下限,则输出下限;如果时间积分大于上限,则输出上限。示例中的对话框分别指定下限和上限以及初始条件 -5.0、5.0 和 0。示波器显示当输入为振幅为5的正弦波时产生的输出。
您可以使用封装工具为 S-Function 模块创建自定义对话框和图标。封装对话框可以更轻松地为 S-Function指定其他参数。
何时使用 S-Function
您可以将 S-Function用于各种应用,包括:
- 创建新的通用模块
- 添加表示硬件设备驱动程序的模块
- 将现有 C 代码合并到仿真中(请参见 使用代码继承工具集成 C 函数 )
- 将系统描述为一组数学方程
- 使用图形动画(请参阅倒摆示例“ 带动画的倒立摆锤 ”)
S-Function最常见的用途是创建自定义 Simulink 模块。使用 S-Function创建通用模块时,可以在模型中多次使用它,每个模块实例的参数都不同。
S-Function概念
直接馈通
直接馈通 意味着输出(或可变采样时间模块的可变采样时间)由输入端口信号的值直接控制。通常,S-Function输入端口具有直接馈通,如果
- 输出函数(mdlOutputs)是输入 u 的函数。也就是说,如果输入 u 由 mdlOutputs 访问,则存在直接馈通。输出还可以包括图形输出。
- 可变采样时间 S-Function的“下次命中时间”函数(mdlGetTimeOfNextVarHit)访问输入 u 。
需要其输入(即具有直接馈通)的系统示例是操作
y = k × u ,
其中 u 是输入, k 是增益, y 是输出。
不需要其输入(即没有直接馈通)的系统示例是简单的积分算法
y = x ,
̇x = u ,
其中 x 是状态 , x 是相对于时间的状态导数, u 是输入, y 是输出。Simulink®集成了变量 ̇x 。
正确设置直接馈通标志非常重要,因为它会影响模型中模块的执行顺序,用于检测代数环(请参阅 使用 Simulink 中的代数环概念)。如果包含 S-Function的模型的仿真结果不收敛,或者仿真失败,则可能是因为未正确设置直接馈通标志。尝试打开直接馈通标志,并将 代数环 求解器诊断设置为警告(请参阅 Simulink 图形用户界面 中“模型配置参数:诊断参考”页上的“代数环”选项)。随后运行仿真将显示模型中的任何代数环,并显示引擎是否已将 S-Function放置在代数环中。
动态调整大小的数组
您可以编写 S-Function以支持任意输入维度。在这种情况下,Simulink 引擎通过评估驱动 S-Function的输入向量的尺寸来确定仿真启动时的实际输入维度。S-Function还可以使用输入维度来确定连续状态数、离散状态数和输出数。
注意
对于特定模型中或不同仿真期间 S-Function的每个实例,动态大小的输入可以具有不同的大小,但是 S-Function的每个实例的输入大小在特定仿真过程中是静态的。
C MEX S-Function和 2 级 MATLAB® S-Function可以有多个输入和输出端口,每个端口可以有不同的尺寸。维度的数量和每个维度的大小可以动态确定。
例如,下图显示了模型中同一 S-Function 模块的两个实例。
上部 S-Function模块由具有三元输出矢量的模块驱动。较低的 S-Function 模块由具有标量输出的模块驱动。通过指定 S-Function模块具有动态大小的输入,同一个 S-Function可以同时适应这两种情况。Simulink 引擎使用适当大小的输入向量自动调用模块。同样,如果将其他模块特征(如输出数或离散或连续状态数)指定为动态大小,则引擎会将这些向量定义为与输入向量的长度相同。
设置采样时间和偏移量
2 级 MATLAB 和 C MEX S-Function都提供以下采样时间选项,这些选项在指定 S-Function执行时间方面具有高度的灵活性:
- 连续采样时间— 适用于具有连续状态和/或非采样零交叉的 S-Function。对于这种类型的 S-Function,输出在较小的时间步长中会发生变化。
- 连续,但在小时间步长采样时间内固定— 适用于需要在每个主要仿真步骤中执行但在小时间步长内固定值的 S-Function。
- 离散采样时间— 如果 S-Function的行为是离散时间间隔的函数,则可以定义采样时间来控制 Simulink 引擎调用 S-Function的时间。您还可以定义一个偏移量,以延迟每次采样时间命中。偏移量的值不能超过相应的采样时间。
采样时间命中 发生在由公式确定的时间值处
TimeHit = (n * 周期) + 偏移量
其中整数 n 是当前仿真步骤。n 的第一个值始终为零。
如果定义了离散采样时间,则引擎会在每次采样时间命中时调用 S-Function mdlOutputs 和 mdlUpdate 例程(如前面的等式中所定义)。
- 可变采样时间— 一种离散的采样时间,其中采样命中之间的间隔可以变化。在每个仿真步骤开始时,将查询具有可变采样时间的 S-Function,以确定下一次命中的时间。
- 继承采样时间— 有时 S-Function没有固有的采样时间特征(即连续的或离散的,具体取决于系统中其他模块的采样时间)。在这种情况下,您可以指定 继承 采样时间。一个简单的例子是增益模块,它从驱动它的模块继承其采样时间。
S-Function可以从以下位置继承其采样时间
- 驱动模块
- 目标模块
- 系统中最快的采样时间
要指定继承的 S-Function采样时间,请在 2 级 MATLAB S-Function中使用 -1,在 C MEX S-Function中使用 INHERITED_SAMPLE_TIME 作为采样时间。
S函数可以是单速率或多速率;多速率 S-Function具有多个采样时间。
采样时间以以下格式成对指定:[sample_time,offset_time]。
有效的 C MEX S-Function采样时间
C MEX S-Function的有效采样时间对为
[CONTINUOUS_SAMPLE_TIME, 0.0]
[CONTINUOUS_SAMPLE_TIME, FIXED_IN_MINOR_STEP_OFFSET]
[ discrete_sample_time_period , offset ]
[VARIABLE_SAMPLE_TIME, 0.0]
其中
CONTINUOUS_SAMPLE_TIME = 0.0
FIXED_IN_MINOR_STEP_OFFSET = 1.0
VARIABLE_SAMPLE_TIME = -2.0
斜体的变量名称表示需要实数值。
或者,您可以指定采样时间是从驱动模块继承的。在本例中,C MEX S-Function只有一个采样时间对,或者
[INHERITED_SAMPLE_TIME, 0.0]
或
[INHERITED_SAMPLE_TIME,FIXED_IN_MINOR_STEP_OFFSET]
其中
INHERITED_SAMPLE_TIME = -1.0
有效的 2 级 MATLAB S-Function采样时间
2 级 MATLAB S-Function的有效采样时间对为
[0 offset ] % Continuous sample time
[ discrete_sample_time_period , offset ] % Discrete sample time
[-1, 0] % Inherited sample time
[-2, 0] % Variable sample time
其中斜体的变量名称表示需要实际值。使用连续采样时间时,偏移量为 1 表示输出在较小的积分时间步长中是固定的。偏移量为 0 表示每个小积分时间步长处的输出变化。
选择采样时间的指南
使用以下准则获取有关指定采样时间的帮助:
- 在小集成步骤中更改的连续 S-Function应注册 [CONTINUOUS_SAMPLE_TIME, 0.0] 采样时间。
- 在小集成步骤中回定的连续 S-Function应注册 [CONTINUOUS_SAMPLE_TIME,FIXED_IN_MINOR_STEP_OFFSET] 采样时间。
- 以指定速率变化的离散 S-Function应注册离散采样时间对 [ discrete_sample_time_period , offset ],其中
- discrete_sample_period > 0.0
且
0.0 ≤ offset < discrete_sample_period
- 以可变速率变化的离散 S-Function应记录可变步长离散采样时间。
- [VARIABLE_SAMPLE_TIME, 0.0]
在 C MEX S-Function中,调用 mdlGetTimeOfNextVarHit 例程以获取可变步长离散任务的下一个采样命中时间。在 Level-2 MATLAB S-Function中,在 Outputs 方法中设置 NextTimeHit 属性以设置下一个示例命中。
如果 S-Function没有内部采样时间,则必须指示采样时间是继承的。有两种情况:
- S-Function在其输入更改时(即使在较小的集成步骤中)也会发生变化,也应注册 [INHERITED_SAMPLE_TIME, 0.0] 采样时间。
- S-Function会随着其输入的变化而变化,但在小集成步骤中不会改变(即,在小时间步长内保持固定),则应注册 [INHERITED_SAMPLE_TIME,FIXED_IN_MINOR_STEP_OFFSET] 采样时间。
Scope 模块是此类模块的一个很好的例子。该模块以其驱动模块的速率运行,无论是连续的还是离散的,但从不以小步长运行。如果是这样,示波器显示将显示求解器的中间计算,而不是每个时间点的最终结果。
S-Function 实现
Matlab允许你使用以下五种方式之一来实现S-Function:
- Level 1 M文件S-Function----这种方式提供了一个简单的M文件接口,可以与少部分的S-FunctionAPI交互。Matlab对于这种方式的支持更多的是为了保持与以前版本的兼容,现在推荐采用的是Level 2 M文件S-Function。
- Level 2 M文件S-Function----支持访问更多地S-FunctionAPI,支持代码生成。当你要使用M文件来实现一个S-Function的时候,选择Level 2 M文件S-Function。
- 手写C MEX S-Function----这种方式提供了最大的编程自由度,你可以使用C MEX S-Function来实现你的算法,也可以写一个wrapper S-Function,调用已经存在的C,C++,Fortran代码。使用这种方式编写一个新的S-Function需要对于S-Function API的了解,如果你写的S-Function要产生内联代码,还需要熟悉TLC。
- S-Function创建器----这是采用图形用户界面的方式实现部分S-Function功能的方式。如果你对于编写C MEX S-Function不熟悉,可以使用S-Function创建器来产生新的S-Function,或者将已有的C,C++代码集成到S-Function中而不需要与S-Function API打交道。S-Function创建器可以同时生成TLC文件,这些TLC文件可以在自动代码生成时为你的S-Function生成内联代码。
- 代码继承工具---是一系列的Matlab命令,这些命令帮助你集成以前的C,C++代码以生成一个S-Function。与S-Function创建器一样,代码继承工具可以产生TLC文件,不过相比于S-Function创建器和手写的C MEX S-Function,代码继承工具只能访问很少的S-FunctionAPI。
使用 mex 实用工具将 S-Function 编译为 MEX 文件。请参阅 编译 C MEX S-Function 。与其他 MEX 文件一样,S-Function 是动态链接的子例程,MATLAB 执行引擎可以自动加载和执行它们。如果您有 Simulink Coder™,则可以为内联的 2 级 MATLAB S-Function 生成代码。
转换 1 级 C MEX S-Function
将 1 级 C MEX S-Function转换为 2 级的准则
2 级 S-Function随 Simulink® 2.2 版一起引入。1 级 S-Function是指为与 Simulink 2.1 版和早期版本配合使用而编写的 S-Function。1级S功能与Simulink 2.2版及后续版本兼容;您可以在新模型中使用它们,而无需进行任何代码更改。但是,要利用 S-函数中的新功能,必须将 1 级 S-Function更新为 2 级 S-Function。以下是一些准则:
- 首先看看 simulink/src/sfunctmpl_doc.c。此模板 S-Function文件简明扼要地总结了 2 级 S-Function。
- 在 S-Function文件的顶部,添加以下定义:
#define S_FUNCTION_LEVEL 2
- 更新 mdl初始化大小的内容。特别是,为 S-Function参数的数量添加以下错误处理:
ssSetNumSFcnParams(S, NPARAMS);/*预期参数数量*/
if (ssGetNumSFcnParams(S)!= ssGetSFcnParamsCount(S)) {
/* 如果预期的数量!= 实际参数的数量,则返回 */
return;
}
- 使用以下命令设置输入:
if (!ssSetNumInputPorts(S, 1)) return;/*输入端口数量 */
ssSetInputPortWidth(S, 0, width); /* 输入宽度
端口 1(索引 0)*/
ssSetInputPortDirectFeedThrough(S, 0, 1);/* 直接馈通
或端口 1 */
ssSetInputPortRequiredContiguous(S, 0);
- 使用以下命令设置输出:
if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, width); /* 输出端口宽度
一个(索引 0)*/
- 如果您的 S-Function具有非空 mdlInitializeConditions,请将其更新为以下格式:
#define MDL_INITIALIZE_CONDITIONS
static void mdlInitializeConditions(SimStruct *S)
{
}
否则,请删除该函数。
- 使用 ssGetContStates 访问连续状态。ssGetX 宏已被删除。
- 使用 ssGetRealDiscStates(S)访问离散状态。ssGetX 宏已被删除。
- 对于混合连续和离散状态 S-Function,状态向量不再由连续状态后跟离散状态组成。这些状态保存在单独的向量中,因此在内存中可能不是连续的。
- mdlOutputs 原型更改
static void mdlOutputs( real_T *y, const real_T *x,
const real_T *u, SimStruct *S, int_T tid)
更改为
static void mdlOutputs(SimStruct *S, int_T tid)
由于 y、x 和 u 未显式传递到 2 级 S-Function,因此必须使用
- ssGetInputPortSignal to access inputs
- ssGetOutputPortSignal 用于访问输出
- ssGetContStates 或 ssGetRealDiscStates 以访问状态
- mdl更新函数原型从
void mdlUpdate(real_T *x, real_T *u, Simstruct *S, int_T tid)
更改为
void mdlUpdate(SimStruct *S, int_T tid)
- 如果您的 S-Function具有非空 mdlUpdate,请将其更新为以下格式:
#define MDL_UPDATE
static void mdlUpdate(SimStruct *S, int_T tid)
{
}
否则,请删除该函数。
- 如果您的 S-Function具有非空 mdlDerivatives,请将其更新为以下形式:
#define MDL_DERIVATIVES
static void mdlderivatives(SimStruct *S)
{
}
否则,请删除该函数。
- 替换所有过时的 SimStruct 宏。有关过时宏的完整列表,请参阅过时的宏。
- 将 1 级 S-Function转换为 2 级 S-Function时,应使用完整(即最高)警告级别构建 S-Function。例如,如果 UNIX1®系统上有 gcc,请将这些选项与 mex 实用程序一起使用。
- mex CC=gcc CFLAGS=-Wall sfcn.c
如果您的系统具有 Lint,请使用此代码。
lint -DMATLAB_MEX_FILE -I<matlabroot>/simulink/include
- Imatlabroot /extern/include sfcn.c
在 PC 上,若要使用最高警告级别,必须在集成开发环境(IDE)内为正在使用的编译器创建项目文件。在项目文件中,定义MATLAB_MEX_FILE并添加
matlabroot /simulink/include
matlabroot /extern/include
到路径(确保在对齐设置为 8 的情况下进行构建)。
过时的宏
以下宏已过时。请将每个过时的宏替换为下表中指定的宏。
| 过时的宏 | 替换为 |
| ssGetU(S), ssGetUPtrs(S) | ssGetInputPortSignalPtrs(S,port), ssGetInputPortSignal(S,port) |
| ssGetY(S) | ssGetOutputPortRealSignal(S,port) |
| ssGetX(S) | ssGetContStates(S), ssGetRealDiscStates(S) |
| ssGetStatus(S) | 通常不使用,但 ssGetErrorStatus(S)可用。 |
| ssSetStatus(S,msg) | ssSetErrorStatus(S,msg) |
| ssGetSize(S) | 对所需项目的特定调用(即 ssGetNumContStates(S)) |
| ssGetMinStepSize(S) | 不再支持 |
| ssGetPresentTimeEvent(S,sti) | ssGetTaskTime(S,sti) |
| ssGetSampleTimeEvent(S,sti) | ssGetSampleTime(S,sti) |
| ssSetSampleTimeEvent(S,t) | ssSetSampleTime(S,sti,t) |
| ssGetOffsetTimeEvent(S,sti) | ssGetOffsetTime(S,sti) |
| ssSetOffsetTimeEvent(S,sti,t) | ssSetOffsetTime(S,sti,t) |
| ssIsSampleHitEvent(S,path,time) | ssIsSampleHit(S,path,time) |
| ssGetNumInputArgs(S) | ssGetNumSFcnParams(S) |
| ssSetNumInputArgs(S, numInputArgs) | ssSetNumSFcnParams(S,numInputArgs) |
| ssGetNumArgs(S) | ssGetSFcnParamsCount(S) |
| ssGetArg(S,argNum) | ssGetSFcnParam(S,argNum) |
| ssGetNumInputs | ssGetNumInputPorts(S)和 ssGetInputPortWidth(S,port) |
| ssSetNumInputs | ssSetNumInputPorts(S,nInputPorts)和 ssSetInputPortWidth(S,port,val) |
| ssGetNumOutputs | ssGetNumOutputPorts(S)和 ssGetOutputPortWidth(S,port) |
| ssSetNumOutputs | ssSetNumOutputPorts(S,nOutputPorts)和 ssSetOutputPortWidth(S,port,val) |
创建C MEX S-Function基础
关于 C MEX S-Function
您可以使用以下任一方法创建 C MEX S-Function:
- 手写 S-Function —您可以从头开始编写 C MEX S-Function。( 创建基本 C MEX S-Function 提供了一个分步示例。有关 C MEX S-Function 的完整框架实现,请参阅 C S-Function模板,该函数可用作创建自己的 S-Function的起点。
- S-Function Builder —此模块集成了 C/C++代码,并根据使用图形用户界面提供的规范和代码片段构建 C MEX S-Function。这样就无需从头开始编写 S-Function。
- 代码继承工具— 此实用程序根据您使用 MATLAB®代码提供的现有 C 代码和规范构建 C MEX S 函数。
这些方法中的每一种都涉及编写 S-Function的易用性和 S-Function支持的功能之间的权衡。虽然手写 S-Function支持最广泛的功能,但它们可能很难编写。S-Function Builder 模块简化了编写 C MEX S-functions 的任务,但支持的功能较少。代码继承工具提供了从现有 C 代码创建 C MEX S-Function的最简单方法,但支持的功能最少。
如果您有 Simulink Coder™,除了前三种方法之外,Simulink Coder 产品还提供了一种从图形子系统生成 C MEX S-Function的方法。如果您不熟悉编写 C MEX S-Function,则可以在 Simulink 子系统中构建应用程序的各个部分,并使用 S-Function目标将其转换为 S-Function。生成的文件提供了有关如何在 S-Function中实现特定模块的见解。
您可以开发一个 S-Function,使用与 Simulink 引擎交互的 API 来表示外部代码。将此 S-Function与代码生成器一起使用以生成代码。有关代码生成中不同类型的 S-Function的详细信息,请参阅 S-Function和代码生成 (Simulink Coder)。
在仿真期间,C MEX S-Function必须向 Simulink 引擎提供有关该函数的信息。随着仿真的进行,引擎、ODE 求解器和 C MEX S-Function将进行交互以执行特定任务。这些任务包括定义初始条件和模块特征,以及计算导数、离散状态和输出。
与 MATLAB S-Function一样,Simulink 引擎通过调用 S-Function实现的回调方法与 C MEX S-Function进行交互。每种方法都执行一个预定义的任务,例如计算模块输出,这是仿真 S-Function定义的模块的功能所必需的。但是,S-Function可以根据 S-Function实现的功能在每个方法中自由执行任务。例如,mdlOutputs 方法必须在当前仿真时计算模块输出。但是,S-Function可以通过适合于该函数的任何方式计算这些输出。此基于回调的 API 允许您创建任何所需功能的 S-Function,从而创建自定义模块。
C MEX S-Function可以实现的回调方法集比可用于 MATLAB S-Function的回调方法集更大。C MEX S-Function只需要在 S-Function API 中实现回调方法的一小部分。如果您的模块没有实现特定功能,例如矩阵信号,则可以自由省略实现功能所需的回调方法。这使您可以非常快速地创建简单的模块。
C MEX S-Function的一般格式如下所示:
#define S_FUNCTION_NAME your_sfunction_name_here
#define S_FUNCTION_LEVEL 2
#include “simstruc.h”
static void mdlInitializeSizes(SimStruct *S)
{
}
<附加的 S-Function例程/代码>
static void mdlTerminate(SimStruct *S)
{
}
#ifdef MATLAB_MEX_FILE /* 此文件是否正在编译为MEX文件?*/
#include “simulink.c” /* MEX 文件接口机制 */
#else
#include “cg_sfun.h” /* 代码生成注册功能 */
#endif
mdlInitializeSize 是 Simulink 引擎在与 S-Function交互时调用的第一个例程。引擎随后调用其他 S-Function方法(全部以 mdl 开头)。在仿真结束时,引擎调用 mdlTerminate。
C MEX S-Function 基础示例
本节提供 C MEX S-Function的示例,您可以将其用作创建简单 C S-Function的模型。示例 S-Function timestwo.c 输出两倍的输入。
以下模型使用 timestwo S-Function将正弦波的振幅加倍,并将其绘制在示波器中。
S-Function的模块对话框将 timestwo 指定为 S-Function名称;参数字段为空。
timestwo S-Function包含下图所示的 S-Function回调方法。在 S-Function的末尾,包括 Simulink/Simulink Coder 接口中所述的代码片段。
timestwo.c的内容如下所示:(示例之后提供了代码说明)
#define S_FUNCTION_NAME timestwo /* Defines and Includes */
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 0);
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
return; /* Parameter mismatch reported by the Simulink engine*/
}
if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED);
ssSetInputPortDirectFeedThrough(S, 0, 1);
if (!ssSetNumOutputPorts(S,1)) return;
ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);
ssSetNumSampleTimes(S, 1);
/* Take care when specifying exception free code - see sfuntmpl.doc */
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
}
static void mdlOutputs(SimStruct *S, int_T tid)
{
int_T i;
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
real_T *y = ssGetOutputPortRealSignal(S,0);
int_T width = ssGetOutputPortWidth(S,0);
for (i=0; i<width; i++) {
*y++ = 2.0 *(*uPtrs[i]);
}
}
static void mdlTerminate(SimStruct *S){}
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
此示例包含三个部分:
- 定义并包括
- 回调方法实现
- Simulink(或 Simulink Coder)产品接口
宏定义和包含
该示例从以下定义语句开始。
#define S_FUNCTION_NAME timestwo
#define S_FUNCTION_LEVEL 2
第一个 define 语句指定 S-Function (timestwo)的名称。第二个 define 语句指定 S-Function采用 级别 2 格式。
定义这两个项目后,该示例包括 simstruc.h ,这是一个头文件,用于访问 SimStruct 数据结构和 MATLAB 应用程序接口(API)函数。
#define S_FUNCTION_NAME timestwo
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
simstruc.h 文件定义了一个名为 SimStruct 的数据结构,Simulink 引擎使用该结构来维护有关 S-Function的信息。simstruc.h 文件还定义了宏,这些宏使 MEX 文件能够设置值并从 SimStruct 中获取值(例如模块的输入和输出信号)。
回调方法实现
timestwo S-Function的下一部分包含所需回调方法的实现。
mdl初始化大小
Simulink 引擎调用 mdlInitializeSizes 来查询输入和输出端口的数量、端口的大小以及 S-Function所需的任何其他信息(如状态数)。
mdl初始化大小的时间两个实现指定以下大小信息:
- 零参数
因此,“ S-Function模块参数 ”对话框的“S-Function参数”字段必须为空。如果它包含任何参数,引擎将报告参数不匹配。
- 一个输入端口和一个输出端口
输入和输出端口的宽度是动态调整大小的。这告诉引擎 S-Function可以接受任何宽度的输入信号。默认情况下,当 S-Function只有一个输入和输出端口时,动态大小的输入和输出端口的宽度相等。
- 一次采样时间
mdlInitializeSampleTimes 回调方法指定采样时间的实际值。
- 无异常代码
指定无异常代码可加快 S-Function的执行速度。指定此选项时必须小心。通常,如果 S-Function未与 MATLAB 环境交互,则可以安全地指定此选项。
mdl初始化采样时间
Simulink 引擎调用 mdlInitializeSampleTimes 来设置 S-Function的采样时间。每当执行驱动模块时,就会执行 timestwo 模块。因此,它具有单个继承的采样时间,INHERITED_SAMPLE_TIME。
mdlOutputs
引擎在每个时间步长调用 mdlOutputs 来计算模块输出。mdlOutputs 的 timestwo 实现将输入信号乘以 2 并将答案写入输出。
行:
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
访问输入信号。ssGetInputPortRealSignalPtrs 宏返回一个指针向量,您必须使用该指针来访问该向量
*uPtrs[i]
行:
real_T *y = ssGetOutputPortRealSignal(S,0);
访问输出信号。ssGetOutputPortRealSignal 宏返回一个指针,指向包含模块输出的数组。
行:
int_T width = ssGetOutputPortWidth(S,0);
获取通过模块的信号的宽度。S-Function在输入上循环计算输出。
mdlTerminate
引擎调用 mdlTerminate 为 S-Function提供在仿真结束时执行任务的机会。这是一个必需的 S-Function例程。timestwo S-Function不执行任何终止操作,并且此例程为空。
Simulink/Simulink Coder Interfaces
在 S-Function的末尾,包含以下代码以将 S-Function附加到 Simulink 或 Simulink Coder 产品。
#ifdef MATLAB_MEX_FILE
#include “simulink.c”
#else
#include “cg_sfun.h”
#endif
每个 S-Function的末尾都需要此文件尾。如果省略它,则编译 S-Function的任何尝试都将中止,并在生成导出文件错误消息期间失败。
构建Timestwo示例
要编译此 S-Function,请输入
mex timestwo.c
在命令行中,mex 命令使用默认编译器编译并链接 timestwo.c 文件。mex 命令创建一个动态可加载的可执行文件,供 Simulink 软件使用。如果您有多个 MATLAB 支持的编译器,则可以使用 mex -setup 命令更改默认值。
生成的可执行文件被称为MEX S-Function,其中MEX代表“MATLAB可执行文件”。MEX文件扩展名因平台而异。例如,在64位Microsoft® Windows®系统上,MEX文件扩展名为.mexw64。
编译 C MEX 函数
举例如何编译示例 C MEX 函数 arrayProduct。使用示例验证系统的 C 编译配置。有关编译 C++ MEX 函数的信息,请参阅编译 C++ MEX 程序。
验证您是否安装了支持的兼容编译器。
mex -setup C
将源 MEX 文件复制到您的路径上的可写文件夹中。
copyfile(fullfile(matlabroot,'extern','examples','mex','arrayProduct.c'),'.','f')
调用 mex 命令来编译该函数。
mex arrayProduct.c -R2018a
此命令将创建文件 arrayProduct.ext,其中 ext 是由 mexext 函数返回的值。
测试该函数。arrayProduct 函数接受一个标量并将其与 1×N 矩阵相乘。像调用 MATLAB®函数一样调用 arrayProduct。
s = 5;
A = [1.5, 2, 9];
B = arrayProduct(s,A)
B =
7.5000 10.0000 45.0000
编译 C++ MEX 程序
使用 MATLAB®支持 C++11 的编译器 mex 编译您的 C++ MEX 应用程序,以设置您的环境并编译 C++ 源代码。
使用 mex 命令编译 .cpp 文件
如果您安装了受支持的编译器之一,请使用 mex 命令为 C++ MEX 应用程序设置编译器。当提供了用于选择编译器的选项时,选择 MATLAB C++ MEX 支持的一个已安装的编译器。
mex -setup C++
使用 MATLAB mex 命令编译您的 C++ MEX 程序。
mex MyMEXCode.cpp
MEX include 文件
在您的 C++ 源代码中包含以下头文件。头文件包含函数声明以及您在 API 库中访问的例程的原型。这些文件位于 matlabroot/extern/include 文件夹中,并且与用于 Windows®、Mac 和 Linux®系统的文件相同。C++ MEX 文件使用以下头文件:
- mex.hpp - C++ MEX API 的定义
- mexAdapter.hpp - C++ MEX 函数运算符所需的实用工具
注意
在跨多个文件的 MEX 应用程序中,将 mexAdapter.hpp 与 MexFunction 类定义仅包含一次。
文件扩展名
MEX 文件是特定于平台的。MATLAB 通过特定于平台的扩展名来识别 MEX 文件。下表列出了 MEX 文件的特定于平台的扩展名。
MEX 文件与平台相关的扩展名
| 平台 | 二进制 MEX 文件扩展名 |
| Linux(64 位) | mexa64 |
| Apple Mac(64 位) | mexmaci64 |
| Windows(64 位) | mexw64 |
S-Function 和代码生成
通过 S-Function 的应用编程接口 (API),您可以非常灵活地在 Simulink 环境中实现通用算法。如果您打算在模型中将 S-Function 用于代码生成,灵活度可能会有所不同。例如,需要生成代码的 S-Function 不能访问 MATLAB®工作区。
本节介绍使用 S-Function 需要注意的条件。但是,利用本节介绍的技巧,可为大多数使用生成代码的应用创建 S-Function。
虽然 S-Function 提供了通用且灵活的解决方案,可在模型中实现复杂的算法,但基础 API 会在内存和计算资源方面产生一定的开销。通常,额外增加的资源开销对实时快速原型系统来说是可以接受的。然而,通常情况下实时嵌入式应用中并没有额外的可用资源。通过使用代码生成器附带的 Target Language Compiler 技术来内联 S-Function,最大程度降低内存和计算要求。如果您为现有的外部代码生成 S-Function,可以考虑使用代码继承工具生成 S-Function 和相关的 TLC 文件。
S-Function 的类型
以下情形适合用 S-Function 来实现仿真和代码生成:
- “我不关心效率,我只想让我的算法能够自动在 Simulink 和代码生成器产品中工作。”
- “我想在 Simulink 和代码生成器产品中实现一种高度优化的算法,它看起来就像是一个内置模块,并且能生成高效的代码。”
- “我有很多人工代码需要集成。我希望高效地从 Simulink 和代码生成器产品中调用我的函数。”
上述情况分别对应于以下 MathWorks®术语:
- 非内联 S-Function
- 内联 S-Function
- 为外部代码自动生成的 S-Function
非内联 S-Function
非内联 S-Function 是 C 或 C++ MEX S-Function,Simulink 引擎和生成的代码以相同的方式处理这种函数。一般情况下,您只需根据 S-Function API 实现一次算法即可。Simulink 引擎和生成的代码将在模型执行过程中调用 S-Function 例程(例如 mdlOutputs)。
非内联 S-Function 模块的每个实例都需要一些额外的内存和计算资源。但是,这种在模型和代码生成应用中嵌入算法的例程通常发生在工程的原型构建阶段,此时效率并不重要。牺牲效率所获得的好处是能够快速更改模型参数和结构。
编写非内联 S-Function 不涉及 TLC 编码。您在模型中内建一个 MEX S-Function 后,便可以直接按下 Ctrl+B 开始编译模型,而无需进行其他准备工作,从这个意义上讲,非内联 S-Function 是编译过程的默认情形。
生成联编文件时,关于非内联 S-Function 文件的名称和位置有一些限制。请参阅 编写非内联 S-Function 。
内联 S-Function
要使 S-Function 能够在 Simulink 环境中工作,需要生成一些开销代码。当代码生成器从包含 S-Function(不含 sfunction.tlc 文件)的模型中生成代码时,会在生成的代码中嵌入一些开销代码。如果要优化您的实时代码并消除一些开销代码,必须 内联 (或嵌入)您的 S-Function。这涉及到编写一个 TLC (sfunction.tlc) 文件,以便从生成的代码中消除开销代码。Target Language Compiler 通过处理 sfunction.tlc 文件来定义如何在生成的代码中内联您的 S-Function 算法。
注意
不要将 内联 一词与 C++ 语言中的 inline 关键字混淆。内联的意思是:通过指定文本来代替对通用 S-Function API 例程(如 mdlOutputs)的调用。例如,使用 TLC 文件内联 S-Function 时,生成的代码将包含通常出现在 S-Function 例程中的 C/C++ 代码,而 S-Function 本身已从编译过程中删除。
完全内联的 S-Function 采用与内置模块相同的方式将算法(模块)嵌入到生成的代码中。通常,完全内联的 S-Function 需要实现两次算法:一次针对 Simulink 模型 (C/C++ MEX S-Function),一次针对代码生成(TLC 文件)。TLC 文件的复杂程度取决于算法的复杂程度以及您要在生成的代码中实现的效率水平。TLC 文件的结构具有不同的复杂程度。
为原有代码或自定义代码自动生成的 S-Function
如果您需要在模型中调用手写的 C/C++ 代码,可以考虑使用 Simulink 代码继承工具。代码继承工具可以根据您在代码继承工具数据结构体中注册的信息自动生成完全内联的 S-Function 和对应的 TLC 文件。
实现非内联和内联 S-Function 需要的文件
本节简要介绍创建非内联和内联 S-Function 需要的文件和函数。
- 非内联 S-Function 需要 C 或 C++ MEX S-Function 源代码(sfunction.c 或 sfunction.cpp)。
- 完全内联的 S-Function 需要一个 sfunction.tlc 文件。完全内联的 S-Function 可为参数化 S-Function 生成最优代码。参数化 S-Function 是在特定模式下运行、依赖于固定的 S-Function 参数(这些参数在模型执行过程中不会改变)的 S-Function。对于给定的操作模式,sfunction.tlc 文件指定为该模式实现算法而生成的确切代码。例如,Write Fully Inlined S-Functions with mdlRTW Routine中的直接索引查找表 S-Function 包含两种操作模式 - 一种用于等间距 x-data,另一种用于非等间距 x-data。
注意
为调用原有代码或自定义 C/C++ 代码而生成的完全内联的 S-Function 还需要一个 sfunction.tlc 文件,此文件由代码继承工具生成。
完全内联的 S-Function 可能需要在 S-Function MEX 文件 sfunction.c 或 sfunction.cpp 中放置 mdlRTW 例程。mdlRTW 例程允许您将信息放在 model.rtw 中。model.rtw 是用来指定模型的记录文件,生成代码时,代码生成器将在执行 sfunction.tlc 之前调用 Target Language Compiler 来处理此文件。
当您要在 TLC 文件中引入不可调参数时,包含一个 mdlRTW 例程很有用。此类参数用于确定在 S-Function 的给定实例中,哪个操作模式处于活动状态。基于此信息,S-Function 的 TLC 文件可为该操作模式生成高效的最优代码。
编写支持代码生成的 S-Function 的规范
- 可 C/C++ MEX、MATLAB 语言和 Fortran MEX S-Function 用于代码生成。
- 可通过提供内联 TLC 文件来内联 S-Function 进行代码生成。请参阅 Target Language Compiler 中的 S-Function 内联。MATLAB 和 Fortran MEX S-Function 必须是内联的。可以内联 C/C++ MEX S-Function 以提高代码效率,也可以不内联。
- 要自动生成完全内联的 C MEX S-Function 以调用现有代码或自定义代码,请使用代码继承工具。
- 如果代码效率不是最需要考虑的因素,例如您要快速进行原型构建,则可以选择不内联 C/C++ MEX S-Function。有关详细信息,请参阅 编写非内联 S-Function 。
编写非内联 S-Function
非内联 S-Function 是 C 或 C++ MEX S-Function,Simulink®引擎和生成的代码以相同的方式处理这种函数。您只需根据 S-Function API 实现一次算法即可。Simulink 引擎和生成的代码将在模型执行过程中调用 S-Function 例程(例如 mdlOutputs)。
非内联 S-Function 的特点是您的 S-Function 缺少 sfunction.tlc 文件。文件名根据您的平台而有所不同。例如,在 64 位 Microsoft® Windows®系统上,该文件名为 sfunction.mexw64。在 MATLAB®命令行窗口中键入 mexext 可查看系统使用的扩展名。
编写非内联 S-Function 的规范
- MEX 文件不能调用 MATLAB 函数。
- 如果 MEX 文件使用 MATLAB 外部接口库中的函数,请包括头文件 cg_sfun.h 而不是 mex.h 或 simulink.c。对于头文件 cg_sfun.h,在 S-Function 的末尾包括以下行:
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
- 仅使用代码生成器支持的 MATLAB API 函数。支持的 API 函数有:
- mxGetDoubles
- mxGetEps
- mxGetInf
- mxGetM
- mxGetN
- mxGetNaN
- mxGetPr(不推荐。请改用 mxGetDoubles。)
- mxGetScalar
- mxGetString
- mxIsEmpty
- mxIsFinite
- mxIsInf
- 生成的代码不支持 MEX Library 调用。要在 MEX 文件中而不是在生成的代码中使用此类调用,请添加以下条件:
#ifdef MATLAB_MEX_FILE
#endif
- 只能使用仅包含实数数据的满矩阵。
- 不能为 mxGetString 的调用指定返回值。如果您指定返回值,MEX 文件将不会编译。在这种情况下,请使用函数的第二个输入参数,它返回指向字符向量的指针。
- 使用正确的 #define s-function_name 语句。您指定的 S-Function 名称必须与 S-Function 的文件名完全一致。
- 尽量使用 real_T 和 int_T 数据类型,而不要使用 double 和 int。数据类型 real_T 和 int_T 更通用,可在多个环境中使用。
- 为编译过程提供用来编译 S-Function 的模块的名称。使用模板联编文件、set_param 函数或 S-Function 模块参数对话框的 S-function modules 字段。例如,假设您使用以下命令编译 S-Function:
mex sfun_main.c sfun_module1.c sfun_module2.c
则可以使用以下 set_param 调用来包含所需的模块:
set_param(sfun_block, "SFunctionModules","sfun_module1 sfun_module2")
准备好生成代码之后,强制代码生成器重新编译顶层模型。
非内联 S-Function 的参数类型限制
非内联 S-Function 的参数只能是以下类型:
- 双精度
- 标量、向量或二维矩阵中的字符
要想更灵活地选择提供给 S-Function 或 S-Function 中的运算的参数类型,请内联 S-Function 并考虑使用 mdlRTW S-Function 例程。
不支持使用 MATLAB matrix.h API 或其他 MATLAB API(如 mex.h 和 mat.h)中的其他函数。如果您从 S-Function 源文件中调用不支持的 API,将会发生编译器错误。有关支持的 MATLAB API 函数的详细信息,请参阅文件 matlabroot/rtw/c/src/ rt_matrx.h 和 matlabroot/rtw/c/src/ rt_matrx.c 。
如果您对空矩阵使用 mxGetPr,该函数不会返回 NULL。它会返回一个随机值。因此,您必须使用 mxIsEmpty 保护对 mxGetPr 的调用。
使用代码继承工具集成 C 函数
概述
您可以使用代码继承工具将现有的 C(或 C++)函数(如设备驱动程序、查找表以及常规函数和接口)集成到 Simulink®模型中。使用您作为 MATLAB®代码提供的规范,该工具将现有函数转换为可包含在 Simulink 模型中的 C MEX S-Function。如果使用 Simulink Coder™生成代码,则代码继承工具可以将对 C 函数的适当调用插入到生成的代码中。有关详细信息,请参阅 使用代码继承工具将对外部代码的调用导入到生成的代码 中(Simulink Coder)。
与使用 S-Function Builder 或编写 S-function 相比,Legacy Code Tool 更易于使用,并且可以生成嵌入式系统通常需要的优化代码(不生成包装代码)。但是,请考虑混合系统的替代方法,例如包含设备和控制器的系统,或者用 C 或C++以外的语言编写的系统组件。替代方法更灵活,因为它们支持更多的函数和编程语言。
要与代码继承工具进行交互,您需要
- 使用代码继承工具数据结构指定
- S-Function的名称
- 现有 C 函数的规范
- 编译所需的文件和路径
- 生成 S-Function的选项
- 使用 legacy_code 函数
- 初始化给定 C 函数的代码继承工具数据结构
- 生成 S-Function以在仿真期间使用
- 编译生成的 S-Function并将其链接到动态可加载的可执行文件中
- 生成用于调用生成 S-Function的封装 S-Function模块
- 生成一个 TLC 模块文件,如有必要,生成一个 sFunction _makecfg.m 或 rtwmakecfg.m 文件用于代码生成(需要 Simulink Coder 产品许可证)
注意
在使用legacy_code之前,请确保为 MATLAB 安装设置了 C 编译器。
下图说明了使用代码继承工具的一般过程。使用代码继承工具将 C 函数集成到 Simulink 模型中提供了一个示例,该示例使用代码继承工具将现有 C 函数转换为 C MEX S-Function。
使用代码继承工具将 C 函数集成到 Simulink 模型中
此示例演示如何使用代码继承工具将现有 C 函数集成到 Simulink 模型中。
假设您有一个 C 函数,该函数输出其浮点输入的值乘以 2。该函数在名为 doubleIt.c 的源文件中定义,其声明存在于名为 doubleIt.h 的头文件中。
- 使用legacy_code函数 使用表示代码继承工具属性的字段初始化 MATLAB 结构 定义。
def = legacy_code('initialize')
名为 def 的代码继承工具数据结构在 MATLAB 命令窗口中显示其字段,如下所示:
def =
SFunctionName: ''
InitializeConditionsFcnSpec: ''
OutputFcnSpec: ''
StartFcnSpec: ''
TerminateFcnSpec: ''
HeaderFiles: {}
SourceFiles: {}
HostLibFiles: {}
TargetLibFiles: {}
IncPaths: {}
SrcPaths: {}
LibPaths: {}
SampleTime: 'inherited'
Options: [1x1 struct]
- 为代码继承工具数据结构中的字段指定适当的值,以标识现有 C 函数的属性。例如,通过在 MATLAB 命令提示符处输入以下命令来指定 C 函数源和标头文件名:
- def.SourceFiles = {'doubleIt.c'};
def.HeaderFiles = {'doubleIt.h'};
还必须指定有关代码继承工具从 C 代码生成的 S-Function的信息。例如,通过输入以下内容指定 S-Function的名称及其输出函数声明:
def.SFunctionName = 'ex_sfun_doubleit';
def.OutputFcnSpec = 'double y1 = doubleIt(double u1)';
有关各种数据结构字段的信息,请参阅 legacy_code 参考页。
- 使用 legacy_code函数从现有 C 函数生成 S-Function源文件 。在 MATLAB 命令提示符下,键入:
legacy_code('sfcn_cmex_generate', def);
代码继承工具使用 def 中指定的信息在当前 MATLAB 文件夹中创建名为 ex_sfun_doubleit.c 的 S-Function源文件。
- 使用 legacy_code 函数将 S-Function源文件编译并链接到 Simulink 的动态可加载可执行文件。在 MATLAB 命令提示符下,键入:
legacy_code('compile', def);
以下消息将显示在 MATLAB 命令窗口中:
### Start Compiling ex_sfun_doubleit
mex('ex_sfun_doubleit.c', 'd:\work\lct_demos\doubleIt.c',
'-Id:\work\lct\lct_demos')
### Finish Compiling ex_sfun_doubleit
### Exit
在 64 位 Microsoft® Windows®系统上,生成的 S-Function可执行文件被命名为 ex_sfun_doubleit.mexw64。
- 将封装的 S-Function 模块插入到 Simulink 模型中。
legacy_code('slblock_generate', def);
代码继承工具将模块配置为使用在上一步中创建的 C MEX S-Function。此外,该工具还会封装该模块,使其显示其 OutputFcnSpec 属性的值(请参阅 legacy_code 函数的说明)。
- 将振幅为 1 的正弦波模块添加到 C-MEX S 函数模块的输入端,并将示波器模块添加到输出端。
运行仿真。C-MEX S-Function 模块返回其浮点输入的值乘以 2。它的行为类似于 C 函数 doubleIt。
集成其参数是指向结构的指针的 C 函数
示例演示如何使用代码继承工具集成其参数是指向结构的指针的 C 函数。
在 Simulink®中,创建一个 Simulink.Bus 对象来表示结构类型。在模型中使用总线信号来表示结构化信号和状态。在工作区或模块参数对话框中创建 MATLAB 结构以表示参数结构。
探索外部代码
将此自定义源代码复制到当前文件夹中名为 ex_mySrc_LCT.c 的文件中。
#include "ex_myTypes_LCT.h"
void myFcn(sigStructType *in, paramStructType *params, sigStructType *out)
{
out->sig1 = in->sig1 * params->param1;
out->sig2 = in->sig2 * params->param2 + params->param3;
}
函数 myFcn 的参数是指向结构的指针。该函数接受输入信号参数、参数参数和输出信号参数。
将此自定义标头代码复制到当前文件夹中名为 ex_myTypes_LCT.h 的文件中。
#ifndef _MY_TYPES_H_
#define _MY_TYPES_H_
typedef struct {
double sig1;
double sig2;
} sigStructType;
typedef struct {
double param1;
double param2;
double param3;
} paramStructType;
void myFcn(sigStructType *in, paramStructType *params, sigStructType *out);
#endif
该文件定义了 myFcn 使用的信号和参数结构类型。
创建总线对象以表示 Simulink 中的结构类型
在命令提示符下,使用函数 Simulink.importExternalCTypes 在基本工作区中生成总线对象。
Simulink.importExternalCTypes('ex_myTypes_LCT.h');
总线对象对应于 ex_myTypes_LCT.h 定义的结构类型。
创建模块以执行外部代码
创建一个结构变量 def,以存储调用外部代码的 S-Function的规范。使用函数legacy_code创建结构并设置默认值。
def = legacy_code('initialize');
将 S-Function的名称设置为sfun_ex_mySrc_LCT。
def SFunctionName = 'sfun_ex_mySrc_LCT';
通过文件名标识外部源文件和头文件。
def.SourceFiles = {'ex_mySrc_LCT.c'};
def.HeaderFiles = {'ex_myTypes_LCT.h'};
通过复制外部函数 myFcn 的原型,指定输出函数的原型,模型在每个仿真步骤中调用该函数。将参数的名称设置为 u1、p1 和 y1,以表示输入参数、参数参数和输出参数。指定每个参数都是一个指针。
def.OutputFcnSpec = ['void myFcn(sigStructType u1[1], ',...
'paramStructType p1[1], sigStructType y1[1])'];
使用函数legacy_code从规范 def 创建 S-Function和相应的 C MEX 可执行文件。指定选项“generate_for_sim”,为普通和加速仿真准备 S-Function。
legacy_code('generate_for_sim',def);
### Start Compiling sfun_ex_mySrc_LCT
mex('-I/tmp/Bdoc22a_1891349_243570/tpff56b2da/ex12763634', '-c', '-outdir', '/tmp/Bdoc22a_1891349_243570/tp913d8e4d_9ce6_467a_9241_63da6beb9b2f', '/tmp/Bdoc22a_1891349_243570/tpff56b2da/ex12763634/ex_mySrc_LCT.c')
Building with 'gcc'.
MEX completed successfully.
mex('sfun_ex_mySrc_LCT.c', '-I/tmp/Bdoc22a_1891349_243570/tpff56b2da/ex12763634', '/tmp/Bdoc22a_1891349_243570/tp913d8e4d_9ce6_467a_9241_63da6beb9b2f/ex_mySrc_LCT.o')
Building with 'gcc'.
MEX completed successfully.
### Finish Compiling sfun_ex_mySrc_LCT
### Exit
创建在仿真期间调用 S-Function的封装 S-Function模块。
legacy_code('slblock_generate', def);
该模块将显示在新模型中。
要在模型中使用 S-Function 模块,请创建 sigStructType 类型的总线信号以用作模块输入。模块输出也是一个总线信号。模块封装接受参数 P1 。要设置参数的值,请使用其字段与结构类型 paramStructType 的字段匹配的 MATLAB 结构。
验证外部代码的执行
创建一个工具模型,用于在仿真期间验证外部代码的执行情况。
例如,查看模型ex_lct_struct。
legacy_code('slblock_generate', def);
在“常量模块”对话框中, 常量值 参数设置为其字段与结构类型 sigStructType 的字段匹配的结构。在 “信号属性 ”选项卡上, “输出”数据类型 设置为总线对象 sigStructType。
S-Function模块调用您创建的 S-Functionsfun_ex_mySrc_LCT。该模块的输出进入总线选择器模块,该模块提取信号元素 sig1 和 sig2。
S-Function 模块通过封装对话框接受参数。创建一个 MATLAB 结构结构参数以用作参数的值。
structParam = struct;
structParam.param1 = 15;
structParam.param2 = 20;
structParam.param3 = 5;
(可选)使用 Simulink.Parameter 对象来包含结构。如果使用参数对象,则可以使用总线对象 paramStructType 设置结构的数据类型。
structParam = Simulink.Parameter(structParam);
structParam.DataType = 'Bus: paramStructType';
在封装对话框中,将 P1 设置为 structParam。
set_param('ex_lct_struct/sfun_ex_mySrc_LCT','SParameter1','structParam')
仿真模型。作用域模块显示 S-Function 模块调用外部函数 myFcn。
open_system('ex_lct_struct/Scope')
open_system('ex_lct_struct/Scope1')
sim('ex_lct_struct')
%
注册代码继承工具数据结构
使用代码继承工具的第一步是使用表示现有 C 代码的属性和正在生成的 S-Function的字段注册一个或多个 MATLAB 结构。注册过程非常灵活。您可以选择以多种方式设置资源和启动注册,包括
- 将所有必需的头文件和源文件放在当前工作文件夹或分层文件夹结构中
- 在当前工作文件夹中生成和放置一个或多个 S-Function
- 在同一文件夹中有一个或多个注册文件
注册代码继承工具数据结构:
- 使用legacy_code函数,指定“initialize”作为第一个参数。
lct_spec = legacy_code('initialize')
名为 lct_spec 的代码继承工具数据结构在 MATLAB 命令窗口中显示其字段,如下所示:
lct_spec =
SFunctionName: ''
InitializeConditionsFcnSpec: ''
OutputFcnSpec: ''
StartFcnSpec: ''
TerminateFcnSpec: ''
HeaderFiles: {}
SourceFiles: {}
HostLibFiles: {}
TargetLibFiles: {}
IncPaths: {}
SrcPaths: {}
LibPaths: {}
SampleTime: 'inherited'
Options: [1x1 struct]
- 为应用于现有 C 函数和要生成的 S-Function的数据结构字段(属性)定义值。至少,您必须指定
- 现有 C 函数的源文件和头文件(源文件和头文件)
- S-Function的名称(SFunctionName)
- S-Function至少有一个函数规范(InitializeConditionsFcnSpec、OutputFcnSpec、StartFcnSpec、TerminateFcnSpec)
如果定义了指定编译资源的字段并指定了相对路径,则代码继承工具将按以下顺序搜索与以下目录相关的资源:
- 当前工作文件夹
- C-MEX S-Function文件夹(如果与当前工作文件夹不同)
- 您指定的目录
- 头文件的 IncPath
- 源文件的路径
- 目标库和主机库的 LibPath
- MATLAB 搜索路径上的目录,不包括工具箱目录
声明代码继承工具函数规范
在代码继承工具数据结构中定义的 InitializeConditionsFcnSpec、 OutputFcnSpec、StartFcnSpec 和 TerminateFcnSpec 字段(请参阅legacy_code 函数的说明)需要符合特定语法格式的字符向量值。所需的语法格式使代码继承工具能够将现有 C 函数的返回值和参数映射到该工具生成的 S-Function的返回值、输入、输出、参数和工作向量。
常规语法
return-spec = function-name ( argument-spec )
例如,以下字符向量指定一个名为 doubleIt 的函数,其返回规范为 double y1,输入参数规范为 double u1。
def.OutputFcnSpec = 'double y1 = doubleIt(double u1)';
有关声明函数规范的更多详细信息,请参阅
返回规格
返回规范定义现有 C 函数的返回值的数据类型和变量名称。
返回类型返回变量
| 返回类型 | 支持的数据类型中列出的数据类型。 |
| 返回变量 | y1, y2, ...,yn,其中 n 是输出参数的总数。 |
如果函数不返回值,则可以省略返回规范或将其指定为 void。
下表显示了整数返回值的有效函数规范语法。使用该表确定应用于 C 函数原型的语法。
| 返回类型 | C 函数原型 | 代码继承工具函数规范 |
| 无返回值 | void myfunction(...) | void myfunction(...) |
| 标量值 | int = myfunction(...) | int16 y1 = myfunction(...) |
函数名称
指定的函数名必须与现有的 C 函数名相同。
例如,请考虑以下 C 函数原型:
float doubleIt(float inVal);
在这种情况下,代码继承工具函数规范中的函数名称必须是 doubleIt。
不应指定 C 宏的名称。如果必须,请将字段 Options.isMacro 设置为 true,以防启用表达式折叠。
参数规范
参数规范定义一个或多个数据类型和令牌对,这些数据类型和令牌对表示现有 C 函数的输入、输出、参数和工作向量参数。函数输入和输出参数映射到模块输入和输出端口,参数映射到工作区参数。
参数类型参数令牌
| 参数类型 | 支持的数据类型中列出的数据类型。 |
| 参数令牌 | 以下形式之一的令牌:输入— u1, u2, ...,un,其中 n 是输入参数的总数输出— y1, y2, ...,yn,其中 n 是输出参数的总数参数— p1, p2, ...,pn,其中 n 是参数参数的总数工作向量(持久内存)— 工作1,工作 2, ...,workn,其中 n 是工作向量参数的总数 |
-
="block" data-draft-type="table" data-size="normal" data-row-style="normal">
参数类型 支持的数据类型中列出的数据类型。 参数令牌 以下形式之一的令牌:输入— u1, u2, ...,un,其中 n 是输入参数的总数输出— y1, y2, ...,yn,其中 n 是输出参数的总数参数— p1, p2, ...,pn,其中 n 是参数参数的总数工作向量(持久内存)— 工作1,工作 2, ...,workn,其中 n 是工作向量参数的总数 -
="block" data-draft-type="table" data-size="normal" data-row-style="normal">
参数类型 支持的数据类型中列出的数据类型。 参数令牌 以下形式之一的令牌:输入— u1, u2, ...,un,其中 n 是输入参数的总数输出— y1, y2, ...,yn,其中 n 是输出参数的总数参数— p1, p2, ...,pn,其中 n 是参数参数的总数工作向量(持久内存)— 工作1,工作 2, ...,workn,其中 n 是工作向量参数的总数 -
="block" data-draft-type="table" data-size="normal" data-row-style="normal">
参数类型 支持的数据类型中列出的数据类型。 参数令牌 以下形式之一的令牌:输入— u1, u2, ...,un,其中 n 是输入参数的总数输出— y1, y2, ...,yn,其中 n 是输出参数的总数参数— p1, p2, ...,pn,其中 n 是参数参数的总数工作向量(持久内存)— 工作1,工作 2, ...,workn,其中 n 是工作向量参数的总数 -
-draft-type="table" data-size="normal" data-row-style="normal">
参数类型 支持的数据类型中列出的数据类型。 参数令牌 以下形式之一的令牌:输入— u1, u2, ...,un,其中 n 是输入参数的总数输出— y1, y2, ...,yn,其中 n 是输出参数的总数参数— p1, p2, ...,pn,其中 n 是参数参数的总数工作向量(持久内存)— 工作1,工作 2, ...,workn,其中 n 是工作向量参数的总数
如果函数没有参数,则可以省略参数规范或将其指定为 void。
请考虑以下 C 函数原型:
float powerIt(float inVal, int exponent);
若要生成在每个时间步长调用上述函数的 S-Function,请将“代码继承工具”数据结构字段 OutputFcnSpec 设置为以下内容:
'single y1 = powerIt(single u1, int16 p1)'
使用此函数规范,代码继承工具将映射以下信息。
| 返回值或参数 | C 型 | 到令牌 | 数据类型 |
| Return value | float | y1 | single |
| inVal | float | u1 | single |
| exponent | int | p1 | int16 |
如果您的函数需要具有多个输入和输出端口的 Simulink S 函数模块,请使用唯一编号的 u 令牌将函数参数映射到输入端口。对于输出端口,请使用唯一编号的 y 令牌。前面的参数规范表中介绍了这些标记。例如,请考虑以下 C 函数原型:
void myfunc(double *y2, double u2, double u3, double u1, double *y1);
将参数映射到输入和输出端口的 OutputFcnSpec 字符向量类似于以下内容:
'void myfunc(double y2[1], double u2, double u3, double u1, double y1[1])'
由此产生的 S 函数模块包括三个输入端口和两个输出端口。第一个输入映射到函数参数 u1,第二个输入映射到 u2,第三个输入映射到 u3。对于输出端口,函数参数 y1[1] 映射到第一个输出,参数 y2[1] 映射到第二个输出。
下表显示了整数类型参数的有效函数规范语法。使用该表可以标识并调整应用于 C 函数原型的语法。
| 参数类型 | C 函数原型 | 代码继承工具函数规范 |
| 输入参数 | ||
| 无参数 | function(void) | function(void) |
| 标量按值传递 | function(int in1) | function(int16 u1) |
| 标量通过指针传递 | function(int *in1) | function(int16 u1[1]) |
| 固定数组 |
function(int in1[10]) 或
function(int *in1) |
function(int16 u1[10]) |
| 可变数组 |
function(int in1[]) 或
function(int *in1) |
function(int16 u1[]) |
| 固定矩阵 |
function(int in1[15]) 或
function(int in1[]) 或 function(int *in1) |
function(int16 u1[3][5]) |
| 变量矩阵 |
function(int in1[]) 或
function(int *in1) |
function(int16 u1[][]) |
| 输出参数 | Output Arguments | |
| 标量指针 | function(int *y1) | function(int16 y1[1]) |
| 固定数组 |
function(int y1[10]) 或
function(int *y1) |
function(int16 y1[10]) |
| 固定矩阵 |
function(int y1[15]) 或
function(int y1[]) 或 function(int *y1) |
function(int16 y1[3][5]) |
| 参数参数 | Parameter Arguments | |
| 标量按值传递 | function(int p1) | function(int16 p1) |
| 标量通过指针传递 | function(int *p1) | function(int16 p1[1]) |
| 固定数组 |
function(int p1[10]) 或
function(int *p1) |
function(int16 p1[10]) |
| 可变数组 |
function(int p1[]) 或
function(int *p1) |
function(int16 p1[]) |
| 固定矩阵 |
function(int p1[15]) 或
function(int p1[]) 或 function(int *p1) |
function(int16 p1[3][5]) |
| 变量矩阵 |
function(int p1[]) 或
function(int *p1) |
function(int16 p1[][]) |
| 工作向量参数 | W或k Vect或 Arguments | |
| 按值传递的标量 | function(int w或k1) | function(int16 w或k1) |
| 标量指针 |
function(int *w或k1)
function(void *w或k1) function(void **w或k1) |
function(int16 w或k1[1])
void function(void *w或k1) void function(void **w或k1) |
| 固定数组 |
function(int w或k1[10]) 或
function(int *w或k1) |
function(int16 w或k1[10]) |
| 固定矩阵 |
function(int w或k1[15]) 或
function(int w或k1[]) 或 function(int *w或k1) |
function(int16 w或k1[3][5]) |
支持的数据类型
| 数据类型 | 是否支持输入和输出? | 是否支持参数? | 是否支持工作向量? |
| Simulink 支持的数据类型(字符串除外) | Yes | Yes | Yes |
| Simulink.Bus1 | Yes | Yes | Yes |
| Array of Simulink.Bus2 | Yes | No | Yes |
| Simulink.NumericType3 | Yes | Yes | Yes |
| Simulink.AliasType1 | Yes | Yes | Yes |
| enum1 | Yes | Yes | Yes |
| Fixed-point4 | Yes | Yes | Yes |
| Fi objects | N/A | Yes | N/A |
| Complex numbers5 | Yes | Yes | Yes |
| 1-D array | Yes | Yes | Yes |
| 2-D array6 | Yes | Yes | Yes |
| n-D array7 | Yes | Yes | Yes |
| void * | No | No | Yes |
| void ** | No | No | Yes |
- 必须提供定义总线结构、定义枚举类型或定义与别名同名的数据类型的头文件。在头文件中声明的总线的结构必须与总线对象的结构(例如,元素的数量和顺序、元素的数据类型和宽度等)匹配。
若要生成与 C 代码定义的自定义数据类型相对应的数据类型对象和枚举类,请使用 Simulink.importExternalCTypes 函数。
- 总线元素可能很复杂,但只能使用 Simulink 内置数据类型。支持将数组嵌套到任何级别。
- 仅当数值数据类型也是别名时,才必须提供定义数据类型的头文件。
- 您必须将数据声明为 Simulink.NumericType 对象(不支持未指定的缩放)。
- 仅限于与 Simulink 内置数据类型一起使用。若要指定复杂数据类型,请将内置数据类型括在尖括号内(<>),并在前面加上单词 complex(例如,complex<double>)。
- MATLAB、Simulink 和 Simulink Coder 产品以列主格式存储多维数组数据作为向量。如果外部函数代码是为行主数据编写的,请使用legacy_code中的 convertNDArrayToRowMajor S-Function选项 。
- 对于多维信号,可以使用 size 函数来确定信号中的元素数。
代码继承工具函数规范规则
legacy_code的规格必须遵守以下规则:
- 如果参数不是标量参数,则必须通过引用传递参数。
- 输入、输出、参数和工作向量参数标记的编号必须从 1 开始,并单调增加。
- 对于给定的代码继承工具数据结构,输入、输出、参数和工作向量参数的数据类型和大小在 StartFcnSpec、InitializeConditionsFcnSpec、OutputFcnSpec 和 TerminateFcnSpec 的函数规范中必须相同。
- 您可以使用使用以下内容的表达式指定参数维度:
- 函数:名称, 大小
- 参数值
- 运算符:+、-、* 和 /
- 整数和浮点文本
- 用于对子表达式进行分组的括号
例如:
def.OutputFcnSpec=
foo4(int8 p1[], int8 u1[], double y1[numel(u1)+2][numel(u1)+3], ...
int32 (numel(p1)+numel(u1))*2+size(y1,2))';
继承 C 函数规则
要使用代码继承工具集成 C 函数,该函数必须遵守以下规则:
- 该函数不得更改输入参数的值。如果输入信号是传递给函数的按引用参数,则该函数不得修改参数所指向的数据。
- 函数的返回值不能是指针。
- 为 StartFcnSpec、InitializeConditionsFcnSpec 或 TerminateFcnSpec 定义的函数规范无法访问输入或输出参数。对于 StartFcnSpec 和 InitializeConditionsFcnSpec,如果“S-Function”选项 outputsConditionallyWriting 设置为 true,则可以访问输出端口。使用此选项设置,生成的 S-Function 指定与每个输出端口关联的内存不能被覆盖,并且是全局的(SS_NOT_REUSABLE_AND_GLOBAL)。
生成和编译 S-Function
为现有 C 函数注册代码继承工具数据结构后,请使用下面介绍的 legacy_code 函数生成、编译和链接 S-Function。
- 根据结构中定义的信息生成 C MEX S-Function。调用legacy_code,以“sfcn_cmex_generate”作为第一个参数,以数据结构的名称作为第二个参数。
legacy_code(lct_spec sfcn_cmex_generate);
- 编译并链接 S-Function。此步骤假定已为 MATLAB 安装设置了 C 编译器。调用legacy_code,其中“compile”作为第一个参数,数据结构的名称作为第二个参数。
- legacy_code('compile', lct_spec);
类似于以下内容的信息性消息出现在 MATLAB 命令窗口中,并生成可动态加载的可执行文件。在 32 位 Windows 系统上,Simulink 软件将文件命名为 ex_sfun_doubleit.mexw32。
### Start Compiling ex_sfun_doubleit
mex ex_sfun_doubleit.c -Id:\work\lct\lct_demos
### Finish Compiling ex_sfun_doubleit
### Exit
为方便起见,您可以通过调用带有字符向量“generate_for_sim”的legacy_code,在单个步骤中生成、编译和链接 S-Function。如果代码继承工具数据结构的 Options.useTlcWithAccel 字段设置为 1,则该函数还会生成一个 TLC 文件,用于加速仿真。
生成动态可加载的可执行文件后,您或其他人可以通过添加指定已编译的 S-Function的 S-Function 模块在模型中使用它。
生成用于调用生成的 S-Function的封装 S-Function模块
您可以选择使用代码继承工具生成一个封装的 S 函数模块(图形表示形式),该模块配置为调用生成的 C MEX S-Function。若要生成此类模块,请使用“slblock_generate”作为第一个参数,以代码继承工具数据结构的名称作为第二个参数调用legacy_code。
legacy_code(lct_spec slblock_generate);
该工具会封装该模块,使其显示“输出FcnSpec”字段的值。然后,您可以手动将模块添加到模型中。
如果您希望代码继承工具自动将模块添加到模型中,请将模型的名称指定为第三个参数。例如:
legacy_code('slblock_generate', lct_spec, 'myModel');
如果指定的模型(例如 myModel)存在,legacy_code打开该模型并添加代码继承工具数据结构描述的封装 S 函数模块。如果模型不存在,则该函数将创建一个具有指定名称的新模型,并添加封装的 S 函数模块。
强制联动加速器模式使用 S-Function TLC 内联代码
如果您使用的是 Simulink 加速器™模式,则可以为代码继承工具生成的 S-Function生成并强制使用 TLC 内联代码。为此,请执行以下操作:
- 通过调用legacy_code 函数(其中 “sfcn_tlc_generate”作为第一个参数,代码继承工具数据结构的名称作为第二个参数)为 S-Function生成 TLC 模块文件。
legacy_code(lct_spec sfcn_tlc_generate);
要为模型生成 TLC 文件,请输入以下命令:
legacy_code('sfcn_tlc_generate', def);
- 强制加速器模式使用 TLC 文件,方法是使用 ssSetOptions SimStruct 函数设置 S-Function选项SS_OPTION_USE_TLC_WITH_ACCELERATOR。
调用继承C++函数
若要在初始化代码继承工具数据结构后调用继承C++函数,请将值“C++”分配给 Options.language 字段。例如
def = legacy_code('initialize');
def Options.language = 'C++';
要验证新设置,请输入
def.Options.language
处理多个注册文件
您可以在同一文件夹中有多个注册文件,并通过对legacy_code的一次调用为每个文件生成一个 S-Function。同样,您可以使用单个调用来legacy_code以编译和链接 S-Function,并使用另一个调用来生成相应的 TLC 模块文件(如果适用)。
请考虑以下示例,其中lct_register_1、lct_register_2和lct_register_3创建和初始化代码继承工具结构的字段。
defs1 = lct_register_1;
defs2 = lct_register_2;
defs3 = lct_register_3;
defs = [defs1(:);d efs2(:);d efs3(:)];
然后,您可以使用以下调用序列来legacy_code,以便基于三个注册文件生成文件:
legacy_code('sfcn_cmex_generate', defs);
legacy_code('compile', defs);
legacy_code('sfcn_tlc_generate', defs);
或者,您可以单独处理每个注册文件。例如:
defs1 = lct_register1;
legacy_code('sfcn_cmex_generate', defs1);
legacy_code('compile', defs1);
legacy_code('sfcn_tlc_generate', defs1);
.
.
.
defs2 = lct_register2;
legacy_code('sfcn_cmex_generate', defs2);
legacy_code('compile', defs2);
legacy_code('sfcn_tlc_generate', defs2);
.
.
.
defs3 = lct_register3;
legacy_code('sfcn_cmex_generate', defs3);
legacy_code('compile', defs3);
legacy_code('sfcn_tlc_generate', defs3);
部署生成的 S-Function
您可以部署使用代码继承工具生成的 S-Function,以供其他人使用。若要部署仅用于仿真用途的 S-Function,只需共享已编译的动态可加载可执行文件。
代码继承工具示例
有关代码继承工具的示例,请参阅使用代码继承工具实现算法。
代码继承工具限制
代码继承工具
- 为用 C 或 C++ 编写的现有函数生成 C MEX S-Function。该工具不支持 MATLAB 或 Fortran 函数的转换。
- 可以与C++函数交互,但不能与C++对象交互。解决此限制的一种方法是使用 S-Function生成器生成 S-Function的 shell,然后从 S-Function的 mdlOutputs 回调函数调用继承C++代码。
- 不支持仿真连续或离散状态。这会阻止您使用 mdlUpdate 和 mdlDerivatives 回调函数。如果您的应用程序需要此支持,请参阅使用 S-Function生成器合并代码继承。
- 始终为直接馈通(size.DirFeedthrough)到 true。由于此设置和上述限制,生成的 S-Function无法中断代数环。
- 仅支持连续但固定的次要时间步长、采样时间和偏移选项。
- 支持复数,但仅适用于 Simulink 内置数据类型。
- 不支持使用函数指针作为被调用的旧函数的输出。
- 不支持以下 S-Function函数:
- 工作向量,常规 DWork 向量除外
- 基于帧的输入和输出信号
- 基于端口的采样时间
- 基于多个模块的采样时间
- 不支持使用作用域(::)运算符,用于访问C++类数据和方法。对于静态方法,可以编写类似于以下内容的简单预处理器宏来解决此问题:
#define CCommon_computeVectorDotProduct CCommon::computeVectorDotProduct
- 如果函数规范包含具有属性 HeaderFile 的 Simulink 数据类型,则在尚未指定终止函数时可以生成终止函数。对于导出函数模型,此终止函数可能会使生成的 S-Function与代码生成不兼容。
使用代码继承工具在生成的代码中导入对外部代码的调用
代码继承工具和代码生成
您可以使用 Simulink®代码继承工具为现有代码或自定义代码生成完全内联的 C MEX S-Function。S-Function 针对设备驱动程序和查找表等嵌入式组件进行了优化,可以调用现有的 C 或 C++ 函数。
注意
代码继承工具可与 C++ 函数对接,但不能与 C++ 对象对接。要解决此问题,使该工具能够与 C++ 对象对接,请参阅Legacy Code Tool Limitations。
可以使用该工具:
- 编译和构造生成的 S-Function 以进行仿真。
- 生成封装的 S-Function 模块,此模块被配置为调用现有的外部代码。
如果要在打算为其生成代码的模型中包含这些类型的 S-Function,请使用该工具生成 TLC 模块文件。TLC 模块文件指定为模型生成的代码如何调用现有的 C 或 C++ 函数。
如果 S-Function 依赖的是其他文件夹中的文件,而不是包含 S-Function 动态加载的可执行文件的文件夹中的文件,请使用该工具为 S-Function 生成 sFunction_makecfg.m 或 rtwmakecfg.m 文件。生成这样的文件可在编译包含 S-Function 的模型时保持这些依存关系。例如,对于某些应用程序(如自定义目标),您可能希望在目标特定的位置找到文件。编译过程将在与 S-Function 动态加载可执行文件相同的文件夹中查找 sFunction_makecfg.m 或 rtwmakecfg.m,并调用此文件中的函数。
有关详细信息,请参阅 Integrate C Functions Using Legacy Code Tool 。
生成内联 S-Function 文件以进行代码生成
要为使用 S-Function 的模型生成代码,请根据应用程序的代码生成要求执行以下操作之一:
- 为内联 S-Function 生成一个 .cpp 文件。在从现有 C 函数生成 S-Function 源文件之前,请在代码继承工具数据结构体中将 Options.singleCPPMexFile 字段的值设置为 true。例如:
- def.Options.singleCPPMexFile = true;
legacy_code('sfcn_cmex_generate', def);
- 为内联 S-Function 生成源文件和 TLC 模块文件。例如:
- def.Options.singleCPPMexFile = false;
- legacy_code('sfcn_cmex_generate', def);
legacy_code('sfcn_tlc_generate', def);
有关 singleCPPMexFile 的限制
在下列情况下,不能将 singleCPPMexFile 字段设置为 true:
- Options.language='C++'
- 您使用以下 Simulink 对象之一并将 IsAlias 属性设置为 true:
- Simulink.Bus
- Simulink.AliasType
- Simulink.NumericType
- 代码继承工具函数规范中包含 void* 或 void**,以表示状态参数的标量工作数据
- 代码继承工具结构体的 HeaderFiles 字段指定多个头文件
对已有函数应用代码样式设置
要将代码样式的模型配置参数应用于已有函数,请执行以下操作:
- 初始化代码继承工具数据结构体。例如:
- def = legacy_code('initialize');
- 在数据结构体中,将 Options.singleCPPMexFile 字段的值设置为 true。例如:
def.Options.singleCPPMexFile = true;
要检查设置,请输入:
def.Options.singleCPPMexFile
有关 singleCPPMexFile 的限制
在下列情况下,不能将 singleCPPMexFile 字段设置为 true:
- Options.language='C++'
- 您使用以下 Simulink 对象之一并将 IsAlias 属性设置为 true:
- Simulink.Bus
- Simulink.AliasType
- Simulink.NumericType
- 代码继承工具函数规范中包含 void* 或 void**,以表示状态参数的标量工作数据
- 代码继承工具结构体的 HeaderFiles 字段指定多个头文件
解决对不同位置的文件的依存关系
默认情况下,代码继承工具假定 S-Function 所依赖的文件与 S-Function 的动态加载可执行文件位于同一个文件夹中。如果您的 S-Function 所依赖的文件位于其他位置,而您使用的是模板联编文件编译过程,请为 S-Function 生成一个 sFunction_makecfg.m 或 rtwmakecfg.m 文件。例如,如果您的代码继承工具数据结构体将编译资源定义为路径名称,则您可能需要生成此文件。
要生成 sFunction_makecfg.m 或 rtwmakecfg.m 文件,请调用 legacy_code 函数,并将 'sfcn_makecfg_generate' 或 'rtwmakecfg_generate' 作为第一个参数,将代码继承工具数据结构体的名称作为第二个参数。例如:
legacy_code('sfcn_makecfg_generate', lct_spec);
如果您使用同一个文件夹中的多个注册文件,并通过调用一次 legacy_code 为每个文件生成一个 S-Function,则指定 'sfcn_makecfg_generate' 或 'rtwmakecfg_generate' 的 legacy_code 调用必须对所有注册文件都是通用的。
例如,如果将 defs 定义为代码继承工具结构体数组,则可以使用 'sfcn_makecfg_generate' 调用一次 legacy_code。
defs = [defs1(:);defs2(:);defs3(:)];
legacy_code('sfcn_makecfg_generate', defs);
部署 S-Function 以进行仿真和代码生成
您可以部署使用代码继承工具生成的 S-Function,以便其他人也能使用它们。要部署 S-Function 进行仿真和代码生成,请共享以下文件:
- 注册文件
- 已编译的动态加载可执行文件
- TLC 模块文件
- sFunction_makecfg.m 或 rtwmakecfg.m 文件
- 生成的 S-Function 所依赖的头文件、源文件和 include 文件
使用这些部署的文件时:
- 在 Simulink 模型中使用部署的文件之前,请将包含 S-Function 文件的文件夹添加到 MATLAB®路径中。
- 如果代码继承工具数据结构体将所需的文件注册为绝对路径,而文件位置发生改变,请重新生成 sFunction_makecfg.m 或 rtwmakecfg.m 文件。
集成外部 C++ 对象
代码继承工具可与 C++ 函数对接,但不能与 C++ 对象对接。以上一个示例为基础,下面的示例说明如何应对这一限制。
- 在新文件 adder_cpp.hpp 中修改 adder 的类定义。添加三个新的宏,分别用于动态分配新的 adder 对象,调用 add_one() 方法和释放分配的内存。每个宏通过一个指针指向一个 adder 对象。因为代码继承工具调用的每个函数必须具有一个与 C 语言类似的签名,所以该指针以 void* 的形式进行缓存和传递。因此,您必须在宏中明确转换为 adder*。adder 的新类定义:
#ifndef _ADDER_CPP_
#define _ADDER_CPP_
class adder {
private:
int int_state;
public:
adder(): int_state(0) {};
int add_one(int increment);
int get_val() {return int_state;};
};
// Method wrappers implemented as macros
#define createAdder(work1) \
*(work1) = new adder
#define deleteAdder(work1) \
delete(static_cast<adder*>(*(work1)))
#define adderOutput(work1, u1) \
(static_cast<adder*> ((work1)))->add_one(u1)
#endif /* _ADDER_CPP_ */
- 更新 adder_cpp.cpp。采用类修改方式(而不是一个全局实例),生成的每个 S-Function 可以管理其自己的 adder 对象。
#include "adder_cpp.hpp"
int adder::add_one(int increment)
{
int_state += increment;
return int_state;
}
- 使用以下更改更新 rtwdemo_sfun_adder_cpp.cpp:
- StartFcnSpec 调用的宏负责分配新的 adder 对象并缓存指针。
- def.StartFcnSpec = 'createAdder(void **work1)';
- OutputFcnSpec 调用的宏负责调用 add_one() 方法并提供 S-Function 特定的 adder 指针对象。
def.OutputFcnSpec = 'int32 y1 = adderOutput(void *work1, int32 u1)';
- TerminateFcnSpec 调用的宏负责释放内存。
def.TerminateFcnSpec = 'deleteAdder(void **work1)';
从子系统生成 S-Function
“ 生成 S-Function ”菜单选项可自动从子系统生成 S-Function。“ 生成 S-Function ”菜单选项显示子系统中使用的参数,并允许您将所选参数声明为可调参数。
有关从子系统创建 S-Function模块的信息,请参阅 从子系统创建 S-Function模块 。
例如,考虑SourceSubsys。目标是从模型中自动提取SourceSubsys,并从中构建一个S-Function模块。工作空间变量 K 是 SourceSubsys 中 Gain 模块的增益因子,它被声明并生成为可调变量。
要从 SourceSubsys 自动生成具有可调参数 K 的 S-Function,请执行以下操作:
- 打开 SourceSubsys 模型后,单击子系统以将其选中。
- 右键单击子系统,然后选择“ C/C++ 代码 > 生成 S-Function” 。
- 在 “生成 S-Function ”窗口中,您可以看到在子系统中作为模块参数引用的变量或数据对象。您可以将它们声明为可调参数。
窗口的上部窗格显示以下列:
- 变量名称 :参数的名称。
- 类 :如果参数是工作区变量,则显示其数据类型。如果参数是数据对象,则显示其名称和类。
- 可调 参数:允许您选择可调参数。若要声明参数可调参数,请选中该复选框。在此示例中,参数 K 被声明为可调参数。
在上部窗格中选择参数时,下部窗格将显示引用该参数的模块以及每个模块的父系统。
- 选择可调参数后,单击“ 生成 ”按钮。生成过程使用 S-Function目标启动 S-Function的代码生成和编译。“ 创建新模型 ”选项将自动启用。
- 构建过程在 MATLAB®命令窗口中显示状态消息。构建完成后,可调参数窗口将关闭,并打开一个新的无标题模型窗口。
- 模型窗口包含一个 S-Function 模块,其名称与生成该模块的子系统的名称相同(在此示例中为 SourceSubsys)。(可选)您可以保存包含生成的模块的生成模型。
为 S-Function 模块生成的代码存储在当前工作文件夹中。这些文件将写入顶级文件夹:
- subsys _sf.c或.cpp,其中 subsys 是子系统名称(例如,SourceSubsys_sf.c)
- subsys _sf.h
- subsys _sf. mexext ,其中 mexext 是依赖于平台的 MEX 文件扩展名(例如,SourceSubsys_sf.mexw64)
S-Function的源代码将写入子文件夹 subsys _sfcn_rtw。顶级.c或.cpp文件是一个存根文件,其中包含一个包含指令,您可以使用该指令将其他 C/C++ 代码连接到生成的代码。
注意
请参见 S-Function部署所需的文件。
生成的 S-Function 模块具有入口和输出端口,其宽度和采样时间与原始模型的宽度和采样时间相对应。
以下代码来自生成的 S-Function代码(SourceSubsys_sf.c)的 mdlOutputs 例程,显示了如何使用对 MEX API 的调用来引用可调变量 K。
static void mdlOutputs(SimStruct *S, int_T tid)
...
/* Gain: '<S1>/Gain' incorporates:
* Sum: '<S1>/Sum'
*/
rtb_Gain_n[0] = (rtb_Product_p + (*(((const
real_T**)ssGetInputPortSignalPtrs(S, 2))[0]))) * (*(real_T
*)(mxGetData(K(S))));
rtb_Gain_n[1] = (rtb_Product_p + (*(((const
real_T**)ssGetInputPortSignalPtrs(S, 2))[1]))) * (*(real_T
*)(mxGetData(K(S))));
在自动生成 S-Function时,将清除“ 可调参数使用值 ”选项,或者在命令行中设置为“关闭”。
注意
仅在创建包装器的 MATLAB 版本中使用 MEX S-Function包装器。
宏参数
假设您将“定义”等存储类应用于 Simulink.Parameter 对象,以便该参数在生成的代码中显示为宏。如果在从中生成 ERT S-Function的子系统内使用参数对象,则无法选择该参数对象作为可调参数。相反,S-Function代码生成器将自定义存储类应用于参数对象。通过 S-Function代码中的这一代宏,您可以从包含变体元素(如变体子系统模块)的子系统生成 S-Function,您可以配置这些元素以在生成的代码中生成预处理器条件。在模拟 S-Function期间,不能更改参数的值。
要选择参数对象作为可调参数,请应用其他存储类或创建自己的存储类。将参数视为宏的存储类包括“定义”、“导入定义”、“编译器标志”以及通过将“ 数据初始化 ”设置为“自定义存储类设计器”中的“宏”而创建的存储类。如果使用非宏存储类,则不能将参数对象用作变量控制变量并生成预处理器条件。
如果应用的存储类将参数对象视为导入的宏,则在生成 ERT S-Function之前,请提供宏定义。例如,假设您将存储类 ImportEdDefine 应用于 Simulink.Parameter 对象,并将该参数对象用作子系统中的变量控制变量。如果将自定义属性 HeaderFile 设置为'myHdr.h',则在生成 S-Function时,将自定义头文件 myHdr.h 放在当前文件夹中。生成的 S-Function使用头文件中的宏值,而不是参数对象的 Value 属性中的值。
若要使用通过编译器选项定义的宏(例如,通过应用存储类 CompilerFlag),请使用模型配置参数 “代码生成” > “自定义代码 > 代码信息 > 定义” 来指定编译器选项。有关更多信息,请参见代码生成窗格:自定义代码:其他生成信息:定义
参考文献:
【1】 https:// ww2.mathworks.cn/help
免责声明:内容源于Mathworks官方指导文档,如有侵权,请联系作者删除。