TwinCAT FB Outputs: Read After the Call, Not Before

Claire Rousseau5 min read
BeckhoffPLC HardwareTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

TwinCAT Structured Text does not “call” a function-block output. Declare an instance, execute that instance, then consume its VAR_OUTPUT values either through Instance.Output or an output mapping in the call. The call must execute before downstream logic reads the new result.

Function-block instance declaration

Before anything else, confirm that the called function block has an instance in the caller’s VAR section. A function-block declaration defines a type; the instance stores that block’s inputs, outputs, and internal state.

PROGRAM MAIN
VAR
    Numbers     : One;
    NumberTotal : INT;
END_VAR

Here, One is the function-block type and Numbers is its instance. Calls and output references use Numbers, not the type name.

Declaration element Purpose Example
Function-block type Defines the executable logic and interface One
Instance Holds one copy of the block’s data and state Numbers
Destination variable Receives or uses an output value NumberTotal

Do not move on until the declaration compiles and the instance appears as a structured variable with its declared members.

Input assignment and block execution

Call the instance cyclically and assign its inputs with :=. Named input assignment makes the connection independent of argument order and keeps larger calls readable.

Numbers(DigIn_1 := DigIn_1);

The call executes the body of One. Reading Numbers.Number1 without first executing Numbers(...) only reads the value currently stored in the instance. On the first scan that may be an initialization value; on later scans it may be a result retained from an earlier execution.

  1. Write the external signal or calculated value to the input argument.
  2. Execute the function-block instance.
  3. Inspect the block outputs online before adding downstream calculations.

The original block condition also needs correction. DigOut_1 is declared as an output but is tested before the shown logic assigns it. Test the input when the calculation is meant to follow the input state:

FUNCTION_BLOCK One
VAR_INPUT
    DigIn_1 : BOOL;
END_VAR
VAR_OUTPUT
    DigOut_1 : BOOL;
    Number1  : INT;
    Number2  : INT;
END_VAR

DigOut_1 := DigIn_1;

IF DigIn_1 THEN
    Number1 := 3;
    Number2 := 5;
END_IF

Confirm that forcing or changing DigIn_1 executes the intended branch and updates the watched outputs.

Output access through the instance

After the call, access a function-block output with dot notation: Instance.Output. The proposed expression is valid only when it follows the instance call in execution order.

Numbers(DigIn_1 := DigIn_1);
NumberTotal := Numbers.Number1 + Numbers.Number2;

With the shown block logic and a true input, Number1 becomes 3, Number2 becomes 5, and NumberTotal becomes 8. This access method is useful when several expressions consume the same output or when the caller needs the value later in its logic.

Execution order remains significant. This sequence reads the previous stored values before calculating the current ones:

NumberTotal := Numbers.Number1 + Numbers.Number2;
Numbers(DigIn_1 := DigIn_1);

Do not move on until the watch window shows the outputs changing at the call and NumberTotal changing immediately afterward in the same scan path.

Output mapping in the call

An output can also be connected to a caller variable in the instance call. Output association uses =>, while input assignment uses :=.

VAR
    Block1 : firstblock;
    In1    : INT;
    Out1   : INT;
END_VAR

Block1(
    firstin  := In1,
    firstout1 => Out1
);
Connection Syntax Data direction
Input assignment formal_input := expression Caller to block
Output mapping formal_output => variable Block to caller
Direct output read Instance.Output Stored instance value to expression

Choose one clear consumption pattern for each result. Mapping an output is convenient when a caller variable is the established interface. Dot notation avoids a duplicate variable when downstream logic can use the instance member directly.

An omitted output connection does not prevent the block from executing. Its output remains available as an instance member:

Block1(firstin := In1);
Out1 := Block1.firstout1;

Confirm that the mapped variable and Block1.firstout1 show the same value after the call.

Caller and block I/O separation

Keep physical I/O addresses in the program or I/O-mapping layer and pass their values into reusable function blocks. The sample declares DigIn_1 AT %IX0.0 and DigOut_1 AT %QX0.0 in MAIN; those are concrete bit addresses. A reusable block can expose ordinary Boolean inputs and outputs without binding itself to physical process-image locations.

PROGRAM MAIN
VAR
    DigIn_1  AT %IX0.0 : BOOL;
    DigOut_1 AT %QX0.0 : BOOL;
    Numbers             : One;
    NumberTotal         : INT;
END_VAR

Numbers(DigIn_1 := DigIn_1);
DigOut_1 := Numbers.DigOut_1;
NumberTotal := Numbers.Number1 + Numbers.Number2;

This structure separates three operations: read the mapped input, execute the block, and write the mapped output. It also prevents an instance from hiding process-image ownership inside its implementation.

  1. Verify that %IX0.0 changes with the intended input.
  2. Verify that Numbers.DigIn_1 receives that state at the call.
  3. Verify that Numbers.DigOut_1 changes after execution.
  4. Verify that assigning it to %QX0.0 changes the intended output image bit.

Do not move on while more than one logic path writes the same physical output.

Function and function-block distinction

A function block requires a declared instance because its outputs and internal variables belong to that instance. A function does not require an instance and returns one value through the function name.

result := functionname(inp1, inp2, inp3);

For the shown function declaration, functionname : REAL defines the return type, and the body assigns the return value to functionname. By contrast, a function block may expose multiple VAR_OUTPUT members and retain instance data between calls.

POU type Caller declaration Result access
Function block Declare an instance such as Numbers : One Numbers.Number1 or Number1 => destination
Function No instance declaration destination := functionname(...)

Confirm the POU type before choosing syntax; changing argument names between a function declaration and its call does not create a connection. Named arguments must use the formal names declared by that POU, while positional arguments depend on declaration order.

FAQ

What happens if I read a TwinCAT function-block output before calling the block?

You read the value already stored in that instance, which may be an initialization value or the previous execution’s result. Place the instance call before the output expression.

What happens if I leave a function-block output unconnected in the call?

The block still executes, and the output remains accessible through Instance.Output. Connect it later with dot notation or map it using =>.

What happens if I use := for a function-block output?

:= assigns an input from the caller to the block. Use => to map a declared output to a caller variable, or read the output through the instance.

What happens if I call the function-block type name instead of its instance?

A function block needs an instance such as Numbers : One. Execute Numbers(...); reserve direct calls without an instance for functions.

How do I verify a TwinCAT function-block output end to end?

Watch the physical input, the instance input, the instance output, the destination variable, and the physical output in that order. Change %IX0.0 and confirm the shown true-state calculation produces Number1 = 3, Number2 = 5, and NumberTotal = 8 after the call.

Back to blog