Skip to content

CAN Bus Hardware Interfaces, Higher-Layer Protocols, and Security

Document ID: CAN-04 Series: Telematics Tutorial Series Target audience: Intermediate — you should understand CAN frame formats (CAN-02) and have basic familiarity with Linux networking or embedded development.


Learning Objectives

By the end of this document, you will be able to:

  • Select a Controller Area Network (CAN) hardware interface (USB, Peripheral Component Interconnect Express (PCIe), embedded) for your application
  • Configure and use socketCAN on Linux to send and receive CAN frames
  • Describe the purpose and positioning of higher-layer protocols (CANopen, DeviceNet, J1939, International Organization for Standardization (ISO) Transport Protocol (ISO-TP)) within the CAN protocol stack
  • Identify the key features of CAN Extra Long (CAN XL) and how it differs from CAN FD
  • Recognize common CAN bus security threats and basic countermeasures
  • Understand the role of Unified Diagnostic Services (UDS, ISO 14229), a standardized diagnostic protocol used across the automotive industry for vehicle diagnostics, ECU reprogramming, and production-line testing (covered in detail in OBD-02)

1. CAN Hardware Interfaces

Quick guide: For beginners, start with a PCAN-USB or Canable adapter (~$20-80). For CAN FD development, choose the PCAN-USB FD or Kvaser Leaf Light v2. For production/fleet, consider Vector or Intrepid hardware with professional tooling support.

To connect a computer, Single-Board Computer (SBC), or development system to a CAN bus, you need a CAN interface — a device that bridges the gap between your host system and the CAN physical layer. Interfaces vary widely in features, form factor, and cost.

1.1 USB CAN Interfaces

Universal Serial Bus (USB) CAN adapters are the most common interface type for development, testing, and field diagnostics. They connect to a computer’s USB port and provide one or more CAN channels.

