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

Arrays, Structs and Enumerations

A TYPE block declares a type of your own. Everything in this chapter lives in such a block, or directly in the declaration of a variable.

TYPE Point:
    STRUCT
        x, y: DINT;
    END_STRUCT
END_TYPE

Arrays

An array holds a fixed number of elements of one type. You write the range of the index, not the count:

VAR
    samples: ARRAY[1..3] OF DINT := [10, 20, 30];
    grid: ARRAY[0..1, 0..2] OF DINT := [1, 2, 3, 4, 5, 6];
END_VAR

samples[2] := 25;
grid[1, 0] := 7;

The range can start anywhere, so ARRAY[1..3] and ARRAY[0..2] both hold three elements. A comma adds a dimension. The initial values of such an array stay in one flat list, in which the last index changes fastest.

The bounds must be known while the program is compiled, so they are literals or constants:

VAR CONSTANT
    COUNT: INT := 16;
END_VAR

VAR
    buffer: ARRAY[0..COUNT - 1] OF BYTE;
END_VAR

An index that is a constant outside the range is rejected. An index that is computed while the program runs is not checked, by the compiler or at run time. The compiler emits the address calculation with no test, so a wrong index reads or writes memory outside the array. Check the index yourself where the value comes from outside.

A function can take an array of any size. That form, ARRAY[*], is explained with the other parameter rules.

Structs

An array holds many values of one type. A struct groups a few values of different types, and every member has a name. The members are read and written through a dot:

TYPE Motor:
    STRUCT
        speed: INT;
        running: BOOL;
        name: STRING[20];
    END_STRUCT
END_TYPE

PROGRAM Plant
    VAR
        pump: Motor := (speed := 100, running := FALSE, name := 'pump');
    END_VAR

    pump.speed := 120;
END_PROGRAM

Members lie in memory in the order of their declaration. A struct can hold another struct, an array, or an instance of a function block, and the dot chains: plant.pump.speed.

Assigning one struct to another copies every member.

Enumerations

An array and a struct collect values. An enumeration instead lists the values that one variable may take. Each name stands for a number, counting from zero, and a name can set its own value, after which counting continues from there:

TYPE State: (Idle, Running := 5, Stopped);   (* 0, 5, 6 *)
END_TYPE

PROGRAM Machine
    VAR
        current: State := Idle;
    END_VAR

    IF current = Running THEN
        (* ... *)
    END_IF
END_PROGRAM

The names are visible without the type in front of them. A name that two enumerations declare resolves to the enumeration that was declared first, and the compiler does not warn you. Give every variant a name of its own, and write the type in front of a name where you want to be explicit:

current := State#Stopped;

An enumeration is an integer underneath, so it fits everywhere an integer fits, and CASE works on it.

Subranges

An enumeration limits a variable to a list of names. A subrange limits an integer to a range of numbers:

TYPE Percent: INT (0..100);
END_TYPE

The compiler does not enforce the range by itself. It enforces it when the project provides a check function, and then every assignment to such a variable goes through that function, which decides what happens:

FUNCTION CheckRangeSigned: DINT
    VAR_INPUT
        value: DINT;
        lower: DINT;
        upper: DINT;
    END_VAR

    IF value < lower THEN
        CheckRangeSigned := lower;
    ELSIF value > upper THEN
        CheckRangeSigned := upper;
    ELSE
        CheckRangeSigned := value;
    END_IF
END_FUNCTION

With that function in the project, an assignment of 200 to a Percent stores 100. CheckRangeUnsigned does the same for the unsigned types. Without such a function, a subrange behaves like the type it is based on.

Aliases

A subrange adds a range to an existing type. A type declaration that adds nothing makes an alias, and the alias can carry an initial value:

TYPE Signal: INT := -1;
END_TYPE

PROGRAM Reader
    VAR
        reading: Signal;   (* an INT that starts at -1 *)
    END_VAR
END_PROGRAM

Use an alias to give a meaning to a plain type, for example TYPE Celsius: INT; END_TYPE.

What’s next

You now have the types. The next chapter combines their values into expressions.