Generate C++ Code for Export-Function Models That Process Unbounded Variable-Size Data
This example shows how you can 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 generates dynamic arrays to allocate memory dynamically at run time.
In this example, after code generation, you interface the generated code with custom main application code to exchange data between them.
Example Model
This example uses the ex_uvsmdlcomp
model. Open the model by using
this command in the Command
Window:
openExample('ex_uvsmdlcomp')
The top model ex_uvsmdlcomp
contains four components Data
Generator
, Event Scheduler
, Data Processor
and Data Logger
. The components Data Generator
and
Data Processor
are referenced models
ex_DataGenerator
and ex_DataProcessor
.
At each time step of model simulation, the referenced model
ex_DataGenerator
outputs a one-dimensional array that is carried by an
unbounded variable-size signal. This array is generated using a MATLAB Function block
createArray
and the size of the array changes based on input values.
The Event Scheduler
schedules the data transfer between
ex_DataGenerator
and ex_DataProcessor
.
The referenced model ex_DataProcessor
is an export -function model,
which is modeled by using a Simulink Function block to process the unbounded variable-size
data that passes to it 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 of2
and calculates the array length.Output of the MATLAB Function block is passed to the subsystem
processDataUsingIteratorSS
. The subsystem contains a For Iterator Subsystem block that iterates as the length of the input array.The For Iterator Subsystem block amplifies the input value by a factor of
10
and outputs the unbounded variable-size signalprocesssedDataOut
.A Bus Creator block, which creates a non-virtual bus
processsedDataBus
that includes the unbounded variable-size signalprocesssedDataOut
and the array sizeprocessedDataSize
.
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 the Data Processor
.
Model Configuration for Simulation and Code Generation
To prepare the top model ex_uvsmdlcomp
and its referenced models
ex_DataGenerator
and ex_DataProcessor
for simulation
and code generation, in the Configuration Parameters dialog box, these parameters are set.
On the Solver pane, Solver is set to
discrete (no continuous states)
.On the Data Import/Export pane, Format is set to
Dataset
.On the Code Generation pane, Language is set to
C++
.The Dynamic memory allocation in MATLAB functions selected.
On the Interface pane, Array layout is set to
Column-major
.
For the top model and referenced models, these configuration settings are
loaded from dHarness.mat
.
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 ex_DataProcessor
Model
Once you are satisfied with the logged data of ex_DataProcessor
, you
can generate C++ code for the export-function model. To generate code:
Open the
ex_DataProcessor
model as the top-model.Open the Embedded Coder app.
On the C++ Code tab, click Build.
The generated code appears in the Code view next to the model.
Inspect C++ Code
Inspect the header file
ex_DataProcessor.h
. The code generator defines unbounded dynamic arrays by using a container class. Because the model uses theert.tlc
system target file, the code generator uses thecoder::array
container class. The generated code generates instances ofcoder:array
to represent the input and output of the MATLAB Function blockprocessDataInMATLABFunction
and the unbounded variable-size signal from the For Iterator Subsystem block.struct B_ex_DataProcessor_T { coder::array<real_T,1> u; // '<S1>/u' coder::array<real_T,1> TmpSignalConversionAtuOutport1;// '<S1>/u' coder::array<real_T,1> TmpSignalConversionAtxInport1;// '<S1>/u' coder::array<real_T,1> TmpSignalConversionAtyInport1;// '<S1>/processDataUsingIteratorSS' coder::array<real_T,1> assign; // '<S4>/assign' coder::array<real_T,1> processedData;// '<S1>/processDataInMATLABFunction' };
To view the implementation of the class template, open the header file
coder_array.h
generated in_Sharedutils
. This header file contains details about thecoder::array
class interface.In
coder_array.h
, the code generator also implements theset_size
method in thecoder
namespace. The method uses theCODER_ALLOC
andCODER_DEALLOC
macros throughensureCapacity()
to perform memory allocation and freeing that memory dynamically.void set_size(Dims... dims) { coder::detail::match_dimensions<N == sizeof...(dims)>::check(); set_size_i<0>(dims...); ensureCapacity(numel());
To view the use of the
set_size
method, open theex_DataProcessor.cpp
file. The code calls theset_size
method to allocate and free memory dynamically for the defined dynamic arrays at run time. For example, the following code lines access the size of the first dimension of the input ofprocessDataInMATLABFunction
and set the output data size accordingly at run-time.// MATLAB Function: '<S1>/processDataInMATLABFunction' incorporates: // SignalConversion generated from: '<S1>/u' ex_DataProcessor_B.processedData.set_size (ex_DataProcessor_B.TmpSignalConversionAtuOutport1.size(0)); loop_ub = ex_DataProcessor_B.TmpSignalConversionAtuOutport1.size(0); for (i = 0; i < loop_ub; i++) { ex_DataProcessor_B.processedData[i] = ex_DataProcessor_B.TmpSignalConversionAtuOutport1[i] * 2.0; }
Integrate Generated Code with Custom Code
If you want to interface the generated code with custom C++ code to call an entry-point
function that accepts or returns dynamic arrays, you must define dynamic arrays in the
custom code. To define dynamic arrays in the custom code, you need to include the
coder_array.h
header file in your custom .cpp
file.
Once you include the coder_array.h
file, you can use the templates and
methods implemented in coder_array.h
in your custom code. You can
interact with the dynamic arrays by accessing the size
vector of the
defined arrays and using the standard C++ array indexing. For more information on the APIs
that you can use to interface the generated code containing dynamic arrays with custom code,
see Use Dynamically Allocated C++ Arrays in Generated Function Interfaces.
In this example, you customize the generated example main application code
ert_main.cpp
to call the model-step function
process_data
.
Open the
ert_main.cpp
file. Include thecoder_array.h
header file inert_main.cpp
.#include <stdio.h> #include "ex_DataProcessor.h" #include "coder_array.h"
The function prototype of the generated
process_data
function inex_DataProcessor.cpp
is:void ex_DataProcessor::process_data(const coder::array<real_T, 1U> &rtu_u, coder:: array<real_T, 1U> &rty_x, coder::array<real_T, 1U> &rty_y, real_T *rty_z)
The function accepts a one-dimensional dynamic array of data type
real_T
as an input and returns three outputs. Two of the outputs are also one-dimensional dynamic arrays of data typereal_T
.To interface
process_data
with the main application code, inert_main.cpp
, define an input arraymyArray
and two output arraysmyResult_x
andmyResult_y
as class templates.// Instantiate the input variable by using coder::array template coder::array<int32_T, 1> myArray; // Instantiate the result variable by using coder::array template coder::array<real_T, 1> myResult_x; coder::array<real_T, 1> myResult_y; real_T myResult_z;
Set the size of the dimension of
myArray
to100
by using theset_size
method and set the input values in the array elements.// Allocate initial memory for the array myArray.set_size(100); // Access array with standard C++ indexing for (int i = 0; i < myArray.size(0); i++) { myArray[i] = i; }
Use the instance
ex_DataProcessor_Obj
of the model classex_DataProcessor
to call theprocess_data
function and display the output values ofmyResult_x
through indexing.// Pass the input and result arrays to the generated method ex_DataProcessor_Obj.process_data(myArray, myResult_x, myResult_y, &myResult_z); // Print result for (int i = 0; i < myResult_x.size(0); i++) { if (i > 0) std::cout << " "; std::cout << myResult[i]; if (((i+1) % 10) == 0) std::cout << std::endl; } std::cout << std::endl;
If the dimension of
myArray
changes later on during execution, the generated code reallocates memory based on the new size.
After interfacing the generated code with the main application code in
ert_main.cpp
, you can make further changes to the application code to
deploy into the target application. The application code and interfaced generated code
capable of handling unbounded size data is suitable for deployment to desktop quality
targets. For more information, see Deploy Applications to Target Hardware (Embedded Coder).
If you have an Embedded Coder® license, you can use the Dynamic array container
type (Embedded Coder) model configuration parameter to generate instances of
std::vector
instead of coder::array
.
Related Topics
- Variable-Size Signals in Generated Code (Embedded Coder)
- Use Dynamic Memory Allocation for Unbounded Variable-Size Signals in C++ Code Generation (Embedded Coder)
- Use Unbounded Variable-Size Signals Between Model Components
- Generate Component Source Code for Export to External Code Base (Embedded Coder)