Establish Data Ownership in a Model Hierarchy
This example shows how to establish ownership of global data in the code generated from referenced models.
Create a global variable in the generated code by applying a storage class to a data element in a referenced model. For more information, see C Data Code Interface 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 difficult to determine which model is responsible for the data and to manage code changes in a team-based development environment.
To place the definition with the code generated from the relevant model, apply a storage class such as ExportToFile
and specify the Owner custom attribute. Alternatively, if you use a Simulink.Parameter
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.
Explore Example Model
2. Run the script prepare_sldemo_fuelsys_dd_ctrl.m
. 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 output signals 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 C Code tab, under Code Interface, click Default Code Mappings.
5. In the Code Mappings Editor, open the Outports tab.
6. In the airflow_calc
model, select the est_airflow
Outport block.
7. In the Code Mappings 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 Code Mappings Editor, select the Parameters tab and click the Refresh button. The Code Mappings 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/States 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 Code Mappings Editor for this model, open the Inports tab. The Inport blocks est_airflow
and fb_correction
use the same storage class, ExportToFile
, as the corresponding Outport blocks in airflow_calc
. In the Outports tab, the Outport block labeled fuel_rate
also uses ExportToFile
.
Generate and Inspect Code
1. Generate code from the controller model, sldemo_fuelsys_dd_controller
.
evalc('slbuild(''sldemo_fuelsys_dd_controller'')');
2. In the dropdown menu at the top of the Code Generation Report window, change sldemo_fuelsys_dd_controller
to airflow_calc
. From the left pane, click airflow_calc.c
.
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'); coder.example.extractLines(file,'/* Definition for custom storage class: ExportToFile */',... 'real32_T e0;',1,1)
/* Definition for custom storage class: ExportToFile */ real32_T e0; /* '<Root>/Sum1' */
3. 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'); coder.example.extractLines(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.
Configure Data Ownership
In this example, 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
andfb_correction
.The code generated for
airflow_calc
defines the parameter data,numerator_param
.The code generated for
fuel_calc
defines the output signal thatfuel_calc
calculates,fuel_rate
.
1. In the airflow_calc
model, on the Modeling tab, under Design, click Property Inspector.
2. In the Code Mappings Editor, select the 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 Code Mappings Editor and the Property Inspector to set Owner to airflow_calc
.
6. From the Code Mappings Editor, 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 to 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 Code Mappings Editor and the Property Inspector to set Owner to airflow_calc
. The fuel_calc
code does not define the variables with this configuration.
12. For the Outport block, use the Code Mappings Editor and the Property Inspector to set Owner to fuel_calc
.
Alternatively, to configure the data in the models, enter this code 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, specify the owner of the data object. In the Configuration Parameters window, click Code Generation > Code Placement, then select Use owner from data object for data definition placement*. When this setting is cleared, the code generator ignores the values specified 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 Improved Code
1. Generate code from the controller model, sldemo_fuelsys_dd_controller
.
evalc('slbuild(''sldemo_fuelsys_dd_controller'')');
Now, the file sldemo_fuelsys_dd_controller.c
does not define any of the global variables.
2. 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'); coder.example.extractLines(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 } ;
3. 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'); coder.example.extractLines(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' */
Create Single Point of Specification for Signals That Pass Between Models
The Outport blocks in airflow_calc
and Inport blocks in fuel_calc
represent the signal data elements est_airflow
and fb_correction
. To change the configuration of one of these signals, you must make the change in both models. For example, to change the storage class of est_airflow
from ExportToFile
to Volatile
, you must change the storage class for the Outport block in airflow_calc
and 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. Open the Property Inspector, then click the est_airflow
outport block.
2. In the Property Inspector, next to the Signal name property, click the button with the three vertical dots next to the Signal name property. Select est_airflow: 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, enter these commands at the command prompt:
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('slbuild(''sldemo_fuelsys_dd_controller'')');