CAN Bus for Embedded Developers: Protocol Basics, Bit Timing, Arbitration, and Practical Implementation

CAN (Controller Area Network) is the backbone of automotive and industrial communication. Created by Bosch in the 1980s for vehicle networks, it has become the standard for reliable, real-time communication in harsh environments.

This guide provides embedded developers with practical knowledge: how CAN works, bit timing configuration, implementing CAN on microcontrollers, and debugging real-world CAN networks.


What is CAN Bus?

CAN is a multi-master, message-based serial communication protocol designed for:

  • Automotive applications (engine control, ABS, airbags, infotainment)
  • Industrial automation (PLCs, sensors, actuators)
  • Medical devices (surgical equipment, patient monitors)
  • Aerospace (avionics, flight control systems)

Key Features:

Multi-master: Any node can transmit when bus is idle
Message-based: No node addresses, messages identified by ID
Priority arbitration: High-priority messages win bus access
Error detection: 5 error detection mechanisms (CRC, ACK, bit monitoring, frame check, bit stuffing)
Error confinement: Faulty nodes self-isolate from network
Real-time: Deterministic latency for high-priority messages
Robustness: Differential signaling, works in noisy environments


CAN Physical Layer

Differential Signaling:

CAN uses two wires: CAN_H (High) and CAN_L (Low).

Recessive bit (1):

  • CAN_H = CAN_L = 2.5V
  • Voltage difference = 0V

Dominant bit (0):

  • CAN_H = 3.5V
  • CAN_L = 1.5V
  • Voltage difference = 2V

Why differential?

  • Noise affects both wires equally (common-mode rejection)
  • Long cable lengths (up to 40m @ 1 Mbps, 1000m @ 50 kbps)
  • Works in electrically noisy environments (engines, motors, EMI)

CAN Transceiver:

Microcontrollers have CAN controllers (protocol handling), but need external transceivers to drive the physical bus.

Common transceivers:

  • TJA1050 (NXP) - 1 Mbps, 3.3V/5V
  • MCP2551 (Microchip) - 1 Mbps, 5V
  • SN65HVD230 (TI) - 1 Mbps, 3.3V
  • TCAN1051 (TI) - 5 Mbps (CAN FD capable)
┌─────────────┐      ┌──────────────┐      ┌────────────┐
│ MCU         │      │ Transceiver  │      │  CAN Bus   │
│             │ TX──▶│              │ CANH ├────────────┤
│ CAN         │ RX◀──│ TJA1050      │ CANL ├────────────┤
│ Controller  │      │              │      │            │
└─────────────┘      └──────────────┘      └────────────┘

Bus Termination:

CAN bus requires 120Ω termination resistors at both ends to prevent signal reflections.

120Ω                  CAN_H                    120Ω
┌────┐               ────────────              ┌────┐
│Node├─────────────────┬────┬─────────────────┤Node│
│ 1  │                 │    │                  │ N  │
└────┘                 │    │                  └────┘
120Ω                  CAN_L                    120Ω

Without termination: Signal reflections cause bit errors.
With termination: Clean signal edges, reliable communication.


CAN Message Frame

Standard Frame (CAN 2.0A):

┌───┬───┬───────────┬───┬───┬───┬────────┬───────┬─────┬───┬───┬───┐
│SOF│   │    ID     │RTR│IDE│r0 │  DLC   │ DATA  │ CRC │ACK│EOF│IFS│
│   │   │ 11 bits   │   │   │   │ 4 bits │0-8 B  │15bits│  │   │   │
└───┴───┴───────────┴───┴───┴───┴────────┴───────┴─────┴───┴───┴───┘

Extended Frame (CAN 2.0B):

  • 29-bit identifier (instead of 11-bit)
  • Used when 2048 message IDs (11-bit) are insufficient
  • Common in J1939 (heavy-duty vehicles)

Frame Fields:

FieldBitsDescription
SOF1Start of Frame (dominant bit)
ID11/29Message identifier (priority)
RTR1Remote Transmission Request
IDE1Identifier Extension (0=standard, 1=extended)
DLC4Data Length Code (0-8 bytes)
DATA0-64Payload data
CRC15Cyclic Redundancy Check
ACK2Acknowledgment (receiver confirms)
EOF7End of Frame
IFS3Inter-Frame Space

Arbitration: Priority-Based Bus Access

Key concept: Lower ID = Higher priority.

Non-Destructive Arbitration:

When multiple nodes transmit simultaneously, the node with the lowest ID wins without any frame loss.

Example:

Node A transmits ID 0x100 (binary: 00100000000)
Node B transmits ID 0x101 (binary: 00100000001)

