Main Content

编写包装器 S-Function 和 TLC 文件

利用包装器的概念,创建能与 Simulink® 和代码生成器产品无缝协作的 S-Function。您可以:

  • 通过编写 MEX S-Function 包装器 (sfunction.mex),在 Simulink 模型中引入您的算法。

  • 通过创建 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++ 代码。前三个语句:

  1. 定义 S-Function 的名称(您在 Simulink S-Function 模块对话框中输入的内容)。

  2. 指定 S-Function 使用 2 级格式。

  3. 提供对 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 文件,但存在 sfunction.mex 文件。为非内联 S-Function 生成代码时,代码生成器通过函数指针生成对 mdlOutputs 的调用,在此示例中,该函数指针将调用 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 创建一个 sfunction.tlc 文件。TLC 文件必须包含对 my_alg 的函数调用。下图显示了算法、S-Function 包装器和 sfunction.tlc 文件之间的关系。

要内联对 my_alg 的调用,请将函数调用放在与 S-Function 同名的 sfunction.tlc 文件中(本示例中为 wrapsfcn.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_algextern 的形式缓存在生成的头文件 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 以上的内存使用量。

相关主题