C MEX S-functions allow you to call existing C code within your Simulink® models. For example, consider the simple C function doubleIt.c
that outputs a value two times the value of the function
input.
double doubleIt(double u) { return(u * 2.0); }
You can create an S-function that calls doubleIt.c
by either:
Writing a wrapper S-function. Using this method, you hand write a new C S-function and associated TLC file. This method requires the most knowledge about the structure of a C S-function.
Using an S-Function Builder block. Using this method, you enter the characteristics of the S-function into a block dialog. This method does not require any knowledge about writing S-functions. However, a basic understanding of the structure of an S-function can make the S-Function Builder dialog box easier to use.
Using the Legacy Code Tool. Using this command line method, you define the characteristics of your S-function in a data structure in the MATLAB® workspace. This method requires the least amount of knowledge about S-functions.
You can also call external C code from a Simulink model using a MATLAB Function block. For more information see Integrate C Code Using the MATLAB Function Block.
The following sections describe how to create S-functions for use in a Simulink simulation and with Simulink
Coder™ code generation, using the previous three methods. The model
sfcndemo_choosing_sfun
contains blocks that use these S-functions.
Copy this model and the files doubleIt.c
and doubleIt.h
from the folder
docroot
/toolbox/simulink/sfg/examples
into
your working folder if you plan to step through the examples.
The S-function wrapsfcn.c
calls the legacy function doubleIt.c
in
its mdlOutputs
method. Save the wrapsfcn.c
file into
your working folder, if you are planning to compile the S-function to run in the example
model sfcndemo_choosing_sfun
.
To incorporate the legacy code into the S-function, wrapsfcn.c
begins
by declaring doubleIt.c
with the following line:
extern real_T doubleIt(real_T u);
Once declared, the S-function can use doubleIt.c
in its
mdlOutputs
method. For example:
/* Function: mdlOutputs ======================================= * Abstract: * Calls the doubleIt.c function to multiple the input by 2. */ static void mdlOutputs(SimStruct *S, int tid){ InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); real_T *y = ssGetOutputPortRealSignal(S,0); *y = doubleIt(*uPtrs[0]); }
To compile the wrapsfcn.c
S-function, run the following
mex
command. Make sure that the doubleIt.c
file is
in your working folder.
mex wrapsfcn.c doubleIt.c
To generate code for the S-function using the Simulink
Coder code generator, you need to write a Target Language Compiler (TLC) file. The
following TLC file wrapsfcn.tlc
uses the BlockTypeSetup
function to
declare a function prototype for doubleIt.c
. The TLC file's
Outputs
function then tells the Simulink
Coder code generator how to inline the call to doubleIt.c
. For
example:
%implements "wrapsfcn" "C" %% File : wrapsfcn.tlc %% Abstract: %% Example tlc file for S-function wrapsfcn.c %% %% Function: BlockTypeSetup ================================ %% Abstract: %% Create function prototype in model.h as: %% "extern double doubleIt(double u);" %% %function BlockTypeSetup(block, system) void %openfile buffer %% PROVIDE ONE LINE OF CODE AS A FUNCTION PROTOTYPE extern double doubleIt(double u); %closefile buffer %<LibCacheFunctionPrototype(buffer)> %%endfunction %% BlockTypeSetup %% Function: Outputs ======================================= %% Abstract: %% CALL LEGACY FUNCTION: y = doubleIt( u ); %% %function Outputs(block, system) Output /* %<Type> Block: %<Name> */ %assign u = LibBlockInputSignal(0, "", "", 0) %assign y = LibBlockOutputSignal(0, "", "", 0) %% PROVIDE THE CALLING STATEMENT FOR "doubleIt" %<y> = doubleIt( %<u> ); %endfunction %% Outputs
For more information on the TLC, see Target Language Compiler Basics (Simulink Coder).
The S-Function Builder automates the creation of S-functions and TLC files that
incorporate legacy code. For this example, in addition to doubleIt.c
, you
need the header file doubleIt.h
that declares the doubleIt.c
function
format, as follows:
extern real_T doubleIt(real_T in1);
The S-Function Builder block in sfcndemo_choosing_sfun
shows how to configure the block dialog to call
the legacy function doubleIt.c
. In the S-Function Builder block
dialog:
The S-function name field in the
Parameters pane defines the name
builder_wrapsfcn
for the generated S-function.
The Data Properties pane names the input and output ports as
in1
and out1
, respectively.
The Libraries pane provides the interface to the legacy code.
The Library/Object/Source files field contains the source
file name doubleIt.c
.
The Includes field contains the following line to include the header file that declares the legacy function:
#include <doubleIt.h>
The Outputs pane calls the legacy function with the lines:
/* Call function that multiplies the input by 2 */ *out1 = doubleIt(*in1);
The Build Info pane selects the Generate wrapper TLC option.
When you click Build, the S-Function Builder generates three files.
File Name | Description |
---|---|
builder_wrapsfcn.c | The main S-function. |
builder_wrapsfcn_wrapper.c | A wrapper file containing separate functions for the code entered in the Outputs, Continuous Derivatives, and Discrete Updates panes of the S-Function Builder. |
builder_wrapsfcn.tlc | The S-function's TLC file. |
The builder_wrapsfcn.c
file follows a standard format:
The file begins with a set of #define
statements that incorporate
the information from the S-Function Builder. For example, the following lines define the
first input port:
#define NUM_INPUTS 1 /* Input Port 0 */ #define IN_PORT_0_NAME in1 #define INPUT_0_WIDTH 1 #define INPUT_DIMS_0_COL 1 #define INPUT_0_DTYPE real_T #define INPUT_0_COMPLEX COMPLEX_NO #define IN_0_FRAME_BASED FRAME_NO #define IN_0_DIMS 1-D #define INPUT_0_FEEDTHROUGH 1
Next, the file declares all the wrapper functions found in the
builder_wrapsfcn_wrapper.c
file. This example requires only a
wrapper function for the Outputs code.
extern void builder_wrapsfcn_Outputs_wrapper(const real_T *in1, real_T *out1);
Following these definitions and declarations, the file contains the S-function
methods, such as mdlInitializeSizes
, that initialize the S-function's
input ports, output ports, and parameters. See Process View for a list of methods that are called during the S-function
initialization phase.
The file mdlOutputs
method calls the
builder_wrapsfcn_wrapper.c
function. The method uses the input and
output names in1
and out1
, as defined in the
Data Properties pane, when calling the wrapper function. For
example:
/* Function: mdlOutputs ============================================= * */ static void mdlOutputs(SimStruct *S, int_T tid) { const real_T *in1 = (const real_T*) ssGetInputPortSignal(S,0); real_T *out1 = (real_T *)ssGetOutputPortRealSignal(S,0); builder_wrapsfcn_Outputs_wrapper(in1, out1); }
The file builder_wrapsfcn.c
concludes with the required
mdlTerminate
method.
The wrapper function builder_wrapsfcn_wrapper.c
has three parts:
The Include Files
section includes the
doubleIt.h
file, along with the standard S-function header
files:
/* * Include Files * */ #if defined(MATLAB_MEX_FILE) #include "tmwtypes.h" #include "simstruc_types.h" #else #include "rtwtypes.h" #endif /* %%%-SFUNWIZ_wrapper_includes_Changes_BEGIN --- EDIT HERE TO _END */ #include <math.h> #include <doubleIt.h> /* %%%-SFUNWIZ_wrapper_includes_Changes_END --- EDIT HERE TO _BEGIN */
The External References
section contains information from the
External reference declarations field on the
Libraries pane. This example does not use this section.
The Output functions
section declares the function
builder_wrapfcn_Outputs_wrapper
, which contains the code entered in
the S-Function Builder block dialog's Outputs pane:
/* * Output functions * */ void builder_wrapfcn_Outputs_wrapper(const real_T *in1, real_T *out1) { /* %%%-SFUNWIZ_wrapper_Outputs_Changes_BEGIN --- EDIT HERE TO _END */ /* Call function that multiplies the input by 2 */ *out1 = doubleIt(*in1); /* %%%-SFUNWIZ_wrapper_Outputs_Changes_END --- EDIT HERE TO _BEGIN */ }
Note
Compared to a handwritten S-function, the S-Function Builder places the call to the
legacy C function down an additional level through the wrapper file
builder_wrapsfcn_wrapper.c
.
The TLC file builder_wrapsfcn.tlc
generated by the S-Function Builder is similar to
the previous handwritten version. The file declares the legacy function in
BlockTypeSetup
and calls it in the Outputs
method.
%implements builder_wrapsfcn "C" %% Function: BlockTypeSetup ==================================== %% %% Purpose: %% Set up external references for wrapper functions in the %% generated code. %% %function BlockTypeSetup(block, system) Output %openfile externs extern void builder_wrapsfcn_Outputs_wrapper(const real_T *in1, real_T *out1); %closefile externs %<LibCacheExtern(externs)> %% %endfunction %% Function: Outputs =========================================== %% %% Purpose: %% Code generation rules for mdlOutputs function. %% %function Outputs(block, system) Output /* S-Function "builder_wrapsfcn_wrapper" Block: %<Name> */ %assign pu0 = LibBlockInputSignalAddr(0, "", "", 0) %assign py0 = LibBlockOutputSignalAddr(0, "", "", 0) %assign py_width = LibBlockOutputSignalWidth(0) %assign pu_width = LibBlockInputSignalWidth(0) builder_wrapsfcn_Outputs_wrapper(%<pu0>, %<py0> ); %% %endfunction
The section Integrate C Functions into Simulink Models with Legacy Code Tool in “Writing S-Functions in C” shows how to use the Legacy Code Tool to
create an S-function that incorporates doubleIt.c
. For a script that
performs the steps in that example, copy the file lct_wrapsfcn.m
to your working folder. Make sure that the
doubleIt.c
and doubleIt.h
files are in your working folder then run the script by
typing lct_wrapsfcn
at the MATLAB command prompt. The script creates and compiles the S-function
legacy_wrapsfcn.c
and creates the TLC file
legacy_wrapsfcn.tlc
via the following commands.
% Create the data structure def = legacy_code('initialize'); % Populate the data struture def.SourceFiles = {'doubleIt.c'}; def.HeaderFiles = {'doubleIt.h'}; def.SFunctionName = 'legacy_wrapsfcn'; def.OutputFcnSpec = 'double y1 = doubleIt(double u1)'; def.SampleTime = [-1,0]; % Generate the S-function legacy_code('sfcn_cmex_generate', def); % Compile the MEX-file legacy_code('compile', def); % Generate a TLC-file legacy_code('sfcn_tlc_generate', def);
The S-function legacy_wrapsfcn.c
generated by the Legacy Code Tool begins by
including the doubleIt.h
header file. The mdlOutputs
method then directly calls the doubleIt.c
function, as follows:
static void mdlOutputs(SimStruct *S, int_T tid) { /* * Get access to Parameter/Input/Output/DWork/size information */ real_T *u1 = (real_T *) ssGetInputPortSignal(S, 0); real_T *y1 = (real_T *) ssGetOutputPortSignal(S, 0); /* * Call the legacy code function */ *y1 = doubleIt( *u1); }
The S-function generated by the Legacy Code Tool differs from the S-function generated by the S-Function Builder as follows:
The S-function generated by the S-Function Builder calls the legacy function
doubleIt.c
through the wrapper function
builder_wrapsfcn_wrapper.c
. The S-function generated by the Legacy
Code Tool directly calls doubleIt.c
from its
mdlOutputs
method.
The S-Function Builder uses the input and output names entered into the
Data Properties pane, allowing you to customize these names in
the S-function. The Legacy Code Tool uses the default names y
and
u
for the outputs and inputs, respectively. You cannot specify
customized names to use in the generated S-function when using the Legacy Code
Tool.
The S-Function Builder and Legacy Code Tool both specify an inherited sample time,
by default. However, the S-Function Builder uses an offset time of
0.0
while the Legacy Code Tool specifies that the offset time is
fixed in minor time steps.
The TLC file legacy_wrapsfcn.tlc
supports expression folding by defining
BlockInstanceSetup
and BlockOutputSignal
functions.
The TLC file also contains a BlockTypeSetup
function to declare a
function prototype for doubleIt.c
and an Outputs
function to tell the Simulink
Coder code generator how to inline the call to
doubleIt.c
.:
%% Function: BlockTypeSetup =============================================== %% %function BlockTypeSetup(block, system) void %% %% The Target Language must be C %if ::GenCPP==1 %<LibReportFatalError("This S-Function generated by the Legacy Code Tool must be only used with the C Target Language")> %endif %<LibAddToCommonIncludes("doubleIt.h")> %<LibAddToModelSources("doubleIt")> %% %endfunction %% Function: BlockInstanceSetup =========================================== %% %function BlockInstanceSetup(block, system) void %% %<LibBlockSetIsExpressionCompliant(block)> %% %endfunction %% Function: Outputs ====================================================== %% %function Outputs(block, system) Output %% %if !LibBlockOutputSignalIsExpr(0) %assign u1_val = LibBlockInputSignal(0, "", "", 0) %assign y1_val = LibBlockOutputSignal(0, "", "", 0) %% %<y1_val = doubleIt( %<u1_val>); %endif %% %endfunction %% Function: BlockOutputSignal ============================================ %% %function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void %% %assign u1_val = LibBlockInputSignal(0, "", "", 0) %assign y1_val = LibBlockOutputSignal(0, "", "", 0) %% %switch retType %case "Signal" %if portIdx == 0 %return "doubleIt( %<u1_val>)" %else %assign errTxt = "Block output port index not supported: %<portIdx>" %endif %default %assign errTxt = "Unsupported return type: %<retType>" %<LibBlockReportError(block,errTxt)> %endswitch %% %endfunction