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

User-Defined Types

IEC 61131-3 allows you to define custom types using TYPE ... END_TYPE blocks. This includes structures, enumerations, arrays, subrange types, and type aliases. User-defined types improve readability, enforce constraints, and enable structured data modeling.

TYPE Block Syntax

All user-defined types are declared inside TYPE ... END_TYPE:

TYPE
  MyType : <type definition>;
END_TYPE

Multiple types can be declared in a single block.

Structures (STRUCT)

Structures group related variables into a single composite type:

TYPE
  MotorData : STRUCT
    speed    : REAL;
    current  : REAL;
    running  : BOOL;
    fault    : BOOL;
    op_hours : LREAL;
  END_STRUCT;
END_TYPE

Using structures

PROGRAM Main
  VAR
    pump_motor : MotorData;
    fan_motor  : MotorData;
  END_VAR

  pump_motor.speed := 1450.0;
  pump_motor.running := TRUE;

  IF pump_motor.fault THEN
    pump_motor.speed := 0.0;
    pump_motor.running := FALSE;
  END_IF;
END_PROGRAM

Nested structures

Structures can contain other structures:

TYPE
  Coordinate : STRUCT
    x : REAL;
    y : REAL;
    z : REAL;
  END_STRUCT;

  RobotArm : STRUCT
    position : Coordinate;
    target   : Coordinate;
    speed    : REAL;
    gripper  : BOOL;
  END_STRUCT;
END_TYPE

PROGRAM Main
  VAR
    arm : RobotArm;
  END_VAR

  arm.position.x := 100.0;
  arm.target.x := 200.0;
  arm.gripper := TRUE;
END_PROGRAM

Initialization

Structure variables can be initialized with named field values:

VAR
  origin : Coordinate := (x := 0.0, y := 0.0, z := 0.0);
  arm    : RobotArm := (speed := 50.0, gripper := FALSE);
END_VAR

Fields not explicitly initialized default to their zero value.

Enumerations

Enumerations define a type with a fixed set of named values:

TYPE
  MachineState : (IDLE, STARTING, RUNNING, STOPPING, FAULTED);
END_TYPE

Using enumerations

PROGRAM Main
  VAR
    state : MachineState := MachineState#IDLE;
  END_VAR

  CASE state OF
    MachineState#IDLE:
      IF start_button THEN
        state := MachineState#STARTING;
      END_IF;

    MachineState#STARTING:
      state := MachineState#RUNNING;

    MachineState#RUNNING:
      IF stop_button THEN
        state := MachineState#STOPPING;
      END_IF;

    MachineState#FAULTED:
      IF reset_button THEN
        state := MachineState#IDLE;
      END_IF;
  END_CASE;
END_PROGRAM

The qualified syntax MachineState#IDLE avoids ambiguity when multiple enumerations share a value name.

Enumerations with explicit values

You can assign integer values to enumeration members:

TYPE
  AlarmPriority : (
    NONE     := 0,
    LOW      := 1,
    MEDIUM   := 2,
    HIGH     := 3,
    CRITICAL := 4
  );
END_TYPE

This is useful when the enumeration must map to specific protocol codes, register values, or database entries.

TYPE
  CommStatus : (
    OK            := 0,
    TIMEOUT       := 16#01,
    CRC_ERROR     := 16#02,
    FRAME_ERROR   := 16#04,
    DISCONNECTED  := 16#FF
  );
END_TYPE

Arrays

Arrays are declared with index ranges using ARRAY[low..high] OF type:

TYPE
  TenReals : ARRAY[1..10] OF REAL;
  SensorArray : ARRAY[0..7] OF INT;
END_TYPE

Inline array declarations

Arrays can also be declared directly in variable sections without a separate TYPE:

VAR
  temperatures : ARRAY[1..8] OF REAL;
  error_log    : ARRAY[0..99] OF INT;
END_VAR

Multi-dimensional arrays

TYPE
  Matrix3x3 : ARRAY[1..3, 1..3] OF REAL;
