Main Content

Generate C++ Code for Export-Function Models That Process Unbounded Variable-Size Data

This example shows how to generate C++ code for a function in an export-function model that processes unbounded variable-size data. For unbounded variable-size signals transmitting unbounded variable-size data, the code generator by default generates std::vector containers to allocate memory dynamically at run time. These containers enable you to integrate your generated code with third-party applications that use std::vector to represent variable-size data.

In this example, after code generation, you integrate the generated code with custom main application code to exchange data between their functions.

Example Model

Open the UVSCompMdl model.

open_system('UVSCompMdl');

The top model UVSCompMdl contains four components: Data Generator, Event Scheduler, Data Processor, and Data Logger. The components Data Generator and Data Processor are the referenced models DataGenerator and DataProcessor.

At each time step of model simulation, the referenced model DataGenerator outputs a one-dimensional array that is carried by an unbounded variable-size signal. The MATLAB Function block createArray generates this array, whose size changes based on input values. The Event Scheduler component schedules the data transfer between DataGenerator and DataProcessor.

The referenced model DataProcessor is an export-function model, in which the Simulink Function block process_data processes the unbounded variable-size data that it receives as an input.

To process the unbounded variable-size data, the Simulink Function block contains:

  • The MATLAB Function block processDataInMATLABFunction, which amplifies the input array values by a factor of 2 and calculates the array length.

  • The subsystem processDataUsingIteratorSS, which receives the output of the MATLAB Function block. The subsystem contains a For Iterator Subsystem block that iterates over the length of the input array, amplifies the input value by a factor of 10, and outputs the unbounded variable-size signal processedDataOut.

  • A Bus Creator block, which creates a non-virtual bus processedDataBus that includes the unbounded variable-size signal processedDataOut and the array size processedDataSize.

The generated data, processed data, and their sizes at each time step of model simulation are logged using Outport blocks. The purpose of the Event Scheduler and Data Generator components is to test the behavior of the Data Processor component through simulation and data logging. Once you are satisfied with the simulation results, you can generate code for the function implemented in Data Processor.

Configure Model for Simulation and Code Generation

To prepare the top model UVSCompMdl and its referenced models DataGenerator and DataProcessor for simulation and code generation, the model loads dHarness.mat into the base workspace. Doing so imports a configuration set with the following parameter settings for the top model and referenced models.

These settings enable dynamic memory allocation for a model containing unbounded variable-size signals:

  • On the Solver pane, set Solver to discrete (no continuous states).

  • On the Data Import/Export pane, set Format to Dataset.

  • On the Simulation Target pane, select Dynamic memory allocation in MATLAB functions.

  • On the Code Generation pane, set Language to C++.

  • On the Code Generation pane, in the Interface section, select Support: variable-size signals.

These settings enable generation of std::vector containers representing unbounded variable-size signals:

  • On the Code Generation pane, set System target file to ert.tlc.

  • On the Code Generation, in the Code Style section, set Dynamic array container type to std::vector.

Simulate Top Model

Simulate the top model and review the logged data. For more information about simulation and data logging, see Use Unbounded Variable-Size Signals Between Model Components.

Generate C++ Code for DataProcessor Model

Once you are satisfied with the logged data of DataProcessor, you can generate C++ code for the export-function model. To generate code, follow these steps.

1. Open the DataProcessor model as the top model.

open_system('DataProcessor');

2. Build the model, but do not compile the generated code.

slbuild('DataProcessor',GenerateCodeOnly=true);
### Starting build procedure for: DataProcessor
### Successful completion of code generation for: DataProcessor

Build Summary

Top model targets:

Model          Build Reason                                         Status           Build Duration
===================================================================================================
DataProcessor  Information cache folder or artifacts were missing.  Code generated.  0h 0m 12.899s 

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 15.776s

3. In the header file DataProcessor.h, the code generator defines dynamic arrays for the input and output of the MATLAB Function block processDataInMATLABFunction and for the unbounded variable-size signal passing from the For Iterator subsystem as instances of the class template std::vector. To view the dynamic arrays, open the DataProcessor.h file.

cfile = fullfile('DataProcessor_ert_rtw','DataProcessor.h');
coder.example.extractLines(cfile,"struct B_DataProcessor_T {", "// External outputs", 1, 0);
  struct B_DataProcessor_T {
    std::vector<real_T> u;             // '<S1>/u'
    std::vector<real_T> TmpSignalConversionAtuOutport1;// '<S1>/u'
    std::vector<real_T> TmpSignalConversionAtxInport1;// '<S1>/u'
    std::vector<real_T> TmpSignalConversionAtyInport1;// '<S1>/processDataUsingIteratorSS' 
    std::vector<real_T> assign;        // '<S4>/assign'
    std::vector<real_T> processedData; // '<S1>/processDataInMATLABFunction'
  };

Integrate Generated Code with Custom Code

To integrate the generated code with custom C++ code and call an entry-point function that accepts or returns std::vector containers, you must define such containers in the custom code. You can interact with std::vector containers by accessing their size method and using the standard C++ array indexing.

In this example, you customize the generated example main application code ert_main.cpp to call the model-step function process_data.

1. Open ert_main.cpp. Include the <iostream> header.

#include <stdio.h>
#include <iostream>
#include "DataProcessor.h"             // Model header file

2. The function prototype of the generated process_data function in DataProcessor.cpp is the following:

cfile = fullfile('DataProcessor_ert_rtw','DataProcessor.cpp');
coder.example.extractLines(cfile,"void", "rty_z", 1, 1);
void DataProcessor::process_data(const std::vector<real_T> &rtu_u, std::vector<
  real_T> &rty_x, std::vector<real_T> &rty_y, real_T *rty_z)

The function accepts a reference to a std::vector container of data type real_T as an input argument, and has three output arguments. Two of the output arguments are also references to std::vector containers of data type real_T.

To integrate process_data with the main application code, in ert_main.cpp, define an input array myArray and two output arrays myResult_x and myResult_y as class templates.

// Instantiate the input variable by using std::vector template
std::vector<real_T> myArray;
// Instantiate the result variable by using std::vector template
std::vector<real_T> myResult_x;
std::vector<real_T> myResult_y;
real_T myResult_z;

3. Set the size of myArray to 100 by using the resize method and set the input values in the array elements.

// Allocate initial memory for the array
myArray.resize(100); 
// Access array with standard C++ indexing
for (int i = 0; i < myArray.size(); i++) {
    myArray[i] = i;                   
}

4. Use the instance DataProcessor_Obj of the model class DataProcessor to call the process_data function and display the output values of myResult_x through indexing.

// Pass the input and result arrays to the generated method
DataProcessor_Obj.process_data(myArray, myResult_x, myResult_y, &myResult_z);
// Print result
for (int i = 0; i < myResult_x.size(); i++) {
    if (i > 0) std::cout << " ";
    std::cout << myResult_x[i];
    if (((i+1) % 10) == 0) std::cout << std::endl;
}
std::cout << std::endl;

5. Compile the generated code and run the executable.

codebuild('DataProcessor_ert_rtw')
!DataProcessor.exe

After integrating the generated code with the main application code in ert_main.cpp, you can make further changes to the application code you deploy into the target application. The application code and integrated generated code are capable of handling unbounded size data and are suitable for deployment to desktop quality targets. For more information, see Deploy Applications to Target Hardware.

Related Topics