graph TB
    subgraph USB["USB CAN Interfaces"]
        direction TB
        PEAK["PCAN-USB FD
━━━━━━━━━━
$350-500 · 1ch
CAN FD · socketCAN
Production testing"] KVASER["Kvaser U100
━━━━━━━━━━
$400-600 · 1ch
CAN FD · socketCAN
Isolated · Production"] CANABLE["CANable 2.0
━━━━━━━━━━
$30-60 · 1ch
CAN FD · socketCAN
Development"] INNO["Innomaker USB-CAN
━━━━━━━━━━
$20-50 · 1-2ch
CAN FD · socketCAN
Budget prototyping"] end subgraph SPI["Embedded SPI Interfaces"] direction TB MCP2515["MCP2515
━━━━━━━━━━
$5-10 · 1ch
CAN FD · Linux driver
Embedded / RPi"] MCP2518["MCP2518FD
━━━━━━━━━━
$8-15 · 1ch
CAN FD · Linux driver
Embedded CAN FD"] TCAN["TCAN4550
━━━━━━━━━━
$10-20 · 1ch
CAN FD · Ctrl+Xcvr
All-in-one"] end style PEAK fill:#BBDEFB,stroke:#1565C0 style KVASER fill:#BBDEFB,stroke:#1565C0 style CANABLE fill:#C8E6C9,stroke:#388E3C style INNO fill:#C8E6C9,stroke:#388E3C style MCP2515 fill:#FFF9C4,stroke:#F9A825 style MCP2518 fill:#FFF9C4,stroke:#F9A825 style TCAN fill:#FFF9C4,stroke:#F9A825

Figure: CAN04 02 hardware comparison

Interface Manufacturer Channels CAN FD socketCAN Price range Notes
PCAN-USB PEAK-System 1 No Yes (peak_usb) $250–350 Industry standard, very reliable
PCAN-USB FD PEAK-System 1 Yes Yes (peak_usb) $350–500 CAN FD support, same reliability
Kvaser Leaf Light v2 Kvaser 1 No Yes (kvaser_usb) $300–400 Excellent software ecosystem
Kvaser U100 Kvaser 1 Yes Yes (kvaser_usb) $400–600 CAN FD, galvanic isolation
CANable 2.0 Protofusion 1 Yes Yes (gs_usb) $30–60 Open-source, budget-friendly
USB2CAN 8devices 1 No Yes (usb_8dev) $100–200 Compact, reliable
Innomaker USB-CAN Innomaker 1–2 Yes Yes (gs_usb) $20–50 Very affordable, gs_usb compatible

💡 Tip: For development and prototyping, the CANable 2.0 or Innomaker USB-CAN provide excellent value. For production testing and field diagnostics where reliability is non-negotiable, invest in a PEAK or Kvaser adapter. The cost difference is negligible compared to the time lost debugging a flaky adapter.

1.2 PCIe CAN Interfaces

PCIe CAN cards are designed for high-performance applications: Hardware-in-the-Loop (HIL) testing, Electronic Control Unit (ECU) simulation, and high-channel-count gateways.

Interface Manufacturer Channels CAN FD socketCAN Price range Notes
PCAN-PCIe FD PEAK-System 1–4 Yes Yes $500–1,500 Low latency, hardware timestamping
Kvaser PCIEcan 4xHS Kvaser 4 Yes Yes $1,500–2,500 Four isolated CAN channels
IXXAT CAN-IB 640 HMS Networks 4 Yes No (proprietary API) $2,000–3,500 Used in professional HIL systems. Note: IXXAT USB-to-CAN V2 interfaces use a proprietary API on Windows. Linux support requires the IXXAT socketCAN driver (ixxat_usb), which is available in the mainline Linux kernel since v5.11.
ESD CAN-PCIe/402-FD esd electronics 2–4 Yes Yes $1,000–2,000 Industrial grade

1.3 Embedded CAN Controllers

Most microcontrollers used in automotive and industrial applications include built-in CAN controllers. You connect an external CAN transceiver (see CAN-01, Section 5) to the controller’s CAN TX and CAN RX pins.

Platform CAN Controller CAN FD Notes
STM32F4 bxCAN No 3 TX mailboxes, 2 FIFOs, 28 filter banks
STM32G4/H7 FDCAN Yes Message Random Access Memory (RAM) based, flexible filtering
NXP S32K1 FlexCAN Yes Up to 64 message buffers
NXP i.MX RT FlexCAN Yes High-performance, dual CAN instances
Microchip PIC32 CAN Module No 32 message buffers, hardware filtering
Microchip SAM E5x CAN (MCAN) Yes Based on Bosch M_CAN Intellectual Property (IP)
Texas Instruments (TI) TMS320 DCAN No Used in automotive Digital Signal Processors (DSP)
Infineon AURIX M_CAN Yes Multi-core automotive MCU, up to 4 CAN FD instances
ESP32 (TWAI) Two-Wire Automotive Interface (TWAI) No Built into ESP32, 3.3 V logic, needs external transceiver. Note: newer ESP32 variants (ESP32-C6, ESP32-H2) include CAN FD support via the TWAI v2.0 peripheral.

1.4 Microcontroller (MCU) + CAN Transceiver: Serial Peripheral Interface (SPI)-Based Solutions

If your MCU lacks a built-in CAN controller, or you need additional CAN channels, SPI-based CAN controllers provide a complete CAN interface through the SPI bus:

Part Manufacturer CAN FD Interface Notes
MCP2515 Microchip No SPI Extremely popular, well-documented, Linux driver available
MCP2518FD Microchip Yes SPI CAN FD version of MCP2515
TCAN4550 TI Yes SPI Integrated controller + transceiver in one IC

📝 Note: Some CAN controllers also offer **Inter-Integrated Circuit (I2C)** or **Universal Asynchronous Receiver-Transmitter (UART)** host interfaces instead of SPI. However, SPI is by far the most common for standalone CAN controllers due to its higher throughput and lower latency. UART-based CAN adapters (e.g., serial-line CAN — SLCAN) are sometimes used for simple, low-cost connections but have limited throughput and no hardware timestamping.

# Example: Reading CAN frames with MCP2515 on Raspberry Pi (python-can + socketCAN)
# Note: The MCP2515/MCP2518FD connects to the host via SPI at the hardware level,
# but the Linux kernel driver (mcp251x / mcp251xfd) exposes it as a standard
# socketCAN network interface (can0). As a result, application-level code is
# identical to any other socketCAN interface — no SPI-specific API is needed.
import can

bus = can.interface.Bus(
    channel='can0',
    interface='socketcan',
    bitrate=500000
)

# Receive a single frame
msg = bus.recv(timeout=5.0)
if msg:
    print(f"ID: 0x{msg.arbitration_id:03X}  Data: {msg.data.hex()}")
else:
    print("No message received within timeout")

This example uses the python-can library with a socketCAN interface. The MCP2515 connects to the host via SPI, but the Linux mcp251x kernel driver exposes it as a standard can0 socketCAN network interface — so application code is identical whether the underlying hardware is SPI-based, USB, or PCIe.


2. socketCAN on Linux

socketCAN is the standard CAN networking subsystem in the Linux kernel. It treats CAN interfaces as network devices (like Ethernet), allowing you to use familiar networking tools and the Berkeley sockets Application Programming Interface (API) to send and receive CAN frames.

2.1 Setting Up a CAN Interface

# Load the CAN kernel modules (if not loaded automatically)
sudo modprobe can
sudo modprobe can_raw
sudo modprobe vcan     # Virtual CAN for testing without hardware

# Create a virtual CAN interface for testing
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

# Configure a physical CAN interface (e.g., PEAK USB adapter)
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0

# Configure CAN FD with dual bit rate
sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on
sudo ip link set up can0

Each command is explained below:

  • modprobe can loads the core CAN protocol module
  • modprobe can_raw loads the raw CAN socket protocol (frame-level access)
  • ip link set can0 type can bitrate 500000 configures the interface for 500 kbit/s
  • fd on enables CAN FD mode with the specified data bit rate (dbitrate)

2.2 Command-Line Tools

socketCAN includes can-utils, a suite of command-line tools:

# Install can-utils (Debian/Ubuntu)
sudo apt install can-utils

# Send a CAN frame: ID 0x123, data bytes 0xDE 0xAD 0xBE 0xEF
cansend can0 123#DEADBEEF

# Send a CAN FD frame: ID 0x456, 12 bytes of data
cansend can0 456##1.112233445566778899AABBCC

# Monitor all frames on the bus
candump can0

# Monitor with timestamps and filtering
# Filter syntax: candump can0,<can_id>:<can_mask>
# Semantics: a received frame passes if (received_id & can_mask) == (can_id & can_mask)
# Example: 0x100:0x7FF passes frames where (received_id & 0x7FF) == (0x100 & 0x7FF),
# i.e., only frames with CAN ID 0x100 (all 11 bits must match).
# To receive a RANGE of IDs, you must compute a mask that covers the range.
# For example, 0x100:0x780 passes IDs 0x100–0x17F (upper 4 bits match).
candump can0,0x100:0x7FF  # Only CAN ID 0x100 (exact match — all 11 bits tested)

# Generate periodic test traffic
cangen can0 -g 10 -I 0x200 -L 8 -D rand  # Random 8-byte frames every 10ms

# Show bus statistics
canbusload can0 500000  # Monitor bus load at 500 kbit/s

2.3 Programming with socketCAN (C)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int main(void) {
    int sock;
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_frame frame;
    int nbytes;

    /* Create a raw CAN socket */
    sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    /* Bind to the can0 interface */
    strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
    ifr.ifr_name[IFNAMSIZ - 1] = '\0';
    if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
        perror("ioctl SIOCGIFINDEX");
        close(sock);
        return 1;
    }
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(sock);
        return 1;
    }

    /* Build and send a CAN frame */
    memset(&frame, 0, sizeof(frame));
    frame.can_id = 0x123;
    frame.can_dlc = 4;
    frame.data[0] = 0xDE;
    frame.data[1] = 0xAD;
    frame.data[2] = 0xBE;
    frame.data[3] = 0xEF;
    nbytes = write(sock, &frame, sizeof(frame));
    if (nbytes != sizeof(frame)) {
        perror("write");
        close(sock);
        return 1;
    }
    printf("Sent: ID=0x%03X DLC=%d\n", frame.can_id, frame.can_dlc);

    /* Receive a CAN frame */
    nbytes = read(sock, &frame, sizeof(frame));
    if (nbytes < 0) {
        perror("read");
        close(sock);
        return 1;
    }
    printf("Received: ID=0x%03X DLC=%d Data=",
           frame.can_id & CAN_EFF_MASK, frame.can_dlc);
    for (int i = 0; i < frame.can_dlc; i++)
        printf("%02X ", frame.data[i]);
    printf("\n");

    close(sock);
    return 0;
}

