CAN Bus (SocketCAN / CAN FD)¶
Overview¶
Controller Area Network (CAN) is a multi-master serial bus for real-time communication in automotive, industrial, and embedded systems. This implementation uses Linux SocketCAN with support for both CAN 2.0 (8-byte payload) and CAN FD (64-byte payload with flexible data rate).
Status: Implemented
Full implementation with CAN 2.0A/B, CAN FD, hardware filters, error frame monitoring, and virtual CAN testing.
Protocol Specification¶
CAN 2.0 Frame¶
┌─────────┬─────────────────┬──────┬────────────────────┬─────┬─────┬─────┐
│ SOF │ Identifier │ RTR │ Data (0–8 bytes) │ CRC │ ACK │ EOF │
│ (1 bit)│ (11 or 29 bit) │(1 bit)│ │(15b)│(2b) │(7b) │
└─────────┴─────────────────┴──────┴────────────────────┴─────┴─────┴─────┘
Standard frame (CAN 2.0A): 11-bit identifier
Extended frame (CAN 2.0B): 29-bit identifier (set CAN_EFF_FLAG)
Remote frame (RTR): Request data from another node (no payload)
CAN FD Frame¶
┌─────────┬─────────┬──────┬──────┬─────────────────────┬──────┬─────┐
│ SOF │ ID │ FDF │ BRS │ Data (0–64 bytes) │ CRC │ EOF │
│ (1 bit)│(11/29b) │(1 bit)│(1 bit)│ │(17/21)│(7b)│
└─────────┴─────────┴──────┴──────┴─────────────────────┴──────┴─────┘
FDF (FD Format): Distinguishes FD frames from classic CAN
BRS (Bit Rate Switch): Switch to higher bitrate for data phase
Valid DLC values: 0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64
CAN FD DLC Mapping¶
| DLC | Length (bytes) | DLC | Length (bytes) |
|---|---|---|---|
| 0–8 | 0–8 (identity) | 12 | 20 |
| 9 | 12 | 13 | 24 |
| 10 | 16 | 14 | 32 |
| 11 | 20 | 15 | 64 |
Bus Arbitration¶
Node A (ID=0x100): ─┐ ┌─┐ ┌─────────────── (wins, lower ID)
└─┘ └───┘
Node B (ID=0x200): ─┐ ┌─┐ ┌─────── STOPS ──── (loses arbitration)
└─┘ └─┘
Wire (AND logic): ─┐ ┌─┐ ┌─────────────── (follows winner)
└─┘ └───┘
API Reference¶
Data Structures¶
typedef struct {
int fd; /* Socket file descriptor */
int if_index; /* Interface index */
char if_name[IFNAMSIZ]; /* Interface name */
bool fd_enabled; /* CAN FD mode enabled */
bool loopback; /* Loopback mode */
uint64_t frames_tx; /* Frames transmitted */
uint64_t frames_rx; /* Frames received */
uint64_t errors; /* Error frames received */
} can_socket_t;
Socket Lifecycle¶
can_open¶
Open a SocketCAN raw socket and bind to an interface. Optionally enables CAN FD support for 64-byte payloads. Automatically enables error frame reception.
| Parameter | Type | Description |
|---|---|---|
if_name | const char * | Interface name ("can0", "vcan0") |
enable_fd | bool | Enable CAN FD mode |
can_set_filters¶
Set receive filters. Only frames matching the filter criteria will be delivered.
/* Filter example: accept only IDs 0x100-0x1FF */
struct can_filter filter = {
.can_id = 0x100,
.can_mask = 0x700, /* Match upper 3 bits of 11-bit ID */
};
can_set_filters(&cs, &filter, 1);
can_set_nonblocking¶
can_close¶
CAN 2.0 Operations¶
can_send¶
Send a CAN 2.0 frame (up to 8 bytes payload).
| Parameter | Type | Description |
|---|---|---|
id | uint32_t | CAN ID. Set CAN_EFF_FLAG for 29-bit extended ID |
data | const void * | Payload (up to 8 bytes) |
dlc | uint8_t | Data Length Code (0–8) |
can_recv¶
Receive a CAN frame with timeout.
Returns: 0 on success, -ETIMEDOUT on timeout, -EIO on error frame.
CAN FD Operations¶
can_fd_send¶
Send a CAN FD frame (up to 64 bytes). Set brs = true for Bit Rate Switch (higher data-phase bitrate).
can_fd_recv¶
int can_fd_recv(can_socket_t *cs, uint32_t *id, void *data, uint8_t *len,
uint8_t *flags, int timeout_ms);
Usage Examples¶
Basic Send/Receive on Virtual CAN¶
#include "can.h"
#include <stdio.h>
int main(void)
{
can_socket_t cs;
int rc = can_open(&cs, "vcan0", false);
if (rc < 0) {
fprintf(stderr, "CAN open failed: %s\n", strerror(-rc));
return 1;
}
/* Send a frame */
uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF};
can_send(&cs, 0x123, data, 4);
printf("Sent CAN frame ID=0x123, DLC=4\n");
/* Receive (will get our own frame due to loopback) */
uint32_t rx_id;
uint8_t rx_data[8], rx_dlc;
rc = can_recv(&cs, &rx_id, rx_data, &rx_dlc, 1000);
if (rc == 0) {
printf("Received: ID=0x%03x, DLC=%d, Data=", rx_id & CAN_SFF_MASK, rx_dlc);
for (int i = 0; i < rx_dlc; i++) printf("%02x ", rx_data[i]);
printf("\n");
}
can_close(&cs);
return 0;
}
CAN FD with Bit Rate Switch¶
#include "can.h"
#include <stdio.h>
int main(void)
{
can_socket_t cs;
can_open(&cs, "vcan0", true); /* Enable FD */
/* Send 32-byte CAN FD frame with BRS */
uint8_t payload[32];
for (int i = 0; i < 32; i++) payload[i] = (uint8_t)i;
can_fd_send(&cs, 0x456, payload, 32, true); /* BRS = true */
printf("Sent CAN FD frame: ID=0x456, len=32, BRS=on\n");
/* Receive */
uint32_t id;
uint8_t data[64], len, flags;
if (can_fd_recv(&cs, &id, data, &len, &flags, 1000) == 0) {
printf("FD Recv: ID=0x%03x, len=%d, BRS=%s\n",
id & CAN_SFF_MASK, len,
(flags & CANFD_BRS) ? "yes" : "no");
}
can_close(&cs);
return 0;
}
ID-Based Filtering¶
#include "can.h"
#include <stdio.h>
int main(void)
{
can_socket_t cs;
can_open(&cs, "vcan0", false);
/* Accept only IDs 0x200-0x2FF (engine control) */
struct can_filter filters[] = {
{ .can_id = 0x200, .can_mask = 0x700 },
};
can_set_filters(&cs, filters, 1);
printf("Listening for IDs 0x200-0x2FF...\n");
for (int i = 0; i < 10; i++) {
uint32_t id;
uint8_t data[8], dlc;
if (can_recv(&cs, &id, data, &dlc, 5000) == 0) {
printf(" 0x%03x [%d] ", id & CAN_SFF_MASK, dlc);
for (int j = 0; j < dlc; j++) printf("%02x ", data[j]);
printf("\n");
}
}
can_close(&cs);
return 0;
}
Error Frames¶
CAN error frames are automatically captured when the bus reports errors:
| Error Flag | Meaning |
|---|---|
CAN_ERR_TX_TIMEOUT | TX timeout |
CAN_ERR_LOSTARB | Lost arbitration |
CAN_ERR_CRTL | Controller errors (RX/TX overflow) |
CAN_ERR_PROT | Protocol violation |
CAN_ERR_TRX | Transceiver error |
CAN_ERR_ACK | No ACK received |
CAN_ERR_BUSOFF | Bus-off state |
CAN_ERR_BUSERROR | Bus error (bit/stuff/form/CRC) |
When can_recv returns -EIO, the id field contains the error flags.
Build & Run¶
# Compile
gcc -Wall -Wextra -Werror -O3 -std=c11 -o can_demo can.c
# Setup virtual CAN (no hardware needed)
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
# Run
./can_demo
# Monitor with can-utils
candump vcan0 &
cansend vcan0 123#DEADBEEF
Virtual CAN Testing Setup¶
# Create virtual CAN interface
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
# For CAN FD testing (MTU must be 72 for FD)
sudo ip link add dev vcan1 type vcan
sudo ip link set vcan1 mtu 72
sudo ip link set up vcan1
# Useful tools (can-utils package)
candump vcan0 # Monitor all frames
cansend vcan0 123#DEADBEEF # Send test frame
cangen vcan0 -g 10 -I 100 # Generate traffic
Test Output¶
$ ./build/can_demo
[can] Opened vcan0 (CAN FD enabled)
[can] TX: ID=0x123, DLC=4, data=de ad be ef
[can] RX: ID=0x123, DLC=4, data=de ad be ef (loopback)
[can] TX FD: ID=0x456, len=32, BRS=on
[can] RX FD: ID=0x456, len=32, BRS=on, flags=0x01
[can] Filter set: accept 0x200-0x2FF only
[can] Stats: TX=2, RX=2, errors=0
[PASS] All can tests passed