Deploy Generated Standalone Executable Programs To Target Hardware

By default, the Embedded Coder® software generates standalone executable programs that do not require an external real-time executive or operating system. A standalone program requires minimal modification to be adapted to the target hardware. The standalone program architecture supports execution of models with either single or multiple sample rates.

Generate a Standalone Program

To generate a standalone program:

  1. Select the model configuration parameter Generate an example main program. This enables the Target operating system menu.

  2. From the Target operating system menu, select BareBoardExample.

  3. Generate code.

Different code is generated for multirate models depending on these factors:

  • Whether the model executes in single-tasking or multitasking mode.

  • Whether or not reusable code is being generated.

These factors can impact the scheduling algorithms used in generated code, and in some cases impact the API for the model entry-point functions. The following sections discuss these variants.

Standalone Program Components

The core of a standalone program is the main loop. On each iteration, the main loop executes a background or null task and checks for a termination condition.

The main loop is periodically interrupted by a timer. The function rt_OneStep is either installed as a timer interrupt service routine (ISR), or called from a timer ISR at each clock step.

The execution driver, rt_OneStep, sequences calls to the model_step functions. The operation of rt_OneStep differs depending on whether the generating model is single-rate or multirate. In a single-rate model, rt_OneStep simply calls the model_step function. In a multirate model, rt_OneStep prioritizes and schedules execution of blocks according to the rates at which they run.

Main Program

Overview of Operation

The following pseudocode shows the execution of a main program.

main()
{
  Initialization (including installation of rt_OneStep as an 
    interrupt service routine for a real-time clock)
  Initialize and start timer hardware
  Enable interrupts
  While(not Error) and (time < final time)
    Background task
  EndWhile
  Disable interrupts (Disable rt_OneStep from executing)
  Complete any background tasks
  Shutdown
}

The pseudocode is a design for a harness program to drive your model. The main program only partially implements this design. You must modify it according to your specifications.

Guidelines for Modifying the Main Program

This section describes the minimal modifications you should make in your production version of the main program module to implement your harness program.

  1. Call model_initialize.

  2. Initialize target-specific data structures and hardware, such as ADCs or DACs.

  3. Install rt_OneStep as a timer ISR.

  4. Initialize timer hardware.

  5. Enable timer interrupts and start the timer.

    Note

    rtModel is not in a valid state until model_initialize has been called. Servicing of timer interrupts should not begin until model_initialize has been called.

  6. Optionally, insert background task calls in the main loop.

  7. On termination of the main loop (if applicable):

    • Disable timer interrupts.

    • Perform target-specific cleanup such as zeroing DACs.

    • Detect and handle errors. Note that even if your program is designed to run indefinitely, you may need to handle severe error conditions, such as timer interrupt overruns.

      You can use the macros rtmGetErrorStatus and rtmSetErrorStatus to detect and signal errors.

rt_OneStep and Scheduling Considerations

Overview of Operation

The operation of rt_OneStep depends upon

  • Whether your model is single-rate or multirate. In a single-rate model, the sample times of all blocks in the model, and the model's fixed step size, are the same. A model in which the sample times and step size do not meet these conditions is termed multirate.

  • Your model's solver mode (SingleTasking versus MultiTasking)

Permitted Solver Modes for Embedded Real-Time System Target Files summarizes the permitted solver modes for single-rate and multirate models. Note that for a single-rate model, only SingleTasking solver mode is allowed.

Permitted Solver Modes for Embedded Real-Time System Target Files

ModeSingle-RateMultirate

SingleTasking

Allowed

Allowed

MultiTasking

Disallowed

Allowed

Auto

Allowed

(defaults to SingleTasking)

Allowed

(defaults to MultiTasking)

The generated code for rt_OneStep (and associated timing data structures and support functions) is tailored to the number of rates in the model and to the solver mode. The following sections discuss each possible case.

Single-Rate Single-Tasking Operation

The only valid solver mode for a single-rate model is SingleTasking. Such models run in “single-rate” operation.

The following pseudocode shows the design of rt_OneStep in a single-rate program.

rt_OneStep()
{
  Check for interrupt overflow or other error
  Enable "rt_OneStep" (timer) interrupt
  Model_Step()  -- Time step combines output,logging,update
}

For the single-rate case, the generated model_step function is

void model_step(void)

Single-rate rt_OneStep is designed to execute model_step within a single clock period. To enforce this timing constraint, rt_OneStep maintains and checks a timer overrun flag. On entry, timer interrupts are disabled until the overrun flag and other error conditions have been checked. If the overrun flag is clear, rt_OneStep sets the flag, and proceeds with timer interrupts enabled.

The overrun flag is cleared only upon successful return from model_step. Therefore, if rt_OneStep is reinterrupted before completing model_step, the reinterruption is detected through the overrun flag.

Reinterruption of rt_OneStep by the timer is an error condition. If this condition is detected rt_OneStep signals an error and returns immediately. (Note that you can change this behavior if you want to handle the condition differently.)

Note that the design of rt_OneStep assumes that interrupts are disabled before rt_OneStep is called. rt_OneStep should be noninterruptible until the interrupt overflow flag has been checked.

Multirate Multitasking Operation

In a multirate multitasking system, code generation uses a prioritized, preemptive multitasking scheme to execute the different sample rates in your model.

