Write Wrapper S-Function and TLC Files
Create S-functions that work seamlessly with the Simulink® and code generator products by using the wrapper concept. You can:
Interface your algorithms in Simulink models by writing MEX S-function wrappers (
).sfunction
.mexDirect the code generator to insert your algorithm into the generated code by creating a TLC S-function wrapper (
).sfunction
.tlc
MEX S-Function Wrapper
Creating S-functions by using an S-function wrapper enables you to insert C/C++ code algorithms in Simulink models and the generated code with little or no change to your original C/C++ function. A MEX S-function wrapper is an S-function that calls code, which resides in another module.
Note
Use a MEX S-function wrapper only in the MATLAB® version in which you created the wrapper.
Suppose that you have an algorithm (that is, a C function) called
my_alg
that resides in the file my_alg.c
.
You can integrate my_alg
into a Simulink model by creating a MEX S-function wrapper (for example,
wrapsfcn.c
). A Simulink model can then call my_alg
from an S-Function
block. The Simulink S-function contains a set of empty functions that the Simulink engine requires for various API related purposes. For example,
although only mdlOutputs
calls my_alg
, the
engine calls mdlTerminate
, even though this S-function routine
performs no action.
You can embed the call to my_alg
in the generated code by
creating a TLC S-function wrapper (for example, wrapsfcn.tlc
).
You can eliminate the empty function calls. You can avoid the overhead of executing
the mdlOutputs
function and you can then eliminate the
my_alg
function.
Wrapper S-functions are useful when you are creating algorithms that are procedural or when you are integrating legacy code into a Simulink model. If you want to create code that is:
Interpretive in nature (that is, highly parameterized by operating modes)
Heavily optimized (that is, no extra tests to decide what mode the code is operating in)
then you must create a fully inlined TLC file for your S-function.
The next figure shows the wrapper S-function concept.
Using an S-function wrapper to import algorithms into your Simulink model means that the S-function serves as an interface that calls your
C/C++ algorithms from mdlOutputs
. You can quickly integrate
large standalone C/C++ programs into your model without having to change the
code.
This sample model includes an S-function wrapper.
Two files are associated with the wrapsfcn
block: the
S-function wrapper and the C/C++ code that contains the algorithm. The first three
statements:
Define the name of the S-function (what you enter in the Simulink S-Function block dialog box).
Specify that the S-function is using the level 2 format.
Provide access to the
SimStruct
data structure.SimStruct
contains pointers to data used during simulation and code generation and defines macros that store data in and retrieve data from theSimStruct
.
#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
For more information, see Templates for C S-Functions.
The S-function routine mdlOutputs
contains a function call to
my_alg
, which is the C function containing the algorithm
that the S-function performs. For my_alg.c
, the code is:
#ifdef MATLAB_MEX_FILE #include "tmwtypes.h" #else #include "rtwtypes.h" #endif real_T my_alg(real_T u) { return(u * 2.0); }
For more information, see Manage Build Process File Dependencies.
The wrapper S-function wrapsfcn
calls
my_alg
, which computes u * 2.0
. To build
wrapsfcn.mex
, use this command:
mex wrapsfcn.c my_alg.c
TLC S-Function Wrapper
A TLC S-function wrapper is a TLC file that specifies how the code generator calls
your code. For example, you can inline the call to my_alg
in
the mdlOutputs
section of the generated code. In the MEX S-Function Wrapper example, the call to my_alg
is embedded in the mdlOutputs
section as:
*y = my_alg(*uPtrs[0]);
When you are creating a TLC S-function wrapper, the goal is to embed the same type of call in the generated code.
Look at how the code generator executes S-functions that are not inlined. A
noninlined S-function is identified by the absence of the file
and the
existence of sfunction
.tlc
. When
generating code for a noninlined S-function, the code generator produces a call to
sfunction
.mexmdlOutputs
through a function pointer that, in this
example, then calls my_alg
.
The wrapper example contains one S-function, wrapsfcn.mex
. You
must compile and link an additional module, my_alg
, with the
generated code. At the MATLAB command prompt, enter:
set_param('wrapper/S-Function','SFunctionModules','my_alg')
Code Overhead for Noninlined S-Functions
The code generated when using grt.tlc
as the system target file
without
wrapsfcn.tlc
is:
<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 */
The wrapper.reg
generated file contains the initialization of
the SimStruct
for the wrapper S-Function block. There is one
child SimStruct
for each S-Function block in your model. You can
significantly reduce this overhead by creating a TLC wrapper for the
S-function.
Inline a Wrapper S-Function
The generated code makes the call to your S-function,
wrapsfcn.c
, in mdlOutputs
by using this
code:
SimStruct *rts = ssGetSFunction(rtS, 0); sfcnOutputs(rts, tid);
This call has computational overhead associated with it. The Simulink engine creates a SimStruct
data structure for the
S-Function block. The code generator constructs a call through a function pointer to
execute mdlOutputs
, then mdlOutputs
calls
my_alg
. By inlining the call to your C/C++ algorithm,
my_alg
, you can eliminate both the
SimStruct
and the extra function call, thereby improving the
efficiency and reducing the size of the generated code.
Inlining a wrapper S-function requires an
file for the
S-function. The TLC file must contain the function call to
sfunction
.tlcmy_alg
. The figure shows the relationships between the
algorithm, the wrapper S-function, and the
file.sfunction
.tlc
To inline the call to my_alg
, place your function call in an
file with the
same name as the S-function (in this example, sfunction
.tlcwrapsfcn.tlc
). The
Target Language Compiler overrides the default method of placing calls to your
S-function in the generated code.
This code is the TLC file wrapsfcn.tlc
that inlines
wrapsfcn.c
:
%% 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
The first section of this code inlines the wrapsfcn
S-Function
block and generates the code in C:
%implements "wrapsfcn" "C"
The next task is to inform the code generator that the routine
my_alg
must be declared as external in the generated
wrapper.h
file for any
wrapsfcn
S-Function blocks in the model. Do this declaration once
for all
wrapsfcn
S-Function blocks by using the
BlockTypeSetup
function. In this function, you direct the
Target Language Compiler to create a buffer and cache the my_alg
as extern
in the wrapper.h
generated header
file.
The final step is the inlining of the call to the function
my_alg
. The Outputs
function inlines
the call. In this function, you access the block input and output and place a direct
call to my_alg
. The call is embedded in
wrapper.c
.
The Inlined Code
The code generated when you inline your wrapper S-function is similar to the
default generated code. The mdlTerminate
function does not
contain a call to an empty function and the mdlOutputs
function
now directly calls 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
does not create a child
SimStruct
for the S-function because the generated code is
calling my_alg
directly, eliminating over 1 KB of memory
usage.