This example shows how to establish ownership of global data in the code generated from referenced models.
You can create a global variable in the generated code by applying a storage class to a data element in a referenced model (see C Code Generation Configuration for Model Interface Elements). Under certain conditions, the code generator places the variable definition with the code generated from the top model in the hierarchy. This default placement can make it more difficult to determine which model is responsible for the data and to manage code changes in a team-based development environment.
To establish ownership of a global variable by placing the definition with the code generated from the relevant model, apply a storage class such as ExportToFile
and specify the Owner custom attribute. Alternatively, for parameter objects such as Simulink.Parameter
that you use in only one model, you can establish ownership by storing the object in a model workspace instead of the base workspace or a data dictionary.
1. Copy the script file prepare_sldemo_fuelsys_dd_ctrl.m
to your current folder.
[~, ~] = copyfile(fullfile(matlabroot,'examples','ecoder','main','prepare_sldemo_fuelsys_dd_ctrl.m'),... 'prepare_sldemo_fuelsys_dd_ctrl.m','f');
2. Run the copy of the script in your current folder. The script opens the model sldemo_fuelsys_dd_controller
and prepares it for this example.
prepare_sldemo_fuelsys_dd_ctrl
open_system('sldemo_fuelsys_dd_controller')
This controller model contains two models, airflow_calc
and fuel_calc
. The two outputs of airflow_calc
, est_airflow
and fb_correction
, are inputs of fuel_calc
.
3. Open the airflow_calc
model.
open_system('airflow_calc')
4. On the Modeling tab, click Model Data Editor.
5. In the Model Data Editor, set the Change view drop-down list to Code
.
6. In the airflow_calc
model, select the est_airflow
Outport block.
7. In the Model Data Editor, inspect the value in the Storage Class column. The signal that this block represents, est_airflow
, uses the storage class ExportToFile
. With this setting, the signal appears in the generated code as a global variable. The Outport block labeled fb_correction
also uses this setting.
8. In the Model Data Editor, select the Parameters tab and click the Show/refresh additional information button. The Model Data Editor now shows information about workspace variables and objects, such as Simulink.Parameter
objects, that the model uses to set block parameter values.
9. In the Filter contents box, enter numerator
. The Simulink.Parameter
object numerator_param
, which is in the base workspace, sets the value of the Numerator parameter in the Discrete Filter block labeled Throttle Transient
. The object uses the storage class ExportToFile
.
10. Select the Signals tab. In the Filter contents box, enter e0
. The signal e0
, which is internal to the airflow_calc
referenced model, also uses ExportToFile
. The signal is internal because it is not a root-level input or output of the model.
11. Open the fuel_calc
model.
open_system('fuel_calc')
12. In the Model Data Editor for this model, on the Inports/Outports tab, set Change view to Code
. The Inport blocks est_airflow
and fb_correction
use the same storage class, ExportToFile
, as the corresponding Outport blocks in airflow_calc
. The Outport block labeled fuel_rate
also uses ExportToFile
.
Generate code from the controller model, sldemo_fuelsys_dd_controller
.
evalc('rtwbuild(''sldemo_fuelsys_dd_controller'')');
In the code generation report, under Referenced Models, click the hyperlink to inspect the code generated for airflow_calc
.
The file airflow_calc.c
defines the global variable that represents the signal e0
. Because the blocks that write to and read from e0
exist only in airflow_calc
, the code generator assumes that this model owns the signal.
file = fullfile('slprj','ert','airflow_calc','airflow_calc.c'); rtwdemodbtype(file,'/* Definition for custom storage class: ExportToFile */',... 'real32_T e0;',1,1)
/* Definition for custom storage class: ExportToFile */ real32_T e0; /* '<Root>/Sum1' */
In the code generation report, return to the code generated for sldemo_fuelsys_dd_controller
.
The file sldemo_fuelsys_dd_controller.c
defines the other global variables.
file = fullfile('sldemo_fuelsys_dd_controller_ert_rtw',... 'sldemo_fuelsys_dd_controller.c'); rtwdemodbtype(file,... '/* Definition for custom storage class: ExportToFile */',... 'real32_T numerator_param[2] = { 0.01F, -0.01F } ;',1,1);
/* Definition for custom storage class: ExportToFile */ real32_T est_airflow; /* '<Root>/airflow_calc' */ real32_T fb_correction; /* '<Root>/airflow_calc' */ real32_T fuel_rate; /* '<Root>/fuel_calc' */ real32_T numerator_param[2] = { 0.01F, -0.01F } ;
Because the signals est_airflow
and fb_correction
pass between the two models and through the top-level controller model, the code generator assumes that sldemo_fuelsys_dd_controller
owns the signals. The code generator makes a similar assumption about fuel_rate
. Because the parameter object numerator_param
exists in the base workspace, any model in the hierarchy can use the object. Therefore, the code generator assumes that sldemo_fuelsys_dd_controller
owns the parameter.
You can configure each shared signal and the parameter object so that the corresponding variable definition appears in the code generated for the relevant model.
In this example, you configure code generation settings so that:
The code generated for the airflow_calc
model defines the signals that pass between the two models, est_airflow
and fb_correction
.
The code generated for airflow_calc
defines the parameter data, numerator_param
.
The code generated for fuel_calc
defines the output signal that it calculates, fuel_rate
.
1. In the airflow_calc
model, on the Modeling tab, under Design, click Property Inspector.
2. In the Model Data Editor, select the Inports/Outports tab.
3. Select the row that corresponds to the Outport block est_airflow
. The Property Inspector shows the properties of the Outport block.
4. In the Property Inspector, under Code, set Owner to airflow_calc
. The code generator places the definition of the global variable with the code generated for airflow_calc
.
5. For fb_correction
, use the Model Data Editor and the Property Inspector to set Owner to airflow_calc
.
6. Select the Parameters tab.
7. Find the row that corresponds to the parameter object, numerator_param
. In the row, double-click the icon in the left column. The Model Explorer opens and displays the properties of numerator_param
.
8. In the Model Hierarchy pane, expand the airflow_calc node so that you can see the subordinate Model Workspace node.
9. Use the Model Explorer to move numerator_param
from the base workspace to the airflow_calc
model workspace. For example, in the Model Hierarchy pane, select Base Workspace. Then, drag numerator_param
from the Contents pane to the Model Workspace node in the Model Hierarchy pane. With this change, the code generator assumes that airflow_calc
owns numerator_param
, and places the variable definition in the code generated for airflow_calc
.
10. In the fuel_calc
model, on the Modeling tab, under Design, click Property Inspector.
11. For the Inport blocks est_airflow
and fb_correction
, use the Model Data Editor and the Property Inspector to set Owner to airflow_calc
. With this configuration, the fuel_calc
code does not define the variables.
12. For the Outport block, use the Model Data Editor and the Property Inspector to set Owner to fuel_calc
.
Alternatively, to configure the data in the models, use these commands at the command prompt:
temp = Simulink.Signal; temp.CoderInfo.StorageClass = 'Custom'; temp.CoderInfo.CustomStorageClass = 'ExportToFile'; temp.CoderInfo.CustomAttributes.Owner = 'airflow_calc'; set_param('airflow_calc/est_airflow','SignalName','est_airflow') set_param('airflow_calc/est_airflow','SignalObject',copy(temp)) set_param('airflow_calc/fb_correction','SignalName','fb_correction') set_param('airflow_calc/fb_correction','SignalObject',copy(temp)) mdlwks = get_param('airflow_calc','ModelWorkspace'); assignin(mdlwks,'numerator_param',copy(numerator_param)); portHandles = get_param('fuel_calc/est_airflow','portHandles'); outportHandle = portHandles.Outport; set_param(outportHandle,'Name','est_airflow') set_param(outportHandle,'SignalObject',copy(temp)) portHandles = get_param('fuel_calc/fb_correction','portHandles'); outportHandle = portHandles.Outport; set_param(outportHandle,'Name','fb_correction') set_param(outportHandle,'SignalObject',copy(temp)) temp.CoderInfo.CustomAttributes.Owner = 'fuel_calc'; set_param('fuel_calc/fuel_rate','SignalName','fuel_rate') set_param('fuel_calc/fuel_rate','SignalObject',copy(temp)) clear temp portHandles outportHandle numerator_param
13. In each of the three models, select Configuration Parameters > Use owner from data object for data definition placement. With this setting cleared (the default), the code generator ignores the values that you specify for Owner.
set_param('fuel_calc','EnableDataOwnership','on') set_param('airflow_calc','EnableDataOwnership','on') set_param('sldemo_fuelsys_dd_controller','EnableDataOwnership','on')
14. Save the referenced models.
save_system('fuel_calc') save_system('airflow_calc')
Generate code from the controller model, sldemo_fuelsys_dd_controller
.
evalc('rtwbuild(''sldemo_fuelsys_dd_controller'')');
Now, the file sldemo_fuelsys_dd_controller.c
does not define any of the global variables.
In the code generation report, inspect the code generated for airflow_calc
.
The file airflow_calc.c
now defines the global variables that belong to airflow_calc
.
file = fullfile('slprj','ert','airflow_calc','airflow_calc.c'); rtwdemodbtype(file,'/* Definition for custom storage class: ExportToFile */',... 'real32_T numerator_param[2] = { 0.01F, -0.01F } ;',1,1)
/* Definition for custom storage class: ExportToFile */ real32_T e0; /* '<Root>/Sum1' */ real32_T est_airflow; /* '<Root>/Sum' */ real32_T fb_correction; /* '<Root>/Discrete Integrator' */ real32_T numerator_param[2] = { 0.01F, -0.01F } ;
Inspect the code generated for fuel_calc
. The file fuel_calc.c
defines the global variable fuel_rate
.
file = fullfile('slprj','ert','fuel_calc','fuel_calc.c'); rtwdemodbtype(file,'/* Definition for custom storage class: ExportToFile */',... 'real32_T fuel_rate;',1,1)
/* Definition for custom storage class: ExportToFile */ real32_T fuel_rate; /* '<S2>/Merge' */
The signal data elements est_airflow
and fb_correction
are represented by Outport blocks in airflow_calc
and by Inport blocks in fuel_calc
. If you want to change the configuration of one of these signals, you must make the change in both models. For example, if you want to change the storage class of est_airflow
from ExportToFile
to Volatile
, you must change the storage class twice: once for the Outport block in airflow_calc
and again for the Inport block in fuel_calc
.
To make maintenance of each signal easier, store the code generation settings in a Simulink.Signal
object, which can exist in the base workspace or a data dictionary.
1. In the Model Data Editor for airflow_calc
, on the Inports/Outports tab, find the row that corresponds to est_airflow
. For that row, in the Signal Name column, click the cell.
2. In the cell, next to the est_airflow
text, click the action button (with three vertical dots). Select Create and Resolve.
3. In the Create New Data dialog box, set Value to Simulink.Signal
and click Create. A Simulink.Signal
object named est_airflow
appears in the base workspace. In the Model Data Editor, for est_airflow
, the check box in the Resolve check box is selected, which means the Outport block acquires code generation settings from the signal object in the base workspace.
4. In the est_airflow
property dialog box, set Storage class to ExportToFile
.
5. Set Owner to airflow_calc
.
6. Use the Model Data Editor to create a similar signal object for fb_correction
.
In the Model Data Editor for fuel_calc
, on the Inports/Outports tab, in the Resolve column, select the check boxes for est_airflow
and fb_correction
. Now, each Inport block acquires code generation settings from the corresponding signal object.
Alternatively, to create the signal object and configure the blocks and lines in the model, at the command prompt, use these commands:
est_airflow = Simulink.Signal; est_airflow.CoderInfo.StorageClass = 'Custom'; est_airflow.CoderInfo.CustomStorageClass = 'ExportToFile'; est_airflow.CoderInfo.CustomAttributes.Owner = 'airflow_calc'; fb_correction = Simulink.Signal; fb_correction.CoderInfo.StorageClass = 'Custom'; fb_correction.CoderInfo.CustomStorageClass = 'ExportToFile'; fb_correction.CoderInfo.CustomAttributes.Owner = 'airflow_calc'; set_param('airflow_calc/est_airflow', 'StorageClass', 'Auto') set_param('airflow_calc/est_airflow','MustResolveToSignalObject','on') set_param('airflow_calc/fb_correction', 'StorageClass', 'Auto') set_param('airflow_calc/fb_correction','MustResolveToSignalObject','on') portHandles = get_param('fuel_calc/est_airflow','portHandles'); outportHandle = portHandles.Outport; set_param(outportHandle, 'StorageClass', 'Auto') set_param(outportHandle,'MustResolveToSignalObject','on') portHandles = get_param('fuel_calc/fb_correction','portHandles'); outportHandle = portHandles.Outport; set_param(outportHandle, 'StorageClass', 'Auto') set_param(outportHandle,'MustResolveToSignalObject','on') clear portHandles outportHandle
7. Save the models and generate code from sldemo_fuelsys_dd_controller
. The code is the same as it was before you created the Simulink.Signal
objects. Now, you can make changes to the signal objects instead of the corresponding blocks and lines in the models.
save_system('airflow_calc') save_system('fuel_calc') evalc('rtwbuild(''sldemo_fuelsys_dd_controller'')');