Bit-by-bit:
Bit 10: Both send 0 (dominant) → Continue
Bit 9:  Both send 0 (dominant) → Continue
...
Bit 1:  Both send 0 (dominant) → Continue
Bit 0:  A sends 0 (dominant), B sends 1 (recessive)
        → A's dominant bit overrides B's recessive bit
        → B detects mismatch, stops transmitting
        → A wins bus access and completes frame

Result:

  • Node A (ID 0x100) wins
  • Node B (ID 0x101) waits and retries
  • No collision, no data loss

This is why CAN is deterministic: High-priority messages always get through quickly.


Bit Timing Configuration

Bit timing is the most critical and confusing part of CAN configuration. Get it wrong, and the bus won’t work at all.

Bit Timing Segments:

Each bit is divided into segments:

┌─────────┬────────┬─────────────┬─────────────┐
│  SYNC   │  PROP  │   PHASE1    │   PHASE2    │
│  1 TQ   │  1-8TQ │   1-8 TQ    │   1-8 TQ    │
└─────────┴────────┴─────────────┴─────────────┘
        ▲                          ▲
    Sample Point              Resync Point
  • Time Quantum (TQ): Basic time unit
  • SYNC_SEG: Synchronization segment (always 1 TQ)
  • PROP_SEG: Propagation segment (compensates for bus delay)
  • PHASE_SEG1: Phase buffer segment 1
  • PHASE_SEG2: Phase buffer segment 2
  • Sample Point: Where bit value is read (typically 75-87.5%)

Formula:

Bit Time = SYNC_SEG + PROP_SEG + PHASE_SEG1 + PHASE_SEG2
Bit Time = 1 TQ + PROP + PS1 + PS2

Baudrate = APB Clock / (Prescaler × Bit Time)

Example: 500 kbps CAN on STM32 with 42 MHz APB1 clock

Target: 500 kbps, 75% sample point

Calculation:

Desired Bit Time = 1 / 500 kbps = 2 µs

APB1 Clock = 42 MHz
Prescaler candidates: Try 6

TQ = Prescaler / APB1 Clock = 6 / 42 MHz = 142.86 ns

Bit Time in TQs = 2 µs / 142.86 ns = 14 TQ

Distribute TQs:
SYNC_SEG = 1 TQ (always)
PROP_SEG = 5 TQ
PHASE_SEG1 = 4 TQ
PHASE_SEG2 = 4 TQ
Total = 1 + 5 + 4 + 4 = 14 TQ ✓

Sample Point = (1 + 5 + 4) / 14 = 10 / 14 = 71.4% (close to 75%)

STM32 Configuration:

CAN_FilterConfTypeDef filter;
hcan.Instance = CAN1;
hcan.Init.Prescaler = 6;
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_9TQ;  // PROP + PHASE1 = 5 + 4 = 9
hcan.Init.TimeSeg2 = CAN_BS2_4TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE;
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;

HAL_CAN_Init(&hcan);

Common Baudrates:

BaudrateMax Cable LengthTypical Use
1 Mbps40 mHigh-speed automotive (CAN-HS)
500 kbps100 mStandard automotive
250 kbps250 mIndustrial automation
125 kbps500 mBuilding automation
50 kbps1000 mLong-distance industrial

Rule of thumb: Baudrate × Cable Length ≈ Constant (40,000 for CAN)


CAN Filters

MCUs often receive hundreds of CAN messages. Filters allow accepting only relevant IDs, reducing interrupt load.

STM32 CAN Filter Example:

// Accept only ID 0x123
CAN_FilterTypeDef filter;
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = (0x123 << 5);  // Shift for 11-bit ID
filter.FilterIdLow = 0;
filter.FilterMaskIdHigh = (0x7FF << 5);  // Match all bits
filter.FilterMaskIdLow = 0;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &filter);

// Accept range: IDs 0x100-0x10F
filter.FilterIdHigh = (0x100 << 5);
filter.FilterMaskIdHigh = (0x7F0 << 5);  // Mask lower 4 bits
HAL_CAN_ConfigFilter(&hcan, &filter);

Sending and Receiving CAN Messages

Transmit (STM32 HAL):

#include "stm32f4xx_hal.h"

CAN_TxHeaderTypeDef txHeader;
uint8_t txData[8];
uint32_t txMailbox;

void CAN_Send_Example(void) {
    txHeader.StdId = 0x123;              // Message ID
    txHeader.ExtId = 0;
    txHeader.RTR = CAN_RTR_DATA;         // Data frame (not remote)
    txHeader.IDE = CAN_ID_STD;           // Standard 11-bit ID
    txHeader.DLC = 8;                    // 8 bytes of data
    txHeader.TransmitGlobalTime = DISABLE;
    
    // Prepare data
    txData[0] = 0x01;
    txData[1] = 0x02;
    txData[2] = 0x03;
    txData[3] = 0x04;
    txData[4] = 0x05;
    txData[5] = 0x06;
    txData[6] = 0x07;
    txData[7] = 0x08;
    
    // Send message
    if (HAL_CAN_AddTxMessage(&hcan, &txHeader, txData, &txMailbox) != HAL_OK) {
        Error_Handler();
    }
}

