使用社交账号登录
And more importantly, it could be helpful to take a look into the the simulation functionalities that comes with the OpenPLC development tool, how does it work, what is simulated and what is not, the limitations, and more importantly how exactly is it implemented.
Previously we looked at a example that runs on Arduino written by ladder diagram PLC programming langrage, and it brings several questions:
Fortunately, there's another example in OpenPLC editor that are composed of several units written in different IEC 61131-3 languages, and it would be easier to examine how the building blocks are composed and how they are interacting with each other in this example that contains Program Organization Units ( POUs ) in different languages.
OpenPLC editor uses a fair amount of legacy code of another open source PLC development tool called Beremiz ( which is also an IDE for PLC programs in IEC 61131-3 languages, and it also uses the PLC open editor and MatIEC compiler to translate the PLC programs into ANSI C code ), there's a more detailed compilation process of the PLC program from Beremiz's documentation.
beremiz.xml and plc.xml)These are the initial 2 files used by the PLC open editor. The editor would save and load the PLC programs using XML markup files ( in this case plc.xml ) according to PLCopen TC6-XML Schemes [2].
PLCOpen Editor and XML
"PLCOpen Editor and XML, source: https://beremiz.org/doc"
The PLCOpen XML project files may not exactly correspond to the DOM (Document Object Model) in web development terms, the XML files store a textual representation of the graphical PLC programs( such as the coordinate system and positions of the graphical components ), and they serve as an intermediate format that OpenPLC can interpret and manipulate.

PLCOpenDatamodel to IEC61131-3 languages
The PLC open editor can parse the XML file back to the graphical representations, meanwhile it also has built-in export filter that convert graphical languages to their equivalent textual form.
However, the parsing process itself doesn't guarantee the correctness of the PLC program. The schema is the constrain which ensures the XML-documents are compiled in a certain well-define manner.
XSLT is used to transform XML documents into other formats or different XML structures. For example, if there is an XML file in a format specific to a vendor, an XSLT stylesheet can be applied to transform that XML into the PLCopen standard XML format used by OpenPLC. This process ensures compatibility between different PLC programming environments.

Compilation from IEC61131-3 to C program
The MatIEC works this way:
A Program Organization Unit (POU) is a fundamental concept in IEC 61131-3, one important goal of the IEC 61131-3 standard is to introduce and restrict the terminologies which referred to the building blocks of a PLC programs. POUs are the basic building blocks of a PLC program, allowing modularity, reusability, and better organization of the control logic. [4]
In the IEC-61131-3 standard, there are three types of POUs: Programs (PRG), Function Blocks (FB) and Functions (FUN).
Functions are reusable logic components that return a single value and do not retain internal state between execution cycles.They are used for small, stateless operations, such as mathematical calculations (e.g., addition, subtraction) or boolean logic (e.g., AND, OR). Functions don't retain any internal state, which also means that they are guaranteed to generate identical output by providing same input.
A function block is a reusable POU that has internal state memory, meaning it can store values between cycles.
Function Blocks are similar to classes in object-oriented programming. They encapsulate specific control behaviors (like a timer, counter, or PID controller) and can be instantiated multiple times within a program or across different programs.
Unlike simple functions, function blocks have internal memory, meaning that they can retain values (such as timer values or counters) between execution cycles. This allows them to handle operations that need to maintain state, such as accumulating values over time.
In the following Multi_Language example in OpenPLC editor, all the function blocks named Counter%%% have their local (internal) variable called Cnt, which retains the state of the execution in each loop.
The main unit where the control logic resides. A program is what the PLC executes cyclically (or triggered based on conditions) during its operation. The program is the top-level POU and is often where the primary control logic of the system is implemented. A program can call other POUs like Functions (FUN) and Function Blocks (FB).
| POU Type | Retains State | Reusability | Example Use Case |
|---|---|---|---|
| Program (PRG) | No | Main logic | Overall system logic or control sequence |
| Function Block (FB) | Yes | Reusable | Timer, Counter, PID controller |
| Function (FUN) | No | Reusable | Math operations, Logical comparisons |
Programs (PRG) call Function Blocks (FB) and Functions (FUN) to implement control logic. A Function Block (FB) can be used to manage operations that need to store internal states, like a timer or a motor control routine. Functions (FUN) perform stateless operations like mathematical calculations or logical comparisons.
Multi_Language exampleHere is the structure of the this Multi_Language example project:
Multi_Language project structure
There's several building blocks here:
AverageVal : this is a Function (FUN) written in structural text;Function Blocks : this is a collection of several Function Blocks (FB) written in different languages;plc_prg : this is the Program (PRG) that integrates the above building blocks to perform the control logic. In terms of the hierarchy, this could also be considered as the outer-most layer of this PLC project, and its input and output can be accessed by external entities. This plc_prg is written in Function Block Diagram language;Res0 : this unit contains the configuration of the whole project, for example the iteration of each iteration of the execution loop.
the program
The input of this application is the Reset bool signal, from this example the reset is the input to all the Counter%%% function block. What the Counter%%% does is to generate an incrementing integer value at each cycle of the PLC program, the only difference is that from the suffix the the name.
Take a look at CounterST, CounterIL and CounterLD for example, they all implement the same function, which is defining the Reset as an input and generating an incrementing integer as the output, however depending on the difference between the chosen languages, there might be some slight difference in the generated C code in POUS.c. These differences might seem subtle at this stage, however suppose we attempt to generate FMUs using these different implementations, the differences between them could potentially also lead to different details of the FMU structure or different behaviors within the FMUs.



