Main Content

Wrap User Code with TLC

wrapper Tutorial Overview

Objective: Learn the architecture of wrapper S-functions and how to create an inlined wrapper S-function using TLC.

Open the Example:

openExample('simulinkcoder/AdviceAboutTLCTutorialsExample')
cd('tlctutorial/wrapper')

Wrapper S-functions enable you to use existing C functions without fully rewriting them in the context of Simulink® S-functions. Each wrapper you provide is an S-function “shell” that merely calls one or more existing, external functions. This tutorial explains and illustrates wrappers as follows:

  • Why Wrap User Code? — Reason for building TLC wrapper functions

  • Getting Started — Set up the wrapper exercise

  • Generate Code Without a Wrapper — How the code generator handles external functions by default

  • Generate Code Using a Wrapper — Bypass the API overhead

Why Wrap User Code?

Many Simulink users want to build models incorporating algorithms that they have already coded, implemented, and tested in a high-level language. Typically, such code is brought into Simulink as S-functions. To generate an external application that integrates user code, you can take several approaches:

  • You can construct an S-function from user code that hooks it to the Simulink generic API. This is the simplest approach, but sacrifices efficiency for standalone applications.

  • You can inline the S-function, reimplementing it as a TLC file. This improves efficiency, but takes time and effort, can introduce errors into working code, and leads to two sets of code to maintain for each algorithm, unless you use the Legacy Code Tool (see Import Calls to External Code into Generated Code with Legacy Code Tool).

  • You can inline the S-function via a TLC wrapper function. By doing so, you need to create only a small amount of TLC code, and the algorithm can remain coded in its existing form.

The next figure illustrates how S-function wrappers operate.

Wrapping a function eliminates the need to recode it, requiring only a bit of extra TLC code to integrate it. Wrappers also enable object modules or libraries to be used in S-functions. This may be the only way to deploy functions for which source code is unavailable, and also allows users to distribute models to others without divulging implementation details that may be proprietary.

For example, you might have an existing object file compiled for a processor on which Simulink does not run. You can write a dummy C S-function and use a TLC wrapper that calls the external function, despite not having its source code. You could similarly access functions in a library of algorithms optimized for the target processor. Accomplishing this requires making changes to a template makefile, or otherwise providing a means to link against the library.

Note

Object files that lack source code and are created with Microsoft® Visual C and Microsoft Visual C++® Compiler (MSVC) work only with MSVC.

The only restriction on S-function wrappers is for the number of block inputs and outputs match number of inputs and outputs of the wrapped external function. Wrapper code may include computations, but usually these are limited to transforming values (for example, scaling or reformatting) passed to and from the wrapped external functions.

Getting Started

In the example folder, the “external function” is found in the file my_alg.c. You are also provided with a C S-function called wrapsfcn.c that integrates my_alg.c into Simulink. Set up the exercise as follows:

  1. Make tlctutorial/wrapper your current folder.

  2. In MATLAB®, open the model externalcode from your working folder. The block diagram looks like this:

  3. Activate the Scope block by double-clicking it.

  4. Run the model (from the Simulation tab, or type Ctrl+T). You will get an error telling you that wrapsfcn does not exist. Can you figure out why?

  5. The error occurs because a mex file does not exist for wrapsfcn. To rectify this, in the MATLAB Command Window type

    mex wrapsfcn.c

    Note

    An error might occur if you have not previously run mex -setup.

  6. Run the simulation again with the S-function present.

The S-Function block multiplies its input by two. Looking at the Scope block, you see a sine wave that oscillates between -2.0 and 2.0. The variable yout that is created in your MATLAB workspace steps through these values.

In the remainder of the exercise, you build and run a standalone version of the model, then write some TLC code that allows the code generator to build a standalone executable that calls the S-function my_alg.c directly.

Generate Code Without a Wrapper

