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.
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:
FUNCTION function_name : return_type- declares the function with its name and the type of value it returns.VAR_INPUT ... END_VAR- declares the input parameters that callers must provide. This is how data flows into the function.VAR ... END_VAR- declares local variables used only within the function body. These are reset on every call.function_name := result;- the return value is set by assigning to the function’s own name. This is the standard IEC 61131-3 convention.
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;
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;
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.
When to Use Functions
Use a function when:
- The operation is a pure calculation - it takes inputs and produces an output without side effects.
- No state is needed between calls - the function does not need to remember anything from previous invocations.
- You want a return value - functions return a single typed value that can be used directly in expressions.
- The logic is reusable - the same calculation is needed in multiple places or recipes.
Do not use a function when:
- You need to maintain state between scan cycles (use a function block).
- You need multiple output values (use a function block with output variables).
- You are implementing a timer, trigger, or any time-dependent behavior (use a function block).
- You are wrapping CAN communication or I/O operations (use a function block).
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 |