编写包装器 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 以上的内存使用量。