Main Content

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 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 mdlOutputs method of the file 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.

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