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

Functions

A function computes a result from its arguments and forgets everything else. Two calls with the same arguments give the same answer, as long as the function does not read a global variable.

FUNCTION Scale: DINT
    VAR_INPUT
        value: DINT;
        factor: DINT;
    END_VAR

    Scale := value * factor;
END_FUNCTION

The type after the colon is the result type. Inside the body, the name of the function is the result: you assign to it, and the last value you assigned is what the caller gets. A function without a result type returns nothing, and a call of it is a statement.

Nothing survives a call

Every local variable of a function is temporary, also in a VAR block:

FUNCTION Counter: DINT
    VAR
        n: DINT;
    END_VAR

    n := n + 1;
    Counter := n;
END_FUNCTION

Both calls of Counter() return 1. When you need the value of the previous call, use a function block.

A function may call itself. The compiler sets no limit on the depth, but the stack of the target does.

Parameters

Four blocks describe what goes in and what comes out:

FUNCTION Measure: DINT
    VAR_INPUT
        raw: INT;            (* a copy for the function *)
    END_VAR
    VAR_INPUT {ref}
        label: STRING;       (* the caller's text, not a copy *)
    END_VAR
    VAR_IN_OUT
        total: DINT;         (* read and written in the caller *)
    END_VAR
    VAR_OUTPUT
        valid: BOOL;         (* a second result *)
    END_VAR

    (* ... *)
END_FUNCTION

VAR_INPUT gives the function a copy, so a change inside the function stays inside it. This holds for large values as well: a string, an array, or a struct in VAR_INPUT is copied, and the caller does not see a write to it.

VAR_INPUT {ref} marks the whole block as “do not copy”. The function then works on the value of the caller, which is what a foreign function usually expects, and what you want for a large value that the function only reads.

VAR_IN_OUT is for data that the function reads and writes. It is always the caller’s variable.

VAR_OUTPUT carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. An output that is a variable length array, a REFERENCE TO, or an alias (AT) is the exception: it keeps what the caller passed. The function does this through the caller’s variable when it starts, so a variable that the body can reach in another way is already reset when the body reads it. That is the case when the caller passes one variable as an output and also as a VAR_IN_OUT or VAR_INPUT {ref} of the same call, and when the output target is a global variable, or an instance member in a method, that the body reads. bump(o => g) with the body o := g + 1 therefore yields the initial value of o plus one, whatever g held before the call. Pass such a value in through an input instead.

Calling

A call supplies every parameter, including the outputs. Either by position, in the order of the declaration, or by name:

r := Measure(raw, 'sensor', total, valid);
r := Measure(raw := raw, label := 'sensor', total := total, valid => valid);

:= gives a value to an input or to an in-out, => takes a value out of an output.

A call may also mix the two forms. Each named argument claims the parameter its name says, and the positional arguments fill the parameters left over, in the order you write them. plc explain E132 describes this, and plc explain E131 describes the one mix the compiler rejects: a positional argument whose parameter a later named argument also names.

Give every argument a name, or give none, when the POU has a parameter that takes a variable and not a value: a VAR_IN_OUT, a VAR_OUTPUT of a function, a VAR_INPUT {ref}, or a REFERENCE TO. The compiler tests such a parameter by the place of the argument in the call, not by the rule above. In a mixed call it can therefore report an argument that is correct:

error[E031]: Expected a reference for parameter total because their type is InOut

and it can accept a value where the parameter needs a variable, which then binds a temporary that the caller never sees.

An output argument may also be left empty, as in scale(5, overflow => ). The call then binds a temporary that starts at the parameter’s initial value, or zero, and the function writes there instead of into a variable of the caller. A VAR_IN_OUT or REFERENCE TO argument cannot be empty.

A missing argument is an error, in both forms:

error[E032]: this POU takes 4 arguments but 2 arguments were supplied

An input with a default value (factor: DINT := 2;) is the exception: you can leave it out and get the default. This applies to the inputs at the end of the list only. When a parameter without a default comes after it, you must supply both. A call of a function block instance may leave out any parameter, because the instance keeps what the previous call gave it.

The functions that the compiler brings itself, such as SEL, SUB, and SHL, take their arguments the same way. Built-in Functions lists their parameter names, and says which of them take a variadic parameter that has no name to call it by.

Arrays of any size

A function that works on an array of any length declares the parameter with * instead of a range, one * per dimension. LOWER_BOUND and UPPER_BOUND then give the bounds of the array that the caller passed:

FUNCTION Sum: DINT
    VAR_INPUT
        values: ARRAY[*] OF DINT;
    END_VAR
    VAR
        i: DINT;
    END_VAR

    FOR i := LOWER_BOUND(values, 1) TO UPPER_BOUND(values, 1) DO
        Sum := Sum + values[i];
    END_FOR
END_FUNCTION

The second argument of the two functions is the number of the dimension. They accept a parameter of this kind only, not an array of a fixed size, and they are built in. Such a parameter is always passed by reference, also in a VAR_INPUT block, and the compiler says so:

warning[E047]: Variable Length Arrays are always by-ref, even when declared in a by-value block

Results that are not a number

A result type can be a string, an array, or a struct:

FUNCTION Describe: STRING[20]
    VAR_INPUT
        code: DINT;
    END_VAR

    Describe := 'code ';
END_FUNCTION

The caller provides the memory for such a result. The compiler makes the result a hidden first parameter, a pointer to a variable of the caller, and the function writes the result through it.

What’s next

Functions forget. The next chapter is about the POUs that remember: function blocks and programs.