The following pseudocode shows the design of rt_OneStep in a multirate multitasking program.

rt_OneStep()
{
  Check for base-rate interrupt overrun
  Enable "rt_OneStep" interrupt
  Determine which rates need to run this time step

  Model_Step0()        -- run base-rate time step code

  For N=1:NumTasks-1   -- iterate over sub-rate tasks
    If (sub-rate task N is scheduled)
    Check for sub-rate interrupt overrun
      Model_StepN()    -- run sub-rate time step code
    EndIf
  EndFor
}

Task Identifiers.  The execution of blocks having different sample rates is broken into tasks. Each block that executes at a given sample rate is assigned a task identifier (tid), which associates it with a task that executes at that rate. Where there are NumTasks tasks in the system, the range of task identifiers is 0..NumTasks-1.

Prioritization of Base-Rate and Subrate Tasks.  Tasks are prioritized, in descending order, by rate. The base-rate task is the task that runs at the fastest rate in the system (the hardware clock rate). The base-rate task has highest priority (tid 0). The next fastest task (tid 1) has the next highest priority, and so on down to the slowest, lowest priority task (tid NumTasks-1).

The slower tasks, running at multiples of the base rate, are called subrate tasks.

Rate Grouping and Rate-Specific model_step Functions.  In a single-rate model, the block output computations are performed within a single function, model_step. For multirate, multitasking models, the code generator tries to use a different strategy. This strategy is called rate grouping. Rate grouping generates separate model_step functions for the base rate task and each subrate task in the model. The function naming convention for these functions is

model_stepN

where N is a task identifier. For example, for a model named my_model that has three rates, the following functions are generated:

void my_model_step0 (void);
void my_model_step1 (void);
void my_model_step2 (void);

Each model_stepN function executes the blocks sharing tid N; in other words, the block code that executes within task N is grouped into the associated model_stepN function.

Scheduling model_stepN Execution.  On each clock tick, rt_OneStep maintains scheduling counters and event flags for each subrate task. The counters are implemented as taskCounter arrays indexed on tid. The event flags are implemented as arrays indexed on tid.

The scheduling counters and task flags for sub-rates are maintained by rt_OneStep. The scheduling counters are basically clock rate dividers that count up the sample period associated with each sub-rate task. A pair of tasks that exchanges data maintains an interaction flag at the faster rate. Task interaction flags indicate that both fast and slow tasks are scheduled to run.

The event flags indicate whether or not a given task is scheduled for execution. rt_OneStep maintains the event flags based on a task counter that is maintained by code in the main program module for the model. When a counter indicates that a task's sample period has elapsed, the main code sets the event flag for that task.

On each invocation, rt_OneStep updates its scheduling data structures and steps the base-rate task (rt_OneStep calls model_step0 because the base-rate task must execute on every clock step). Then, rt_OneStep iterates over the scheduling flags in tid order, unconditionally calling model_stepN for any task whose flag is set. The tasks are executed in order of priority.

Preemption.  Note that the design of rt_OneStep assumes that interrupts are disabled before rt_OneStep is called. rt_OneStep should be noninterruptible until the base-rate interrupt overflow flag has been checked (see pseudocode above).

The event flag array and loop variables used by rt_OneStep are stored as local (stack) variables. Therefore, rt_OneStep is reentrant. If rt_OneStep is reinterrupted, higher priority tasks preempt lower priority tasks. Upon return from interrupt, lower priority tasks resume in the previously scheduled order.

Overrun Detection.  Multirate rt_OneStep also maintains an array of timer overrun flags. rt_OneStep detects timer overrun, per task, by the same logic as single-rate rt_OneStep.

Note

If you have developed multirate S-functions, or if you use a customized static main program module, see Rate Grouping Compliance and Compatibility Issues for information about how to adapt your code for rate grouping compatibility. This adaptation lets your multirate, multitasking models generate more efficient code.

Multirate Single-Tasking Operation

In a multirate single-tasking program, by definition, sample times in the model must be an integer multiple of the model's fixed-step size.

In a multirate single-tasking program, blocks execute at different rates, but under the same task identifier. The operation of rt_OneStep, in this case, is a simplified version of multirate multitasking operation. Rate grouping is not used. The only task is the base-rate task. Therefore, only one model_step function is generated:

void model_step(void)

On each clock tick, rt_OneStep checks the overrun flag and calls model_step. The scheduling function for a multirate single-tasking program is rate_scheduler (rather than rate_monotonic_scheduler). The scheduler maintains scheduling counters on each clock tick. There is one counter for each sample rate in the model. The counters are implemented in an array (indexed on tid) within the Timing structure within rtModel.

The counters are clock rate dividers that count up the sample period associated with each subrate task. When a counter indicates that a sample period for a given rate has elapsed, rate_scheduler clears the counter. This condition indicates that blocks running at that rate should execute on the next call to model_step, which is responsible for checking the counters.

Guidelines for Modifying rt_OneStep

rt_OneStep does not require extensive modification. The only required modification is to reenable interrupts after the overrun flags and error conditions have been checked. If applicable, you should also

  • Save and restore your FPU context on entry and exit to rt_OneStep.

  • Set model inputs associated with the base rate before calling model_step0.

  • Get model outputs associated with the base rate after calling model_step0.

    Note

    If you modify rt_OneStep to read a value from a continuous output port after each base-rate model step, see the relevant cautionary guideline below.

  • In a multirate, multitasking model, set model inputs associated with subrates before calling model_stepN in the subrate loop.

  • In a multirate, multitasking model, get model outputs associated with subrates after calling model_stepN in the subrate loop.

