🔍
v1.3.8

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.

Prerequisite: Before transmitting or receiving CAN frames, you must initialize the CAN controller with 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);
Changing CAN_RX Filter ID at Runtime

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;
Tip: Set 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)
Warning: 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)
0x7E80x7E0
0x7E90x7E1
0x7EA0x7E2
0x7EB0x7E3
0x7EC0x7E4
0x7ED0x7E5
0x7EE0x7E6
0x7EF0x7E7

For 29-bit extended addressing, the broadcast address is 0x18DB33F1.

Standard UDS DIDs

Common DIDs used with ReadDataByIdentifier (service 0x22):

DIDNameDescription
0xF190VINVehicle Identification Number (17 chars)
0xF187SparePartNumberSpare part number
0xF188SoftwareVersionECU software version
0xF18CSerialNumberECU serial number
0xF191HardwareVersionECU hardware version
0xF192SupplierIDSupplier identification
0xF194CalibrationIDCalibration 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.

DANGER: Call ONLY ONCE - Use an INIT Flag!
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

VehicleBAUDRATEFD_BAUDRATE
Mercedes5001000
BMW5002000
VW / Audi5002000

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
Tip: Use 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.

Hardware-Dependent

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.

DANGER: Call ONLY ONCE - Use an INIT Flag!
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;
Note: Mask slot 0 applies to filter slots 0–2. Mask slot 1 applies to filter slots 3–5. Plan your filter groups accordingly.

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
Tip: When sending a broadcast request (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;