Skip to content

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

int can_open(can_socket_t *cs, const char *if_name, bool enable_fd);

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

int can_set_filters(can_socket_t *cs, const struct can_filter *filters, size_t count);

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

int can_set_nonblocking(can_socket_t *cs);

can_close

void can_close(can_socket_t *cs);

CAN 2.0 Operations

can_send

int can_send(can_socket_t *cs, uint32_t id, const void *data, uint8_t dlc);

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

int can_recv(can_socket_t *cs, uint32_t *id, void *data, uint8_t *dlc, int timeout_ms);

Receive a CAN frame with timeout.

Returns: 0 on success, -ETIMEDOUT on timeout, -EIO on error frame.

CAN FD Operations

can_fd_send

int can_fd_send(can_socket_t *cs, uint32_t id, const void *data, uint8_t len, bool brs);

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