Comments in rt_OneStep indicate the place to add your code.

In multirate rt_OneStep, you can improve performance by unrolling for and while loops.

In addition, you may choose to modify the overrun behavior to continue execution after error recovery is complete.

Also observe the following cautionary guidelines:

  • You should not modify the way in which the counters, event flags, or other timing data structures are set in rt_OneStep, or in functions called from rt_OneStep. The rt_OneStep timing data structures (including rtModel) and logic are critical to the operation of the generated program.

  • If you have customized the main program module to read model outputs after each base-rate model step, be aware that selecting model configuration parameters Support: continuous time and Single output/update function together can cause output values read from main for a continuous output port to differ slightly from the corresponding output values in the model's logged data. This is because, while logged data is a snapshot of output at major time steps, output read from main after the base-rate model step potentially reflects intervening minor time steps. To eliminate the discrepancy, either separate the generated output and update functions (clear the Single output/update function parameter) or place a Zero-Order Hold block before the continuous output port.

  • It is possible to observe a mismatch between results from simulation and logged MAT file results from generated code if you do not set model inputs before each time you call the model step function. In the generated example main program, the following comments show the locations for setting the inputs and stepping the model with your code:

    /* Set model inputs here */
    /* Step the model */

    If your model applies signal reuse and you are using MatFileLogging for comparing results from simulation against generated code, modify rt_OneStep to write model inputs in every time step as directed by these comments. Alternatively, you could Choose a SIL or PIL Approach for verification.

Static Main Program Module

Overview

In most cases, the easiest strategy for deploying generated code is to use the Generate an example main program option to generate the ert_main.c or .cpp module (see Generate a Standalone Program).

However, if you turn the Generate an example main program option off, you can use a static main module as an example or template for developing your embedded applications. Static main modules provided by MathWorks® include:

  • matlabroot/rtw/c/src/common/rt_main.c — Supports Nonreusable function code interface packaging.

  • matlabroot/rtw/c/src/common/rt_malloc_main.c — Supports Reusable function code interface packaging. You must select model configuration parameter Use dynamic memory allocation for model initialization and set parameter Pass root-level I/O as to Part of model data structure.

  • matlabroot/rtw/c/src/common/rt_cppclass_main.cpp — Supports C++ class code interface packaging.

The static main module is not part of the generated code; it is provided as a basis for your custom modifications, and for use in simulation. If your existing applications depend upon a static ert_main.c (developed in releases before R2012b), rt_main.c, rt_malloc_main.c, or rt_cppclass_main.cpp, you may need to continue using a static main program module.

When developing applications using a static main module, you should copy the module to your working folder and rename it before making modifications. For example, you could rename rt_main.c to model_rt_main.c. Also, you must modify the template makefile or toolchain settings such that the build process creates a corresponding object file, such as model_rt_main.obj (on UNIX®, model_rt_main.o), in the build folder.

The static main module contains

  • rt_OneStep, a timer interrupt service routine (ISR). rt_OneStep calls model_step to execute processing for one clock period of the model.

  • A skeletal main function. As provided, main is useful in simulation only. You must modify main for real-time interrupt-driven execution.

For single-rate models, the operation of rt_OneStep and the main function are essentially the same in the static main module as they are in the automatically generated version described in Deploy Generated Standalone Executable Programs To Target Hardware. For multirate, multitasking models, however, the static and generated code are slightly different. The next section describes this case.

Rate Grouping and the Static Main Program

Targets based on the ERT target sometimes use a static main module and disallow use of the Generate an example main program option. This is done because target-specific modifications have been added to the static main module, and these modifications would not be preserved if the main program were regenerated.

Your static main module may or may not use rate grouping compatible model_stepN functions. If your main module is based on the static rt_main.c, rt_malloc_main.c, or rt_cppclass_main.cpp module, it does not use rate-specific model_stepN function calls. It uses the old-style model_step function, passing in a task identifier:

void model_step(int_T tid);

By default, when the Generate an example main program option is off, the ERT target generates a model_step “wrapper” for multirate, multitasking models. The purpose of the wrapper is to interface the rate-specific model_stepN functions to the old-style call. The wrapper code dispatches to the model_stepN call with a switch statement, as in the following example:

void mymodel_step(int_T tid) /* Sample time:  */
{

  switch(tid) {
   case 0 :
    mymodel_step0();
    break;
   case 1 :
    mymodel_step1();
    break;
   case 2 :
    mymodel_step2();
    break;
   default :
    break;
  }
}

The following pseudocode shows how rt_OneStep calls model_step from the static main program in a multirate, multitasking model.

rt_OneStep()
{
  Check for base-rate interrupt overflow
  Enable "rt_OneStep" interrupt
  Determine which rates need to run this time step

  ModelStep(tid=0)     --base-rate time step

  For N=1:NumTasks-1  -- iterate over sub-rate tasks
    Check for sub-rate interrupt overflow
    If (sub-rate task N is scheduled)
      ModelStep(tid=N)    --sub-rate time step
    EndIf
  EndFor
}