END_TYPE

PROGRAM Main
  VAR
    transform : Matrix3x3;
  END_VAR

  transform[1, 1] := 1.0;
  transform[2, 2] := 1.0;
  transform[3, 3] := 1.0;
  // identity matrix diagonal
END_PROGRAM

Array initialization

VAR
  weights : ARRAY[1..5] OF REAL := [1.0, 2.0, 3.0, 4.0, 5.0];
  flags   : ARRAY[0..3] OF BOOL := [TRUE, FALSE, FALSE, TRUE];
END_VAR

Arrays of structures

TYPE
  SensorReading : STRUCT
    channel : INT;
    value   : REAL;
    valid   : BOOL;
  END_STRUCT;
END_TYPE

VAR
  readings : ARRAY[1..16] OF SensorReading;
END_VAR
FOR i := 1 TO 16 DO
  IF readings[i].valid THEN
    total := total + readings[i].value;
    count := count + 1;
  END_IF;
END_FOR;

Subrange Types

A subrange type restricts an integer type to a specific range of values:

TYPE
  Percentage : INT(0..100);
  DayOfWeek  : USINT(1..7);
  MotorRPM   : UINT(0..3600);
END_TYPE

Assigning a value outside the declared range is a runtime error:

VAR
  level : Percentage;
END_VAR

level := 50;   // OK
level := 150;  // runtime error: out of range

Subrange types are useful for self-documenting code and catching logic errors early.

Type Aliases

A type alias gives a new name to an existing type:

TYPE
  Temperature : REAL;
  Pressure    : REAL;
  FlowRate    : REAL;
  ErrorCode   : DINT;
END_TYPE

Aliases improve readability and allow you to change the underlying type in one place:

VAR
  tank_temp  : Temperature := 25.0;
  tank_press : Pressure := 101.3;
  flow       : FlowRate;
  error      : ErrorCode;
END_VAR

Note that aliases are structurally typed – a Temperature and a Pressure are both REAL and can be used interchangeably. They do not create distinct types for type-checking purposes.

Complete Example: Recipe System

TYPE
  IngredientUnit : (GRAMS, MILLILITERS, UNITS);

  Ingredient : STRUCT
    name     : STRING[50];
    amount   : REAL;
    unit     : IngredientUnit;
    added    : BOOL;
  END_STRUCT;

  Recipe : STRUCT
    name        : STRING[100];
    ingredients : ARRAY[1..10] OF Ingredient;
    step_count  : INT;
    mix_time    : TIME;
    temperature : REAL;
  END_STRUCT;

  BatchState : (
    WAITING  := 0,
    LOADING  := 1,
    MIXING   := 2,
    HEATING  := 3,
    COMPLETE := 4,
    ERROR    := 99
  );
END_TYPE

PROGRAM BatchController
  VAR
    recipe : Recipe;
    state  : BatchState := BatchState#WAITING;
    step   : INT := 1;
  END_VAR

  CASE state OF
    BatchState#WAITING:
      IF start_cmd THEN
        state := BatchState#LOADING;
        step := 1;
      END_IF;

    BatchState#LOADING:
      IF step <= recipe.step_count THEN
        IF recipe.ingredients[step].added THEN
          step := step + 1;
        END_IF;
      ELSE
        state := BatchState#MIXING;
      END_IF;

    BatchState#MIXING:
      mixer := TRUE;
      // transition after mix_time elapsed

    BatchState#COMPLETE:
      mixer := FALSE;
      heater := FALSE;
  END_CASE;
END_PROGRAM

Naming Caveat

When naming types or variables, be aware that all IEC 61131-3 keywords are case-insensitive. A type or variable named dt, Dt, or DT conflicts with the DT (Date and Time) keyword. Similarly, tod, int, or real as variable names will conflict with their respective keywords. Always use descriptive names that avoid keyword collisions:

  • Use date_time instead of dt
  • Use time_of_day instead of tod
  • Use count instead of int