🔍
v1.3.8

Function Blocks

Function blocks are the backbone of TSharkRex programming. They are stateful, reusable components that encapsulate behavior which persists across scan cycles. Almost everything beyond simple calculations - timers, CAN communication, edge detection, output control, diagnostic requests - is implemented as a function block.

This chapter is one of the most important in this reference. Getting the function block syntax right is essential, and there is a critical difference between functions and function blocks that trips up many developers.

Function Block Definition

A function block is defined using the FUNCTION_BLOCK ... END_FUNCTION_BLOCK construct:

FUNCTION_BLOCK block_name
    VAR
        // ALL variables in ONE VAR block
        // Inputs, outputs, and internal - all together
        ENABLE : BOOL;       // Input (set by caller)
        PT : TIME;           // Input (set by caller)
        Q : BOOL;            // Output (read by caller)
        ET : TIME;           // Output (read by caller)
        internal : DINT;     // Internal variable
    END_VAR;

    // Function block body
    IF ENABLE THEN
        Q := TRUE;
    END_IF;
END_FUNCTION_BLOCK;

Notice the structure:

CRITICAL: Function vs. Function Block

This is the most common mistake in TSharkRex programming. FUNCTION_BLOCK is fundamentally different from FUNCTION. They use different variable declaration syntax, and confusing the two will cause compiler errors or incorrect behavior.

Here is the core difference:

Side-by-Side Comparison

Feature FUNCTION FUNCTION_BLOCK
Parameters VAR_INPUT VAR (all in one block)
State Stateless (reset each call) Stateful (persists between calls)
Return value Yes (assign to function name) No (use output variables)
Instantiation Called directly Must create an instance
Use case Pure calculations Timers, triggers, CAN, I/O, diagnostics
Multiple copies N/A (no state to isolate) Each instance has independent state

Common Mistake

This is wrong - do not use VAR_INPUT inside a function block:

// WRONG - will cause compiler errors!
FUNCTION_BLOCK MY_BLOCK
    VAR_INPUT            // ERROR: VAR_INPUT is for FUNCTION, not FUNCTION_BLOCK
        ENABLE : BOOL;
    END_VAR

    VAR_OUTPUT           // ERROR: VAR_OUTPUT is not valid in FUNCTION_BLOCK
        Q : BOOL;
    END_VAR

    Q := ENABLE;
END_FUNCTION_BLOCK;

This is correct:

// CORRECT - single VAR block with all variables
FUNCTION_BLOCK MY_BLOCK
    VAR
        ENABLE : BOOL;   // Input (set by caller)
        Q : BOOL;        // Output (read by caller)
    END_VAR;

    Q := ENABLE;
END_FUNCTION_BLOCK;
If you are coming from standard IEC 61131-3 environments that use VAR_INPUT / VAR_OUTPUT in function blocks, be aware that TSharkRex differs here. In TSharkRex, function blocks use only VAR. This is a deliberate language design choice.

Instantiation and Usage

Unlike functions, which are called directly, function blocks must be instantiated. You create a named instance of a function block type in a VAR block, then call that instance:

VAR
    TMR : TON;           // Create an instance of the TON timer function block
    start_condition : BOOL;
    timerDone : BOOL;
END_VAR;