You can use the TLC variable RateBasedStepFcn to specify that only the rate-based step functions are generated, without the wrapper function. If your target calls the rate grouping compatible model_stepN function directly, set RateBasedStepFcn to 1. In this case, the wrapper function is not generated.

You should set RateBasedStepFcn prior to the %include "codegenentry.tlc" statement in your system target file. Alternatively, you can set RateBasedStepFcn in your target_settings.tlc file.

Modify the Static Main Program

As with the generated ert_main.c or .cpp, you should make a few modifications to the main loop and rt_OneStep. See Guidelines for Modifying the Main Program and Guidelines for Modifying rt_OneStep.

Also, you should replace the rt_OneStep call in the main loop with a background task call or null statement.

Other modifications you may need to make are

  • If applicable, follow comments in the code regarding where to add code for reading/writing model I/O and saving/restoring FPU context.

    Note

    If you modify rt_main.c, rt_malloc_main.c, or rt_cppclass_main.cpp to read a value from a continuous output port after each base-rate model step, see the relevant cautionary guideline in Guidelines for Modifying rt_OneStep.

  • When the Generate an example main program option is off, rtmodel.h is generated to provide an interface between the main module and generated model code. If you create your own static main program module, you would normally include rtmodel.h.

    Alternatively, you can suppress generation of rtmodel.h, and include model.h directly in your main module. To suppress generation of rtmodel.h, use the following statement in your system target file:

    %assign AutoBuildProcedure = 0
  • If you have cleared the Terminate function required option, remove or comment out the following in your production version of rt_main.c, rt_malloc_main.c, or rt_cppclass_main.cpp:

    • The #if TERMFCN... compile-time error check

    • The call to MODEL_TERMINATE

  • For rt_main.c (not applicable to rt_cppclass_main.cpp): If you do not want to combine output and update functions, clear model configuration parameter Single output/update function and make the following changes in your production version of rt_main.c:

    • Replace calls to MODEL_STEP with calls to MODEL_OUTPUT and MODEL_UPDATE.

    • Remove the #if ONESTEPFCN... error check.

  • The static rt_main.c module does not support Reusable function code interface packaging. The following error check raises a compile-time error if Reusable function code interface packaging is used illegally.

    #if MULTI_INSTANCE_CODE==1

Modify Static Main to Allocate and Access Model Instance Data

If you are using a static main program module, and your model is configured for Reusable function code interface packaging, but the model configuration parameter Use dynamic memory allocation for model initialization is cleared, model instance data must be allocated statically or dynamically by the calling main code. Pointers to the individual model data structures (such as Block IO, DWork, and Parameters) must be set up in the top-level real-time model data structure.

To support main modifications, the build process generates a subset of the following real-time model (RTM) macros, based on the data requirements of your model, into model.h.

RTM Macro SyntaxDescription
rtmGetBlockIO(rtm)

Get the block I/O data structure

rtmSetBlockIO(rtm,val)

Set the block I/O data structure

rtmGetContStates(rtm)

Get the continuous states data structure

rtmSetContStates(rtm,val)

Set the continuous states data structure

rtmGetDefaultParam(rtm)

Get the default parameters data structure

rtmSetDefaultParam(rtm,val)

Set the default parameters data structure

rtmGetPrevZCSigState(rtm)

Get the previous zero-crossing signal state data structure

rtmSetPrevZCSigState(rtm,val)

Set the previous zero-crossing signal state data structure

rtmGetRootDWork(rtm)

Get the DWork data structure

rtmSetRootDWork(rtm,val)

Set the DWork data structure

rtmGetU(rtm)

Get the root inputs data structure (when root inputs are passed as part of the model data structure)

rtmSetU(rtm,val)

Set the root inputs data structure (when root inputs are passed as part of the model data structure)

rtmGetY(rtm)

Get the root outputs data structure (when root outputs are passed as part of the model data structure)

rtmSetY(rtm,val)

Set the root outputs data structure (when root outputs are passed as part of the model data structure)

Use these macros in your static main program to access individual model data structures within the RTM data structure. For example, suppose that the example model rtwdemo_reusable is configured with Reusable function code interface packaging, Use dynamic memory allocation for model initialization cleared, Pass root-level I/O as set to Individual arguments, and Optimization pane option Remove root level I/O zero initialization cleared. Building the model generates the following model data structures and model entry-points into rtwdemo_reusable.h:

/* Block states (auto storage) for system '<Root>' */
typedef struct {
  real_T Delay_DSTATE;                 /* '<Root>/Delay' */
} D_Work;

/* Parameters (auto storage) */
struct Parameters_ {
  real_T k1;                           /* Variable: k1
                                        * Referenced by: '<Root>/Gain'
                                        */
};

/* Model entry point functions */
extern void rtwdemo_reusable_initialize(RT_MODEL *const rtM, real_T *rtU_In1,
  real_T *rtU_In2, real_T *rtY_Out1);
extern void rtwdemo_reusable_step(RT_MODEL *const rtM, real_T rtU_In1, real_T
  rtU_In2, real_T *rtY_Out1);

Additionally, if model configuration parameter Generate an example main program is not selected for the model, rtwdemo_reusable.h contains definitions for the RTM macros rtmGetDefaultParam, rtmsetDefaultParam, rtmGetRootDWork, and rtmSetRootDWork.

Also, for reference, the generated rtmodel.h file contains an example parameter definition with initial values (non-executing code):