Compile with: gcc -o can_example can_example.c

The socketCAN API mirrors standard network socket programming. A CAN socket uses AF_CAN (address family) and CAN_RAW (protocol), and you bind() it to a specific CAN interface. Frames are sent and received with write() and read().

2.4 CAN FD Send/Receive with socketCAN (C)

The following example demonstrates sending and receiving CAN FD frames using struct canfd_frame. CAN FD frames can carry up to 64 bytes per frame and optionally use a higher bit rate for the data phase (Bit Rate Switch — BRS).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int main(void) {
    int sock;
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct canfd_frame txframe, rxframe;
    int enable_canfd = 1;
    int nbytes;

    /* Create a raw CAN socket */
    sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    /* Enable CAN FD support on this socket */
    if (setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
                   &enable_canfd, sizeof(enable_canfd)) < 0) {
        perror("setsockopt CAN_RAW_FD_FRAMES");
        close(sock);
        return 1;
    }

    /* Bind to the can0 interface */
    strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
    ifr.ifr_name[IFNAMSIZ - 1] = '\0';
    if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
        perror("ioctl SIOCGIFINDEX");
        close(sock);
        return 1;
    }
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(sock);
        return 1;
    }

    /* Build and send a CAN FD frame with BRS (Bit Rate Switch) */
    memset(&txframe, 0, sizeof(txframe));
    txframe.can_id = 0x456;
    txframe.len = 24;  /* CAN FD DLC: 24 bytes */
    txframe.flags = CANFD_BRS;  /* Enable Bit Rate Switch for data phase */
    for (int i = 0; i < txframe.len; i++)
        txframe.data[i] = (uint8_t)(i & 0xFF);

    nbytes = write(sock, &txframe, sizeof(txframe));
    if (nbytes != sizeof(txframe)) {
        perror("write CAN FD frame");
        close(sock);
        return 1;
    }
    printf("Sent CAN FD frame: ID=0x%03X len=%d BRS=%s\n",
           txframe.can_id, txframe.len,
           (txframe.flags & CANFD_BRS) ? "on" : "off");

    /* Receive a CAN FD frame */
    nbytes = read(sock, &rxframe, sizeof(rxframe));
    if (nbytes < 0) {
        perror("read");
        close(sock);
        return 1;
    }

    if (nbytes == (int)sizeof(struct canfd_frame)) {
        printf("Received CAN FD frame: ID=0x%03X len=%d BRS=%s Data=",
               rxframe.can_id & CAN_EFF_MASK, rxframe.len,
               (rxframe.flags & CANFD_BRS) ? "on" : "off");
    } else {
        /* Received a classic CAN frame (sizeof struct can_frame) */
        struct can_frame *classic = (struct can_frame *)&rxframe;
        printf("Received classic CAN frame: ID=0x%03X DLC=%d Data=",
               classic->can_id & CAN_EFF_MASK, classic->can_dlc);
    }

    int print_len = (nbytes == (int)sizeof(struct canfd_frame))
                        ? rxframe.len
                        : ((struct can_frame *)&rxframe)->can_dlc;
    for (int i = 0; i < print_len; i++)
        printf("%02X ", rxframe.data[i]);
    printf("\n");

    close(sock);
    return 0;
}

Compile with: gcc -o canfd_example canfd_example.c

📝 Note: Before running, ensure the CAN interface is configured with CAN FD enabled: `sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on && sudo ip link set up can0`. The `CAN_RAW_FD_FRAMES` socket option must be set; otherwise, the kernel rejects CAN FD frames.

2.5 Programming with python-can

import can

# Create a CAN bus interface
bus = can.interface.Bus(channel='can0', interface='socketcan', bitrate=500000)

# Send a frame
msg = can.Message(
    arbitration_id=0x123,
    data=[0xDE, 0xAD, 0xBE, 0xEF],
    is_extended_id=False
)
bus.send(msg)
print(f"Sent: {msg}")

# Receive frames in a loop
for msg in bus:
    print(f"Received: ID=0x{msg.arbitration_id:03X} Data={msg.data.hex()}")
    if msg.arbitration_id == 0x7FF:
        break  # Exit on specific ID

bus.shutdown()

The python-can library abstracts the socketCAN interface behind a clean Python API. It also supports non-Linux interfaces (PCAN, Kvaser, Vector) through interface-specific backends.

Python CAN FD send/receive example:

import can

# Create a CAN FD bus interface
bus = can.interface.Bus(channel='can0', interface='socketcan', fd=True)

# Send a CAN FD frame with Bit Rate Switch (BRS)
msg = can.Message(
    arbitration_id=0x456,
    data=list(range(24)),  # 24 bytes of data
    is_fd=True,
    bitrate_switch=True,   # Enable BRS for higher data-phase bit rate
    is_extended_id=False
)
bus.send(msg)
print(f"Sent CAN FD: ID=0x{msg.arbitration_id:03X} len={len(msg.data)} BRS={msg.bitrate_switch}")

# Receive a CAN FD frame
rx = bus.recv(timeout=5.0)
if rx is not None:
    print(f"Received: ID=0x{rx.arbitration_id:03X} len={len(rx.data)} "
          f"FD={rx.is_fd} BRS={rx.bitrate_switch} Data={rx.data.hex()}")
else:
    print("No message received within timeout")

