Main Content

Integrate External Application Code with Code Generated from PID Controller

This example shows how to generate C code from a control algorithm model and integrate that code with existing, external application code. In the complete application, which consists of three modules, the algorithm module must exchange data with other modules through global variables. In the example, you configure the generated code to interact with existing external variables, resemble the external code in appearance and organization, and conform to specific coding requirements.

Inspect External Code

Set your current folder to a writable location.

Copy the script prepare_ext_code to your current folder and run the script. The script copies external code files into several folders.

try
copyfile(fullfile(matlabroot,'examples','ecoder','main','prepare_ext_code.m'),...
    'prepare_ext_code.m','f');
catch
end
run('prepare_ext_code.m');

In your current folder, open ext_code_main.c. This application code represents an embedded system with a trivial scheduling algorithm (a while loop) and three modules whose algorithms run in every execution cycle: ex_ext_inputs_proc, ex_ext_ctrl_alg, and ex_ext_outputs_proc.

type('ext_code_main.c')

In the shared folder, inspect ex_ext_projTypes.h. The file defines two custom data types (typedef) that the data in the modules use.

type(fullfile('shared','ex_ext_projTypes.h'))

In the io_drivers folder, inspect ex_sensor_accessors.c. This file defines functions, get_fromSensor_flow and get_fromSensor_temp, that return raw data recorded by a flow sensor and a temperature sensor. For this example, the functions return trivial sinusoidal stimuli for the control algorithm.

type(fullfile('io_drivers','ex_sensor_accessors.c'))

In the ex_ext_inputs_proc folder, inspect ex_ext_inputs_proc.c. The ex_ext_inputs_proc module reads the sensor data (by calling the accessor functions), filters the data, and stores it in two global variables, PROC_INPUT_FLOW and PROC_INPUT_TEMP. These global variables are defined in the file ex_ext_proc_inputs.c and declared in ex_ext_proc_inputs.h.

type(fullfile('ex_ext_inputs_proc','ex_ext_inputs_proc.c'))

The filter algorithm in this module and the algorithms in the other two modules require state data, which must persist between execution cycles of the application. Each module stores relevant state data as global variables. For example, in the ex_ext_inputs_proc module, ex_ext_inputs_proc.c defines these variables:

  • flowFilterIn_state_data

  • tempFilterIn_state_data

  • flowFilterIn_tmp_data

  • tempFilterIn_tmp_data

The empty ex_ext_ctrl_alg folder is a placeholder for the generated code. The ex_ext_ctrl_alg module must perform PID control on the filtered flow and temperature measurements. Later, you examine the requirements for this code, which are based on the code in the other modules.

In the ex_ext_outputs_proc folder, inspect ex_ext_outputs_proc.c. This module reads the PID output signals from global variables named CONTR_SIG_FLOW and CONTR_SIG_TEMP. The ex_ext_ctrl_alg module (the generated code) must define these variables. The ex_ext_outputs_proc module then filters these signals and passes the filtered values to functions that represent device drivers for actuators (for example, a valve and a heater filament).

type(fullfile('ex_ext_outputs_proc','ex_ext_outputs_proc.c'))

The actuator functions are defined in the io_drivers folder in the file ex_actuator_accessors.c.

The figure shows the intended flow of data through the system. The figure also shows the code files that define and declare the data (global variables) that cross the module boundaries.

Inspect Simulink Model and Determine Requirements for Generated Code

Open the example model ex_ext_ctrl_alg.

open_system('ex_ext_ctrl_alg')

The blocks in the model implement the required PID control algorithm. The model uses default code generation settings for an ERT-based system target file (ert.tlc).

To complete the application by performing the function of the ex_ext_ctrl_alg module, the code generated from this model must:

  • Use the custom data types dataPath_flow_T and dataPath_temp_T from the external file ex_ext_projTypes.h.

  • Read filtered sensor data from the global variables PROC_INPUT_FLOW and PROC_INPUT_TEMP. The generated code must not define these variables because the module ex_ext_inputs_proc defines them, declaring them in ex_ext_proc_inputs.h.

  • Write PID control signals to global variables named CONTR_SIG_FLOW and CONTR_SIG_TEMP. The generated code must define these variables and declare them in a file named ex_ext_ctrl_sigs.h. Then, the ex_ext_outputs_proc module can read the raw control signals from them.

  • Conform to variable naming standards that govern the external application. For example, the generated code must store state data in global variables whose names end in _data. In addition, for this example, the names of local function variables must end in _local.

  • Conform to standards that govern the organization of the code in each of the external files. For example, each file contains sections, delimited by comments, that aggregate similar code constructs such as type definitions, variable declarations, and function definitions.

