🔍
v1.3.8

Functions

Functions in TSharkRex are reusable, stateless units of computation. They accept input parameters, perform a calculation, and return a single value. Functions are ideal for pure computations like value clamping, byte extraction, unit conversions, and mathematical operations.

Functions are one of two code reuse mechanisms in TSharkRex. The other is function blocks (covered in the next chapter). The key distinction is that functions are stateless - they do not remember anything between calls - while function blocks are stateful and retain their variables across scan cycles.

Function Definition

A function is defined using the FUNCTION ... END_FUNCTION block. The function name is followed by a colon and the return type:

FUNCTION function_name : return_type
    VAR_INPUT
        param1 : INT;
        param2 : BYTE;
    END_VAR;

    VAR
        local : DINT;
    END_VAR;

    // Function body
    function_name := param1 + Z_EXT(param2, INT);  // Set return value by assigning to function name
END_FUNCTION;

Key elements of a function definition:

Functions use VAR_INPUT for parameters. This is different from function blocks, which use a single VAR block for everything. Mixing up these conventions is a common source of compiler errors. See the Function Blocks chapter for the comparison.

Setting the Return Value

The return value is set by assigning to the function name anywhere in the function body. The last assignment before END_FUNCTION determines what is returned:

FUNCTION DOUBLE_IT : INT
    VAR_INPUT
        value : INT;
    END_VAR;

    DOUBLE_IT := value * 2;
END_FUNCTION;

You can assign to the function name multiple times - for example, within different branches of an IF statement:

FUNCTION ABS_VALUE : DINT
    VAR_INPUT
        value : DINT;
    END_VAR;

    IF value < 0 THEN
        ABS_VALUE := 0 - value;
    ELSE
        ABS_VALUE := value;
    END_IF;
END_FUNCTION;
Make sure every code path assigns a return value. If the function exits without assigning to its name, the return value is undefined.

Calling Functions

Functions are called by name with positional arguments in parentheses. The return value can be used directly in expressions or assigned to a variable:

FUNCTION DOUBLE_IT : INT
    VAR_INPUT
        value : INT;
    END_VAR;
    DOUBLE_IT := value * 2;
END_FUNCTION;

FUNCTION ABS_VALUE : DINT
    VAR_INPUT
        value : DINT;
    END_VAR;
    IF value < 0 THEN
        ABS_VALUE := 0 - value;
    ELSE
        ABS_VALUE := value;
    END_IF;
END_FUNCTION;

FUNCTION VAL_CLAMP : DINT
    VAR_INPUT
        INVAL : DINT;
        MIN : DINT;
        MAX : DINT;
    END_VAR;
    IF INVAL > MAX THEN INVAL := MAX; END_IF;
    IF INVAL < MIN THEN INVAL := MIN; END_IF;
    VAL_CLAMP := INVAL;
END_FUNCTION;
VAR
    result : INT;
    clamped : DINT;
    temperatur : DINT;
    raw_value : DINT;
END_VAR;

// Assign return value to a variable
result := DOUBLE_IT(42);

// Use directly in an expression
IF ABS_VALUE(temperatur) > 100 THEN
    // Handle extreme temperature
END_IF;

// Nested function calls
clamped := VAL_CLAMP(ABS_VALUE(raw_value), 0, 1000);

Example: Value Clamping

A classic utility function that constrains a value to a minimum/maximum range:

FUNCTION VAL_CLAMP : DINT
    VAR_INPUT
        INVAL : DINT;
        MIN : DINT;
        MAX : DINT;
    END_VAR;

    IF INVAL > MAX THEN INVAL := MAX; END_IF;
    IF INVAL < MIN THEN INVAL := MIN; END_IF;
    VAL_CLAMP := INVAL;
END_FUNCTION;

Usage:

FUNCTION VAL_CLAMP : DINT
    VAR_INPUT
        INVAL : DINT;
        MIN : DINT;
        MAX : DINT;
    END_VAR;
    IF INVAL > MAX THEN INVAL := MAX; END_IF;
    IF INVAL < MIN THEN INVAL := MIN; END_IF;
    VAL_CLAMP := INVAL;
END_FUNCTION;
VAR
    raw_speed : DINT;
    safe_speed : DINT;
END_VAR;

// Clamp speed to valid range 0..250
safe_speed := VAL_CLAMP(raw_speed, 0, 250);

This function modifies its input parameter INVAL internally - this is safe because VAR_INPUT parameters are passed by value, so the caller’s original variable is not affected.

Example: Byte Extraction

Extracting the high or low byte from a 16-bit value is a frequent operation when constructing or parsing CAN frame data:

FUNCTION HIGH_BYTE : BYTE
    VAR_INPUT
        value : INT;
    END_VAR;
    HIGH_BYTE := SHR(value, 8) BAND 0xFF;
