Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Statements

Statements are the executable instructions that make up a POU body. Structured Text provides assignment, conditional branching, looping, and flow control. Every statement ends with a semicolon.

Assignment

The assignment operator is :=. The left side must be a variable; the right side is any expression of a compatible type.

VAR
  speed    : REAL;
  counter  : INT;
  running  : BOOL;
END_VAR

speed   := 1500.0;
counter := counter + 1;
running := speed > 0.0;

IF / ELSIF / ELSE / END_IF

The IF statement executes blocks conditionally. ELSIF and ELSE branches are optional.

IF temperature > 100.0 THEN
  alarm := TRUE;
  heater := FALSE;
ELSIF temperature < 60.0 THEN
  heater := TRUE;
ELSE
  // in range, maintain
  heater := heater;
END_IF;

Multiple ELSIF branches are allowed:

IF level = 0 THEN
  state_name := 'IDLE';
ELSIF level = 1 THEN
  state_name := 'LOW';
ELSIF level = 2 THEN
  state_name := 'MEDIUM';
ELSIF level = 3 THEN
  state_name := 'HIGH';
ELSE
  state_name := 'UNKNOWN';
END_IF;

For many discrete values, prefer CASE (below).

CASE / OF / END_CASE

The CASE statement selects among branches based on an integer or enumeration value.

CASE machine_state OF
  0:
    // Idle
    motor := FALSE;
    valve := FALSE;

  1:
    // Starting
    motor := TRUE;
    valve := FALSE;

  2:
    // Running
    motor := TRUE;
    valve := TRUE;

  3:
    // Stopping
    motor := FALSE;
    valve := TRUE;

ELSE
  // Unknown state, emergency stop
  motor := FALSE;
  valve := FALSE;
  alarm := TRUE;
END_CASE;

Multiple values per branch

A single branch can match several values using commas or ranges:

CASE error_code OF
  0:
    status := 'OK';

  1, 2, 3:
    status := 'WARNING';

  10..19:
    status := 'SENSOR_FAULT';

  20..29:
    status := 'COMM_FAULT';

ELSE
  status := 'UNKNOWN';
END_CASE;

FOR / TO / BY / DO / END_FOR

The FOR loop iterates a counter from a start value to an end value with an optional step.

VAR
  i     : INT;
  total : INT := 0;
  data  : ARRAY[1..10] OF INT;
END_VAR

FOR i := 1 TO 10 DO
  total := total + data[i];
END_FOR;

BY clause

The BY keyword specifies the step. It defaults to 1 if omitted.

// Count down from 10 to 0
FOR i := 10 TO 0 BY -1 DO
  // process in reverse
END_FOR;
// Step by 2 (even indices only)
FOR i := 0 TO 100 BY 2 DO
  even_sum := even_sum + i;
END_FOR;

The loop variable should not be modified inside the loop body. Doing so leads to undefined behavior in standard IEC 61131-3.

WHILE / DO / END_WHILE

The WHILE loop repeats as long as its condition is TRUE. The condition is checked before each iteration, so the body may never execute.

VAR
  pressure : REAL;
END_VAR

WHILE pressure < 100.0 DO
  pressure := pressure + ReadPressureIncrement();
END_WHILE;

A practical example – draining a buffer:

VAR
  buffer_count : INT;
END_VAR

WHILE buffer_count > 0 DO
  ProcessNextItem();
  buffer_count := buffer_count - 1;
END_WHILE;

REPEAT / UNTIL / END_REPEAT

The REPEAT loop is like WHILE but checks its condition after the body, so the body always executes at least once.

VAR
  attempts : INT := 0;
  success  : BOOL := FALSE;
END_VAR

REPEAT
  attempts := attempts + 1;
  success := TryConnect();
UNTIL success OR (attempts >= 3)
END_REPEAT;

Note: the condition follows UNTIL without a THEN or DO. The loop exits when the condition becomes TRUE.

RETURN

RETURN exits the current POU immediately. In a function, it returns whatever value has been assigned to the function name so far. In a program or function block, it ends the current scan cycle execution for that POU.

FUNCTION SafeDivide : REAL
  VAR_INPUT
    numerator   : REAL;
    denominator : REAL;
  END_VAR

  IF denominator = 0.0 THEN
    SafeDivide := 0.0;
    RETURN;
  END_IF;

  SafeDivide := numerator / denominator;
END_FUNCTION

EXIT

EXIT breaks out of the innermost FOR, WHILE, or REPEAT loop.

VAR
  i     : INT;
  found : INT := -1;
  data  : ARRAY[1..100] OF INT;
END_VAR

FOR i := 1 TO 100 DO
  IF data[i] = 42 THEN
    found := i;
    EXIT;
  END_IF;
END_FOR;
// If found <> -1, data[found] = 42

EXIT only affects the innermost loop. In nested loops, the outer loop continues:

VAR
  row, col : INT;
END_VAR

FOR row := 1 TO 10 DO
  FOR col := 1 TO 10 DO
    IF matrix[row, col] = 0 THEN
      EXIT;  // exits inner loop only
    END_IF;
  END_FOR;
  // continues with next row
END_FOR;

Empty Statement

A lone semicolon is a valid empty statement. It does nothing and can be useful as a placeholder:

IF condition THEN
  ;  // intentionally empty, to be implemented
END_IF;

Complete Example: Simple State Machine

PROGRAM BatchMixer
  VAR
    state     : INT := 0;
    timer     : INT := 0;
    fill_done : BOOL;
    mix_time  : INT := 100;
  END_VAR

  CASE state OF
    0: // IDLE
      IF start_cmd THEN
        state := 1;
        timer := 0;
      END_IF;

    1: // FILLING
      fill_valve := TRUE;
      IF fill_done THEN
        fill_valve := FALSE;
        state := 2;
        timer := 0;
      END_IF;

    2: // MIXING
      mixer_motor := TRUE;
      timer := timer + 1;
      IF timer >= mix_time THEN
        mixer_motor := FALSE;
        state := 3;
      END_IF;

    3: // DRAINING
      drain_valve := TRUE;
      IF level <= 0 THEN
        drain_valve := FALSE;
        state := 0;
      END_IF;

  ELSE
    // fault recovery
    state := 0;
  END_CASE;
END_PROGRAM
st-cli run batch_mixer.st