// Call the instance with named parameters
TMR(IN := start_condition, PT := T#1s);

// Access outputs through the instance
timerDone := TMR.Q;

// Reset the timer
TMR(IN := FALSE);

Each instance is an independent object with its own state. Creating the instance (in the VAR block) allocates the memory for all of the function block’s internal variables. Calling the instance (in the program body) executes the function block’s logic.

Named Parameters

Function blocks use named parameter syntax when called. Each parameter is specified as name := value:

VAR
    CANSEND : CAN_TX;
    SENDDATA : ARRAY[0..7] OF BYTE;
END_VAR;

CANSEND(ENABLE := TRUE, ID := 0x7DF, EXT := FALSE,
        DATALENGTH := 8, DATA := SENDDATA);

You only need to provide parameters you want to change. Parameters not specified retain their previous values (since function blocks are stateful):

// First call - set all parameters
TMR(IN := TRUE, PT := T#5s);

// Later calls - only change what needs updating
TMR(IN := FALSE);    // PT remains T#5s from previous call
Named parameters make function block calls self-documenting. Always use them - they make it clear which value is being assigned to which parameter, especially with function blocks that have many inputs like CAN_TX or DID_EXT.

Accessing Outputs

After calling a function block instance, you access its output variables using dot notation:

VAR
    TMR : TON;
    timer_done : BOOL;
    elapsed : TIME;
END_VAR;

TMR(IN := TRUE, PT := T#2s);

// Read outputs using dot notation
timer_done := TMR.Q;      // Has the timer elapsed?
elapsed := TMR.ET;         // How much time has passed?

Post-Assignment (=> Operator)

TSharkRex provides a shorthand for reading output values at the time of the function block call using the => (post-assignment) operator:

VAR
    TMR : TON;
    timer_done : BOOL;
    elapsed : TIME;
END_VAR;

// Assign outputs in the same call
TMR(IN := TRUE, PT := T#1s, Q => timer_done, ET => elapsed);

// timer_done and elapsed now contain the latest output values

The => operator copies the output variable’s value into the specified variable after the function block executes. It is syntactic sugar for calling the block and then reading outputs with dot notation, but it keeps the code more compact.

The direction of the arrows indicates data flow:

Multiple Instances

One of the most powerful aspects of function blocks is that each instance maintains its own independent state. You can create as many instances as you need:

VAR
    TMR_WAKE : TON;      // Wake-up delay timer
    TMR_SLEEP : TON;     // Sleep timeout timer
    TMR_POLL : TON;      // Polling interval timer
    condition1 : BOOL;
    condition2 : BOOL;
END_VAR;

// Each timer runs independently with its own state
TMR_WAKE(IN := condition1, PT := T#5s);
TMR_SLEEP(IN := condition2, PT := T#30s);
TMR_POLL(IN := NOT TMR_POLL.Q, PT := T#100ms);

In this example, three TON timer instances run simultaneously. Each has its own IN, PT, Q, and ET variables that do not interfere with the others.

The last timer, TMR_POLL, demonstrates a common pattern: a self-resetting timer. When TMR_POLL.Q becomes TRUE (timer elapsed), the input IN becomes FALSE (because NOT TRUE = FALSE), which resets the timer. On the next cycle, Q is FALSE again, so IN becomes TRUE, restarting the timer. This creates a repeating pulse every 100ms.

Instance Naming Conventions

Use descriptive names for function block instances that indicate their purpose:

VAR
    // Good - clear purpose
    CAN_SKICKA_DID : CAN_TX;
    CAN_TA_EMOT_SVAR : CAN_RX;
    TMR_UPPSTART : TON;
    TRIG_BLINKERS : R_TRIG;

    // Avoid - ambiguous
    TX1 : CAN_TX;
    RX1 : CAN_RX;
    T1 : TON;
END_VAR;

Stateful Behavior

The key feature of function blocks is that their variables persist between calls. This enables time-dependent and history-dependent behavior that is impossible with plain functions.

Consider a simple toggle function block:

FUNCTION_BLOCK TOGGLE
    VAR
        TRIG : BOOL;         // Input: trigger signal
        STATE : BOOL;        // Output: current toggle state
        prev_trig : BOOL;    // Internal: previous trigger value
    END_VAR;

    // Detect rising edge of trigger
    IF TRIG AND NOT prev_trig THEN
        STATE := NOT STATE;   // Toggle the state
    END_IF;

    prev_trig := TRIG;       // Remember for next cycle
END_FUNCTION_BLOCK;

The prev_trig variable remembers the trigger value from the previous scan cycle, enabling edge detection. The STATE variable maintains the current toggle position. This kind of behavior is only possible because function block variables persist.

Example: Pulse Generator

Here is a complete custom function block that generates a repeating on/off pulse:

FUNCTION_BLOCK PULS_GENERATOR
    VAR
        ENABLE : BOOL;       // Input: enable the pulse
        ON_TID : TIME;       // Input: on-duration
        AV_TID : TIME;       // Input: off-duration
        UTGANG : BOOL;       // Output: current pulse state
        tmr : TON;           // Internal: timer instance
        state : BOOL;        // Internal: current phase
    END_VAR;

    IF NOT ENABLE THEN
        UTGANG := FALSE;
        state := FALSE;
        tmr(IN := FALSE);
    ELSE
        IF state THEN
            // ON phase
            tmr(IN := TRUE, PT := ON_TID);
            UTGANG := TRUE;
            IF tmr.Q THEN
                state := FALSE;
                tmr(IN := FALSE);
            END_IF;
        ELSE
            // OFF phase
            tmr(IN := TRUE, PT := AV_TID);
            UTGANG := FALSE;
            IF tmr.Q THEN
                state := TRUE;
                tmr(IN := FALSE);
            END_IF;
        END_IF;
    END_IF;
END_FUNCTION_BLOCK;

Usage:

VAR
    BLINK : PULS_GENERATOR;
    LAMPA : OUTPUT;
END_VAR;

BLINK(ENABLE := TRUE, ON_TID := T#500ms, AV_TID := T#500ms);

IF BLINK.UTGANG THEN
    LAMPA(VALUE := 1000);
END_IF;
Notice how the function block itself contains an instance of another function block (tmr : TON). Function blocks can be nested - a function block can use instances of other function blocks as internal components.

Function Blocks Belong in Libraries

Like functions, function blocks should be defined in libraries, not in your main recipe code. This is especially important for function blocks because:

Always place custom function block definitions in libraries. Your main recipe should only instantiate and call function blocks, not define them. The only exception is quick prototyping during development.

Built-in Function Blocks

TSharkRex provides a rich set of built-in function blocks through its standard libraries. These cover all the fundamental operations needed for automotive automation:

CAN Communication

BlockDescriptionChapter
CAN_TXTransmit a CAN frame14
CAN_RXReceive a specific CAN frame by ID14
CAN_RX_ALLReceive any CAN frame (promiscuous)14
CAN_MODESet CAN bus mode (normal, listen-only, etc.)14
CAN_FILTERConfigure CAN hardware filter14
CAN_MASKConfigure CAN hardware mask14

Timers

BlockDescriptionChapter
TONTimer On-delay - output turns on after a delay15
TOFTimer Off-delay - output stays on after input drops15

Edge Detection

BlockDescriptionChapter
R_TRIGRising edge trigger - pulse on FALSE-to-TRUE transition16
F_TRIGFalling edge trigger - pulse on TRUE-to-FALSE transition16

Output Control

BlockDescriptionChapter
OUTPUTControl a physical output (0–1000 range)17
PWM100PWM output at 100 Hz17
PWM1000PWM output at 1000 Hz17
INFO_VALUEDisplay a numeric value in the app17
INFO_ONOFFDisplay an on/off status in the app17

Settings

BlockDescriptionChapter
SETTING_*User-configurable settings (various types)18

Hardware and Diagnostics

BlockDescriptionChapter
HARDWAREAccess hardware features (LEDs, buttons, voltage)19
DID_EXTUDS Diagnostic communication (Read Data By Identifier)22

Each of these function blocks is covered in detail in its respective chapter. The chapters that follow will progressively introduce them in context, starting with CAN communication.

Summary

Aspect Detail
Declaration FUNCTION_BLOCK name ... END_FUNCTION_BLOCK
Variables Single VAR ... END_VAR block (no VAR_INPUT/VAR_OUTPUT)
Instantiation instance : BLOCK_TYPE; in a VAR block
Calling instance(PARAM := value, ...);
Reading outputs instance.OUTPUT or OUTPUT => variable
State Stateful - all variables persist between calls
Return value None - use output variables instead
Best practice Define in libraries, instantiate in recipes