Data Types
TSharkRex is a statically typed language - every variable must be declared with a specific type, and the compiler enforces type rules at compile time. This chapter covers all available data types, their sizes, ranges, and practical usage.
Integer Types
TSharkRex provides a range of integer types with different sizes and signedness, following IEC 61131-3 naming conventions:
| Type | Size | Range | Sign |
|---|---|---|---|
BOOL |
1 bit (8-bit storage) | TRUE / FALSE (0 / 1) |
- |
BYTE |
8 bit | 0 .. 255 | unsigned |
SINT |
8 bit | -128 .. 127 | signed |
INT |
16 bit | -32,768 .. 32,767 | signed |
UINT |
16 bit | 0 .. 65,535 | unsigned |
DINT |
32 bit | -2,147,483,648 .. 2,147,483,647 | signed |
UDINT |
32 bit | 0 .. 4,294,967,295 | unsigned |
WORD |
16 bit | 0 .. 65,535 | unsigned (alias for UINT) |
DWORD |
32 bit | 0 .. 4,294,967,295 | unsigned (alias for UDINT) |
WORD is an alias for UINT and DWORD is an alias for
UDINT. They are interchangeable - use whichever is clearer in your context.
WORD/DWORD are common in IEC 61131-3 and low-level CAN data manipulation.
BOOL
The BOOL type represents a logical true/false value. Use it for flags, conditions, and signal states:
VAR
ar_aktiv : BOOL := FALSE;
forsta_start : BOOL := TRUE;
END_VAR;
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL; // On/off signal from vehicle
END_VAR;
IF SIGNAL_HELLJUS THEN
ar_aktiv := TRUE;
END_IF;
BOOL is logically a single bit, it occupies 8 bits (1 byte) of storage in memory. This is because the ARM Cortex-M4 architecture addresses memory in bytes. You do not need to worry about this in practice, but it explains why arrays of BOOL use more memory than you might expect.
BYTE
BYTE is an 8-bit unsigned integer. It is commonly used for raw CAN data, bitmask operations, and byte-level manipulation:
VAR
can_byte : BYTE := 0x00; // Hex literal
bitmask : BYTE := 0x0F; // Lower nibble mask
resultat : BYTE;
END_VAR;
resultat := can_byte BAND bitmask; // Bitwise AND
SINT
SINT (Short Integer) is an 8-bit signed integer. Use it when you need a small signed value, for example temperature offsets or small delta values:
VAR
temp_offset : SINT := -10; // Range: -128 to 127
END_VAR;
INT
INT is a 16-bit signed integer. It is the natural choice for signal values like speed, RPM, and temperature that fit within the range of -32,768 to 32,767:
VAR_SIGNAL
SIGNAL_HASTIGHET : INT; // Speed: 0..250 km/h fits easily
SIGNAL_TEMPERATUR : INT; // Temperature: -40..80 °C
SIGNAL_VARVTAL : INT; // RPM: 0..8000 (fits in INT)
END_VAR;
VAR
max_hastighet : INT := 0;
END_VAR;
// Track maximum speed seen
IF SIGNAL_HASTIGHET > max_hastighet THEN
max_hastighet := SIGNAL_HASTIGHET;
END_IF;
UINT
UINT is a 16-bit unsigned integer. Use it when you know the value is never negative and you need the extra positive range (up to 65,535):
VAR
raknare : UINT := 0; // Counter that never goes negative
can_id : UINT := 0x0320; // CAN identifier (11-bit fits in UINT)
END_VAR;
raknare := raknare + 1;
DINT
DINT (Double Integer) is a 32-bit signed integer. It is the most commonly used integer type for general-purpose values in TSharkRex programs:
VAR
total_distance : DINT := 0; // Large accumulator
tidsstampel : DINT := 0; // Timestamp in ms
can_data_32 : DINT; // 32-bit CAN signal value
END_VAR;
XBB devices have limited RAM. A typical production recipe uses 40–80% of available RAM, so choosing the right data type is important. Use the smallest type that fits your data:
BOOLfor on/off signals (1 byte)BYTEfor CAN data, small counters, flags (1 byte)INTfor signal values, speed, temperature (2 bytes)DINTonly when you actually need the full 32-bit range (4 bytes)
Every unnecessary DINT where a BYTE or INT
would suffice wastes 2–3 bytes of RAM. In a recipe with hundreds of variables,
this adds up quickly.
UDINT
UDINT is a 32-bit unsigned integer. It is used internally for the TIME type and is useful for large counters and addresses:
VAR
cykliskt_raknare : UDINT := 0; // Can count to ~4.3 billion
bitfalt : UDINT := 0xDEADBEEF; // 32-bit bitmask
END_VAR;
Floating-Point Types
TSharkRex supports IEEE 754 floating-point numbers for calculations that require decimal precision:
| Type | Size | Description |
|---|---|---|
REAL |
32 bit | Single-precision floating point |
LREAL |
64 bit | Double-precision floating point |
REAL
REAL provides approximately 7 significant decimal digits. Use it for physical calculations, scaling factors, and sensor values that include fractional parts:
VAR
spaning : REAL; // Battery voltage
skalfaktor : REAL; // Scaling factor
temperatur_c : REAL;
END_VAR;
VAR_SIGNAL
SIGNAL_TEMP_RAW : INT; // Raw sensor value
END_VAR;
// Initialize REAL values in code (not in VAR declarations)
spaning := 12.8;
skalfaktor := 0.1;
// Convert raw sensor reading to Celsius
// Example formula: temp = raw * 0.1 - 40.0
temperatur_c := SINT_TO_FP(SIGNAL_TEMP_RAW, REAL) * skalfaktor - 40.0;
REAL and LREAL variables cannot have initial values in VAR declarations. Declare them without an initializer and assign values in the code body.
LREAL
LREAL provides approximately 15 significant decimal digits. Use it only when REAL does not provide sufficient precision:
VAR
precision_varde : LREAL;
ackumulator : LREAL;
END_VAR;
// LREAL literals use the 'd' suffix
precision_varde := 3.141592653589793d;
ackumulator := 0.0d;
REAL). While LREAL is supported by the compiler, double-precision operations are emulated in software and are significantly slower. Prefer REAL unless you genuinely need the extra precision.
Special Types
In addition to the numeric primitives, TSharkRex provides several special-purpose types:
| Type | Description |
|---|---|
TIME |
Duration in milliseconds (stored as UDINT internally) |
STRING |
Text string (not yet supported by compiler) |
POINTER TO X |
Pointer to a value of type X |
ARRAY[a..b] OF X |
Fixed-size array of type X |
TIME
The TIME type represents a duration in milliseconds. Internally it is stored as a UDINT (32-bit unsigned integer), giving a maximum duration of approximately 49.7 days. TIME is used extensively with timers and delay function blocks:
VAR
fordrojning : TIME := T#500ms; // 500 milliseconds
liten_paus : TIME := T#100ms; // 100 milliseconds
lang_timeout : TIME := T#30s; // 30 seconds
en_minut : TIME := T#1m; // 1 minute
END_VAR;
Time literals use the T# prefix followed by a value and unit:
| Literal | Meaning | Milliseconds |
|---|---|---|
T#100ms |
100 milliseconds | 100 |
T#1s |
1 second | 1,000 |
T#1500ms |
1.5 seconds | 1,500 |
T#5s |
5 seconds | 5,000 |
T#1m |
1 minute | 60,000 |
A practical example using TIME with a timer function block:
VAR
timer1 : TON; // Timer On-Delay
lampan_pa : BOOL := FALSE;
END_VAR;
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL;
END_VAR;
// Delay turning on lights by 2 seconds after high-beam activates
timer1(IN := SIGNAL_HELLJUS, PT := T#2s);
lampan_pa := timer1.Q; // TRUE after 2 seconds
TIME is stored as UDINT internally, you can perform arithmetic on time values. For example, T#1s + T#500ms results in T#1500ms. You can also compare time values with <, >, =, etc.
STRING
The STRING data type is not currently supported by the TSharkRex compiler. String literals (e.g., "Hello") can only be used in specific contexts such as description labels in VAR_OUTPUT declarations (e.g., HELLJUS ["Helljus"] : OUTPUT;). You cannot declare STRING variables or perform string operations in recipes.
POINTER TO
The POINTER TO type creates a pointer (reference) to a value of the specified type. Pointers are an advanced feature used primarily in function block implementations and library code:
VAR
varde : DINT := 42;
pekare : POINTER TO DINT;
END_VAR;
pekare := @varde; // Point to 'varde'
// pekare^ accesses the value (42)
ARRAY
The ARRAY type declares a fixed-size, zero-overhead collection of elements of the same type. Arrays are essential for working with multi-byte CAN data and lookup tables:
VAR
can_data : ARRAY[0..7] OF BYTE; // 8-byte CAN frame data
hastigheter : ARRAY[0..9] OF INT; // 10 speed samples
flaggor : ARRAY[0..31] OF BOOL; // 32 boolean flags
END_VAR;
VAR_SIGNAL
SIGNAL_HASTIGHET : INT;
END_VAR;
// Access elements by index
can_data[0] := 0xFF;
can_data[1] := 0xA0;
// Use in calculations
hastigheter[0] := SIGNAL_HASTIGHET;
// Loop through array (FOR declares loop variable inline)
FOR i : BYTE := 0 TO 7 DO
can_data[i] := 0x00; // Clear all bytes
END_FOR;
Array indices are zero-based (unless you declare a different range). The array bounds are checked at compile time when using literal indices, but runtime bounds checking is not performed for performance reasons.
can_data[8] on an ARRAY[0..7]) causes undefined behavior. The compiler will catch constant out-of-bounds indices, but it cannot check variable indices at compile time. Always ensure your loop bounds and calculated indices are valid.
You can also declare arrays with custom index ranges:
VAR
tabell : ARRAY[0..4] OF DINT; // 5 elements, indices 0 through 4
END_VAR;
tabell[0] := 100;
tabell[2] := 300;
tabell[4] := 500;
Type Conversions
TSharkRex does not perform implicit type conversions (with very few exceptions). When you need to convert between types, you must be explicit about it. The general rules are:
Widening Conversions (Safe)
Converting from a smaller type to a larger type of the same signedness is always safe - no data is lost:
BYTE→UINT→UDINTSINT→INT→DINTREAL→LREAL
VAR
liten : BYTE := 200;
stor : DINT;
END_VAR;
stor := liten; // Safe: BYTE (200) fits in DINT
Narrowing Conversions (Potentially Lossy)
Converting from a larger type to a smaller type may lose information. The value is truncated to fit:
VAR
stor_varde : DINT := 100000;
litet_varde : INT;
END_VAR;
litet_varde := stor_varde; // WARNING: 100000 does not fit in INT
// Result is truncated/wrapped
Float/Integer Conversion
Converting between floating-point and integer types requires care:
VAR
hastighet_float : REAL;
hastighet_int : INT;
avrundad : REAL;
END_VAR;
// REAL cannot have initial values in VAR - assign in code
hastighet_float := 85.7;
// Float to integer: use FP_TO_SINT to convert (truncates fractional part)
hastighet_int := FP_TO_SINT(hastighet_float, INT); // Result: 85 (not 86)
// Integer to float: use SINT_TO_FP to convert
avrundad := SINT_TO_FP(hastighet_int, REAL); // Result: 85.0
Choosing the Right Type
Here is a practical guide for common scenarios:
| Scenario | Recommended Type | Why |
|---|---|---|
| On/off signal (high-beam, etc.) | BOOL |
Logical true/false |
| Raw CAN byte | BYTE |
Matches CAN frame byte size |
| Speed, RPM, temperature | INT |
Typical signal range fits |
| General-purpose counter/value | DINT |
Wide range, native word size |
| CAN ID (11-bit or 29-bit) | UDINT |
Unsigned, 29-bit extended IDs need 32 bits |
| Sensor scaling / physical units | REAL |
Fractional precision needed |
| Timer delays | TIME |
Semantic clarity, works with timer FBs |
| CAN frame data buffer | ARRAY[0..7] OF BYTE |
Standard 8-byte CAN frame |
Practical Examples
The following recipe demonstrates multiple data types working together:
(*
Recipe: Datatypes Demo
Shows various data types in a realistic scenario
*)
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL; // High-beam status
SIGNAL_HASTIGHET : INT; // Speed in km/h
SIGNAL_MOTORTEMP : INT; // Engine temp raw value
END_VAR;
VAR_OUTPUT
EXTRALJUS : OUTPUT; // Auxiliary lights
TEMP_VARNING : OUTPUT; // Temperature warning
END_VAR;
VAR
// Counters and state
cykel_raknare : UDINT := 0; // Scan cycle counter
helljus_tid : TIME := T#0ms; // Time with high-beam
overtemp : BOOL := FALSE;
// Scaling
temp_celsius : REAL;
temp_skalfaktor : REAL;
temp_offset : REAL;
// Thresholds
max_temp : REAL;
END_VAR;
VAR_CONSTANT
HASTIGHETSGRANS : INT := 160;
PERIOD_BLINK : INT := 400;
END_VAR;
// Initialize REAL values (cannot be set in VAR declarations)
temp_skalfaktor := 0.75;
temp_offset := -48.0;
max_temp := 105.0;
// Count scan cycles
cykel_raknare := cykel_raknare + 1;
// Convert raw temperature to Celsius
temp_celsius := SINT_TO_FP(SIGNAL_MOTORTEMP, REAL) * temp_skalfaktor + temp_offset;
// Check overtemperature
IF temp_celsius > max_temp THEN
overtemp := TRUE;
END_IF;
// Calculate output values using intermediate variables
VAR
temp_varning_varde : INT;
extraljus_varde : INT;
END_VAR;
IF overtemp THEN
temp_varning_varde := 1000;
ELSE
temp_varning_varde := 0;
END_IF;
IF SIGNAL_HELLJUS AND (SIGNAL_HASTIGHET < HASTIGHETSGRANS) THEN
extraljus_varde := 1000;
ELSE
extraljus_varde := 0;
END_IF;
// Outputs ALWAYS called every cycle
TEMP_VARNING(VALUE := temp_varning_varde, PERIOD := PERIOD_BLINK);
EXTRALJUS(VALUE := extraljus_varde, PERIOD := 1000);
User-Defined Types (TYPE / STRUCT)
TSharkRex supports user-defined structured types using TYPE and STRUCT.
This lets you group related fields into a single named type:
TYPE SensorData : STRUCT
value : INT;
status : BYTE;
timestamp : UDINT;
END_STRUCT
END_TYPE;
VAR
sensor1 : SensorData;
sensor2 : SensorData;
END_VAR;
sensor1.value := 1234;
sensor1.status := 0x01;
sensor2.timestamp := 0;
User-defined types are primarily used in library development for organizing complex data. Most recipes do not need custom types - built-in types and arrays cover typical use cases.