CAN Communication
CAN (Controller Area Network) is the backbone of every XBB recipe. Through CAN, your recipe
reads vehicle signals - ignition state, speed, light switches, door locks - and
sends commands to other modules on the bus. TSharkRex provides four core function blocks for
CAN communication: CAN_TX for transmitting frames, CAN_RX for
filtered reception, CAN_RX_ALL for unfiltered reception, and CAN_MODE
for controller configuration.
CAN_MODE. See the
CAN_MODE section below. The controller defaults to configuration mode
on startup - no frames will be sent or received until you switch to normal mode.
CAN_TX - Transmit CAN Frame
CAN_TX sends a single CAN frame onto the bus. You populate a data buffer, set
the CAN ID and data length, then call the function block with ENABLE := TRUE.
The frame is transmitted on the next bus cycle.
Parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
ID |
DINT | Input | CAN ID (11-bit standard or 29-bit extended) |
EXT |
BOOL | Input | TRUE = 29-bit extended ID, FALSE = 11-bit standard |
FD |
BOOL | Input | TRUE = CAN-FD frame (up to 64 bytes) |
BRS |
BOOL | Input | Bit rate switch - transmit data phase at higher speed (CAN-FD only) |
DATA |
POINTER TO BYTE | Input | Pointer to the data buffer (byte array) |
DATALENGTH |
DINT | Input | Number of data bytes to send (1–8 for classic CAN, up to 64 for CAN-FD) |
ENABLE |
DINT | Input | Set TRUE to transmit the frame |
Basic Transmit Example
This example sends a UDS diagnostic request (Read Data By Identifier) on the standard
OBD-II broadcast address 0x7DF:
VAR
CANSEND : CAN_TX;
SENDDATA : ARRAY[0..7] OF BYTE;
END_VAR;
SENDDATA[0] := 0x03; // PCI: 3 data bytes follow
SENDDATA[1] := 0x22; // Service: Read Data By Identifier
SENDDATA[2] := 0x45; // DID high byte
SENDDATA[3] := 0x55; // DID low byte
SENDDATA[4] := 0x00; // Padding
SENDDATA[5] := 0x00;
SENDDATA[6] := 0x00;
SENDDATA[7] := 0x00;
CANSEND(ENABLE := TRUE, ID := 0x7DF, EXT := FALSE, DATALENGTH := 8, DATA := SENDDATA);
To change the filter ID on CAN_RX, you must cycle ENABLE
from FALSE to TRUE. The new ID only takes effect on the
FALSE→TRUE transition:
// Change CAN_RX filter ID: must cycle ENABLE
CANRECV(ENABLE := FALSE); // Disable first
CANRECV(ENABLE := TRUE, ID := 0x7E8, DATA := DATA); // New filter ID takes effect
Periodic Transmit with Timer
In most recipes, you do not want to blast CAN frames every scan cycle. Use a timer to control the transmit rate:
VAR
CANSEND : CAN_TX;
SENDDATA : ARRAY[0..7] OF BYTE;
TMR_SEND : TON;
END_VAR;
// Send every 100ms
TMR_SEND(IN := NOT TMR_SEND.Q, PT := T#100ms);
IF TMR_SEND.Q THEN
SENDDATA[0] := 0x03;
SENDDATA[1] := 0x22;
SENDDATA[2] := 0x45;
SENDDATA[3] := 0x55;
CANSEND(ENABLE := TRUE, ID := 0x7DF, EXT := FALSE, DATALENGTH := 8, DATA := SENDDATA);
TMR_SEND(IN := FALSE);
END_IF;
Extended ID (29-bit) Example
For J1939 or ISO 15765-4 with extended addressing, set EXT := TRUE:
VAR
CANSEND : CAN_TX;
SENDDATA : ARRAY[0..7] OF BYTE;
END_VAR;
SENDDATA[0] := 0x03;
SENDDATA[1] := 0x22;
SENDDATA[2] := 0xF1;
SENDDATA[3] := 0x90;
CANSEND(ENABLE := TRUE, ID := 0x18DB33F1, EXT := TRUE, DATALENGTH := 8, DATA := SENDDATA);
CAN-FD Example
CAN-FD allows payloads up to 64 bytes and optionally a higher data-phase bitrate via
BRS:
VAR
CANSEND : CAN_TX;
FD_DATA : ARRAY[0..63] OF BYTE;
END_VAR;
FD_DATA[0] := 0x10;
FD_DATA[1] := 0x20;
// ... fill remaining bytes ...
CANSEND(ENABLE := TRUE, ID := 0x641, EXT := FALSE, FD := TRUE, BRS := TRUE,
DATALENGTH := 64, DATA := FD_DATA);
CAN_RX - Receive CAN Frame (Filtered)
CAN_RX listens for frames with a specific CAN ID. You provide
the ID to filter on, and the function block buffers incoming matches. When
AVAILABLE is greater than zero, new data has arrived in your receive buffer.
Parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
ENABLE |
DINT | Input | Set TRUE to enable reception |
ID |
DINT | Input | CAN ID to filter - only frames matching this ID are received |
EXT |
BYTE | Input | TRUE = expect 29-bit extended ID |
FD |
BYTE | Input | TRUE = expect CAN-FD frame |
BRS |
BYTE | Input | Bit rate switch (CAN-FD only) |
MSG_COUNT |
BYTE | Input | Buffer size - number of messages to queue before overwriting |
DATA |
POINTER TO BYTE | Input | Pointer to the receive buffer (byte array) |
AVAILABLE |
DINT | Output | Number of messages available in buffer (> 0 means new data) |
DATALENGTH |
DINT | Output | Length (in bytes) of the most recently received frame |
Basic Receive Example
Filter for a specific ECU response address and process incoming data:
VAR
CANRECV : CAN_RX;
RECVDATA : ARRAY[0..7] OF BYTE;
SIGNAL_TANDNING : BOOL;
END_VAR;
CANRECV(ENABLE := TRUE, ID := 0x320, EXT := FALSE, MSG_COUNT := 5, DATA := RECVDATA);
IF CANRECV.AVAILABLE > 0 THEN
// Byte 0, bit 0 contains ignition status
SIGNAL_TANDNING := (RECVDATA[0] AND 0x01) > 0;
END_IF;
Processing the Message Queue
When MSG_COUNT is greater than 1, multiple messages can be buffered. Use a
WHILE loop to drain the queue - each call to CAN_RX
after the first pops the next message:
VAR
CANRECV : CAN_RX;
RECVDATA : ARRAY[0..7] OF BYTE;
didValue : DINT;
END_VAR;
CANRECV(ENABLE := TRUE, ID := 0x7E8, EXT := FALSE, MSG_COUNT := 10, DATA := RECVDATA);
WHILE CANRECV.AVAILABLE > 0 DO
// Check if this is a positive response to our DID request
IF RECVDATA[0] = 0x04 AND RECVDATA[1] = 0x62 THEN
// Extract 16-bit DID value from bytes 4-5
didValue := RECVDATA[4] * 256 + RECVDATA[5];
END_IF;
// Pop next message from queue
CANRECV(ENABLE := TRUE, ID := 0x7E8, EXT := FALSE, MSG_COUNT := 10, DATA := RECVDATA);
END_WHILE;
MSG_COUNT large enough to avoid losing messages between
scan cycles. For high-frequency CAN IDs (e.g., engine RPM at 10ms intervals), use
MSG_COUNT := 10 or higher.
CAN_RX_ALL - Receive ALL CAN Frames
CAN_RX_ALL receives every frame on the CAN bus without any
ID filter. This is essential when you need to listen for responses from multiple ECUs
(for example, after a broadcast UDS request) or when scanning the bus to discover which
CAN IDs are present.
CAN_RX vs CAN_RX_ALL
The key difference is the direction of the ID parameter:
| Property | CAN_RX | CAN_RX_ALL |
|---|---|---|
ID |
Input - you specify which ID to filter | Output - read after receive to see which ID arrived |
| Messages received | Only frames matching the specified ID | ALL frames on the bus |
| Use case | Known ECU address (e.g., body controller at 0x320) |
Broadcast responses, bus scanning, multi-ECU discovery |
| Performance | Low overhead (hardware filter) | Higher overhead (software must process every frame) |
CAN_RX_ALL receives every frame on the bus.
On a busy vehicle CAN network this can mean hundreds of messages per second. Always use a
generous MSG_COUNT and drain the queue promptly with a WHILE loop.
Parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
ENABLE |
DINT | Input | Set TRUE to enable reception |
MSG_COUNT |
BYTE | Input | Buffer size (number of messages to queue) |
DATA |
POINTER TO BYTE | Input | Pointer to receive buffer |
ID |
DINT | Output | CAN ID of the received frame |
EXT |
BYTE | Output | TRUE if received frame was 29-bit extended |
AVAILABLE |
DINT | Output | Number of messages available in buffer |
DATALENGTH |
DINT | Output | Length (bytes) of the most recently received frame |
Broadcast DID Request Example
A common pattern is to send a UDS broadcast request on 0x7DF and then capture
all ECU responses. Each ECU responds on its own address (e.g., 0x7E8,
0x7E9, 0x7EA, etc.):
VAR
CANSEND : CAN_TX;
CANRECV_ALL : CAN_RX_ALL;
SENDDATA : ARRAY[0..7] OF BYTE;
RECVDATA : ARRAY[0..7] OF BYTE;
TMR_POLL : TON;
responseId : DINT;
ecuCount : INT := 0;
END_VAR;
// Send broadcast DID request every 500ms
TMR_POLL(IN := NOT TMR_POLL.Q, PT := T#500ms);
IF TMR_POLL.Q THEN
SENDDATA[0] := 0x03;
SENDDATA[1] := 0x22; // Read Data By Identifier
SENDDATA[2] := 0xF1; // DID 0xF190 (VIN)
SENDDATA[3] := 0x90;
CANSEND(ENABLE := TRUE, ID := 0x7DF, EXT := FALSE, DATALENGTH := 8, DATA := SENDDATA);
ecuCount := 0;
TMR_POLL(IN := FALSE);
END_IF;
// Receive all responses
CANRECV_ALL(ENABLE := TRUE, MSG_COUNT := 20, DATA := RECVDATA);
WHILE CANRECV_ALL.AVAILABLE > 0 DO
responseId := CANRECV_ALL.ID;
// Check if this is a UDS positive response (service 0x62)
IF RECVDATA[1] = 0x62 AND responseId >= 0x7E8 AND responseId <= 0x7EF THEN
ecuCount := ecuCount + 1;
// Process response from ECU at address responseId
END_IF;
CANRECV_ALL(ENABLE := TRUE, MSG_COUNT := 20, DATA := RECVDATA);
END_WHILE;
UDS Addressing (TX/RX)
After discovering an ECU via broadcast, calculate its direct TX address using the formula TX = RX − 8:
| ECU responds on (RX) | Send requests to (TX) |
|---|---|
0x7E8 | 0x7E0 |
0x7E9 | 0x7E1 |
0x7EA | 0x7E2 |
0x7EB | 0x7E3 |
0x7EC | 0x7E4 |
0x7ED | 0x7E5 |
0x7EE | 0x7E6 |
0x7EF | 0x7E7 |
For 29-bit extended addressing, the broadcast address is 0x18DB33F1.
Standard UDS DIDs
Common DIDs used with ReadDataByIdentifier (service 0x22):
| DID | Name | Description |
|---|---|---|
0xF190 | VIN | Vehicle Identification Number (17 chars) |
0xF187 | SparePartNumber | Spare part number |
0xF188 | SoftwareVersion | ECU software version |
0xF18C | SerialNumber | ECU serial number |
0xF191 | HardwareVersion | ECU hardware version |
0xF192 | SupplierID | Supplier identification |
0xF194 | CalibrationID | Calibration identification |
CAN_MODE - Set CAN Controller Mode
CAN_MODE configures the CAN controller’s operating mode and baud rate.
This must be called before any CAN communication can take place.
CAN_MODE reinitializes the CAN controller hardware. Calling it every scan cycle
will continuously reset the controller, preventing any communication. Always guard the call
with a one-shot INIT flag so it executes exactly once at startup.
Parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
MODE |
DINT | Input | Operating mode (see constants below) |
BAUDRATE |
INT | Input | Standard CAN bus speed in kbit/s (125, 250, 500, 1000) |
FD_BAUDRATE |
INT | Input | CAN-FD data phase speed in kbit/s (2000, 5000). Only used with CAN-FD. |
Initialization Pattern
VAR
CANMODE : CAN_MODE;
INIT : BOOL := FALSE;
END_VAR;
// Standard CAN at 500 kbit/s (most passenger cars)
IF NOT INIT THEN
CANMODE(MODE := CAN_MODE_NORMAL, BAUDRATE := 500);
INIT := TRUE;
END_IF;
CAN-FD Initialization
For CAN-FD, set both BAUDRATE (arbitration phase) and
FD_BAUDRATE (data phase):
VAR
CANMODE : CAN_MODE;
INIT : BOOL := FALSE;
END_VAR;
// CAN-FD: 500 kbit/s arbitration + 2000 kbit/s data phase
IF NOT INIT THEN
CANMODE(MODE := CAN_MODE_NORMAL, BAUDRATE := 500, FD_BAUDRATE := 2000);
INIT := TRUE;
END_IF;
Common CAN-FD Baudrate Combinations
| Vehicle | BAUDRATE | FD_BAUDRATE |
|---|---|---|
| Mercedes | 500 | 1000 |
| BMW | 500 | 2000 |
| VW / Audi | 500 | 2000 |
Mode Constants
| Constant | Value | Description |
|---|---|---|
CAN_MODE_CONFIG |
0 | Configuration mode - no frames sent or received |
CAN_MODE_NORMAL |
1 | Normal operation - full send and receive |
CAN_MODE_SLEEP |
2 | Sleep mode - low power, wakes on bus activity |
CAN_MODE_DEEP_SLEEP |
3 | Deep sleep - lowest power, manual wake only |
CAN_MODE_SILENT |
4 | Listen only - receives frames but does not transmit ACK bits |
CAN_MODE_SILENT for bus sniffing and diagnostics. In
silent mode the controller does not transmit anything - not even ACK bits - so
it is completely invisible on the bus.
Common Baud Rates
| Baudrate (kbit/s) | Usage |
|---|---|
| 125 | Comfort CAN, older systems, body electronics |
| 250 | J1939, trucks, agricultural and construction vehicles |
| 500 | Most passenger cars - the default for XBB recipes |
| 1000 | High-speed powertrain and ADAS systems |
CAN-FD Baud Rates
When using CAN-FD with bit rate switching (BRS := TRUE), the arbitration phase
runs at the standard baud rate, and the data phase runs at a higher speed:
| Data-Phase Baudrate (kbit/s) | Notes |
|---|---|
| 2000 | Common CAN-FD data rate |
| 5000 | High-speed CAN-FD data rate |
CAN_FILTER and CAN_MASK
Hardware CAN filters allow the controller to reject unwanted frames before they reach your program, reducing CPU load on busy networks.
The number of available filter and mask slots, and how they behave, varies between XBB hardware versions (Dongle v1, Dongle-2, PP-CAN-FD). The filter/mask system is currently being standardized across hardware platforms. Details in this section may change - consult XBB support for your specific hardware if you encounter issues.
Like
CAN_MODE, filter and mask configuration should only happen once at startup.
Calling these every cycle will continuously reconfigure the hardware filter registers.
CAN_FILTER Parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
SLOT |
DINT | Input | Filter slot number (0–5) |
ID |
DINT | Input | CAN ID to accept |
EXT |
BOOL | Input | TRUE = 29-bit extended ID filter |
CAN_MASK Parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
SLOT |
DINT | Input | Mask slot number (0–1) |
ID |
DINT | Input | Mask value - 1 bits must match, 0 bits are ignored |
EXT |
BOOL | Input | TRUE = apply to extended ID bits |
Filter/Mask Truth Table
For each bit position, the mask determines whether the filter bit is compared against the incoming frame’s ID:
| Mask Bit | Filter Bit | Incoming Bit | Result |
|---|---|---|---|
| 0 | X | X | Accept (don’t care) |
| 1 | 0 | 0 | Accept (match) |
| 1 | 0 | 1 | Reject (mismatch) |
| 1 | 1 | 0 | Reject (mismatch) |
| 1 | 1 | 1 | Accept (match) |
Example: Accept Only UDS Responses
Accept CAN IDs 0x7E8 through 0x7EF (all standard ECU response addresses):
VAR
CANFILTER : CAN_FILTER;
CANMASK : CAN_MASK;
CANMODE : CAN_MODE;
INIT : BOOL := FALSE;
END_VAR;
IF NOT INIT THEN
CANMODE(MODE := CAN_MODE_NORMAL, BAUDRATE := 500);
// Mask: bits that must match = 0x7F8 (ignore lower 3 bits)
CANMASK(SLOT := 0, ID := 0x7F8, EXT := FALSE);
// Filter: accept 0x7E8 (with mask, this accepts 0x7E8-0x7EF)
CANFILTER(SLOT := 0, ID := 0x7E8, EXT := FALSE);
INIT := TRUE;
END_IF;
UDS Broadcast Addresses
UDS (Unified Diagnostic Services) uses well-known broadcast addresses to communicate with all ECUs on the bus simultaneously:
| Address | Type | Description |
|---|---|---|
0x7DF |
11-bit standard | OBD-II / ISO 15765-4 broadcast to all ECUs |
0x18DB33F1 |
29-bit extended | ISO 15765-4 extended broadcast (functionally addressed) |
TX/RX Address Calculation
In the standard UDS addressing scheme, each ECU has a request (TX) and response (RX) address pair. The relationship is:
TX address = RX address - 8
So if you send a request to an ECU, you listen for the response at TX + 8:
| ECU | Request (TX) | Response (RX) |
|---|---|---|
| ECU #1 (Engine/PCM) | 0x7E0 |
0x7E8 |
| ECU #2 (Transmission) | 0x7E1 |
0x7E9 |
| ECU #3 (ABS/Brakes) | 0x7E2 |
0x7EA |
| ECU #4 (Airbag) | 0x7E3 |
0x7EB |
| ECU #5 | 0x7E4 |
0x7EC |
| ECU #6 | 0x7E5 |
0x7ED |
| ECU #7 | 0x7E6 |
0x7EE |
| ECU #8 | 0x7E7 |
0x7EF |
| Broadcast (all) | 0x7DF |
0x7E8–0x7EF |
0x7DF), use
CAN_RX_ALL to capture responses from all ECUs. When targeting a specific ECU
(e.g., 0x7E0), use CAN_RX filtered on the response address
(0x7E8).
Complete Example: Read Vehicle Speed via UDS
This example ties together all the CAN function blocks to read the vehicle speed from
the engine ECU using UDS service 0x22 (Read Data By Identifier):
VAR
// CAN function blocks
CANMODE : CAN_MODE;
CANSEND : CAN_TX;
CANRECV : CAN_RX;
// Buffers
SENDDATA : ARRAY[0..7] OF BYTE;
RECVDATA : ARRAY[0..7] OF BYTE;
// Control
INIT : BOOL := FALSE;
TMR_POLL : TON;
// Outputs
SIGNAL_HASTIGHET : INT := 0; // Vehicle speed (km/h)
SIGNAL_HASTIGHET_RAW : DINT := 0; // Raw DID value
END_VAR;
// === INIT (runs once) ===
IF NOT INIT THEN
CANMODE(MODE := CAN_MODE_NORMAL, BAUDRATE := 500);
INIT := TRUE;
END_IF;
// === SEND: Poll speed DID every 200ms ===
TMR_POLL(IN := NOT TMR_POLL.Q, PT := T#200ms);
IF TMR_POLL.Q THEN
SENDDATA[0] := 0x03; // PCI: 3 bytes follow
SENDDATA[1] := 0x22; // Service: Read Data By Identifier
SENDDATA[2] := 0xF4; // DID high byte (example)
SENDDATA[3] := 0x0D; // DID low byte (speed)
SENDDATA[4] := 0x00;
SENDDATA[5] := 0x00;
SENDDATA[6] := 0x00;
SENDDATA[7] := 0x00;
CANSEND(ENABLE := TRUE, ID := 0x7E0, EXT := FALSE, DATALENGTH := 8, DATA := SENDDATA);
TMR_POLL(IN := FALSE);
END_IF;
// === RECEIVE: Process ECU response ===
CANRECV(ENABLE := TRUE, ID := 0x7E8, EXT := FALSE, MSG_COUNT := 5, DATA := RECVDATA);
WHILE CANRECV.AVAILABLE > 0 DO
// Positive response: 0x62 + DID + value
IF RECVDATA[1] = 0x62 AND RECVDATA[2] = 0xF4 AND RECVDATA[3] = 0x0D THEN
SIGNAL_HASTIGHET_RAW := RECVDATA[4] * 256 + RECVDATA[5];
SIGNAL_HASTIGHET := TRUNC(SIGNAL_HASTIGHET_RAW / 100, INT);
END_IF;
CANRECV(ENABLE := TRUE, ID := 0x7E8, EXT := FALSE, MSG_COUNT := 5, DATA := RECVDATA);
END_WHILE;