S-Functions Incorporate Legacy C Code

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 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.

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 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).

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);

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 NameDescription
builder_wrapsfcn.cThe main S-function.
builder_wrapsfcn_wrapper.cA 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.tlcThe 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

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 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