#if 0

/* Example parameter data definition with initial values */
static Parameters rtP = {
  2.0                                  /* Variable: k1
                                        * Referenced by: '<Root>/Gain'
                                        */
};                                     /* Modifiable parameters */

#endif

In the definitions section of your static main file, you could use the following code to statically allocate the real-time model data structures and arguments for the rtwdemo_reusable model:

static RT_MODEL rtM_;
static RT_MODEL *const rtM = &rtM_;    /* Real-time model */
static Parameters rtP = {
  2.0                                  /* Variable: k1
                                        * Referenced by: '<Root>/Gain'
                                        */
};                                     /* Modifiable parameters */

static D_Work rtDWork;                 /* Observable states */

/* '<Root>/In1' */
static real_T rtU_In1;

/* '<Root>/In2' */
static real_T rtU_In2;

/* '<Root>/Out1' */
static real_T rtY_Out1;

In the body of your main function, you could use the following RTM macro calls to set up the model parameters and DWork data in the real-time model data structure:

int_T main(int_T argc, const char *argv[])
{
...
/* Pack model data into RTM */

rtmSetDefaultParam(rtM, &rtP);
rtmSetRootDWork(rtM, &rtDWork);

/* Initialize model */
rtwdemo_reusable_initialize(rtM, &rtU_In1, &rtU_In2, &rtY_Out1);
...
}

Follow a similar approach to set up multiple instances of model data, where the real-time model data structure for each instance has its own data. In particular, the parameter structure (rtP) should be initialized, for each instance, to the desired values, either statically as part of the rtP data definition or at run time.

Rate Grouping Compliance and Compatibility Issues

Main Program Compatibility

When the Generate an example main program option is off, code generation produces slightly different rate grouping code, for compatibility with the older static ert_main.c module. See Rate Grouping and the Static Main Program for details.

Make Your S-Functions Rate Grouping Compliant

Built-in Simulink® blocks, as well as DSP System Toolbox™ blocks, are compliant with the requirements for generating rate grouping code. However, user-written multirate inlined S-functions may not be rate grouping compliant. Noncompliant blocks generate less efficient code, but are otherwise compatible with rate grouping. To take full advantage of the efficiency of rate grouping, your multirate inlined S-functions must be upgraded to be fully rate grouping compliant. You should upgrade your TLC S-function implementations, as described in this section.

Use of noncompliant multirate blocks to generate rate-grouping code generates dead code. This can cause two problems:

  • Reduced code efficiency.

  • Warning messages issued at compile time. Such warnings are caused when dead code references temporary variables before initialization. Since the dead code does not run, this problem does not affect the run-time behavior of the generated code.

To make your S-functions rate grouping compliant, you can use the following TLC functions to generate ModelOutputs and ModelUpdate code, respectively:

OutputsForTID(block, system, tid)
UpdateForTID(block, system, tid)

The code listings below illustrate generation of output computations without rate grouping (Listing 1) and with rate grouping (Listing 2). Note the following:

  • The tid argument is a task identifier (0..NumTasks-1).

  • Only code guarded by the tid passed in to OutputsForTID is generated. The if (%<LibIsSFcnSampleHit(portName)>) test is not used in OutputsForTID.

  • When generating rate grouping code, OutputsForTID and/or UpdateForTID is called during code generation. When generating non-rate-grouping code, Outputs and/or Update is called.

  • In rate grouping compliant code, the top-level Outputs and/or Update functions call OutputsForTID and/or UpdateForTID functions for each rate (tid) involved in the block. The code returned by OutputsForTID and/or UpdateForTID must be guarded by the corresponding tid guard:

    if (%<LibIsSFcnSampleHit(portName)>)

    as in Listing 2.

Listing 1: Outputs Code Generation Without Rate Grouping

%% multirate_blk.tlc

%implements "multirate_blk" "C"


%% Function: mdlOutputs =====================================================
%% Abstract:
%%
%%  Compute the two outputs (input signal decimated by the
%%  specified parameter). The decimation is handled by sample times.
%%  The decimation is only performed if the block is enabled.
%%  Each port has a different rate.
%%
%%  Note, the usage of the enable should really be protected such that
%%  each task has its own enable state. In this example, the enable
%% occurs immediately which may or may not be the expected behavior.
%%
  %function Outputs(block, system) Output
  /* %<Type> Block: %<Name> */
  %assign enable = LibBlockInputSignal(0, "", "", 0)
  {
    int_T *enabled = &%<LibBlockIWork(0, "", "", 0)>;

    %if LibGetSFcnTIDType("InputPortIdx0") == "continuous"
      %% Only check the enable signal on a major time step.
      if (%<LibIsMajorTimeStep()> && ...
                           %<LibIsSFcnSampleHit("InputPortIdx0")>) {
        *enabled = (%<enable> > 0.0);
      }
    %else
      if (%<LibIsSFcnSampleHit("InputPortIdx0")>) {
        *enabled = (%<enable> > 0.0);
      }
    %endif

    if (*enabled) {
      %assign signal = LibBlockInputSignal(1, "", "", 0)
      if (%<LibIsSFcnSampleHit("OutputPortIdx0")>) {
        %assign y = LibBlockOutputSignal(0, "", "", 0)   
        %<y> = %<signal>;
      }
      if (%<LibIsSFcnSampleHit("OutputPortIdx1")>) {
        %assign y = LibBlockOutputSignal(1, "", "", 0)
        %<y> = %<signal>;
      }
    }
  }

  %endfunction