Receive (Interrupt-based):

CAN_RxHeaderTypeDef rxHeader;
uint8_t rxData[8];

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
    // Receive message from FIFO0
    if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) {
        // Check message ID
        if (rxHeader.StdId == 0x123 && rxHeader.IDE == CAN_ID_STD) {
            // Process data
            uint16_t value = (rxData[0] << 8) | rxData[1];
            printf("Received: 0x%04X\n", value);
        }
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_CAN_Init();
    
    // Start CAN and enable interrupt
    HAL_CAN_Start(&hcan);
    HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
    
    while (1) {
        // Main loop
    }
}

MCP2515: External CAN Controller

For MCUs without built-in CAN (Arduino, Raspberry Pi), use MCP2515 SPI-to-CAN controller.

Wiring:

Arduino/MCU          MCP2515          CAN Transceiver
    MOSI    ────────▶ SI
    MISO    ◀──────── SO
    SCK     ────────▶ SCK
    CS      ────────▶ CS
    INT     ◀──────── INT
                       TX ────────▶ TXD
                       RX ◀──────── RXD
                                     CANH ──┬── Bus
                                     CANL ──┘

Arduino Example (using mcp2515 library):

#include <SPI.h>
#include <mcp2515.h>

MCP2515 mcp2515(10);  // CS on pin 10

void setup() {
    Serial.begin(115200);
    SPI.begin();
    
    mcp2515.reset();
    mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ);  // 500 kbps, 8 MHz crystal
    mcp2515.setNormalMode();
    
    Serial.println("CAN initialized");
}

void loop() {
    // Send CAN message
    struct can_frame txFrame;
    txFrame.can_id = 0x123;
    txFrame.can_dlc = 8;
    txFrame.data[0] = 0x11;
    txFrame.data[1] = 0x22;
    txFrame.data[2] = 0x33;
    txFrame.data[3] = 0x44;
    txFrame.data[4] = 0x55;
    txFrame.data[5] = 0x66;
    txFrame.data[6] = 0x77;
    txFrame.data[7] = 0x88;
    
    mcp2515.sendMessage(&txFrame);
    Serial.println("Message sent");
    
    // Receive CAN message
    struct can_frame rxFrame;
    if (mcp2515.readMessage(&rxFrame) == MCP2515::ERROR_OK) {
        Serial.print("ID: 0x");
        Serial.print(rxFrame.can_id, HEX);
        Serial.print(" Data: ");
        for (int i = 0; i < rxFrame.can_dlc; i++) {
            Serial.print(rxFrame.data[i], HEX);
            Serial.print(" ");
        }
        Serial.println();
    }
    
    delay(1000);
}

CAN Error Detection and Handling

CAN has 5 error detection mechanisms:

1. CRC Check

  • 15-bit CRC protects data and control fields
  • Receiver recalculates CRC and compares

2. Frame Check

  • Ensures frame format is valid
  • Checks bit stuffing, fixed bits

3. ACK Error

  • Transmitter expects at least one node to acknowledge
  • If no ACK, frame is retried

4. Bit Monitoring

  • Transmitter reads back what it sent
  • Detects if a recessive bit was overwritten by dominant

5. Bit Stuffing

  • After 5 consecutive identical bits, insert opposite bit
  • Prevents DC bias and helps synchronization

Error States:

CAN nodes track error counters (Transmit Error Counter, Receive Error Counter):

StateTEC/RECBehavior
Error Active< 128Normal operation, sends active error flags
Error Passive128-255Limited operation, sends passive error flags
Bus Off> 255Node disconnects from bus (fail-safe)

Self-healing: Nodes automatically recover when error count decreases.


CAN FD: Flexible Data Rate

CAN FD (ISO 11898-1:2015) improves classical CAN:

FeatureClassical CANCAN FD
Max payload8 bytes64 bytes
Data phase speedSame as arbitrationUp to 8 Mbps
Throughput~1 Mbps~10 Mbps (effective)
Backward compatibleN/ANo (requires CAN FD controller)

Use cases:

  • Automotive (ADAS cameras, radar, Ethernet gateway)
  • Industrial (high-frequency sensor data)
  • Software updates (faster flashing)

CAN FD Frame:

Arbitration Phase: 500 kbps (compatible with classical CAN)
Data Phase: 2-8 Mbps (faster transmission)

Benefit: 8x more data per frame, faster overall throughput.


Debugging CAN Bus

