S-Functions Incorporate Legacy C Code
Setup Working Environment for Incorporating Legacy C Code
This example opens up a directory containing files required for this topic.
Open this example to access the following files.
doubleIt.c
doubleIt.h
wrapsfcn.c
wrapsfcn.tlc
builder_wrapsfcn.c
builder_wrapsfcn.tlc
builder_wrapsfcn_wrapper.c
lct_wrapsfcn.m
legacy_wrapsfcn.c
legacy_wrapsfcn.tlc
Overview
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 by 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 example
model contains blocks that use these S-functions. If you plan to create the model, copy the
files doubleIt.c
and
doubleIt.h
into
your working folder.
Using a Hand-Written S-Function to Incorporate Legacy Code
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 create the example model.
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).
Using the S-Function Builder to Incorporate Legacy Code
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);
Use the S-Function Builder block 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
andout1
, 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
mdlOutputs
method of the file calls thebuilder_wrapsfcn_wrapper.c
function. The method uses the input and output namesin1
andout1
, 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 requiredmdlTerminate
method.
The wrapper function builder_wrapsfcn_wrapper.c
has three parts:
The
Include Files
section includes thedoubleIt.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 functionbuilder_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.
%% File : builder_wrapsfun.tlc %% %% Description: %% Simulink Coder wrapper functions interface generated for %% S-function "builder_wrapsfun.c". %% %implements builder_wrapsfun "C" %% Function: BlockTypeSetup =================================================== %% %% Purpose: %% Set up external references for wrapper functions in the %% generated code. %% %function BlockTypeSetup(block, system) Output %assign realType = LibGetDataTypeNameFromId(::CompiledModel.tSS_DOUBLE) %if IsModelReferenceSimTarget() || CodeFormat == "S-Function" || ::isRAccel %assign hFileName = "builder_wrapsfun_accel_wrapper" %assign hFileNameMacro = FEVAL("upper", hFileName) %openfile hFile = "%<hFileName>.h" %selectfile hFile #ifndef _%<hFileNameMacro>_H_ #define _%<hFileNameMacro>_H_ #ifdef MATLAB_MEX_FILE #include "tmwtypes.h" #else #include "rtwtypes.h" #endif #ifdef __cplusplus #define SFB_EXTERN_C extern "C" #else #define SFB_EXTERN_C extern #endif SFB_EXTERN_C void builder_wrapsfun_Outputs_wrapper_accel(const %<realType> *in1, %<realType> *out1); #undef SFB_EXTERN_C #endif %closefile hFile %assign cFileName = "builder_wrapsfun_accel_wrapper" %openfile cFile = "%<cFileName>.c" %selectfile cFile #include <string.h> #ifdef MATLAB_MEX_FILE #include "tmwtypes.h" #else #include "rtwtypes.h" #endif #include "%<hFileName>.h" extern void builder_wrapsfun_Start_wrapper(void); extern void builder_wrapsfun_Outputs_wrapper(const %<realType> *in1, %<realType> *out1); extern void builder_wrapsfun_Terminate_wrapper(void); void builder_wrapsfun_Outputs_wrapper_accel(const %<realType> *in1, %<realType> *out1){ builder_wrapsfun_Outputs_wrapper(in1, out1); } %closefile cFile %<LibAddToCommonIncludes("%<hFileName>.h")> %else %openfile externs #ifdef __cplusplus #define SFB_EXTERN_C extern "C" #else #define SFB_EXTERN_C extern #endif SFB_EXTERN_C void builder_wrapsfun_Start_wrapper(void); SFB_EXTERN_C void builder_wrapsfun_Outputs_wrapper(const %<realType> *in1, %<realType> *out1); SFB_EXTERN_C void builder_wrapsfun_Terminate_wrapper(void); #undef SFB_EXTERN_C %closefile externs %<LibCacheExtern(externs)> %endif %% %endfunction %% Function: Outputs ========================================================== %% %% Purpose: %% Code generation rules for mdlOutputs function. %% %function Outputs(block, system) Output %% %assign pu0 = LibBlockInputSignalAddr(0, "", "", 0) %assign py0 = LibBlockOutputSignalAddr(0, "", "", 0) %if IsModelReferenceSimTarget() || CodeFormat == "S-Function" || ::isRAccel builder_wrapsfun_Outputs_wrapper_accel(%<pu0>, %<py0>); %else builder_wrapsfun_Outputs_wrapper(%<pu0>, %<py0>); %endif %% %endfunction %% [EOF] builder_wrapsfun.tlc
Using the Legacy Code Tool to Incorporate Legacy Code
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 functionbuilder_wrapsfcn_wrapper.c
. The S-function generated by the Legacy Code Tool directly callsdoubleIt.c
from itsmdlOutputs
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
andu
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