%% [EOF] sfun_multirate.tlc

Listing 2: Outputs Code Generation With Rate Grouping

%% example_multirateblk.tlc

%implements "example_multirateblk" "C"


  %% Function: mdlOutputs =====================================================
  %% Abstract:
  %%
  %% Compute the two outputs (the input signal decimated by the
  %% specified parameter). The decimation is handled by sample times.
  %% The decimation is only performed if the block is enabled.  
  %% All ports have different sample rate.
  %%
  %% Note: the usage of the enable should really be protected such that
  %% each task has its own enable state. In this example, the enable
  %% occurs immediately which may or may not be the expected behavior.
  %%
  %function Outputs(block, system) Output



  %assign portIdxName = ["InputPortIdx0","OutputPortIdx0","OutputPortIdx1"]
  %assign portTID     = [%<LibGetGlobalTIDFromLocalSFcnTID("InputPortIdx0")>, ...
                        %<LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx0")>, ...
                        %<LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx1")>]
  %foreach i = 3
    %assign portName = portIdxName[i]
    %assign tid      = portTID[i]
    if (%<LibIsSFcnSampleHit(portName)>) {
                       %<OutputsForTID(block,system,tid)>
    }
  %endforeach

  %endfunction

  %function OutputsForTID(block, system, tid) Output
  /* %<Type> Block: %<Name> */
  %assign enable = LibBlockInputSignal(0, "", "", 0)  
  %assign enabled = LibBlockIWork(0, "", "", 0)  
  %assign signal = LibBlockInputSignal(1, "", "", 0)

  %switch(tid)
    %case LibGetGlobalTIDFromLocalSFcnTID("InputPortIdx0") 
                         %if LibGetSFcnTIDType("InputPortIdx0") == "continuous"
                           %% Only check the enable signal on a major time step.
                           if (%<LibIsMajorTimeStep()>) {  
                             %<enabled> = (%<enable> > 0.0);
                           }
                         %else
                           %<enabled> = (%<enable> > 0.0);
                         %endif
                         %break
    %case LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx0") 
                         if (%<enabled>) {
                           %assign y = LibBlockOutputSignal(0, "", "", 0)
                           %<y> = %<signal>;
                         }
                         %break
    %case LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx1") 
                         if (%<enabled>) {
                           %assign y = LibBlockOutputSignal(1, "", "", 0)
                           %<y> = %<signal>;
                         }
                         %break
    %default 
                         %% error it out
  %endswitch

  %endfunction

%% [EOF] sfun_multirate.tlc

Generate Code That Dereferences Data from a Literal Memory Address

This example shows how to generate code that reads the value of a signal by dereferencing a memory address that you specify. With this technique, you can generate a control algorithm that interacts with memory that your hardware populates (for example, memory that stores the output of an analog-to-digital converter in a microcontroller).

In this example, you generate an algorithm that acquires input data from a 16-bit block of memory at address 0x8675309. Assume that a hardware device asynchronously populates only the lower 10 bits of the address. The algorithm must treat the address as read-only (const), volatile (volatile) data, and ignore the upper 6 bits of the address.

The generated code can access the data by defining a macro that dereferences 0x8675309 and masks the unnecessary bits:

#define A2D_INPUT ((*(volatile const uint16_T *)0x8675309)&0x03FF)

To configure a model to generate code that defines and uses this macro, you must create an advanced custom storage class and write Target Language Compiler (TLC) code. For an example that shows how to use the Custom Storage Class Designer without writing TLC code, see Create and Apply a Storage Class.

As an alternative to writing TLC code, you can use memory sections to generate code that includes pragmas. Depending on your build toolchain, you can use pragmas to specify a literal memory address for storing a global variable. For more information about memory sections, see Control Data and Function Placement in Memory by Inserting Pragmas.

Derivation of Macro Syntax

In this example, you configure the generated code to define and use the dereferencing macro. To determine the correct syntax for the macro, start by recording the target address.

0x8675309

Cast the address as a pointer to a 16-bit integer. Use the Simulink Coder data type name uint16_T.

(uint16_T *)0x8675309

Add the storage type qualifier const because the generated code must not write to the address. Add volatile because the hardware can populate the address at an arbitrary time.

(volatile const uint16_T *)0x8675309

Dereference the address.

*(volatile const uint16_T *)0x8675309

After the dereference operation, apply a mask to retain only the 10 bits that the hardware populates. Use explicit parentheses to control the order of operations.

(*(volatile const uint16_T *)0x8675309)&0x03FF

As a safe coding practice, wrap the entire construct in another layer of parentheses.

((*(volatile const uint16_T *)0x8675309)&0x03FF)

Create Example Model

Create the example model ex_memmap_simple.

For the Inport block, set the output data type to uint16. Name the signal as A2D_INPUT. The Inport block and the signal line represent the data that the hardware populates.

For the Gain block, set the output data type to double.

Create Package to Contain Definitions of Data Class and Custom Storage Class

In your current folder, create a folder named +MemoryMap. The folder defines a package named MemoryMap.

To make the package available for use outside of your current folder, you can add the folder containing the +MemoryMap folder to the MATLAB path.

Create Custom Storage Class

To generate code that defines and reads A2D_INPUT as a macro, you must create a custom storage class that you can apply to the signal line in the model. Later, you write TLC code that complements the custom storage class.

Open the Custom Storage Class designer in advanced mode. To design a custom storage class that operates through custom TLC code, you must use the advanced mode.

cscdesigner('MemoryMap','-advanced');

In the Custom Storage Class Designer, click New. A new custom storage class, NewCSC_1, appears in the list of custom storage class definitions.

Rename the new custom storage class MemoryMappedAddress.

For MemoryMappedAddress, on the General tab, set:

  • Type to Other. The custom storage class can operate through custom TLC code that you write later.

  • Data scope to Exported. For data items that use this custom storage class, Simulink Coder generates the definition (for example, the #define statement that defines a macro).

  • Data initialization to None. Simulink Coder does not generate code that initializes the data item. Use this setting because this custom storage class represents read-only data. You do not select Macro because the Custom Storage Class Designer does not allow you to use Macro for signal data.

  • Definition file to Specify (leave the text box empty). For data items that consume memory in the generated code, Definition file specifies the .c source file that allocates the memory. However, this custom storage class yields a macro, which does not require memory. Typically, header files (.h), not .c files, define macros. Setting Definition file to Specify instead of Instance specific prevents users of the custom storage class from unnecessarily specifying a definition file.

  • Header file to Instance specific. To control the file placement of the macro definition, the user of the custom storage class must specify a header file for each data item that uses this custom storage class.

  • Owner to Specify (leave the text box empty). Owner applies only to data items that consume memory.

After you finish selecting the settings, click Apply and Save.

Now, when you apply the custom storage class to a data item, such as the A2D_INPUT signal line, you can specify a header file to contain the generated macro definition. However, you cannot yet specify a memory address for the data item. To enable specification of a memory address, create a custom attributes class that you can associate with the MemoryMappedAddress custom storage class.

Define Class to Store Custom Attributes for Custom Storage Class

Define a MATLAB class to store additional information for data items that use the custom storage class. In this case, the additional information is the memory address.

In the MemoryMap package (the +MemoryMap folder), create a folder named @MemoryMapAttribs.

In the @MemoryMapAttribs folder, create a file named MemoryMapAttribs. The file defines a class that derives from the built-in class Simulink.CustomStorageClassAttributes.

classdef MemoryMapAttribs < Simulink.CustomStorageClassAttributes
    properties( PropertyType = 'char' )
        MemoryAddress = '';
    end
end

Later, you associate this MATLAB class with the MemoryMappedAddress custom storage class. Then, when you apply the custom storage class to a data item, you can specify a memory address.

Write TLC Code That Emits Correct C Code

Write TLC code that uses the attributes of the custom storage class, such as HeaderFile and MemoryAddress, to generate correct C code for each data item.

In the +MemoryMap folder, create a folder named tlc.

Navigate to the new folder.

Inspect the built-in template TLC file, TEMPLATE_v1.tlc.

edit(fullfile(matlabroot,...
    'toolbox','rtw','targets','ecoder','csc_templates','TEMPLATE_v1.tlc'))

Save a copy of TEMPLATE_v1.tlc in the tlc folder. Rename the copy memory_map_csc.tlc.

In memory_map_csc.tlc, find the portion that controls the generation of C-code data declarations.

    %case "declare"

      %% LibDefaultCustomStorageDeclare is the default declare function to
      %% declares a global variable whose identifier is the name of the data.
      %return "extern %<LibDefaultCustomStorageDeclare(record)>"
      %%break

    %% ==========================================================================

The declare case (%case) constructs a return value (%return), which the code generator emits into the header file that you specify for each data item. To control the C code that declares each data item, adjust the return value in the declare case.

Replace the existing %case content with this new code, which specifies a different return value:

%case "declare"

        %% In TLC code, a 'record' is a data item (for example, a signal line).
        %% 'LibGetRecordIdentifier' returns the name of the data item.
        %assign id = LibGetRecordIdentifier(record)

        %assign dt = LibGetRecordCompositeDataTypeName(record)

        %% The 'CoderInfo' property of a data item stores a
        %% 'Simulink.CoderInfo' object, which stores code generation settings
        %% such as the storage class or custom storage class that you specify
        %% for the item.
        %assign ci = record.Object.ObjectProperties.CoderInfo
        %% The 'ci' variable now stores the 'Simulink.CoderInfo' object.
        
        %% By default, the 'CustomAttributes' property of a 'Simulink.CoderInfo'
        %% object stores a 'Simulink.CustomStorageClassAttributes' object.
        %% This nested object stores specialized code generation settings
        %% such as the header file and definition file that you specify for
        %% the data item.
        %%
        %% The 'MemoryMap' package derives a new class, 
        %% 'MemoryMapAttribs', from 'Simulink.CustomStorageClassAttributes'.
        %% The new class adds a property named 'MemoryAddress'.
        %% This TLC code determines the memory address of the data item by
        %% acquiring the value of the 'MemoryAddress' property.
        %assign ca = ci.Object.ObjectProperties.CustomAttributes
        %assign address = ca.Object.ObjectProperties.MemoryAddress

        %assign width = LibGetDataWidth(record)

        %% This TLC code constructs the full macro, with correct C syntax,
        %% based on the values of TLC variables such as 'address' and 'dt'.
        %% This TLC code also asserts that the data item must be a scalar.
        %if width == 1
                %assign macro = ...
                    "#define %<id> ((*(volatile const %<dt>*)%<address>) & 0x03FF)"
        %else
                %error( "Non scalars are not supported yet." )
        %endif

        %return "%<macro>"
      %%break

    %% ==========================================================================

The new TLC code uses built-in, documented TLC functions, such as LibGetRecordIdentifier, and other TLC commands and operations to access information about the data item. Temporary variables such as dt and address store that information. The TLC code constructs the full macro, with the correct C syntax, by expanding the variables, and stores the macro in the variable macro.

In the same file, find the portion that controls the generation of data definitions.

    %case "define"

      %% LibDefaultCustomStorageDefine is the default define function to define
      %% a global variable whose identifier is the name of the data.  If the
      %% data is a parameter, the definition is also statically initialized to
      %% its nominal value (as set in MATLAB).
      %return "%<LibDefaultCustomStorageDefine(record)>"
      %%break

    %% ==========================================================================

The define case derives a return value that the code generator emits into a .c file, which defines data items that consume memory.

Replace the existing %case content with this new content:

    %case "define"
      %return ""
      %%break

    %% ==========================================================================

MemoryMappedAddress yields a macro in the generated code, so you use the declare case instead of the define case to construct and emit the macro. To prevent the define case from emitting a duplicate macro definition, the new TLC code returns an empty string.

Find the portion that controls the generation of code that initializes data.

    %case "initialize"

      %% LibDefaultCustomStorageInitialize is the default initialization
      %% function that initializes a scalar element of a global variable to 0. 
      %return LibDefaultCustomStorageInitialize(record, idx, reim)
      %%break

    %% ==========================================================================

The initialize case generates code that initializes data items (for example, in the model_initialize function).

Replace the existing %case content with this new content:

    %case "initialize"
      %return ""
      %%break

    %% ==========================================================================

MemoryMappedAddress yields a macro, so the generated code must not attempt to initialize the value of the macro. The new TLC code returns an empty string.

Complete the Definition of the Custom Storage Class

Your new MATLAB class, MemoryMapAttribs, can enable users of your new custom storage class, MemoryMappedAddress, to specify a memory address for each data item. To allow this specification, associate MemoryMapAttribs with MemoryMappedAddress. To generate correct C code based on the information that you specify for each data item, associate the customized TLC file, memory_map_csc.tlc, with MemoryMappedAddress.

Navigate to the folder that contains the +MemoryMap folder.

Open the Custom Storage Class Designer again.

cscdesigner('MemoryMap','-advanced');

For MemoryMappedAddress, on the Other Attributes tab, set:

  • TLC file name to memory_map_csc.tlc.

  • CSC attributes class to MemoryMap.MemoryMapAttribs.

Click Apply and Save.

Define Signal Data Class

To apply the custom storage class to a signal in a model, in the MemoryMap package, you must create a MATLAB class that derives from Simulink.Signal. When you configure the signal in the model, you select this new data class instead of the default class, Simulink.Signal.

In the MemoryMap package, create a folder named @Signal.

In the @Signal folder, create a file named Signal.m.

classdef Signal < Simulink.Signal
    methods
        function setupCoderInfo( this )
            useLocalCustomStorageClasses( this, 'MemoryMap' );
            return;
        end
    end
end

The file defines a class named MemoryMap.Signal. The class definition overrides the setupCoderInfo method, which the Simulink.Signal class already implements. The new implementation specifies that objects of the MemoryMap.Signal class use custom storage classes from the MemoryMap package (instead of custom storage classes from the Simulink package). When you configure a signal in a model by selecting the MemoryMap.Signal class, you can select the new custom storage class, MemoryMappedAddress.

Apply Custom Storage Class to Signal Line

Navigate to the folder that contains the example model and open the model.

In the model, select View > Property Inspector.

Click the signal named A2D_INPUT.

In the Property Inspector, under Code Generation, set Signal object class to MemoryMap.Signal. If you do not see MemoryMap.Signal, select Customize class lists and use the dialog box to enable the selection of MemoryMap.Signal.

In the Property Inspector, set Storage class to MemoryMappedAddress.

Set Header file to memory_mapped_addresses.h.

Set MemoryAddress to 0x8675309.

Generate and Inspect Code

Generate code from the model.

### Starting build procedure for: ex_memmap_simple
### Successful completion of build procedure for: ex_memmap_simple

Inspect the generated header file memory_mapped_addresses.h. The file defines the macro A2D_INPUT, which corresponds to the signal line in the model.

/* Declaration of data with custom storage class MemoryMappedAddress */
#define A2D_INPUT                      ((*(volatile const uint16_T*)0x8675309) & 0x03FF)

Inspect the generated file ex_memmap_simple.c. The generated algorithmic code (which corresponds to the Gain block) calculates the model output, rtY.Out1, by operating on A2D_INPUT.

/* Model step function */
void ex_memmap_simple_step(void)
{
  /* Outport: '<Root>/Out1' incorporates:
   *  Gain: '<Root>/Gain'
   *  Inport: '<Root>/In1'
   */
  rtY.Out1 = 42.0 * (real_T)A2D_INPUT;
}

Related Topics