So that you and others can interact with the control algorithm during execution, for this example, you also configure the generated code to define and declare const global variables named PARAM_setpoint_flow and PARAM_setpoint_temp, which represent the PID controller setpoints. The definitions must appear in a file named ex_ext_ctrl_params.c and the declarations must appear in a file named ex_ext_ctrl_params.h.

Configure Model to Use Custom Data Types

Set your current folder to the shared folder.

Use the function Simulink.importExternalCTypes to generate Simulink.AliasType objects that represent the custom data types dataPath_flow_T and dataPath_temp_T.

cd('shared')
Simulink.importExternalCTypes('ex_ext_projTypes.h');

The objects appear in the base workspace.

In the ex_ext_ctrl_alg model, on the Modeling tab, click Model Data Editor.

In the Model Data Editor, for the Inport block that represents PROC_INPUT_FLOW, set Data Type to dataPath_flow_T.

For the Inport block that represents PROC_INPUT_TEMP, set Data Type to dataPath_temp_T.

Select the Signals tab.

In the model, select the output signal of each Constant block. In the Model Data Editor, set Data Type to Inherit: Inherit via back propagation. With this setting, each Constant block inherits its output data type from the block immediately downstream, in this case, a Sum block.

Alternatively, to configure the data types, at the command prompt, use these commands.

set_param('ex_ext_ctrl_alg/In1','OutDataTypeStr','dataPath_flow_T')
set_param('ex_ext_ctrl_alg/In2','OutDataTypeStr','dataPath_temp_T')
set_param('ex_ext_ctrl_alg/Flow Setpt','OutDataTypeStr',...
    'Inherit: Inherit via back propagation')
set_param('ex_ext_ctrl_alg/Temp Setpt','OutDataTypeStr',...
    'Inherit: Inherit via back propagation')

Update the block diagram. The diagram shows that, due to data type inheritance and propagation, signals in the model use a custom data type.

Configure Interface Data

Configure the model to generate code that accesses and defines the correct global variables such as PROC_INPUT_FLOW and CONTR_SIG_TEMP.

In the Model Data Editor, select the Inports/Outports tab and set the Change view drop-down list to Code.

Select the rows that correspond to the two Inport blocks.