1. Hardware Tools:

CAN Analyzers:

  • PEAK PCAN-USB ($150) - Professional tool, Windows/Linux
  • CANable ($30) - Open-source USB-CAN adapter
  • Kvaser Leaf Light ($400) - Industrial-grade

2. Software Tools:

CANalyzer / CANoe (Vector) - Industry standard, expensive
Wireshark with SocketCAN (Linux) - Free
PCAN-View (PEAK) - Free with PCAN hardware
Busmaster (Open-source) - Free Windows tool


3. Wireshark with SocketCAN (Linux):

# Load SocketCAN kernel module
sudo modprobe can
sudo modprobe can_raw
sudo modprobe vcan

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

# Or use real hardware (e.g., CANable on slcan)
sudo slcand -o -c -s6 /dev/ttyUSB0 can0
sudo ip link set can0 up

# Capture with Wireshark
wireshark -i can0

# Or use candump
candump can0

4. Common Issues:

ProblemSymptomSolution
No terminationBus errors, intermittentAdd 120Ω resistors at both ends
Wrong bit timingNo communicationRecalculate bit timing, verify clock
Mixed baudratesACK errorsEnsure all nodes use same baudrate
Bus-Off stateNode stops transmittingCheck error counters, reset controller
EMI/noiseSporadic errorsTwisted pair cable, shielding, ferrite beads

Real-World CAN Protocols

1. CANopen (Industrial Automation)

  • Standard: CEN EN 50325-4
  • Use case: Industrial machinery, robotics
  • Features: Object dictionary, SDO (Service Data Objects), PDO (Process Data Objects), NMT (Network Management)

2. J1939 (Heavy-Duty Vehicles)

  • Standard: SAE J1939
  • Use case: Trucks, buses, agricultural equipment
  • Features: 29-bit extended IDs, Parameter Group Numbers (PGN), multi-packet transport

Example J1939 ID:

29-bit ID breakdown:
- Priority (3 bits)
- Reserved (1 bit)
- Data Page (1 bit)
- PDU Format (8 bits)
- PDU Specific (8 bits)
- Source Address (8 bits)

3. OBD-II (Automotive Diagnostics)

  • Standard: ISO 15765-4 (CAN)
  • Use case: Vehicle diagnostics, emission testing
  • IDs: 0x7DF (request), 0x7E8-0x7EF (response)

Example:

// Request engine RPM (PID 0x0C)
txData[0] = 0x02;  // Length
txData[1] = 0x01;  // Mode 1 (current data)
txData[2] = 0x0C;  // PID 0x0C (engine RPM)

Practical Use Cases

Example 1: Engine Control Unit (ECU) Communication

Network:

  • Engine ECU: Transmits RPM, temperature, throttle position
  • Dashboard: Displays values
  • Transmission ECU: Adjusts shift points based on RPM

Message IDs (priority-based):

  • 0x100 (highest priority): Critical engine fault
  • 0x200: Engine RPM (10 ms interval)
  • 0x201: Coolant temperature (100 ms interval)
  • 0x300: Throttle position (20 ms interval)

Example 2: Industrial Sensor Network

Setup:

  • 10 temperature sensors on CAN bus
  • Each sensor has unique ID
  • PLC collects data and controls HVAC

Python CAN with python-can library:

import can
import time

# Initialize CAN bus
bus = can.interface.Bus(channel='can0', bustype='socketcan')

# Send request to sensor 5
msg = can.Message(arbitration_id=0x105, data=[0x01], is_extended_id=False)
bus.send(msg)

# Receive response
response = bus.recv(timeout=1.0)
if response:
    temp = (response.data[0] << 8) | response.data[1]
    temp_celsius = temp / 10.0
    print(f"Sensor 5 temperature: {temp_celsius}°C")

Conclusion

CAN bus is essential for:

  • Automotive electronics (OBD-II, ECUs, body control)
  • Industrial automation (PLCs, distributed I/O)
  • Medical devices (patient monitoring, surgical equipment)
  • Aerospace (avionics, flight control)

Key takeaways:

  • Understand arbitration (lower ID = higher priority)
  • Configure bit timing correctly (use online calculators if needed)
  • Use filters to reduce interrupt load
  • Add 120Ω termination resistors
  • Debug with CAN analyzers (PEAK, CANable, Wireshark)

For your next CAN project:

  1. Choose a microcontroller with built-in CAN (STM32, ESP32-C6) or use MCP2515
  2. Add a CAN transceiver (TJA1050, SN65HVD230)
  3. Calculate bit timing for your target baudrate
  4. Configure filters for relevant message IDs
  5. Test with a CAN analyzer before deploying

Further Reading


Are you using CAN bus in your projects? What challenges have you faced? Share your experience in the comments.