Create Custom Target IDE for Code Generation
Simulink® PLC Coder™ currently supports plugin-based target IDEs such as Selectron CAP1131, Omron Sysmac Studio, and so on. For your plugin-based custom target IDEs that are not supported, generate PLCOpen XML-or ASCII-compliant structured text code. Customize the generated code to meet your target IDE requirements, by leveraging the built-in plugin options in Simulink PLC Coder.
Plugin-Based Code Generation Workflow
To generate code for your custom plugin-based target IDEs:
Follow the flowchart to create the plc_custom_ide.m
and if required
the plc_precg_callback_IDEname.m
, and
plc_postcg_callback_IDEname.m
files. To use these files to
create and generate code for your custom target IDE, see Generate Code by Using Plugin-Based Target IDE.
Create plc_custom_ide.m
file
Create plc_custom_ide.m
by using this
template:
function plc_ide_list = plc_custom_ide % Copyright 2012-2021 The MathWorks, Inc. plc_ide_list(1) = get_ide_info_myplcopen; end function ide_info = get_ide_info_myplcopen ide_info.name = 'myplcopen'; ide_info.description = 'My PLCopen XML'; ide_info.path = ''; % IDE path ide_info.format = 'xml'; % generic|xml ide_info.fileExtension = 'xml'; ide_info.cfg = get_ide_cfg_myplcopen; ide_info.precg_callback = 'plc_precg_callback_myplcopen'; ide_info.postcg_callback = 'plc_postcg_callback_myplcopen'; ide_info.xmltree_callback = PLCCoder.PLCCGMgr.PLC_PLUGIN_CG_CALLBACK_EMPTY; ide_info.pluginVersion = 2.2; ide_info.compatibleBuildVersion = 1.6; end function cfg = get_ide_cfg_myplcopen cfg.fConvertDoubleToSingle = true; cfg.fConvertNamedConstantToInteger = true; cfg.fConvertEnumToInteger = true; cfg.fConvertOutputInitValueToAssignment = true; cfg.fConvertTunableParamToInputVariable = true; cfg.fSimplifyFunctionCallExpr = true; cfg.fConvertOutputInitValueToAssignment = true; end
Set the name of your target IDE, by using
ide_info.name
. The Target IDE configuration setting displays the name set inide_info.description
.If your target IDE is compliant with Generic ST standards, set
ide_info.format = 'generic'
. If your target IDE is compliant with PLCOpen XML, setide_info.format = 'xml'
.Set where the generated code files are placed by using
ide_info.path
.Set the extension for your target IDE files by using
ide_info.fileExtension
.
In the function_cfg
section of the file, set your plugin options.
To enable the plugin, set the plugin option to true
. For example,
cfg.fArrayInitialValueBrackets = true;
enables the plugin. To disable
the plugin, set the plugin option to false
. To decide which plugin
options you need in the plc_custom_ide.m
file, see Plugin Options.
Create Callback Files
If your custom IDE requires pre- and post-code generation processing, create
plc_precg_callback_IDEname.m
and
plc_postcg_callback_IDEname.m
files.
Preprocessing Callback File. The plc_precg_callback_IDEname.m
file provides access
to the intermediate generated code representation. The intermediate code is
stored in the controller
struct data type, which contains
information such as name of the subsystem block, inputs, outputs, generated
code body, and so on. This callback is executed before the target-specific
emitter function is called. To create a
plc_precg_callback_IDEname.m
file, use this
template:
function controller = ... plc_precg_callback_myplcopen(controller) % Copyright 2012-2020 The MathWorks, Inc. % do modifications to the controller struct here, f.ex.: for i = 1:length(controller.components) controller.components(i).body = sprintf('<<header_placeholder>>\r\n%s',controller.components(i).body); end end
Postprocessing Callback File. The plc_postcg_callback_IDEname.m
file provides access to the
generated code file. Use this file to make the generated code match the syntax
requirements for your custom target IDE. This file reads in and makes changes to the
generated code file, which is read in as a string file. To create a
plc_postcg_callback_IDEname.m
file, use this template:
function generatedFiles = plc_postcg_callback_myplcopen(fileNames) % Copyright 2012-2020 The MathWorks, Inc. fileName = fileNames{1}; str = fileread(fileName); % do modifications to str here, f.ex.: % str = regexprep(str,'BOOL_TO_LREAL','BOOL_TO_INT'); % str = regexprep(str,'<USINT/>','<INT/>'); % str = regexprep(str, 'END_STRUCT','END_STRUCT;'); [sHeader,eHeader] = regexp(str,'\(\*.*?\*\)'); header = str(sHeader:eHeader); str = regexprep(str,'<<header_placeholder>>',header); sfprivate ('str2file', str, fileName); generatedFiles = {fileName}; end
Plugin Options
Generate custom code for your custom target IDE, by selecting from the plugin options listed in the table.
Plugins for Data Type Transformation
Plugin Name | Plugin Purpose | When to Use Plugin | Effect of Plugin on Generated Code |
fConvertBooleanCast | Convert boolean type cast function to an if-else assignment. | If your target IDE does not support boolean type cast function or operator. | Generated code with plugin disabled: Out1 := DINT_TO_INT(BOOL_TO_DINT(In1) +... BOOL_TO_DINT(In2)); Generated code with plugin enabled: IF In1 THEN temp1 := DINT#1; ELSE temp1 := DINT#0; END_IF; IF In2 THEN temp2 := DINT#1; ELSE temp2 := DINT#0; END_IF; Out1 := DINT_TO_INT(temp1 + temp2); |
fConvertDoubleToSingle | Convert double data type to single data type. | If your target IDE does not support double data types. The generated code double data type variables are converted
to single data types. A warning message is generated during code generation
that data types that are not supported have been found and converted.
The values in the generated code may be different from the simulation values. | Generated code with plugin disabled: VAR_INPUT ssMethodType: SINT; U: LREAL; END_VAR VAR_OUTPUT Y: LREAL; END_VAR Generated code with plugin enabled: VAR_INPUT ssMethodType: SINT; U: REAL; END_VAR VAR_OUTPUT Y: REAL; END_VAR |
fConvertDoubleToSingleEmitter | Convert double data types to single data types. | If your target IDE does not support double data types and you want to preserve values after conversion. The generated code double data
type variables are converted to single data types. A warning message is
generated during code generation that data types that are not supported have
been found and converted. The values in the generated
code match the simulation values unless they exceed the
| Generated code with plugin disabled: VAR_INPUT ssMethodType: SINT; U: LREAL; END_VAR VAR_OUTPUT Y: LREAL; END_VAR Generated code with plugin enabled: VAR_INPUT ssMethodType: SINT; U: REAL; END_VAR VAR_OUTPUT Y: REAL; END_VAR |
fConvertEnumToInteger | Convert enum data types to integer data types. | If your target IDE does not support enum data types. | Generated code with plugin disabled for a target IDE that supports enum data type: VAR_TEMP rtb_Switch: myEnum; in: myEnum; END_VAR Generated code with plugin enabled: VAR_TEMP rtb_Switch: DINT; in: DINT; END_VAR |
fConvertUnsignedIntToSignedInt | Converts unsigned integer to signed integer. | If your target does not support unsigned integer data type. | Generated code with plugin disabled: FUNCTION_BLOCK Subsystem VAR_INPUT In1: UDINT; In2: UDINT; END_VAR VAR_OUTPUT Out1: UDINT; END_VAR Generated code with plugin enabled: FUNCTION_BLOCK Subsystem VAR_INPUT In1: DINT; In2: DINT; END_VAR VAR_OUTPUT Out1: DINT; END_VAR |
fInt32AsBaseInt | Sets int32 data type as the default integer data type. | Setting int32 as the default internal integer data type might reduce the number of type cast operations in the generated code. | Generated code with plugin disabled: FUNCTION_BLOCK Subsystem VAR_INPUT In1: SINT; In2: SINT; END_VAR VAR_OUTPUT Out1: SINT; END_VAR Out1 := DINT_TO_SINT(SINT_TO_DINT(In1) +... SINT_TO_DINT(In2)); END_FUNCTION_BLOCK Generated code with plugin enabled: FUNCTION_BLOCK Subsystem VAR_INPUT In1: DINT; In2: DINT; END_VAR VAR_OUTPUT Out1: DINT; END_VAR Out1 := In1 + In2)); END_FUNCTION_BLOCK |
fEmitEnumTypeIntegerValue | Displays the enum value and corresponding integer value in the generated code. | To display the enum values and their matching integer values in the generated code. | Generated code with plugin disabled: TYPE PLCCommandState: (FILL, HOLD, EMPTY, ACTIVATE); END_TYPE TYPE PLCVesselState: (EMPTIED, NOT_FULL, FULL); END_TYPE TYPE PLCValveState: (SHUT, OPEN); END_TYPE Generated code with plugin enabled: TYPE PLCCommandState: (FILL:=0, HOLD:=1, EMPTY:=2, ACTIVATE:=3); END_TYPE TYPE PLCVesselState: (EMPTIED:=0, NOT_FULL:=1, FULL:=2); END_TYPE TYPE PLCValveState: (SHUT:=0, OPEN:=1); END_TYPE |
Plugins for Syntax Change
Plugin Name | Plugin Purpose | When to Use Plugin | Effect of Plugin on Generated Code |
fArrayInitialValueBrackets | Encloses array initialization in the declaration area within brackets. | If your target IDE requires enclosing array initialization in the declaration area in brackets. | Generated code with plugin disabled: EnableSetpoint_ZCE: ARRAY [0..2] OF USINT:=3,3,3 Generated code with plugin enabled:: EnableSetpoint_ZCE: ARRAY [0..2] OF USINT:=[3,3,3] |
fConvertAggregateInitValueToAssignment | Converts initial values for aggregate data types to an assignment statement. | Target IDE does not support array initialization in the declaration area. | Generated code with plugin disabled: ARRAY [0..1] OF LREAL := LREAL#0.0,LREAL#0.0998 Generated code with plugin enabled: tb_U[0] := 0.0; tb_U[1] := 0.0998; |
fConvertAggregateTypeFunctionToFB | Converts functions with aggregate data types to a function block (FB). | Target IDE supports aggregate data types for function blocks only. | Generated code with plugin disabled: function foo(...):ARRAY [0..10] OF LREAL Generated code with plugin enabled: FUNCTION_BLOCK foo VAR_OUTPUT out1: ARRAY [0..10] OF LREAL; END_VAR |
fConvertFunctionToFB | Convert function to function block (FB). | Target IDE does not support | |
fConvertOutputInitValueToAssignment | Convert output variable initialization to an assignment. | Target IDE does not allow initial value definition and requires an assignment statement. | Generated code with plugin disabled: FUNCTION_BLOCK foo VAR_OUTPUT somevalue: DINT := 100; END_VAR Generated code with plugin enabled: FUNCTION_BLOCK foo VAR_OUTPUT somevalue: DINT; END_VAR somevalue := 100; |
fEmitVarDeclarationBeforeDescription | Toggles whether the variable description appears before or after the variable declaration. | Target IDE requires variable declaration before the variable description. | Generated code with plugin disabled: VAR_GLOBAL (* Description: Gain value two *) K2: REAL := 0.2; END_VAR Generated code with plugin enabled: VAR_GLOBAL K2: REAL := 0.2; (* Description: Gain value two *) END_VAR |
fErrorOnTrailingUS | Code generation fails when it encounters variable names with a trailing underscore. | Target IDE does not support names with a trailing underscore. | Code generation fails with this message |
fHoistArrayIndexExprs | Moves expressions out of array indices and creates a temporary variable for the expression. | Target IDE does not support expressions for array indices. | Generated code with plugin disabled: EnvCur[TRUNC(j) - 1] Generated code with plugin enabled: temp1 := TRUNC(j) - 1; EnvCur[temp1] |
fSimplifyFunctionCallExpr | Simplifies the function call for simple functions. | Target IDE does not allow assignment expressions in function calls. | Generated code with plugin disabled: y := simplefunction(u_0 := u); Generated code with plugin enabled: y := simplefunction(u); |
fUseQualifiedTypeConstant | Appends data type to constant declaration. | Target IDE requires data type for constants. | Generated code with plugin disabled: a := 11; Generated code with plugin enabled: a := DINT#11; |
Plugins for Interface Changes
Plugin Name | Plugin Purpose | When to Use Plugin | Effect of Plugin on Generated Code |
fConvertTunableParamToInputVariable | Converts tunable parameters to function block (FB) input variables. | You want to convert tunable parameters to function block inputs. This allows you to call a POU with different parameter sets. | Generated code with plugin disabled: FUNCTION_BLOCK Tunable_Param_to_Input 24 VAR_INPUT 25 ssMethodType: SINT; 26 Input1: REAL; 27 END_VAR 28 VAR_OUTPUT 29 Output1: REAL; 30 END_VAR 31 VAR 32 DSTATE: REAL; 33 END_VAR 34 'ST' 35 BODY 36 CASE ssMethodType OF 37 0: 40 UnitDelay_DSTATE := 0.0; 41 42 1: 43 Output1 := (Input1 - DSTATE) * TunableParam; 48 DSTATE := Output1; 50 END_CASE; 52 END_BODY 53 END_FUNCTION_BLOCK 54 TunableParam variable is not
declared as an input to the function block.Generated code with plugin enabled: FUNCTION_BLOCK Tunable_Param_to_Input 24 VAR_INPUT 25 TunableParam: LREAL; 26 ssMethodType: SINT; 27 Input1: REAL; 28 END_VAR 29 VAR_OUTPUT 30 Output1: REAL; 31 END_VAR 32 VAR 33 DSTATE: REAL; 34 END_VAR 35 'ST' 36 BODY 37 CASE ssMethodType OF 38 0: 39 DSTATE := 0.0; 42 43 1: 44 Output1 := (Input1 - DSTATE) * TunableParam; 49 DSTATE := Output1; 51 52 END_CASE; 53 END_BODY 54 END_FUNCTION_BLOCK 55 TunableParam variable is declared
as an input to the function block. |
fDefineFBExternalConstVariable | Defines external variables in | Target IDE requires declaration of external constant variables in
| Generated code with plugin disabled: VAR_GLOBAL CONSTANT SS_INITIALIZE: SINT := 0; K3: LREAL := 0.3; SS_STEP: SINT := 1; END_VAR Generated code with plugin enabled: VAR_GLOBAL CONSTANT K3: REAL := 0.3; |
fDefineFBExternalVariable | Defines external constants in
| Target IDE requires declaration of external constants in
| Generated code with plugin disabled: VAR UnitDelay_DSTATE: LREAL; i0_ExternallyDefinedBlock: ExternallyDefinedBlock; END_VAR Generated code with plugin enabled: VAR_EXTERNAL K1: REAL; END_VAR |
fReplaceShiftFunctions | Replaces target IDE shift functions with functions to match Simulink shift function behavior. | When the number of shifts is greater than the length of the data type, there is a mismatch between output of Simulink shift block and target IDE shift block. Use this plugin to replace target IDE shift blocks with Simulink shift blocks in the generated code. | Generated code with plugin disabled: Out10 := WORD_TO_INT(SHL(IN:=INT_TO_WORD(In1),... N:=Out2_tmp)); SHL shift function.Generated code with plugin enabled: Out10 := WORD_TO_INT(PLC_SHL(INT_TO_WORD(In1),... DINT_TO_USINT(Out2_tmp))); FUNCTION PLC_SHL: WORD VAR_INPUT in1: WORD; in2: USINT; END_VAR 'ST' BODY IF in2 > 16 THEN PLC_SHL := 16#0; ELSE PLC_SHL := SHL(in1, in2); END_IF; END_BODY END_FUNCTION PLC_SHL , which replicates the
Simulink shift block in the target IDE. |
Plugins for Intrinsic Transformation Functions
Plugin Name | Plugin Purpose | When to Use Plugin | Effect of Plugin on Generated Code |
fSimplifyAllIntrinsicFcn | Simplifies the inputs of all intrinsic functions. | Target IDE does not allow compound expressions as a part of intrinsic function arguments. | Generated code with plugin disabled: a := SQRT(x*y); Generated code with plugin enabled: t1 := x*y; a := SQRT(t1); |
fSimplifyIntrinsicFcn | Simplifies intrinsic functions that are arguments of
| Target IDE does not allow compound expressions as a part of intrinsic function arguments. | Generated code with plugin disabled: a := SQRT(x*y); Generated code with plugin enabled: cfg.fSimplifyIntrinsicFcnNameList = { 'SQRT' \} cfg.fSimplifyIntrinsicFcn = true; t1 := x*y; a := SQRT(t1); |
fSimplifyIntrinsicFcnNameList | Creates a list of intrinsic functions. Inputs to these intrinsic
functions are simplified by using
| Target IDE does not allow compound expressions as a part of intrinsic function arguments. | Generated code with plugin disabled: a := SQRT(x*y); Generated code with plugin enabled: cfg.fSimplifyIntrinsicFcnNameList = { 'SQRT' \} cfg.fSimplifyIntrinsicFcn = true; t1 := x*y; a := SQRT(t1); |
fSimplifyOperator | Simplifies inputs of operator functions listed by using
| Target IDE does not allow compound expressions as a part of operator function arguments. | Generated code with plugin disabled: a := SHL(x*y); Generated code with plugin enabled: cfg.fSimplifyOperatorNameList = { 'SHL' \} cfg.fSimplifyOperator = true; t1 := x*y; a := SHL(t1); |
fSimplifyOperatorNameList | Creates a list of operator functions. Inputs to these operator
functions are simplified by using
| Target IDE does not allow compound expressions as a part of operator function arguments. | Generated code with plugin disabled: a := SHL(x*y); Generated code with plugin enabled: cfg.fSimplifyOperatorNameList = { 'SHL' \} cfg.fSimplifyOperator = true; t1 := x*y; a := SHL(t1); |
fSimplifyTrunc | Simplifies inputs of the | Target IDE does not allow compound expressions as arguments for the
| Generated code with plugin disabled: a := TRUNC(x*y); Generated code with plugin enabled: cfg.fSimplifyTrunc = true; t1 := x*y; a := SHL(t1); |
Plugins for XSD Schema Modification
Plugin Name | Plugin Purpose | When to Use Plugin | Effect of Plugin on Generated Code |
fEmitXsdSchema | Generates information related to XSD schema version
| Target IDE requires XSD schema information as a part of the generated PLCOpen XML file. To
generate schema set | Generated code with plugin set to
<?xml version="1.0" encoding="UTF-8"?> Generated
code with plugin set to
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://www.plcopen.org/xml/tc6.xsd"... xmlns:xhtml="http://www.w3.org/1999/xhtml"... xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> Generated
code with plugin set to
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://www.plcopen.org/xml/tc6_0201"... xmlns:ns1="http://www.plcopen.org/xml/tc6.xsd" .. xmlns:xhtml="http://www.w3.org/1999/xhtml"... xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
Generate Code by Using Plugin-Based Target IDE
This example shows how to generate code for a custom target IDE called my
PLCopen XML
by using plugins.
Create a folder called
myplcopen
. Create aplc_custom_ide.m
file in the folder by using this template:function plc_ide_list = plc_custom_ide % Copyright 2012-2021 The MathWorks, Inc. plc_ide_list(1) = get_ide_info_myplcopen; end function ide_info = get_ide_info_myplcopen ide_info.name = 'myplcopen'; ide_info.description = 'My PLCopen XML'; ide_info.path = ''; % IDE path ide_info.format = 'xml'; % generic|xml ide_info.fileExtension = 'xml'; ide_info.cfg = get_ide_cfg_myplcopen; ide_info.precg_callback = 'plc_precg_callback_myplcopen'; ide_info.postcg_callback = 'plc_postcg_callback_myplcopen'; ide_info.xmltree_callback = PLCCoder.PLCCGMgr.PLC_PLUGIN_CG_CALLBACK_EMPTY; ide_info.pluginVersion = 2.2; ide_info.compatibleBuildVersion = 1.6; end function cfg = get_ide_cfg_myplcopen cfg.fConvertDoubleToSingle = true; cfg.fConvertNamedConstantToInteger = true; cfg.fConvertEnumToInteger = true; cfg.fConvertOutputInitValueToAssignment = true; cfg.fConvertTunableParamToInputVariable = true; cfg.fSimplifyFunctionCallExpr = true; cfg.fConvertOutputInitValueToAssignment = true; end
Set your plugin options in the
function_cfg
section of the file. To enable the plugin set the plugin option totrue
. For example,cfg.fArrayInitialValueBrackets = true;
enables the plugin. To disable the plugin, set the plugin option tofalse
.Create
plc_precg_callback_IDEname.m
andplc_postcg_callback_IDEname.m
files by using these templates:function controller = plc_precg_callback_myplcopen(controller) % Copyright 2012-2020 The MathWorks, Inc. % do modifications to the controller struct here, f.ex.: for i = 1:length(controller.components) controller.components(i).body = sprintf('<<header_placeholder>>\r\n%s',controller.components(i).body); end end
function generatedFiles = plc_postcg_callback_myplcopen(fileNames) % Copyright 2012-2020 The MathWorks, Inc. fileName = fileNames{1}; str = fileread(fileName); % do modifications to str here, f.ex.: % str = regexprep(str,'BOOL_TO_LREAL','BOOL_TO_INT'); % str = regexprep(str,'<USINT/>','<INT/>'); % str = regexprep(str, 'END_STRUCT','END_STRUCT;'); [sHeader,eHeader] = regexp(str,'\(\*.*?\*\)'); header = str(sHeader:eHeader); str = regexprep(str,'<<header_placeholder>>',header); sfprivate ('str2file', str, fileName); generatedFiles = {fileName}; end
Create a
plc_header_hook.m
file by using this template:function headerCommentText = plc_header_hook(filePath, blockH, headerCommentText) headerCommentText = [headerCommentText(1:end-7) ... sprintf([' * Plugin Header Copy : Yes \n']) ... headerCommentText(end-6:end)]; end
The
plc_header_hook.m
file copies the header information at the beginning of the generated code file to every function block instance.Add the new folder and files to the MATLAB path.
Right-click the folder and select Add to Path > Selected Folders and Subfolders.
Use the
addpath
function. For example,addpath(genpath('path to your folder'))
.
Run this command:
plccoderpref('plctargetidepaths','default')
Restart your MATLAB session.
Open your model and select the model component for code generation. Open the PLC Coder app. Click Settings. On the PLC Code Generation pane, in Target IDE, select
My PLCopen XML
. Click OK.In the PLC Coder app, PLC Code tab, click Generate PLC Code to generate code for your custom target IDE. The generated code files are placed in the path specified in
ide_info.path = ''; % IDE path
.