Main Content

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 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 fuel_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'')');

Related Topics