POUS.cThe generated code for instruction list and structural text are rather simple, the XML markup for these 2 examples are represented as formatted text based on XHTML[3].
CounterST:
Corresponding node in plc.xml:
The generated C code:
CounterIL:
Corresponding node in plc.xml:
And CouterLD, the generated C code of the ladder diagram representation has more inline functions in the initialization part, they are corresponding to the blocks in the ladder diagram, which works as smaller function units within this module:
Beremiz has some descriptions about the compiling process. (https://beremiz.org/doc)<pou name="CounterST" pouType="functionBlock">
<interface>
<inputVars>
<variable name="Reset">
<type>
<BOOL/>
</type>
</variable>
</inputVars>
<localVars>
<variable name="Cnt">
<type>
<INT/>
</type>
</variable>
</localVars>
<outputVars>
<variable name="OUT">
<type>
<INT/>
</type>
</variable>
</outputVars>
<externalVars constant="true">
<variable name="ResetCounterValue">
<type>
<INT/>
</type>
</variable>
</externalVars>
</interface>
<body>
<ST>
<xhtml:p><![CDATA[IF Reset THEN
Cnt := ResetCounterValue;
ELSE
Cnt := Cnt + 1;
END_IF;
Out := Cnt;]]></xhtml:p>
</ST>
</body>
</pou>
void COUNTERST_init__(COUNTERST *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CNT,0,retain)
__INIT_VAR(data__->OUT,0,retain)
__INIT_EXTERNAL(INT,RESETCOUNTERVALUE,data__->RESETCOUNTERVALUE,retain)
}
// Code part
void COUNTERST_body__(COUNTERST *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if (__GET_VAR(data__->RESET,)) {
__SET_VAR(data__->,CNT,,__GET_EXTERNAL(data__->RESETCOUNTERVALUE,));
} else {
__SET_VAR(data__->,CNT,,(__GET_VAR(data__->CNT,) + 1));
};
__SET_VAR(data__->,OUT,,__GET_VAR(data__->CNT,));
goto __end;
__end:
return;
} // COUNTERST_body__()
<pou name="CounterIL" pouType="functionBlock">
<interface>
<localVars>
<variable name="Cnt">
<type>
<INT/>
</type>
</variable>
</localVars>
<inputVars>
<variable name="Reset">
<type>
<BOOL/>
</type>
</variable>
</inputVars>
<outputVars>
<variable name="OUT">
<type>
<INT/>
</type>
</variable>
</outputVars>
<externalVars constant="true">
<variable name="ResetCounterValue">
<type>
<INT/>
</type>
</variable>
</externalVars>
</interface>
<body>
<IL>
<xhtml:p><![CDATA[LD Reset
JMPC ResetCnt
(* increment counter *)
LD Cnt
ADD 1
JMP QuitFb
ResetCnt:
(* reset counter *)
LD ResetCounterValue
QuitFb:
(* save results *)
ST Cnt
ST Out
]]></xhtml:p>
</IL>
</body>
</pou>
void COUNTERIL_init__(COUNTERIL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->CNT,0,retain)
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->OUT,0,retain)
__INIT_EXTERNAL(INT,RESETCOUNTERVALUE,data__->RESETCOUNTERVALUE,retain)
}
// Code part
void COUNTERIL_body__(COUNTERIL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
__IL_DEFVAR.BOOLvar = __GET_VAR(data__->RESET,);
if (__IL_DEFVAR.BOOLvar) goto RESETCNT;
__IL_DEFVAR.INTvar = __GET_VAR(data__->CNT,);
__IL_DEFVAR.INTvar += 1;
goto QUITFB;
RESETCNT:
;
__IL_DEFVAR.INTvar = __GET_EXTERNAL(data__->RESETCOUNTERVALUE,);
QUITFB:
;
__SET_VAR(data__->,CNT,,__IL_DEFVAR.INTvar);
__SET_VAR(data__->,OUT,,__IL_DEFVAR.INTvar);
goto __end;
__end:
return;
} // COUNTERIL_body__()
static inline INT __COUNTERLD_ADD__INT__INT1(BOOL EN,
UINT __PARAM_COUNT,
INT IN1,
INT IN2,
COUNTERLD *data__)
{
INT __res;
BOOL __TMP_ENO = __GET_VAR(data__->_TMP_ADD11_ENO,);
__res = ADD__INT__INT(EN,
&__TMP_ENO,
__PARAM_COUNT,
IN1,
IN2);
__SET_VAR(,data__->_TMP_ADD11_ENO,,__TMP_ENO);
return __res;
}
static inline INT __COUNTERLD_MOVE__INT__INT2(BOOL EN,
INT IN,
COUNTERLD *data__)
{
INT __res;
BOOL __TMP_ENO = __GET_VAR(data__->_TMP_MOVE15_ENO,);
__res = MOVE__INT__INT(EN,
&__TMP_ENO,
IN);
__SET_VAR(,data__->_TMP_MOVE15_ENO,,__TMP_ENO);
return __res;
}
static inline INT __COUNTERLD_MOVE__INT__INT3(BOOL EN,
INT IN,
COUNTERLD *data__)
{
INT __res;
BOOL __TMP_ENO = __GET_VAR(data__->_TMP_MOVE19_ENO,);
__res = MOVE__INT__INT(EN,
&__TMP_ENO,
IN);
__SET_VAR(,data__->_TMP_MOVE19_ENO,,__TMP_ENO);
return __res;
}
void COUNTERLD_init__(COUNTERLD *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->OUT,0,retain)
__INIT_VAR(data__->CNT,0,retain)
__INIT_EXTERNAL(INT,RESETCOUNTERVALUE,data__->RESETCOUNTERVALUE,retain)
__INIT_VAR(data__->_TMP_ADD11_ENO,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->_TMP_ADD11_OUT,0,retain)
__INIT_VAR(data__->_TMP_MOVE15_ENO,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->_TMP_MOVE15_OUT,0,retain)
__INIT_VAR(data__->_TMP_MOVE19_ENO,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->_TMP_MOVE19_OUT,0,retain)
}
// Code part
void COUNTERLD_body__(COUNTERLD *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__SET_VAR(data__->,_TMP_ADD11_OUT,,__COUNTERLD_ADD__INT__INT1(
(BOOL)__BOOL_LITERAL(TRUE),
(UINT)2,
(INT)__GET_VAR(data__->CNT,),
(INT)1,
data__));
if (__GET_VAR(data__->_TMP_ADD11_ENO,)) {
__SET_VAR(data__->,CNT,,__GET_VAR(data__->_TMP_ADD11_OUT,));
};
__SET_VAR(data__->,_TMP_MOVE15_OUT,,__COUNTERLD_MOVE__INT__INT2(
(BOOL)__GET_VAR(data__->RESET,),
(INT)__GET_EXTERNAL(data__->RESETCOUNTERVALUE,),
data__));
if (__GET_VAR(data__->_TMP_MOVE15_ENO,)) {
__SET_VAR(data__->,CNT,,__GET_VAR(data__->_TMP_MOVE15_OUT,));
};
__SET_VAR(data__->,_TMP_MOVE19_OUT,,__COUNTERLD_MOVE__INT__INT3(
(BOOL)__BOOL_LITERAL(TRUE),
(INT)__GET_VAR(data__->CNT,),
data__));
if (__GET_VAR(data__->_TMP_MOVE19_ENO,)) {
__SET_VAR(data__->,OUT,,__GET_VAR(data__->_TMP_MOVE19_OUT,));
};
goto __end;
__end:
return;
} // COUNTERLD_body__()