Before creating a wrapper, generate code that uses the Simulink generic API. The first step is to build a standalone model.

  1. On the C Code tab, click Build.

    The code generator creates the standalone program in your working folder and places the source and object files in your build folder. The file will be called externalcode.exe on Microsoft Windows® platforms or externalcode on UNIX® platforms.

    As it generates the program, the code generator reports its progress in the MATLAB Command Window. The final lines are:

    ### Created executable: externalcode.exe  
    ### Successful completion of build procedure 
    for model: externalcode
  2. Run the standalone program to see that it behaves the same as the Simulink version. There should not be differences.

    !externalcode
     
    ** starting the model ** 
    ** created externalcode.mat ** 
  3. Notice this line in wrapsfcn.c:

    #include "my_alg.c"

    This pulls in the external function. That function consists entirely of

    /*
     * Copyright 1994-2002 The MathWorks, Inc.
     */
    
    double my_alg(double u)
    {
        return(u * 2.0);
    }

    Inspect the mdlOutputs() function of the code in wrapsfcn.c to see how the external function is called.

    static void mdlOutputs(SimStruct *S, int tid)
    {
        int_T             i;    
        InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
        real_T            *y    = ssGetOutputPortRealSignal(S,0);
        int_T             width = ssGetOutputPortWidth(S,0);
    
        *y = my_alg(*uPtrs[0]);
    }

Tip

For UNIX platforms, run the executable program in the Command Window with the syntax !./executable_name. If preferred, run the executable program from an OS shell with the syntax ./executable_name. For more information, see Run External Commands, Scripts, and Programs.

Generally, functions to be wrapped are either included in the wrapper, as above, or, when object modules are being wrapped, resolved at link time.

Generate Code Using a Wrapper

To create a wrapper for the external function my_alg.c, you need to construct a TLC file that embodies its calling function, wrapsfcn.c. The TLC file must generate C code that provides:

  • A function prototype for the external function that returns a double, and passes the input double u.

  • A function call to my_alg() in the outputs section of the code.

To create a wrapper for my_alg(), do the following:

  1. Open the file change_wrapsfcn.tlc in your editor, and add lines of code where comments indicate to create a workable wrapper.

  2. Save the edited file as wrapsfcn.tlc. It must have the same name as the S-function block that uses it or TLC is not called to inline code.

  3. In MATLAB, open the model externalcode from your working folder. Activate the Scope block by double-clicking it, and run the model (from the Simulation tab, or type Ctrl+T). This gives you a baseline result.

  4. Inform Simulink that your code has an external reference to be resolved. To update the model’s parameters, in the MATLAB Command Window, do one of the following:

    • Type

      set_param('externalcode/S-Function','SFunctionModules','my_alg')

    • In the S-Function block parameters dialog box, in the S-function modules field, specify 'my_alg'.

  5. Create the standalone application, by entering one of the following commands in the Command Window:

    slbuild('my_alg','ForceTopModelBuild',true)
    slbuild('my_alg', 'StandaloneCoderTarget','ForceTopModelBuild',true)

    These commands force the code generator to rebuild the top model, which is required when you make changes associated with external or custom code.

    Alternatively, you can force regeneration of top model code by deleting folders in the Code generation folder, such as slprj or the generated model code folder.

    For more information, see Control Regeneration of Top Model Code.

  6. Run the new standalone application and verify that it yields identical results as in the scope window.

    !externalcode

If you had problems building the application:

  • Find the error messages and try to determine what files are at fault, paying attention to which step (code generation, compiling, linking) failed.

  • Be sure you issued the set_param() command as specified above.

  • Chances are that problems can be traced to your TLC file. It may be helpful to use TLC debugger to step through wrapsfcn.tlc.

  • As a last resort, look at wrapsfcn.tlc in the solutions/tlc_solution folder, also listed below:

    %% File    : wrapsfcn.tlc
    %% Abstract:
    %%      Example tlc file for S-function wrapsfcn.c
    %%
    %% Copyright 1994-2002 The MathWorks, Inc.
    %%
    %% 
    
    %implements "wrapsfcn" "C"
    %% Function: BlockTypeSetup ================================
    %% Abstract:
    %%      Create function prototype in model.h as:
    %%	    "extern double my_alg(double u);" 
    %%
    %function BlockTypeSetup(block, system) void
      %openfile buffer
      %% ASSIGNMENT: PROVIDE A LINE OF CODE AS A FUNCTION PROTOTYPE
      %% FOR "my_alg" AS DESCRIBED IN THE WRAPPER TLC ASSIGNMENT 
      extern double my_alg(double u);
    
      %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 "wrapfcn"
      %<y> = my_alg( %<u> );
    %endfunction %% Outputs

    Look at the highlighted lines. Did you declare my_alg() as extern double? Did you call my_alg() with the expected input and output? Fix mistakes and rebuild the model.

Related Topics