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

Function Blocks

A FUNCTION_BLOCK is a stateful Program Organisation Unit. Unlike plain functions, function blocks retain their internal variables across calls, making them the primary building block for timers, counters, PID loops, state machines, and communication handlers.

Declaration

FUNCTION_BLOCK BlockName
  VAR_INPUT
    enable : BOOL;         (* supplied by caller *)
  END_VAR
  VAR_OUTPUT
    result : INT;          (* readable by caller after the call *)
  END_VAR
  VAR_IN_OUT
    shared : REAL;         (* passed by reference *)
  END_VAR
  VAR
    internal : INT := 0;   (* private persistent state *)
  END_VAR

  (* body *)
END_FUNCTION_BLOCK

Variable Sections

SectionDirectionSemantics
VAR_INPUTInCopied from caller at invocation
VAR_OUTPUTOutReadable by caller via dot notation
VAR_IN_OUTIn/OutPassed by reference; caller must supply a variable, not a literal
VARPrivateInternal state, invisible to caller

Instantiation

Function blocks are used by declaring instances as variables. Each instance owns an independent copy of all internal state.

PROGRAM Main
  VAR
    counter1 : UpCounter;
    counter2 : UpCounter;
  END_VAR

  counter1(increment := TRUE);
  counter2(increment := FALSE);
  (* counter1 and counter2 have completely separate state *)
END_PROGRAM

Calling with Named Parameters

Invoke an instance by writing its name followed by parenthesised named arguments using the := assignment syntax:

delay(IN := start_signal, PT := T#5s);

All VAR_INPUT parameters that have no default must be supplied. Parameters with defaults may be omitted.

Accessing Outputs via Dot Notation

After calling an instance, read its VAR_OUTPUT fields with instance.output:

delay(IN := start_signal, PT := T#5s);

IF delay.Q THEN
  (* 5 seconds have elapsed *)
END_IF;

elapsed := delay.ET;

You may also read outputs without calling first, which returns the value from the previous cycle.

State Persistence Across Calls

VAR and VAR_OUTPUT variables keep their values between calls. This is the key difference from a plain FUNCTION:

FUNCTION_BLOCK UpCounter
  VAR_INPUT
    increment : BOOL;
    reset     : BOOL;
  END_VAR
  VAR_OUTPUT
    count : INT := 0;
  END_VAR
  VAR
    prev_increment : BOOL := FALSE;
  END_VAR

  IF reset THEN
    count := 0;
  ELSIF increment AND NOT prev_increment THEN
    count := count + 1;   (* rising edge detection *)
  END_IF;

  prev_increment := increment;
END_FUNCTION_BLOCK

Each scan cycle picks up exactly where the last one left off.

Realistic Example: Timer-Like Block

FUNCTION_BLOCK CycleTimer
  VAR_INPUT
    IN : BOOL;
    PT : INT;        (* preset in scan cycles *)
  END_VAR
  VAR_OUTPUT
    Q  : BOOL;
    ET : INT := 0;
  END_VAR
  VAR
    running : BOOL := FALSE;
  END_VAR

  IF IN THEN
    IF NOT running THEN
      ET := 0;
      running := TRUE;
    END_IF;
    ET := ET + 1;
    Q := ET >= PT;
  ELSE
    running := FALSE;
    Q := FALSE;
    ET := 0;
  END_IF;
END_FUNCTION_BLOCK

PROGRAM Main
  VAR
    btn     : BOOL;
    tmr     : CycleTimer;
    motor   : BOOL;
  END_VAR

  tmr(IN := btn, PT := 100);
  motor := tmr.Q;
END_PROGRAM

Nesting Function Blocks

A function block can instantiate other function blocks in its VAR section:

FUNCTION_BLOCK Controller
  VAR
    filter  : LowPass;
    limiter : Clamp;
  END_VAR
  (* ... *)
  filter(input := raw_value, alpha := 0.1);
  limiter(value := filter.output, lo := 0.0, hi := 100.0);
  result := limiter.clamped;
END_FUNCTION_BLOCK

Summary

AspectFUNCTIONFUNCTION_BLOCK
State persistenceNoneYes, per instance
Return valueOne, via nameNone (use VAR_OUTPUT)
InstantiationCalled directlyDeclared as a variable
Use casesPure computationTimers, counters, state