编写包装器 S-Function 和 TLC 文件
利用包装器的概念,创建能与 Simulink® 和代码生成器产品无缝协作的 S-Function。您可以:
通过编写 MEX S-Function 包装器 (
),在 Simulink 模型中引入您的算法。sfunction.mex通过创建 TLC S-Function 包装器 (
),指示代码生成器将您的算法插入到生成的代码中。sfunction.tlc
MEX S-Function 包装器
使用 S-Function 包装器创建 S-Function 的优点在于,只需对您的原始 C/C++ 函数进行很少改动或根本不需要改动,即可将 C/C++ 代码算法插入到 Simulink 模型和生成的代码中。MEX S-Function 包装器是一个 S-Function,它调用位于另一个模块中的代码。
注意
MEX S-Function 包装器只能在创建该包装器的 MATLAB® 版本中使用。
假设您有一个名为 my_alg 的算法(即一个 C 函数),该算法位于 my_alg.c 文件中。您可以通过创建 MEX S-Function 包装器(例如 wrapsfcn.c),将 my_alg 集成到 Simulink 模型中。然后 Simulink 模型即可从 S-Function 模块中调用 my_alg。Simulink S-Function 包含一组空函数,供 Simulink 引擎用来实现各种与 API 有关的目的。例如,虽然只有 mdlOutputs 调用 my_alg,但引擎还会调用 mdlTerminate,即使此 S-Function 例程并不执行任何操作。
您可以通过创建 TLC S-Function 包装器(例如 wrapsfcn.tlc),在生成的代码中嵌入对 my_alg 的调用。您可以消除空函数调用。您可以避免执行 mdlOutputs 函数的开销,然后可以消除 my_alg 函数。
当您创建程序性的算法或将原有代码集成到 Simulink 模型中时,S-function 包装器很有用。如果要创建以下代码:
本质上是解释性代码(即按操作模式高度参数化)
高度优化的代码(即不需要通过额外的测试来决定代码的运行模式)
则必须为您的 S-Function 创建一个完全内联的 TLC 文件。
下图显示了 S-Function 包装器的概念。

使用 S-Function 包装器将算法导入 Simulink 模型是指:将 S-Function 用作一个接口来从 mdlOutputs 中调用您的 C/C++ 算法。您可以将大型、独立 C/C++ 程序快速集成到模型中,而无需更改代码。
此示例模型包含一个 S-Function 包装器。

wrapsfcn 模块有两个关联文件:S-Function 包装器和包含算法的 C/C++ 代码。前三个语句:
定义 S-Function 的名称(您在 Simulink S-Function 模块对话框中输入的内容)。
指定 S-Function 使用 2 级格式。
提供对
SimStruct数据结构体的访问。SimStruct结构体包含仿真和代码生成期间所用数据的指针,并定义在SimStruct中存储和检索数据的宏。
#define S_FUNCTION_NAME wrapsfcn
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
extern real_T my_alg(real_T u); /* Declare my_alg as extern */
/*
* mdlInitializeSizes - initialize the sizes array
*/
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams( S, 0); /*number of input arguments*/
if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortDirectFeedThrough(S, 0, 1);
if (!ssSetNumOutputPorts(S,1)) return;
ssSetOutputPortWidth(S, 0, 1);
ssSetNumSampleTimes( S, 1);
}
/*
* mdlInitializeSampleTimes - indicate that this S-function runs
* at the rate of the source (driving block)
*/
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
}
/*
* mdlOutputs - compute the outputs by calling my_alg, which
* resides in another module, my_alg.c
*/
static void mdlOutputs(SimStruct *S, int_T tid)
{
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
real_T *y = ssGetOutputPortRealSignal(S,0);
*y = my_alg(*uPtrs[0]); /* Call my_alg in mdlOutputs */
}
/*
* mdlTerminate - called when the simulation is terminated.
*/
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有关详细信息,请参阅Templates for C S-Functions。
S-Function 例程 mdlOutputs 包含对 my_alg 的函数调用,后者是包含 S-Function 执行的算法的 C 函数。对于 my_alg.c,代码为:
#ifdef MATLAB_MEX_FILE
#include "tmwtypes.h"
#else
#include "rtwtypes.h"
#endif
real_T my_alg(real_T u)
{
return(u * 2.0);
}有关详细信息,请参阅Manage Build Process File Dependencies。
包装器 S-Function wrapsfcn 调用 my_alg 来计算 u * 2.0。要编译 wrapsfcn.mex,请使用以下命令:
mex wrapsfcn.c my_alg.c
TLC S-Function 包装器
TLC S-Function 包装器是一个 TLC 文件,它指定代码生成器如何调用您的代码。例如,您可以在生成的代码的 mdlOutputs 部分内联对 my_alg 的调用。在 MEX S-Function 包装器示例中,对 my_alg 的调用嵌入在 mdlOutputs 部分,如下所示:
*y = my_alg(*uPtrs[0]);
创建 TLC S-Function 包装器时,目的也是在生成的代码中嵌入相同类型的调用。
观察代码生成器如何执行非内联 S-Function。非内联 S-Function 的特点是缺少 文件,但存在 sfunction.tlc 文件。为非内联 S-Function 生成代码时,代码生成器通过函数指针生成对 sfunction.mexmdlOutputs 的调用,在此示例中,该函数指针将调用 my_alg。
包装器示例包含一个 S-Function wrapsfcn.mex。您必须再编译一个模块 my_alg 并将其与生成的代码链接。在 MATLAB 命令提示符下,输入:
set_param('wrapper/S-Function','SFunctionModules','my_alg')
非内联 S-Function 的代码开销
使用 grt.tlc 作为系统目标文件而没有 wrapsfcn.tlc 时生成的代码如下:
<Generated code comments for wrapper model with noninlined wrapsfcn S-function>
#include <math.h>
#include <string.h>
#include "wrapper.h"
#include "wrapper.prm"
/* Start the model */
void mdlStart(void)
{
/* (start code not required) */
}
/* Compute block outputs */
void mdlOutputs(int_T tid)
{
/* Sin Block: <Root>/Sin */
rtB.Sin = rtP.Sin.Amplitude *
sin(rtP.Sin.Frequency * ssGetT(rtS) + rtP.Sin.Phase);
/* Level2 S-Function Block: <Root>/S-Function (wrapsfcn) */
{
/* Noninlined S-functions create a SimStruct object and
* generate a call to S-function routine mdlOutputs
*/
SimStruct *rts = ssGetSFunction(rtS, 0);
sfcnOutputs(rts, tid);
}
/* Outport Block: <Root>/Out */
rtY.Out = rtB.S_Function;
}
/* Perform model update */
void mdlUpdate(int_T tid)
{
/* (update code not required) */
}
/* Terminate function */
void mdlTerminate(void)
{
/* Level2 S-Function Block: <Root>/S-Function (wrapsfcn) */
{
/* Noninlined S-functions require a SimStruct object and
* the call to S-function routine mdlTerminate
*/
SimStruct *rts = ssGetSFunction(rtS, 0);
sfcnTerminate(rts);
}
}
#include "wrapper.reg"
/* [EOF] wrapper.c */生成的文件 wrapper.reg 还包含对包装器 S-Function 模块的 SimStruct 进行初始化。模型中的每个 S-Function 模块都有一个子级 SimStruct。通过为 S-Function 创建 TLC 包装器,可以显著减少这类开销。
内联包装器 S-Function
生成的代码使用以下代码在 mdlOutputs 中调用您的 S-Function wrapsfcn.c:
SimStruct *rts = ssGetSFunction(rtS, 0); sfcnOutputs(rts, tid);
此调用会产生相应的计算开销。Simulink 引擎为 S-Function 模块创建一个 SimStruct 数据结构体。代码生成器通过函数指针构造一个调用来执行 mdlOutputs,然后 mdlOutputs 调用 my_alg。通过内联对 C/C++ 算法 my_alg 的调用,可以消除 SimStruct 和额外的函数调用,从而提高效率并减小生成的代码的大小。
内联包装器 S-Function 需要为 S-Function 创建一个 文件。TLC 文件必须包含对 sfunction.tlcmy_alg 的函数调用。下图显示了算法、S-Function 包装器和 文件之间的关系。sfunction.tlc

