When writing PLC programs in TwinCAT 3, many developers default to using IF
statements for branching logic. However, TwinCAT 3 offers a variety of other branching techniques that can improve readability, efficiency, and control flow. In this post, we will explore different ways to structure your program beyond simple IF
conditions. Note that we will not cover AND_THEN
and OR_ELSE
here—check out the part on Full Evaluation in the blog article Understanding Expression Evaluation in TwinCAT for more details on those operators.
S= Assignment
If the operand of the set assignment switches to TRUE
, the assignment causes the variable to the left of the operator to be assigned a TRUE
. The variable is set.
Syntax
<variable> S=
[<variable2> S=
] [<variableN> S=
] <expression>;
The <variable> must be one of the boolean types BIT
or BOOL
. The <expression> must return a BOOL
.
The parts in square brackets are optional.
Beispiel
All assignments refer to the operand at the end of the code line.
R= Assignment
If the operand of the reset assignment switches to TRUE
, the assignment causes the variable to the left of the operator to be assigned a FALSE
. The variable is unset.
Syntax
<variable> R=
[<variable2> R=
] [<variableN> R=
] <expression>;
The <variable> must be one of the boolean types BIT
or BOOL
. The <expression> must return a BOOL
.
Beispiel
All assignments refer to the
S= and R= Assignment Combined
Beispiel
All assignments refer to the
IF Statement
The IF
statement is used to test a condition and to execute the subsequent instructions if the condition is met. A condition is encoded as a expression that returns a boolean value. If the expression returns TRUE
, the condition is met and the associated statements after THEN
are executed. If the expression returns FALSE
, the following optional conditions marked ELSIF
are evaluated. If an ELSIF
condition returns TRUE
, the instructions after the associated THEN
are executed. If all conditions are FALSE
, the statements after the optional ELSE
are executed.
So at most one branch of the IF
statement is executed. The ELSIF
branches and the ELSE
branch are optional. The multiplicity of ELSIF
is 0..n and the multiplicity of ELSE
is 0..1. The scope of the IF
is closed by an END_IF
.
Syntax
IF <condition> THEN
<statements>
[ELSIF <condition> THEN
<statements>]
[ELSE
<statements>]
END_IF
The parts in square brackets are optional.
Beispiel
CASE Statement
The CASE
instruction is used to group multiple conditional instructions with the same conditional variable in a construct. The conditional variable must be an integer type: BYTE
, SINT
, USINT
, WORD
, INT
, UINT
, DWORD
, DINT
, UDINT
, LWORD
, LINT
, ULINT
, __XWORD
, __XINT
, __UXINT
, or PVOID
. It works with PVOID
but not with a non-dereferenced POITER TO
.
The conditions are values. These values must be constant integer values in the range from -2^63 to 2^64-1 that can be literals, enumerations, as CONSTANT
declared variables or as constant defined types. Each value has the multiplicity 0..1. It is possible to use ranges as value, e.g. 2 .. 4:
, or more than one value per case, e.g. 1, 2, 3, 5, 8, 13:
. Each value can exist only once, no matter if it is hidden in a range.
The value expression is finished with a colon. It is required that every value case has at least one statement. Here, a semicolon counts as a statement, e.g. 1 .. 3:;
. A value has to be compatible with the data type of the conditional variable, e.g. if a (8-Bit singed integer) is used and there is a literal then the literal will be interpreted as -1. Be careful if you change the type of conditional variable. For the default case is the keyword ELSE
, the instruction(s) in the default case will be executed if no other value matches, it does not matter where the ELSE
keyword is. ELSE
has a multiplicity of 0..1, too. Finally, it is possible to create an empty CASE
statement because every value is optional.
Syntax
CASE <varibale> OF
[
[<value1>:
[<instructionA>];
[<instructionN>;]
]
[<value2>:
[<instructionB>];
[<instructionN>;]
]
[<value3, value4, value5>:
[<instructionC>];
[<instructionN>;]
]
[<value6 .. value10>:
[<instructionD>];
[<instructionN>;]
]
[<valueN>:
[<instructionE>];
[<instructionN>;]
]
[ELSE
[<instructionF>];
[<instructionN>;]
]
]
END_CASE
The parts in square brackets are optional.
Processing scheme of a CASE instruction:
If the variable <variable> has the value <value1>, <instructionA> to <instructionN> is executed.
If you want to execute the same instruction for a value range of the <variable>, write the <startValue> and the <endValue> separated by
..
.If you want to execute the same instruction for multiple values (but no range) of the variable, write these values separated by commas.
If the variable <variable> has none of the specified values, the else <instructionF> is executed.
Beispiel
JMP Statement
The JMP
instruction is used to perform an (un)conditional jump to a line, which is marked by a label. It does not matter where the label is: above or below the JMP
instruction, but it must be in the same scope (Editor Window e.g. METHOD
, PROGRAM
, FUNCTION
, etc.) as the JMP
instruction. The label is a freely selectable, unique identifier, which you place at the beginning of a line.
Identifier rules:
It must be no keyword.
It must start with an underscore or a letter a to z or a letter A to Z.
It can contain the digits 0 to 9, but it cannot start with a digit.
It should not use double or more underscores in row, because double underscores are for internal use by Beckhoff. It will compile, but it is a bad practice.
As RegEx, it would look like this /(?<=\\\\s)[a-zA-Z]([a-zA-Z0-9]|(?<![])[_]?)/
.
The JMP
statement is per default unconditional, but it is possible to add an expression in round braces ()
between JMP
and label. The condition must return a boolean value. If the condition is TRUE
, then the jump to the label is performed.
Syntax
JMP[(<condition>)] <label>;
<label>: [<instructions>]
The parts in square brackets are optional.
Beispiel
RETURN Statement
The RETURN
instruction is used to exit a scope (Editor Window, e.g. METHOD
, PROGRAM
, FUNCTION
, etc.). You can make this dependent on a condition. The RETURN
statement is per default unconditional, but it is possible to add an expression in round braces ()
after the RETRUN
keyword. The condition must return a boolean value. If the condition is TRUE
, then the RETURN
performs a jump to the end of the current scope.
Syntax
RETURN[(<condition>)];
The parts in square brackets are optional.
Beispiel
SEL Operator
The SEL
operator functions similarly to a ternary conditional operator. It evaluates a boolean expression and returns the result of one of two expressions, depending on whether the boolean expression evaluates to TRUE
or FALSE
. Although it looks like a function, it behaves differently. In TwinCAT, if the boolean expression evaluates to TRUE
, the expression preceding in0 is not computed, and if it evaluates to FALSE
, the expression preceding in1
is not computed. The precedence is still like a function call. However, this behavior is specific to ST/ExST; in graphical languages like ladder logic, both in0
and in1
are computed regardless of the boolean result.
Ensure that all three positions use expressions of the same type, especially when dealing with user-defined data types. The compiler checks for type consistency and will issue compilation errors if the types do not match. Specifically, assigning instances of a function block to interface variables is not supported. Additionally, REF=
assignments are not allowed, and only S=
, R=
, and :=
assignments are supported.
Syntax
<target> [<Assignment-Operator>] SEL(<Boolean-Expression>,<in0>, <in1>);
The parts in square brackets are optional.
Beispiel
MUX Operator
The MUX
operator works similarly to a CASE
statement. It evaluates an integer expression and selects the nth statement based on the integer value from in0
to inN
. While it appears to function like a typical operation, it behaves differently by computing only the selected statement. If no value matches, it defaults to selecting the last statement.
Ensure that all positions (from in0
, to inN
and the target) use expressions of the same type, particularly when dealing with user-defined data types. The compiler enforces type consistency and will issue compilation errors if types do not match. Notably, assigning instances of a function block to interface variables is not supported. Furthermore, REF=
assignments are not allowed; only S=
, R=
, and :=
assignments are supported.
Permitted data types for the integer expression include:
ANY_BIT
:BYTE
,WORD
,DWORD
,LWORD
, and__XWORD
ANY_INT
:SINT
,USINT
,INT
,UINT
,DINT
,LINT
,ULINT
,UDINT
,__XINT
, and__UXINT
Syntax
<target> [<Assignment-Operator>] MUX(<integer-Expression>,<in0>, <in1>[, … <inN>]);
Mux
requires at least 3 parameters:
Integer expression as selector
in0
as first selectablein1
to have a default case