For either row, set Storage Class to ImportFromFile and Header File to ex_ext_proc_inputs.h. With the storage class ImportFromFile, each Inport block appears in the generated code as a global variable. However, the generated code does not define the variable, instead including (#include) the variable declaration from ex_ext_proc_inputs.h.

For the rows that correspond to the Outport blocks, set:

  • Storage Class to ExportToFile

  • Header file to ex_ext_ctrl_sigs.h

  • Definition File to ex_ext_ctrl_sigs.c

With ExportToFile, the generated code defines the global variable.

Alternatively, to configure the signals, at the command prompt, use these commands.

temp = Simulink.Signal;
temp.CoderInfo.StorageClass = 'Custom';
temp.CoderInfo.CustomStorageClass = 'ImportFromFile';
temp.CoderInfo.CustomAttributes.HeaderFile = 'ex_ext_proc_inputs.h';

portHandles = get_param('ex_ext_ctrl_alg/In1','portHandles');
outportHandle = portHandles.Outport;
set_param(outportHandle,'SignalObject',copy(temp))

portHandles = get_param('ex_ext_ctrl_alg/In2','portHandles');
outportHandle = portHandles.Outport;
set_param(outportHandle,'SignalObject',copy(temp))

temp.CoderInfo.CustomStorageClass = 'ExportToFile';
temp.CoderInfo.CustomAttributes.HeaderFile = 'ex_ext_ctrl_sigs.h';
temp.CoderInfo.CustomAttributes.DefinitionFile = 'ex_ext_ctrl_sigs.c';

set_param('ex_ext_ctrl_alg/Out1','SignalObject',copy(temp))
set_param('ex_ext_ctrl_alg/Out2','SignalObject',copy(temp))

To apply a storage class to a block parameter, such as the Constant value parameter of a Constant block, you must create a parameter object such as Simulink.Parameter. You can use the Model Data Editor to create parameter objects.

Select the Parameters tab and set Change view to Design.

In the model, select the Constant block labeled Flow Setpt.

In the Model Data Editor, set Value to PARAM_setpoint_flow.

Next to PARAM_setpoint_flow, click the action button (with three vertical dots) and select Create.

In the Create New Data dialog box, set Value to Simulink.Parameter(3).

Set Location to Model Workspace and click Create.

In the PARAM_setpoint_flow property dialog box, set Storage class to Const.

Set HeaderFile to ex_ext_ctrl_params.h and DefinitionFile to ex_ext_ctrl_params.c.

For the other Constant block, use the Model Data Editor to create a Simulink.Parameter object named PARAM_setpoint_temp with value 2.

Alternatively, to configure the blocks, at the command prompt, use these commands.

PARAM_setpoint_flow = Simulink.Parameter(3);
PARAM_setpoint_flow.CoderInfo.StorageClass = 'Custom';
PARAM_setpoint_flow.CoderInfo.CustomStorageClass = 'Const';
PARAM_setpoint_flow.CoderInfo.CustomAttributes.HeaderFile = 'ex_ext_ctrl_params.h';
PARAM_setpoint_flow.CoderInfo.CustomAttributes.DefinitionFile = 'ex_ext_ctrl_params.c';

PARAM_setpoint_temp = copy(PARAM_setpoint_flow);
PARAM_setpoint_temp.Value = 2;

mdlwks = get_param('ex_ext_ctrl_alg','ModelWorkspace');
assignin(mdlwks,'PARAM_setpoint_flow',copy(PARAM_setpoint_flow))
assignin(mdlwks,'PARAM_setpoint_temp',copy(PARAM_setpoint_temp))

set_param('ex_ext_ctrl_alg/Flow Setpt','Value','PARAM_setpoint_flow')
set_param('ex_ext_ctrl_alg/Temp Setpt','Value','PARAM_setpoint_temp')

clear temp PARAM_setpoint_flow PARAM_setpoint_temp mdlwks portHandles outportHandle

Configure Internal Data

In the external code, internal data that does not participate in the module interfaces, such as state data and local variables in functions, conform to naming schemes. In the model, configure internal data that appears in the generated code to mimic these naming schemes.

In the model Configuration Parameters dialog box, inspect the Code Generation > Identifiers pane. When you specify values for the naming schemes under Identifier format control:

  • $R represents the name of the model, ex_ext_ctrl_alg.

  • $N represents the name of each model element to which the scheme applies, such as a signal, block state, or standard data structure.

  • $M represents name-mangling text that the code generator inserts, if necessary, to avoid name clashes. For most naming rules, this token is required.

Set Global variables to $R$N_data$M. This setting controls the names of global variables, such as those that represent state data. The naming scheme $R$N_data_$M most closely approximates the scheme that the state variables in the external code use.

Set Local temporary variables and Local block output variables to $N_local$M.

Alternatively, to set the configuration parameters, at the command prompt, use these commands.

set_param('ex_ext_ctrl_alg','CustomSymbolStrGlobalVar','$R$N_data$M')
set_param('ex_ext_ctrl_alg','CustomSymbolStrTmpVar','$N_local$M')
set_param('ex_ext_ctrl_alg','CustomSymbolStrBlkIO','$N_local$M')

Configure the state data in the model to appear in the generated code as separate global variables instead of fields of the standard DWork structure. Enable the Code perspective. In the Apps gallery, click Embedded Coder.

Underneath the block diagram, under Code Mappings > Data Defaults, for the Internal data row, in the Storage Class column select ExportedGlobal.

Alternatively, to configure this default storage class, use these commands at the command prompt:

coder.mapping.create('ex_ext_ctrl_alg');
coder.mapping.defaults.set('ex_ext_ctrl_alg','InternalData',...
    'StorageClass','ExportedGlobal');

Configure Organization of Code in Each Generated File

Set your current folder to the ex_ext_ctrl_alg folder.

cd(fullfile('..','ex_ext_ctrl_alg'))

At the command prompt, navigate to the folder that contains the built-in code generation template ert_code_template.cgt. By default, when you generate code with the system target file ert.tlc, this template governs the organization and, in part, the appearance of the code in each generated file.

currentFolder = pwd;
cd(fullfile(matlabroot,'toolbox','rtw','targets','ecoder'))

Copy the ert_code_template.cgt file to your clipboard.

Return to the ex_ext_ctrl_alg folder. To avoid overwriting your clipboard, instead of using the command prompt to navigate to the folder, you can use the Back button in MATLAB.

cd(currentFolder)
clear currentFolder

Paste the file into the ex_ext_ctrl_alg folder. Rename the file as ex_my_code_template.cgt.

Open the file and replace the contents with this code.

type(fullfile(matlabroot,'examples','ecoder','data','ex_my_code_template.cgt'))

This new template conforms more closely to the organization of the external files. For example, the template organizes similar code constructs into named sections (delimited by comments) and, at the top of the template, specifies only minimal information about each generated file.

In the model, select Configuration Parameters > Code Generation > Templates.

Under Code templates and Data templates, set the four configuration parameters to ex_my_code_template.cgt.

Alternatively, to copy the file and set the parameters, at the command prompt, use these commands.

copyfile(fullfile(matlabroot,'examples','ecoder','data','ex_my_code_template.cgt'),...
    'ex_my_code_template.cgt','f')
set_param('ex_ext_ctrl_alg','ERTSrcFileBannerTemplate','ex_my_code_template.cgt')
set_param('ex_ext_ctrl_alg','ERTHdrFileBannerTemplate','ex_my_code_template.cgt')
set_param('ex_ext_ctrl_alg','ERTDataSrcFileTemplate','ex_my_code_template.cgt')
set_param('ex_ext_ctrl_alg','ERTDataHdrFileTemplate','ex_my_code_template.cgt')

Generate and Inspect Code

Because the external code already defines a main function, select Configuration Parameters > Generate code only and clear Configuration Parameters > Generate an example main program.

set_param('ex_ext_ctrl_alg','GenCodeOnly','on')
set_param('ex_ext_ctrl_alg','GenerateSampleERTMain','off')

Generate code from the model.

rtwbuild('ex_ext_ctrl_alg')

In the code generation report, inspect the generated files. The code meets the requirements. For example, ex_ext_ctrl_sigs.c defines the control output signals, CONTR_SIG_FLOW and CONTR_SIG_TEMP.

file = fullfile('ex_ext_ctrl_alg_ert_rtw','ex_ext_ctrl_sigs.c');
rtwdemodbtype(file,'dataPath_flow_T CONTR_SIG_FLOW;',...
    'dataPath_temp_T CONTR_SIG_TEMP;',1,1)

The setpoint parameters appear in ex_ext_ctrl_params.c.

file = fullfile('ex_ext_ctrl_alg_ert_rtw','ex_ext_ctrl_params.c');
rtwdemodbtype(file,'const dataPath_flow_T PARAM_setpoint_flow = 3.0;',...
    'const dataPath_temp_T PARAM_setpoint_temp = 2.0;',1,1)

The file ex_ext_ctrl_alg.c defines global variables to store state data. The variables follow the naming scheme that you specified. Code is similar to the following:

dataPath_temp_T ex_ext__Integrator_DSTATE_datag;
dataPath_flow_T ex_ext_c_Integrator_DSTATE_data;
dataPath_temp_T ex_ext_ctrl_Filter_DSTATE_datad;
dataPath_flow_T ex_ext_ctrl__Filter_DSTATE_data;

In the same file, the model execution function, ex_ext_ctrl_alg_step, creates local function variables to store temporary calculations. The variables follow the naming scheme that you specified. Code is similar to the following:

dataPath_flow_T Diff_local;
dataPath_flow_T FilterCoefficient_local;
dataPath_temp_T Diff1_local;
dataPath_temp_T FilterCoefficient_locali;

Some of the global and local variable names contain extra mangling characters that prevent name clashes. These extra characters correspond to the $M token that you specified in the naming schemes.

The generated code files are in the generated folders ex_ext_ctrl_alg_ert_rtw and slprj. You must configure file management systems and build tools to use these folders and files.

Related Topics