要内联对 my_alg 的调用,请将函数调用放在与 S-Function 同名的 文件中(本示例中为 sfunction.tlcwrapsfcn.tlc)。目标语言编译器会覆盖在生成的代码中放置对 S-Function 的调用的默认方法。
以下代码是用于内联 wrapsfcn.c 的 TLC 文件 wrapsfcn.tlc:
%% File : wrapsfcn.tlc %% Abstract: %% Example inlined tlc file for S-function wrapsfcn.c %% %implements "wrapsfcn" "C" %% Function: BlockTypeSetup ==================================================== %% Abstract: %% Create function prototype in model.h as: %% "extern real_T my_alg(real_T u);" %% %function BlockTypeSetup(block, system) void %openfile buffer extern real_T my_alg(real_T u); /* This line is placed in wrapper.h */ %closefile buffer %<LibCacheFunctionPrototype(buffer)> %endfunction %% BlockTypeSetup %% Function: Outputs =========================================================== %% Abstract: %% y = my_alg( u ); %% %function Outputs(block, system) Output /* %<Type> Block: %<Name> */ %assign u = LibBlockInputSignal(0, "", "", 0) %assign y = LibBlockOutputSignal(0, "", "", 0) %% PROVIDE THE CALLING STATEMENT FOR "algorithm" %% The following line is expanded and placed in mdlOutputs within wrapper.c %<y> = my_alg(%<u>); %endfunction %% Outputs
这段代码的第一部分内联 wrapsfcn S-Function 模块并生成 C 代码:
%implements "wrapsfcn" "C"
下一个任务是通知代码生成器,必须在生成的 wrapper.h 文件中为模型中的 wrapsfcn S-Function 模块将例程 my_alg 声明为外部例程。使用 BlockTypeSetup 函数可以一次为所有 wrapsfcn S-Function 模块执行此声明。在此函数中,您指示目标语言编译器创建一个缓冲区,并将 my_alg 以 extern 的形式缓存在生成的头文件 wrapper.h 中。
最后一步是内联对 my_alg 函数的调用。Outputs 函数内联调用。在此函数中,您将访问模块的输入和输出,并放置一个对 my_alg 的直接调用。调用嵌入在 wrapper.c 中。
内联代码
内联 S-Function 包装器时生成的代码与默认生成的代码类似。mdlTerminate 函数不包含对空函数的调用,mdlOutputs 函数现在直接调用 my_alg。
void mdlOutputs(int_T tid)
{
/* Sin Block: <Root>/Sin */
rtB.Sin = rtP.Sin.Amplitude *
sin(rtP.Sin.Frequency * ssGetT(rtS) + rtP.Sin.Phase);
/* S-Function Block: <Root>/S-Function */
rtB.S_Function = my_alg(rtB.Sin); /* Inlined call to my_alg */
/* Outport Block: <Root>/Out */
rtY.Out = rtB.S_Function;
}wrapper.reg 不会为 S-Function 创建子级 SimStruct,因为生成的代码会直接调用 my_alg,从而消除了 1 KB 以上的内存使用量。