bus.shutdown()

3. Higher-Layer Protocols

CAN defines only the physical and data link layers (OSI Layers 1 and 2). Higher-layer protocols build on CAN to provide application-layer services: device management, data segmentation, diagnostics, and standardized data definitions.

3.1 Protocol Stack Overview

graph TB
    subgraph APP["Application Layer (7)"]
        direction LR
        CO["CANopen
(CiA 301)
Industrial"] DN["DeviceNet
(ODVA)
Factory"] J1939["J1939
(SAE)
Heavy-duty"] OBD["OBD2 / UDS
(ISO 15031/
ISO 14229)
Diagnostics"] end subgraph TRANSPORT["Transport / Network Layer (3-4)"] direction LR ISOTP["ISO-TP
(ISO 15765-2)
Multi-frame transport"] J1939TP["J1939 TP
BAM / CMDT"] end subgraph DATALINK["Data Link Layer (2)"] CAN["ISO 11898-1
CAN / CAN FD
Frames, Arbitration, Errors"] end subgraph PHYSICAL["Physical Layer (1)"] direction LR HS["ISO 11898-2
High-speed CAN"] FT["ISO 11898-3
Fault-tolerant CAN"] end OBD --> ISOTP J1939 --> J1939TP CO --> CAN DN --> CAN ISOTP --> CAN J1939TP --> CAN CAN --> PHYSICAL style APP fill:#E8F5E9,stroke:#2E7D32 style TRANSPORT fill:#E3F2FD,stroke:#1565C0 style DATALINK fill:#FFF3E0,stroke:#E65100 style PHYSICAL fill:#FCE4EC,stroke:#C62828 style CO fill:#81C784,stroke:#333 style DN fill:#FFB74D,stroke:#333 style J1939 fill:#EF5350,stroke:#333,color:#fff style OBD fill:#CE93D8,stroke:#333

Figure: CAN04 01 protocol stack

The ASCII diagram above provides a text-only fallback; the Mermaid diagram in the diagrams/ directory renders the same information graphically.

3.2 CANopen

CANopen is a higher-layer protocol for embedded control systems, standardized by CAN in Automation (CiA) in the CiA 301 application layer specification.

Key features: - Object Dictionary (OD) — every device exposes a standardized dictionary of parameters, indexed by 16-bit index + 8-bit sub-index - Service Data Objects (SDO) — read/write access to any OD entry (request/response model) - Process Data Objects (PDO) — real-time data exchange (event-driven or periodic, no protocol overhead) - Network Management (NMT) — state machine for device lifecycle (pre-operational, operational, stopped) - Heartbeat — periodic liveness signal from each node - Synchronization (SYNC) — network-wide synchronization message

Common applications: Industrial automation, robotics, medical devices, building automation.

Typical CANopen network operation: In a typical CANopen network, a master node uses Network Management (NMT) to boot all slave nodes through Pre-Operational to Operational states. Configuration data (e.g., sensor calibration values) is read/written via SDO transfers to each node’s Object Dictionary. Once operational, time-critical process data (sensor readings, actuator commands) flows via PDO mappings at configured intervals — typically 10-100 ms for motion control, 100-1000 ms for environmental monitoring.

CAN ID allocation in CANopen:

CAN ID range Function
0x000 NMT command
0x080 SYNC
0x081–0x0FF Emergency (EMCY), per node (0x080 + Node-ID)
0x180–0x57F PDOs (Transmit PDO1-4, Receive PDO1-4 per node)
0x580–0x67F SDO responses / requests
0x700–0x77F NMT heartbeat / boot-up

NMT State Machine:

CANopen defines a Network Management (NMT) state machine for each node:

SDO Read/Write Example (Python using canopen library):

# pip install canopen
import canopen

# Create network and add node with EDS/DCF file
network = canopen.Network()
network.connect(channel='can0', bustype='socketcan')

node = canopen.RemoteNode(1, '/path/to/device.eds')
network.add_node(node)

# NMT: Set node to pre-operational for SDO access
node.nmt.state = 'PRE-OPERATIONAL'

# SDO Read — Read device name from Object Dictionary index 0x1008
device_name = node.sdo[0x1008].raw
print(f"Device name: {device_name}")

# SDO Write — Set heartbeat producer time (index 0x1017) to 500 ms
node.sdo[0x1017].raw = 500
print("Heartbeat interval set to 500 ms")

# NMT: Start node for PDO communication
node.nmt.state = 'OPERATIONAL'

# PDO Read — Read the first Transmit PDO
node.tpdo.read()  # Request current TPDO values
for var in node.tpdo[1]:
    print(f"  {var.name}: {var.raw}")

network.disconnect()

PDO Mapping Example:

PDOs provide real-time data exchange without the overhead of SDO’s request/response protocol. Each PDO can carry up to 8 bytes (64 bytes in CAN FD) of mapped Object Dictionary entries.

import canopen

network = canopen.Network()
network.connect(channel='can0', bustype='socketcan')
node = canopen.RemoteNode(1, '/path/to/device.eds')
network.add_node(node)

# Ensure node is in pre-operational state for PDO configuration
node.nmt.state = 'PRE-OPERATIONAL'

# Configure TPDO1: map two OD entries into a single PDO
node.tpdo[1].clear()
node.tpdo[1].add_variable('Application.StatusWord')   # 16-bit status
node.tpdo[1].add_variable('Application.ActualPosition') # 32-bit position
node.tpdo[1].trans_type = 1    # Transmit on every SYNC
node.tpdo[1].event_timer = 0   # No event-timer fallback
node.tpdo[1].enabled = True
node.tpdo[1].save()            # Write mapping to device via SDO

# Configure RPDO1: receive a command PDO from the master
node.rpdo[1].clear()
node.rpdo[1].add_variable('Application.ControlWord')   # 16-bit command
node.rpdo[1].add_variable('Application.TargetPosition') # 32-bit setpoint
node.rpdo[1].enabled = True
node.rpdo[1].save()

# Switch to operational — PDOs now flow on SYNC
node.nmt.state = 'OPERATIONAL'

# Read current TPDO1 values after a SYNC cycle
node.tpdo[1].wait_for_reception(timeout=2.0)
status = node.tpdo['Application.StatusWord'].raw
position = node.tpdo['Application.ActualPosition'].raw
print(f"Status: 0x{status:04X}, Position: {position}")

network.disconnect()

3.3 DeviceNet

DeviceNet is an industrial network protocol built on CAN, maintained by the Open DeviceNet Vendors Association (ODVA). It is widely used in factory automation for connecting sensors, actuators, and controllers.

Key differences from CANopen: - Uses CAN 2.0A (11-bit IDs) at 125, 250, or 500 kbit/s - Provides power distribution over the CAN cable (24 VDC) - Includes a predefined device profile system (sensors, drives, valves) - Uses a Group 2 connection model with Master/Slave or Peer-to-Peer communication

DeviceNet communication uses two messaging types: - Explicit messaging (request/response): Used for configuration and non-time-critical data. Similar to SDO in CANopen. The master sends a request to a specific device, which responds with the requested data. - Implicit messaging (I/O): Used for real-time cyclic data exchange. Similar to PDO in CANopen. Connections are pre-configured with specific CAN IDs and data mappings.

The predefined master/slave connection set uses CAN IDs allocated as: - Group 1 (CAN ID 0x000–0x3FF): Master-to-slave commands, slave-to-master status - Group 2 (CAN ID 0x400–0x5FF): Explicit messaging (UCMM) - Duplicate Media Access Control (MAC) ID detection: CAN ID 0x600+MAC_ID (note: this is a network-addressing MAC, not a cryptographic Message Authentication Code)

3.4 SAE J1939

Society of Automotive Engineers (SAE) J1939 is the dominant higher-layer protocol for heavy-duty vehicles (trucks, buses, construction equipment, agricultural machinery). It is covered in detail in J19-01, J19-02, and J19-03 in this series.

Key features: - Uses 29-bit extended CAN IDs exclusively - Runs at 250 kbit/s (J1939-11) or 500 kbit/s (J1939-14) - Defines Parameter Group Numbers (PGN) and Suspect Parameter Numbers (SPN) for standardized data - Includes transport protocols for messages larger than 8 bytes (Broadcast Announce Message (BAM), Connection Mode Data Transfer (CMDT)) - Address claiming (the J1939 procedure by which a node negotiates a unique source address on the network using its 64-bit NAME identifier) protocol with 64-bit device NAME

3.5 ISO-TP (ISO 15765-2)

ISO-TP is a transport protocol that enables multi-frame messages over CAN. A single CAN frame can carry at most 8 bytes (or 64 bytes with CAN FD), but many diagnostic and calibration messages exceed this limit. ISO-TP segments large messages into multiple CAN frames with flow control.

Frame types:

Frame type Abbreviation Purpose
Single Frame SF Complete message in one frame (≤ 7 bytes)
First Frame FF First segment of a multi-frame message
Consecutive Frame CF Subsequent segments
Flow Control FC Receiver tells transmitter how to continue (block size, timing)

Primary users: OBD2 diagnostic communication (ISO 15765-4), Unified Diagnostic Services (UDS) (ISO 14229), and ECU calibration protocols (CAN Calibration Protocol (CCP)/Universal Measurement and Calibration Protocol (XCP)).

ISO-TP is covered in detail in OBD-02 in this series.

3.6 Protocol Comparison

Feature CANopen DeviceNet J1939 ISO-TP
Primary domain Industrial automation Factory automation Heavy-duty vehicles Diagnostics transport
CAN ID size 11-bit 11-bit 29-bit 11 or 29-bit
Typical bit rate 250 kbit/s – 1 Mbit/s 125–500 kbit/s 250–500 kbit/s Any
Large message support SDO segmented transfer Fragmented messaging BAM/CMDT transport SF/FF/CF/FC
Device management NMT state machine Predefined master/slave Address claiming None (transport only)
Standardized data model Object Dictionary Device profiles PGN/SPN definitions None (payload agnostic)

4. CAN XL — Next-Generation CAN

CAN XL (CAN Extra Long) is the next evolution of the CAN protocol, defined in CiA 610-1. It bridges the gap between CAN FD and automotive Ethernet by offering significantly larger payloads and higher throughput while maintaining backward compatibility with the CAN physical layer.

4.1 Key Specifications

Parameter CAN FD CAN XL
Max data payload 64 bytes 2,048 bytes
Max data rate 8 Mbit/s 10 Mbit/s
ID field 11 or 29 bits 11-bit priority + 32-bit acceptance field
CRC CRC-17 or CRC-21 CRC-32
Physical layer ISO 11898-2 ISO 11898-2 (same transceivers)
Backward compatible Partial — CAN 2.0 nodes flag CAN FD frames as errors Partial — CAN FD nodes flag CAN XL frames as errors

4.2 CAN XL Protocol-Level Detail

CAN XL introduces a new frame format with a priority field (replacing the arbitration ID), a Service Data Unit (SDU) type field that indicates the upper-layer protocol (e.g., Ethernet payload, classical CAN), and payloads up to 2,048 bytes. The bit rate can reach 20 Mbit/s for the data phase. CAN XL frames are distinguished from CAN FD frames by the XLF (XL Format) bit.

Key protocol differences from CAN FD:

  • Arbitration phase uses the same 11-bit priority field and dominant/recessive signaling as classical CAN, preserving deterministic bus access
  • Data phase switches to a higher bit rate (up to 20 Mbit/s) using a different encoding scheme
  • SDU type field (8 bits) enables protocol multiplexing — a single CAN XL bus can carry CAN, IP/Ethernet, or proprietary payloads, identified by the SDU type
  • Variable payload length from 1 to 2,048 bytes, specified in a data length field (not DLC-encoded like CAN FD)

Target use cases:

  • Gateway consolidation: CAN XL’s 2,048-byte payload can encapsulate full Ethernet frames, allowing a CAN XL backbone to bridge CAN and Ethernet domains without fragmentation
  • Service-oriented communication: SDU type multiplexing supports AUTOSAR Adaptive service-oriented architectures alongside legacy CAN signals on the same bus
  • Firmware over-the-air (FOTA): Large payloads reduce the overhead of firmware update block transfers compared to CAN FD’s 64-byte limit
  • Sensor fusion: High-bandwidth sensor data (lidar point clouds, camera metadata) can be transported over CAN XL without requiring a dedicated Ethernet link

Tooling and silicon status (as of 2026):

  • Linux socketCAN: CAN XL support merged in kernel 6.2 (struct canxl_frame, CAN_RAW_XL_FRAMES socket option)
  • Silicon: Bosch M_CAN IP v4.0+ supports CAN XL; first automotive MCUs with CAN XL expected in volume production by 2027
  • Test tools: PEAK and Vector have announced CAN XL-capable interfaces for development use

4.3 CAN XL Frame Structure

The CAN XL frame is organized into the following fields (in transmission order):

Field Width Purpose
Priority 11 bits Arbitration priority (same signaling as CAN 2.0A ID field)
XLF bit 1 bit Distinguishes CAN XL frames from CAN FD frames
Acceptance Field (AF) 32 bits Message identification and hardware filtering (replaces the traditional CAN ID for routing)
SDU Type 8 bits Upper-layer protocol identifier (CAN, Internet Protocol (IP)/Ethernet, proprietary)
Data Length 11 bits Payload size in bytes (1–2,048)
Data 1–2,048 bytes Application payload
CRC-32 32 bits Error detection over the entire frame

💡 Tip: The separation of arbitration priority (11-bit) from message identification (32-bit AF) is a fundamental design departure from CAN 2.0 and CAN FD. In classical CAN, the CAN ID serves double duty as both a priority indicator and a message identifier. CAN XL decouples these two concerns, allowing flexible message addressing without affecting bus priority scheduling.

4.4 CAN XL and Existing Networks

CAN XL is designed to coexist with CAN FD on the same physical bus:

  • CAN XL frames are distinguished from CAN FD frames at the protocol level
  • CAN FD nodes will detect CAN XL frames as error frames (just as CAN 2.0 nodes detect CAN FD frames as errors)
  • CAN XL-capable nodes can receive and transmit both CAN FD and CAN XL frames
  • The physical layer (ISO 11898-2 transceivers) is unchanged — no hardware modifications needed

📝 Note: As of 2026, CAN XL is still in early adoption. Most automotive **Original Equipment Manufacturers (OEM)** are evaluating it for next-generation architectures. If you are starting a new project, CAN FD is the safe choice for production. Keep CAN XL on your technology radar for future migrations.


5. CAN Bus Security

CAN was designed in the 1980s for a closed, trusted environment inside a vehicle. It has no built-in authentication, encryption, or access control. As vehicles become more connected (telematics, over-the-air updates, Vehicle-to-Everything (V2X) communication), the lack of CAN security has become a significant concern.

5.1 CAN Security Vulnerabilities

No authentication: Any node on the bus can send any CAN ID. There is no mechanism to verify that a frame was sent by an authorized node. A compromised or malicious device can impersonate any other device on the network.

No encryption: All data on the CAN bus is transmitted in plaintext. Any node can read every frame. Sensitive data (Vehicle Identification Number (VIN), GPS coordinates, diagnostic keys) is visible to any device connected to the bus.

No access control: There is no concept of permissions or roles. Every node has equal access to send and receive all messages.

Broadcast medium: CAN is inherently broadcast — every frame is visible to every node. There is no point-to-point communication (though higher-layer protocols like J1939 can address specific nodes, the underlying CAN frames are still broadcast).

5.2 Common Attack Vectors

Attack Description Impact
Frame injection Attacker sends crafted CAN frames to trigger unintended behavior Vehicle control manipulation (steering, braking in research demonstrations — notably Miller and Valasek’s 2015 remote exploitation of a Jeep Cherokee via the Uconnect infotainment system, which demonstrated that CAN bus access through a compromised connected ECU could allow remote vehicle control)
Frame spoofing Attacker sends frames with a legitimate node’s CAN ID to impersonate it False sensor readings, incorrect actuator commands
Denial of Service (DoS) Attacker floods the bus with high-priority (low ID) frames to starve legitimate traffic Loss of communication for safety-critical systems
Bus-off attack Attacker strategically injects dominant bits to force a target node’s Transmit Error Counter (TEC) above 255 (or manipulates the Receive Error Counter (REC)), pushing it bus-off Selective silencing of specific ECUs
Replay attack Attacker records legitimate CAN traffic and replays it later Unlocking doors, starting engine with captured traffic
Man-in-the-Middle (MITM) Attacker physically splices into the bus between two segments, selectively forwarding, modifying, or dropping frames Stealthy data manipulation without triggering simple anomaly detectors
Diagnostic access abuse Attacker uses the On-Board Diagnostics (OBD2) port to access diagnostic services that modify ECU behavior ECU reprogramming, disabling safety features

5.3 Attack Entry Points

                         ↑              ↑
                    Physical       Remote
                    access         access
graph TB
    subgraph VEHICLE["Vehicle CAN Architecture"]
        GW[" Central
Gateway ECU
+ Firewall"] subgraph PT["Powertrain Bus"] ENG["Engine
ECU"] TRANS["Transmission
ECU"] ABS_ECU["ABS
ECU"] end subgraph CH["Chassis Bus"] STEER["Steering
ECU"] BRAKE["Braking
ECU"] end subgraph BODY["Body Bus"] DOOR["Door
Modules"] LIGHT["Lighting"] end subgraph INFO["Infotainment Bus"] HEAD["Head
Unit"] NAV["Navigation"] BT["Bluetooth
Module"] end GW --- PT GW --- CH GW --- BODY GW --- INFO end OBD2[" OBD2 Port
(Physical Access)"] TCU[" Telematics
Control Unit
(Cellular/WiFi)"] AFTER[" Aftermarket
Device"] USBP[" USB/Media
Port"] OBD2 -.->|"Frame injection
Diagnostic abuse"| PT TCU -.->|"Remote exploit
DoS attack"| INFO AFTER -.->|"Replay attack
Spoofing"| OBD2 USBP -.->|"Malicious payload"| HEAD SHIELD1[" Gateway Firewall"] SHIELD2[" SecOC (MAC)"] SHIELD3[" IDS Monitoring"] style GW fill:#1565C0,stroke:#333,color:#fff style PT fill:#FFCDD2,stroke:#D32F2F style CH fill:#BBDEFB,stroke:#1565C0 style BODY fill:#C8E6C9,stroke:#388E3C style INFO fill:#E1BEE7,stroke:#7B1FA2 style OBD2 fill:#FF5722,stroke:#333,color:#fff style TCU fill:#FF5722,stroke:#333,color:#fff style AFTER fill:#FF5722,stroke:#333,color:#fff style USBP fill:#FF5722,stroke:#333,color:#fff style SHIELD1 fill:#4CAF50,stroke:#333,color:#fff style SHIELD2 fill:#4CAF50,stroke:#333,color:#fff style SHIELD3 fill:#4CAF50,stroke:#333,color:#fff

Figure: CAN04 03 security threat model

The ASCII diagram above provides a text-only fallback; the Mermaid diagram in the diagrams/ directory renders the same information graphically.

5.4 Countermeasures

Network segmentation (CAN gateways): Use a CAN gateway ECU to separate the vehicle network into isolated domains (powertrain, chassis, body, infotainment). The gateway filters and forwards only authorized messages between domains. This limits the blast radius of a compromise — an attacker who gains access to the infotainment bus cannot directly send frames to the powertrain bus.

Message Authentication Codes (MAC): Add a cryptographic Message Authentication Code (MAC) to CAN frames to verify the sender’s identity. The AUTomotive Open System ARchitecture (AUTOSAR) SecOC (Secure Onboard Communication) standard defines how to embed MACs in CAN payloads. Limitations: CAN’s 8-byte payload limits the MAC length and the space for application data. CAN FD’s 64-byte payload significantly improves this.

Intrusion Detection Systems (IDS): Monitor CAN bus traffic for anomalous patterns: - Unexpected CAN IDs (frames from IDs that should not be present) - Abnormal message frequency (a frame appearing 10× its normal rate) - Out-of-range signal values (engine RPM reported as 65,535) - Timing anomalies (frames arriving at irregular intervals)

Hardware Security Modules (HSM): Modern automotive MCUs (Infineon AURIX, NXP S32K3, Renesas RH850) include hardware security modules that provide secure key storage, cryptographic acceleration, and secure boot. These enable real-time MAC calculation without significant CPU overhead.

OBD2 port security: - Physical port locks to prevent unauthorized access - Diagnostic session authentication (UDS Security Access, ISO 14229) - Rate limiting on diagnostic requests - Monitoring and logging of OBD2 port activity

5.5 The Reality of CAN Security

⚠️ Warning: No software countermeasure can fully secure a CAN bus against a physically connected attacker. CAN's fundamental architecture (broadcast, no authentication, dominant-overrides-recessive) means that any device electrically connected to the bus can disrupt communication. Security measures raise the bar for attackers but cannot eliminate the risk entirely. The most effective security strategy combines network segmentation (limiting what can be reached) with authentication (verifying who sent it) and monitoring (detecting when something is wrong).

5.6 CAN Bus Security Demonstration (Virtual CAN)

⚠️ Warning: These demonstrations use virtual CAN interfaces only. Never run injection or DoS scripts on a live vehicle CAN bus. These techniques are shown for educational purposes in controlled lab environments.

Setup:

# Create virtual CAN interface
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

Demo 1: Frame Injection (Spoofing Engine RPM)

#!/usr/bin/env python3
"""Demonstrate CAN frame injection on virtual bus.
Spoofs engine RPM by sending fake EEC1 messages.

EDUCATIONAL USE ONLY — never run on a live vehicle.
"""
import can
import time

bus = can.Bus(channel='vcan0', interface='socketcan')

# Spoof engine RPM at 3000 RPM
# EEC1 (PGN 61444, CAN ID 0x0CF00400 for SA=0x00)
# Bytes 4-5: Engine Speed = RPM / 0.125
rpm = 3000
rpm_raw = int(rpm / 0.125)

data = [0xFF, 0xFF, 0xFF,           # Torque fields (not available)
        rpm_raw & 0xFF,              # Engine Speed LSB
        (rpm_raw >> 8) & 0xFF,       # Engine Speed MSB
        0xFF, 0xFF, 0xFF]            # Remaining fields

msg = can.Message(arbitration_id=0x0CF00400, data=data, is_extended_id=True)

print(f"Injecting spoofed RPM={rpm} on vcan0 (Ctrl+C to stop)...")
try:
    while True:
        bus.send(msg)
        time.sleep(0.01)  # 10 ms interval (matching real EEC1 rate)
except KeyboardInterrupt:
    print("Stopped.")
finally:
    bus.shutdown()

Demo 2: Bus DoS with High-Priority Flooding

#!/usr/bin/env python3
"""Demonstrate CAN bus Denial of Service via priority flooding.
Floods the bus with ID 0x000 (highest priority), preventing
all other nodes from winning arbitration.

EDUCATIONAL USE ONLY — never run on a live vehicle.
"""
import can

bus = can.Bus(channel='vcan0', interface='socketcan')
msg = can.Message(arbitration_id=0x000, data=[0]*8, is_extended_id=False)

print("Flooding vcan0 with ID 0x000 (Ctrl+C to stop)...")
count = 0
try:
    while True:
        bus.send(msg)
        count += 1
        if count % 10000 == 0:
            print(f"  Sent {count} frames...")
except KeyboardInterrupt:
    print(f"Stopped after {count} frames.")
finally:
    bus.shutdown()

Demo 3: Simple Anomaly Detector

#!/usr/bin/env python3
"""Simple CAN bus anomaly detector monitoring message frequency.
Alerts when a CAN ID's transmission rate deviates significantly
from its established baseline.

EDUCATIONAL USE ONLY.
"""
import can
import time
from collections import defaultdict

bus = can.Bus(channel='vcan0', interface='socketcan')

# Track message intervals per CAN ID
last_seen = {}
intervals = defaultdict(list)
BASELINE_WINDOW = 100  # Number of messages to establish baseline
ALERT_THRESHOLD = 3.0  # Alert if interval < baseline / threshold

print("Learning CAN bus baseline (send normal traffic first)...")
print("Press Ctrl+C to stop.\n")

try:
    while True:
        msg = bus.recv(timeout=1.0)
        if msg is None:
            continue

        now = msg.timestamp
        cid = msg.arbitration_id

        if cid in last_seen:
            interval = now - last_seen[cid]
            intervals[cid].append(interval)

            # Keep only recent intervals
            if len(intervals[cid]) > BASELINE_WINDOW:
                intervals[cid] = intervals[cid][-BASELINE_WINDOW:]

            # Check for anomaly after baseline is established
            if len(intervals[cid]) >= 10:
                avg_interval = sum(intervals[cid]) / len(intervals[cid])
                if interval < avg_interval / ALERT_THRESHOLD:
                    print(f"[ALERT] CAN ID 0x{cid:03X}: interval {interval*1000:.1f} ms "
                          f"(baseline avg: {avg_interval*1000:.1f} ms) — "
                          f"possible injection/flooding")

        last_seen[cid] = now
except KeyboardInterrupt:
    print("\nStopped.")
finally:
    bus.shutdown()

Cross-References

This document connects to the rest of the Telematics Tutorial Series:

  • For physical layer design (transceivers, termination, wiring) — see CAN-01.
  • For protocol and frame formats (standard vs. extended frames, arbitration, error frames) — see CAN-02.
  • For bit timing configuration (prescaler, segment lengths, sample point, synchronization) — see CAN-03.
  • For DBC file format (signal encoding, message definitions, tooling) — see DBC-01.
  • For OBD-II diagnostics (PIDs, scan tools, emission monitoring) — see OBD-01.
  • For ISO-TP, UDS, and DTC details — see OBD-02.
  • For J1939 fundamentals (PGN/SPN, addressing, transport) — see J19-01.

Troubleshooting

# Symptom Likely cause Diagnostic step Resolution
1 ip link set can0 type can bitrate 500000 fails with “RTNETLINK answers: No such device” CAN kernel module not loaded, or USB adapter not recognized Run dmesg | tail to check for driver messages; lsusb to verify adapter is detected Load correct kernel module (modprobe peak_usb, modprobe gs_usb, etc.)
2 candump shows no frames despite active bus Bit rate mismatch between interface and bus Verify bit rate setting matches all nodes on the bus Reconfigure with correct bit rate: sudo ip link set can0 type can bitrate <correct_rate>
3 CAN interface goes to “bus-off” state immediately Physical wiring problem or no termination Check ip -details link show can0 for error counters; verify termination (60 Ω) Fix wiring/termination; bring interface back up: sudo ip link set can0 down && sudo ip link set can0 up
4 Frames received with incorrect data Byte order mismatch between sender and receiver Compare raw frame bytes with expected encoding (Intel vs Motorola byte order) Verify Database CAN (DBC) file matches the sender’s actual encoding
5 python-can bus.recv() returns None Timeout reached before a frame arrived, or interface not properly configured Verify interface is up with ip link show can0; try candump can0 to confirm frames are present Check interface state, bit rate, and cable connections
6 socketCAN C program crashes on bind() Interface name incorrect or interface not up Verify interface name with ip link show Fix interface name in code; ensure interface is configured and up
7 CANopen/J1939 device not responding to commands Wrong CAN ID, wrong Protocol Data Unit (PDU) format, or device not in operational state Use candump to capture raw traffic; compare against protocol specification Verify CAN IDs, data format, and device state (NMT for CANopen, address claim for J1939)

References

  1. ISO 11898-1:2015 — Road vehicles — Controller area network (CAN) — Part 1: Data link layer and physical signalling. International Organization for Standardization.

  2. ISO 11898-2:2016 — Road vehicles — Controller area network (CAN) — Part 2: High-speed medium access unit. International Organization for Standardization.

  3. ISO 15765-2:2016 — Road vehicles — Diagnostic communication over Controller Area Network (DoCAN) — Part 2: Transport protocol and network layer services (ISO-TP). International Organization for Standardization.

  4. CiA 301 — CANopen Application Layer and Communication Profile. CAN in Automation.

  5. CiA 610-1 — CAN XL Data Link Layer. CAN in Automation.

  6. SAE J1939 — Serial Control and Communications Heavy Duty Vehicle Network. SAE International.

  7. AUTOSAR Specification of Secure Onboard Communication (SecOC) — AUTOSAR Classic Platform.

  8. socketCAN Documentation — Linux kernel networking: CAN. Available at kernel.org/doc/html/latest/networking/can.html.

  9. python-can Documentation — Python CAN bus interface library. Available at python-can.readthedocs.io.

  10. Miller, C. and Valasek, C. (2015) — Remote Exploitation of an Unaltered Passenger Vehicle. Black Hat USA 2015. Their 2015 remote exploitation of a Jeep Cherokee via the Uconnect infotainment system demonstrated that CAN bus access through a compromised connected ECU could allow remote vehicle control — a landmark moment for automotive cybersecurity awareness.


Changelog

Version Date Author Summary of changes
1.0 2026-03-16 Telematics Tutorial Series Initial publication
1.1 2026-03-16 Telematics Tutorial Series Polish pass: expanded acronyms (ISO, SAE, EMCY, SYNC, REC, MITM, I2C, UART), added error checking to classical CAN C example, added Price column to PCIe table, added Python CAN FD example, added CANopen PDO mapping example, expanded CAN XL section with use cases/tooling/frame table, added Cross-References section, disambiguated MAC acronym in DeviceNet

```