Advanced techniques for customizing IEC 61131 code generation for PLCs
Overview
Using Model-Based Design, engineers develop machine functionality and can run hundreds of test scenarios in desktop simulation. After verification, the resulting software can be automatically deployed to several PLC platforms from the same model. This increases software quality and time to market since only well tested code will be deployed to your PLC.
Simulink PLC Coder™ generates hardware-independent IEC 61131-3 Structured Text and Ladder Diagrams from Simulink® models, Stateflow® charts, and MATLAB® functions. The Structured Text and Ladder Diagrams are generated in PLCopen XML and other file formats supported by widely used IDEs, including 3S-Smart Software Solutions CODESYS®, Siemens TIA Portal, B&R Automation Studio, Rockwell Automation Studio 5000 and Omron® Sysmac® Studio. As a result, you can compile and deploy your application to numerous programmable logic controller (PLC) and programmable automation controller (PAC) devices.
This webinar provides in-depth background knowledge Simulink PLC Coder and its application areas.
You will learn:
- about basic and advanced techniques to configure and optimize your generated code
- what customization options Simulink PLC Coder offers
- how to create custom PLC Coder targets
- about existing Add-On Packages created by MathWorks (PackML, PLCOpen)
Highlights
- Overview: Model-Based Design for PLCs
- Simulink PLC Coder key features
- Configuration and optimization settings
- Plugin customization
- Add-On packages
About the Presenter
Desiree Wolfrum
Product Specialist – PLC applications
Recorded: 2 Dec 2020
Welcome to today's webinar. My name is Desiree Wolfrum, and I'm a product specialist at MathWorks with a focus area of embedded code generation for PLCs.
Are you an avid user of Simulink PLC Coder to generate structured text from a Simulink application? Have you ever wondered about changing or optimizing the generated code? Did you ever ask yourself if Simulink PLC Coder-generated code can be adapted to a yet unsupported target?
In this webinar, you will learn about advanced techniques for customizing IEC 61131 code generation for PLCs. You will learn about where PLC code generation fits in the model-based design workflow. I will present the key features of Simulink PLC Coder and also more advanced configuration and optimization settings to optimize the generated code.
With plug-in customization, you can adapt the code even more and create custom targets. There are add-on packages available which can help you simplify your modeling and code generation workflow.
I will give an overview about existing packages like the PLCopen Support and PackML packages, a custom target for Schneider and 580 controllers, and a package for Codesys 3.5 code simulation.
Here, you see the model-based design workflow for PLCs. The model is the starting point. With Simulink PLC Coder, you can generate IEC 61131-3 complaint Structured Text from the control system for a range of different PLC platforms.
And you can test the generated code on hardware in real-time conditions with generating the planned model for a real-time hardware.
Here's an overview of the currently supported PLC platforms. You see that almost all relevant PLC vendors are supported with our tools. A new target, the Selectron target for CAP 1131 has been added with the 20a version. And a target for Schneider M580 PLCs is available as an add-on package.
Simulink PLC Coder generates IEC 61131-3 structured text from Simulink blocks including reuseable subsystems and lookup tables, stateflow charts including truth tables and finite state machines, and MATLAB Function code, including loops and complex math operations.
Data types like boolean, enum, integer, and real are supported. One or multidimensional arrays and structures are supported as well as tunable parameters. You can generate the structured text for a variety of targets. So you can use one Simulink application to target several PLC platforms.
The structured text is admitted in a file which format is adapted to the third party IDE to support import of the generated code to the IDE. Key features of the PLC Coder are generation of code reports, which offer model code traceability, generation of test benches to accomplish back-to-back testing, and optimization features to create efficient and adequately adapted code.
What you see here is how the Simulink model maps to the generated structured text. The format of the structured text can be ASCII text format or XML format adapted to the target. The subsystem inputs and outputs mapped to the function blocks var_input and var_output.
The subsystems DWork variables will be the local variables of the function block. The initialization output and update methods of the subsystem are implemented with the coding methods in the function block code body. Parameters can be inlined like in this example, but can also be made tunable.
To achieve traceability between model and code, a code generation report is generated. A static code matrix report is also generated. The code generation report offers bidirectional traceability between model and code. This simplifies the code review and verification workflow.
The traceability navigation works for Simulink blocks, stateflow charts, MATLAB Functions, truth tables, and state transition tables.
In verification workflows, you need to verify that the code is equivalent to the model. The test sequences of the model can be translated to the code by generating the test bench. Along with the code for the function block, a test bench containing code that compares simulation and code results is generated.
For this, test arrays are generated from the simulation inputs and outputs. The inputs are fed into the generated function block code, and the outputs are compared to the code outputs. A trailing variable test_verify is observed in this back-to-back test.
The test_verify variable shows if a test has failed but does not give insight into the specifics of the fail. With test bench diagnostic code, you can get additional information on the failed test. The test cycle number and variable name is captured to allow a closer, more directed look if a test failed.
Since R 2019b released, the Toolstrip App is available for PLC Coder. Before this, you could bring up PLC Coder menu by right-clicking the subsystem. Now, the Toolstrip lets you conveniently control the subsystem settings for code generation and the code generation process.
With R 2020a release, the support for data dictionary usage was introduced with Simulink PLC Coder. You can now define model parameters and signals data in a data dictionary, which is like a repository of data that is relevant to the Simulink application.
What referencing is not supported with Simulink PLC Coder since the code generation workload is subsystem-based. With 20a release, subsystem reference blocks are supported with Simulink PLC Coder. This gives the advantage of using modular development.
You can control the implementation of parameters by using parameter objects in your model. Without using objects, parameter values will be inlined in the generated code. By specifying Simulink.parameter objects, the parameters can be seen in the code.
The storage class of the objects defines if a parameter is local, global, or a global constant. You can create hierarchies of function blocks by making subsystems and blocks non-virtual. For this, the block needs to be specified as an atomic unit and the function packaging needs to be set as reusable function.
The generated code will contain function block specification for each atomic unit. Top level blocks instantiate sublevel blocks. To simplify software distribution, reusability is automatically achieved if all code reuse conditions are met.
For MATLAB Function blocks you also have control over the inlining behavior by using the Coder.inline directive. With Coder.inline always, subfunctions are inlined to the upper level function block. With coder.inline "never", a hierarchical function or a function block is generated from the subfunction.
When you are using function blocks, global variables or constants or user-defined types, which are part of your IDE libraries, when generating the code you do not need to regenerate these elements.
In fact, you can also use dummies on the Simulink site and still generate perfectly integrated code for your environment. You specify a list of identifier names in the configuration options of your model. When generating code, the definition for these elements is suppressed while the call sites are generated.
With INOUT Variable Support for MATLAB Function blocks, you can create more efficient code. If a MATLAB Function is designed to have input and output with the same name, the generated function uses pass by reference to represent this variable. When output and input have different names, the function is required to copy the output to the input, which is less efficient.
Absolute Time Temporal Logic is supported for all IDEs. In stateflow, you can use operators like after or before to implement Absolute Time Temporal Logic. The generated code contains a timer function which uses TON standard function to implement this logic.
Another way to implement this logic is to use a target-independent integer counter. The temporal counter is determined according to test sample time of the function block and is independent from TON implementation, therefore, target-independent.
Using this when verifying with the test bench feature makes sense since target-dependent sample time matching might differ from simulation data.
Using configuration and optimization settings, you can optimize the generated code by balancing efficiency, code size, and readability of the code. Also, you can adapt the code to the target requirements.
You can specify a threshold value to control loop unrolling for for loops in the generated code. Usually, unrolled loops execute faster but generate more lines of code. Efficiency depends on how efficient the target is with loops.
When casting between integer and floating point data types, overflow protection code is generated to protect from out-of-range values. You can remove this extra code to make the code shorter and better readable.
Signal storage reuse option controls whether a variable associated with a signal can be reused for other signals. With this option activated, the code is more efficient because less memory is used. However, by deactivating this option, readability is improved since every signal gets its own storage variable.
Since 20a release, data conversions between enum data type and underlying integer data type is supported by generating cast functions. Some targets replace enums with the underlying integer because enums are not supported.
In that case, generation of enum cast functions will not work. With the option override target default enum behavior, you can still get the enum cast functions.
You can use the so-called plug-in technology to customize ST code generation for generic ASCII structured text or PLCopen XML code. For this, you need to create a folder on the MATLAB search path and add a plc_custom_ide file along with optional pre- and post-processing callbacks.
You can use predefined transforms to configure the plugin target with customizations. This is previously undocumented functionality, but it will be documented with the upcoming R 2021a release.
As stated before, there are predefined transforms that customize the generated code. You can use those to convert data types. For example, a target does not support double or enum data types. In that case, you can convert double to a single or enums to integers.
Then certain targets require a special API. For example, brackets for initial values of the raise, or the notation of the function call as opposed to the call of a function block. You can also convert initial value assignments to move from the declaration section to the code body section.
For global variables that are referenced by a function block, you can mark those references with var external section. Tunable parameters can be converted to inputs so that they can be tuned with different parameter sets. Intrinsic functions like ABS, AND/OR, TRUNC can be simplified to not use subexpressions in the calls of these intrinsic functions.
On the right, you see the configuration file for a plugin. At the bottom of this file you see some of the transforms like f convert double to a single, f convert enum to integer, or f convert tunable parameter input variable.
Another way to modify the generated code is to use hook files. You can use a hook file to modify the header of the generated code. The header normally contains version and date information and is needed for documenting the application.
You can also adjust the keyword checking process by adding keywords which are specific to your target or application. During the code generation process, the Coder will detect any keywords that are used and will make them unique by prefixing them. This way, you can ensure you won't have any compilation errors when you compile the generated code in the IDE.
Now it's time for a demo. I will show how to set up the files for a plugin-based target and how transforms, callbacks, and hook files can be used to adapt the generated code. Also, I'll show some interesting features to customize enums, external variables initialization, and saturation and rounding behavior.
Now, I will show how to create a plugin-based target. For this, you need to create a folder on your MATLAB search path. Then you create a function with the name plc_custom_ide. This function, which you can see here, contains the configurations for the plugin target.
You specify the name of the target and how it appears in the target IDE list of the PLC code generation options. You need to define if the target derives from generic ST or PLCopen XML. In this example, I chose PLCopen XML.
You can define optional pre- and post-processing callbacks. I will explain this later. And then you can use predefined transforms to customize the code. In this example, we will convert doubles to singles and enums to integers.
We will move initial values for areas from the declaration to the code body, and we will transform parameters to inputs. Also, for simple function calls as opposed to function blocks, I will show how to change the notation for this call site.
As a use case for the pre- and post-processing callbacks and to explain those, I chose the case of modifying the header of an application and moving this header to the code bodies of each function block.
The header of an XML file does not appear in the IDE. Hence, it is moved to the code body of each function so that the code is documented properly.
In the prepossessing callback, you have the representation of the model contents before admitting it to a specific format. In this variable called controller, you have direct access to all the code bodies for each component. In a hierarchical function block, you have several components which translate to functions or function blocks in the generated code.
In post-processing, you receive access to the generated file. So in the case of this PLCopen XML plugin, you can read in the XML file as a string. Because finding the right place to copy the header to is not that easy in the case of the XML, I chose to create a placeholder with the preprocessing as you can see here.
So at the beginning of each code body section, there now is a placeholder. And during post-processing, I will just replace this placeholder with a copy of the header. Why did I not directly copy the header during preprocessing? Because the header is not available at this stage of the generation process yet.
So this is an example of using the pre- and post-processing callback for the right purpose to automate customization of the generated code. So let's look at this in an example. Here's a hierarchical subsystem which contains two subsystems which are defined as atomic and reusable. See here.
The stateflow chart here contains temporal logic. So you can see here. The MATLAB Function is calling a very simple function called simplefunction with only one output and one input. We use the coder.inline directive to make this function an atomic unit as well.
Simple functions like these are more efficient if they're implemented as functions instead of function blocks. There's a difference. In addition, we are using a header hook file called PLC Header Hook in order to change the header of the function block a little bit.
So let's have a look at the generated code. You configure the target to be PLCopen XML. That's the normal PLCopen XML, not the plugin. We also activate the generation of the test bench. And now we generate the code.
You see that a program was created which implements the TestBench instance. The function block and function for the temporal chart and the simplefunction MATLAB Function are instantiated in the top level function block.
The function block for the stateflow chart instantiates a PLC_CODER_TIMER block. The PLC_CODER_TIMER block, which you can see here, uses the standard function TON, timer on delay, to implement the absolute time temporal logic.
The code generated for the simple MATLAB Function, you can see here. It is calling the simplefunction, which is actually implemented as a function. So not a function block. You see here.
For the test bench, you see the test bench arrays. Those are initialized in the variable declarations section. And here's the code to verify the equivalent of the simulation and code. So the actual back-to-back test. As long as the testVerify trailing variable remains true, the test is successful.
Now we change the target to the plugin target, which contains our customizations called My PLCopen XML. Also, we changed the timer option for the Absolute Time Temporal Logic to Target-Independent Counter and regenerate the code.
You can now observe several things. First of all, the header is copied to each function.code body as you can see here. The function block temporal is not instantiating PLC_CODER_TIMER function anymore. Instead, a variable temporal counter is generated. The code will not depend on TON implementation anymore but will behave the same way.
Here's the code with simplefunction. And as you can see here, there's a difference. With the plugin transform f simplify a function call expression, the input to the simplefunction is just the value and does not contain the assignment expression anymore.
Some targets actually do require the call site for a function to not contain assignments. This differs from the notations for function blocks. The test bench array initializations, which were in the variable declarations section before, you see now as part of the code body. This makes the code size a little bit bigger, but it doesn't say that it's any less efficient.
Let's have a look at enum data types. For this, I created another example, a more simple plugin target based on the generic target. For this target, we are not using any callbacks but we do specify a target to not support enums by converting them to integers. You can see here.
This MATLAB Function here contains some code which depends on an enum data type colors, which defines three colors, red, green, and blue as you can see here. Depending on the input, we want the output value to change.
So if we look at the genetic ST code generated, we will see that the enum type is created and the code for the MATLAB Function contains the enum labels as case statements here.
For the plug-in target that I just showed, we assume that the target does not support enums. So let's change to the My Generic ST Target to generate the code. You will see that all enum label information is removed from the model while the functionality remains.
Before the 20a release, it wasn't possible to generate code for enum data type conversions using the data type conversion block. In this subsystem, you see two conversions, from enum to integer, and from integer to enum.
Let's change the target back to generic and generate the code. We actually get an error because we did not activate the enum cast function option. So let's do that and regenerate the code.
Now you can see the conversion functions which are generated to separate functions. So in the case of the plug-in target, this should not work since enums are not supported as we assume. So let's try that.
Yes, we get an error that this conversion is not supported with this target configuration. However, we also might assume that normally we would like to convert the enum labels but we still want to be able to use conversion from and to integer data type.
In that case, we may check the override target default enum behavior option To be able to generate code. Here's the result. So you're still able to work with enums and conversions of enums in your model and still generate target compatible code. Since you might generate code for more than one target and the enum behavior or support of the targets differ, you can still use one model to generate compatible code for several targets.
This model here shows a similar component which uses a global parameter that is tunable. Here's the parameter. If we change this parameter storage class to export it global, in the code we will see that along with the call site of the variable, the global declaration is generated. Let's do that.
So here's the call site of the tunable parameter, and here's the declaration as a global variable. If we change the storage class of the parameter to ImportedExtern, let's have another look at the generated code.
Here you see that the declaration of TunableParam as a global variable will be omitted. So we can assume that the variable is available in the IDE already. When using our plugin target, the code will look different because what we do with the plugin target is to convert the tunable parameter to an input. So here's an input.
So along with the call site of the parameter, we now have the var input to set this parameter. This is useful if you actually want to instantiate this function block several times but use different parameter sets from the outside.
In this example here, you see another usage of external symbols. We have a data store memory called A in this model, which is defined as ImportedExtern. We also have a MATLAB Function called timestwo, which is defined as external by setting it in the external symbols list. Here.
When we look at the generated code, we see that the declaration of the variable A and the function block timestwo is submitted but the call sites are there. Here, A is used. And here, timestwo is instantiated but we don't see the declaration of timestwo or A.
One interesting thing here actually is that the external variable A is initialised in the initialization section. This might actually overwrite any settings that might be set for this external variable in the IDE.
So for this, we have an option to remove initialization statements for externally defined state variables. Regenerate the code now. We see that the initialization for A is actually removed.
Here's an interesting example of changing rounding and saturation behavior of an application. In this model, you see two subsystems. Both contain the exact same equation. This subsystem here is implementing this equation with Simulink blocks, and this function is implementing the same equation as a MATLAB Function. So you see here.
Since we have integer and floating point variables in this equation, there would be saturation in rounding to protect from old overflows. Let's look at this. Here's the code for the Simulink Function. You see a lot of saturation code here, and there actually is an option in the configuration to change this.
It's called remove code from floating point to integer conversions that wraps out-of-range values. So we activate this and regenerate the code. So we see that the code looks much better now. The saturation code is removed. But for the MATLAB Function block, we still see a lot of extra code. Some saturation but also some rounding is going on here.
So in order to configure the MATLAB Function, because this configuration option for the Simulink Function does not have any effect on the MATLAB Function, we need to call the config of the MATLAB Function and configure that saturate on integer overflow to be false.
We now regenerate the code. We do see that it already looks better, but we still have some rounding going on here with floor and ceiling functions. So how to remove that. We can actually remove this by using a fixed-point object, that's here, to explicitly state the output data type and overflow action for this.
When we now regenerate the code, we will see we have the same code for Simulink as for the MATLAB Function. Here's a MATLAB Function code and here's a Simulink Function code.
In addition to all the customization possibilities that you saw, there are also other ways to simplify the model-based design process for PLCs. I will give an overview of some useful packages which are available as so-called PSPs, Pilot Support Packages.
Those are like add-on packages which can be installed on top of your MATLAB version like a tool box and are maintained by MathWorks' Application Engineering Department. You will learn about the PSPs for PLCopen and PackML Support, and about the Schneider target and the Codesys Co-Simulation package.
The PLCopen Support PSP offers a set of coding guidelines implemented as Model Advisor checks. With these checks, you can look for keywords in your model, check the use of case and the use of type prefixes for variables. Comment checking is a way of making sure your application is documented properly.
By implementing these coding guidelines in the model level, you make sure that the generated code is conformant with PLCopen standard. Another part of this PSP is PLCopen safety, a set of safety functions, like for example, an emergency stop or safe stop has been implemented as stateflow charts.
These charts map exactly to the specification and can be used directly in your model without any adjustment. They are completely code generation-ready.
PackML is the standard of the packaging industry derived from ISA-88. The PackML PSP offers automated generation of state machines for PackML applications. Also, Model Advisor checks are part of this to check conformity of the state machines with the standard.
If you would like more information on this, there's a separate webinar available that you can find on our website when searching for PackML.
The Schneider M580 controller is a widely used PLC. The IDE for this hardware is called Unity Pro or Control Expert. That is the new name. Schneider is currently not one of our supported targets. However, the PSP exists to create the custom XML format that is compatible with this target.
The PCP also makes use of plug-in technology to achieve data type transforms since the M580 target does not support a real, enum, or short integers.
With the Codesys 3.5 Co-Simulation PSP, you can generate a co-sim application directly from your Simulink model. You generate a subsystem in your model with Codesys 3.5 target. An S-Function is automatically created. Along with this, Codesys is started and set up with a project and main program.
The communication in this co-simulation workflow is done over Windows Shared Memory. The simulation will do one time step in Simulink, and one time step in Codesys and so on. This PSP only works with the SoftPLC of Codesys. This workflow is a neat way to quickly co-simulate your plant model with a generated code of your controller.
So in this webinar, you learned a lot about Simulink PLC Coder, the key features along with various optimization techniques. You learned how to use plug-in technology to create your own target or better adapt the generated code to your environment. You also learned about some available add-on on packages which can simplify your development process.
If you are interested in any of these packages, or if you would like to learn more or have questions, please do not hesitate to contact me directly. Thank you for listening. Have a nice day.