END_FUNCTION;

FUNCTION LOW_BYTE : BYTE
    VAR_INPUT
        value : INT;
    END_VAR;
    LOW_BYTE := value BAND 0xFF;
END_FUNCTION;

Usage in building a CAN payload:

FUNCTION HIGH_BYTE : BYTE
    VAR_INPUT
        value : INT;
    END_VAR;
    HIGH_BYTE := SHR(value, 8) BAND 0xFF;
END_FUNCTION;

FUNCTION LOW_BYTE : BYTE
    VAR_INPUT
        value : INT;
    END_VAR;
    LOW_BYTE := value BAND 0xFF;
END_FUNCTION;
VAR
    did_nummer : INT := 0x421B;
    SENDDATA : ARRAY[0..7] OF BYTE;
END_VAR;

SENDDATA[0] := 0x03;                    // UDS length
SENDDATA[1] := 0x22;                    // Read Data By Identifier
SENDDATA[2] := HIGH_BYTE(did_nummer);   // 0x42
SENDDATA[3] := LOW_BYTE(did_nummer);    // 0x1B

Example: Sensor Value Scaling

Converting raw sensor data to engineering units is another typical use case:

FUNCTION SCALE_TEMP : INT
    VAR_INPUT
        raw : BYTE;          // Raw sensor value (0..255)
        offset : INT;        // Offset in tenths of degrees
    END_VAR;

    VAR
        scaled : INT;
    END_VAR;

    // Convert raw byte to temperature: (raw - 40) gives Celsius
    scaled := raw - 40;

    // Apply offset
    scaled := scaled + offset;

    SCALE_TEMP := scaled;
END_FUNCTION;

Usage:

FUNCTION SCALE_TEMP : INT
    VAR_INPUT
        raw : BYTE;
        offset : INT;
    END_VAR;

    VAR
        scaled : INT;
    END_VAR;

    scaled := raw - 40;
    scaled := scaled + offset;
    SCALE_TEMP := scaled;
END_FUNCTION;
VAR
    RECVDATA : ARRAY[0..7] OF BYTE;
    motor_temp : INT;
END_VAR;

motor_temp := SCALE_TEMP(RECVDATA[3], 0);

Local Variables

Functions can declare local variables in a VAR block. These variables exist only for the duration of the function call and are reset (re-initialized) on every invocation:

FUNCTION CHECKSUM : BYTE
    VAR_INPUT
        data : ARRAY[0..7] OF BYTE;
        length : BYTE;
    END_VAR;

    VAR
        sum : BYTE;
        i : BYTE;
    END_VAR;

    sum := 0;
    FOR i := 0 TO length - 1 DO
        sum := sum + data[i];
    END_FOR;

    CHECKSUM := sum BAND 0xFF;
END_FUNCTION;
Unlike function block variables, which persist between calls, function local variables are always reset. This means you cannot use a function to accumulate values across scan cycles. If you need persistent state, use a function block instead.

Functions Belong in Libraries

While the compiler allows you to define functions inside a recipe’s main code, best practice is to place functions in libraries. This makes them reusable across multiple recipes and keeps your main recipe focused on application logic.

// --- In library: UTILITY_FUNCTIONS ---

FUNCTION VAL_CLAMP : DINT
    VAR_INPUT
        INVAL : DINT;
        MIN : DINT;
        MAX : DINT;
    END_VAR;
    IF INVAL > MAX THEN INVAL := MAX; END_IF;
    IF INVAL < MIN THEN INVAL := MIN; END_IF;
    VAL_CLAMP := INVAL;
END_FUNCTION;

FUNCTION HIGH_BYTE : BYTE
    VAR_INPUT
        value : INT;
    END_VAR;
    HIGH_BYTE := SHR(value, 8) BAND 0xFF;
END_FUNCTION;

FUNCTION LOW_BYTE : BYTE
    VAR_INPUT
        value : INT;
    END_VAR;
    LOW_BYTE := value BAND 0xFF;
END_FUNCTION;

Once the library is attached to your recipe, you can call these functions directly without any import statement - they become globally available.

The TSharkRex standard libraries already include commonly needed functions. Check the library documentation before writing your own - chances are, the function you need already exists.

When to Use Functions

Use a function when:

Do not use a function when:

The next chapter, Function Blocks, covers the stateful alternative and explains the critical differences in detail.

Summary

Aspect Detail
Declaration FUNCTION name : type ... END_FUNCTION
Parameters VAR_INPUT ... END_VAR
Return value Assign to function name: name := value;
Local variables VAR ... END_VAR (reset each call)
State Stateless - nothing persists between calls
Calling result := name(arg1, arg2);
Best practice Place in libraries for reuse across recipes