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 canloads the core CAN protocol modulemodprobe can_rawloads the raw CAN socket protocol (frame-level access)ip link set can0 type can bitrate 500000configures the interface for 500 kbit/sfd onenables 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_FRAMESsocket 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
-
ISO 11898-1:2015 — Road vehicles — Controller area network (CAN) — Part 1: Data link layer and physical signalling. International Organization for Standardization.
-
ISO 11898-2:2016 — Road vehicles — Controller area network (CAN) — Part 2: High-speed medium access unit. International Organization for Standardization.
-
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.
-
CiA 301 — CANopen Application Layer and Communication Profile. CAN in Automation.
-
CiA 610-1 — CAN XL Data Link Layer. CAN in Automation.
-
SAE J1939 — Serial Control and Communications Heavy Duty Vehicle Network. SAE International.
-
AUTOSAR Specification of Secure Onboard Communication (SecOC) — AUTOSAR Classic Platform.
-
socketCAN Documentation — Linux kernel networking: CAN. Available at kernel.org/doc/html/latest/networking/can.html.
-
python-can Documentation — Python CAN bus interface library. Available at python-can.readthedocs.io.
-
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 |
```