Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 32

Building a CAN Driver: From Bit-Timing to Message Handling

Recommended resources

Hello! Welcome to your next lesson in preparation for your interview.

In our last session, we built an interrupt-driven I2C driver using a state machine. That design pattern is perfect for protocol-heavy peripherals where multiple steps must be managed without blocking the CPU. You demonstrated how to handle a sequence of events (START, ADDR, DATA, STOP) gracefully.

Today, we'll develop a driver for another cornerstone of automotive engineering: the Controller Area Network (CAN). Your extensive experience with AUTOSAR communication stacks means you're already an expert on CAN from a systems perspective. This lesson will focus on the low-level, bare-metal implementation details that an interviewer for a driver development role will probe for.

Our learning outcome is to develop a CAN driver, including bit-timing calculation, message filtering, and handling transmission and reception. We will cover the three fundamental pillars of a robust CAN driver, moving from the physical timing layer up to the application data interface.

1. CAN from a Driver Developer's Perspective

While I2C is a master-slave bus, CAN is a multi-master, message-based protocol. There are no slave addresses; instead, nodes broadcast messages with specific IDs, and any node can choose to listen. The hardware CAN peripheral provides significant support for this, typically including:

  • Transmit Mailboxes/FIFOs: You load a full message, and the hardware handles arbitration and transmission.
  • Receive FIFOs: The hardware stores incoming messages that have passed a filter.
  • Hardware Filtering: Powerful mechanisms to make the peripheral accept only the message IDs you care about, drastically reducing CPU overhead.

Our driver will be structured to configure and manage these hardware blocks. Let's start with a quick overview of the CAN protocol's core principles.

STM32 Tutorial #48 - HOWTO use CAN Peripheral

This video provides a great introduction to the fundamentals of CAN, such as its differential signaling for noise immunity and the multi-master concept. Since you're already familiar with CAN, you can watch this first part as a quick refresher.

Watch the section 'Introduction to CAN' from the beginning to 04:19. It provides excellent context on the physical and logical aspects of the bus, which underpins the driver's requirements.


2. The Foundation: CAN Bit Timing Calculation

One of the most common—and feared—interview topics for a CAN driver role is bit timing. An incorrect configuration will prevent any communication. Unlike a simple UART baud rate, a CAN bit rate is derived from several configurable segments within a single bit's time.

A nominal bit time is divided into four segments measured in units called Time Quanta (TQ):

  1. Synchronization Segment (Sync_Seg): Always 1 TQ long. Used to synchronize the clocks of all nodes on the bus. An edge is expected here.
  2. Propagation Segment (Prop_Seg): Compensates for the physical signal delay across the bus wiring and through transceivers.
  3. Phase Segment 1 (PS1): Used to compensate for phase errors. It can be lengthened during resynchronization.
  4. Phase Segment 2 (PS2): Also compensates for phase errors. It can be shortened during resynchronization. The Sample Point, where the bus level is actually read, is located between PS1 and PS2.

PIC32 CAN Module Block Diagram

A block diagram of a typical CAN peripheral, showing the protocol engine, message buffers (FIFOs), and acceptance filters. These are the core components our driver will manage. (Source: PIC32 Family Reference Manual)

To get a clear, visual understanding of these segments, the following short video is excellent.

Kvaser CAN Protocol Course: CAN Bit Timing (Part 7)

This video from Kvaser, a leading CAN tools company, breaks down the structure of a CAN bit and explains the purpose of each time segment. It's a foundational concept for the calculations we're about to perform.

Watch the first two sections, 'The Layout of a Bit' (0:07 - 1:13) and 'Clock Synchronization' (1:13 - 1:48). Pay close attention to the definition of the four segments and the role of the Synchronization Jump Width (SJW).

Calculating the Register Values

Now, let's turn theory into practice. Your goal is to choose a number of TQ per bit and a Baud Rate Prescaler (BRP) value that, when combined, produce the desired bit rate from your system's peripheral clock.

The key formulas are:

  1. Time Quantum Frequency ():
  2. Baud Rate Prescaler (BRP):

    (Note: Some datasheets, like the PIC32 one, include a factor of 2, others do not. This is a critical detail.)
    The value written to the register is typically BRP - 1.
  3. Total TQ per bit:
    \text{Total TQ} = \text{Sync_Seg} + \text{Prop_Seg} + \text{PS1} + \text{PS2}
    (Remember, Sync_Seg is always 1 TQ).

For a detailed walkthrough of this calculation, the PIC32 Family Reference Manual provides a clear, step-by-step example.

Section 34. Controller Area Network (CAN)

This document section provides the formulas and a worked example for calculating bit timing parameters. It's a perfect text-based companion to the video you just watched.

Please read section 34.10, 'BIT TIMING'. Focus on the formulas in 34.10.4 and the worked example in 34.10.4.3. This will solidify your understanding of how to derive register values from system requirements.

Test your understanding!

Let's say your microcontroller's peripheral clock () is 40 MHz. You need to configure the CAN bus for a 500 kbps bit rate. You decide to use a total of 16 TQ per bit. Assume the formula for the prescaler is .

  1. Calculate .
  2. Calculate the value to be written into the Baud Rate Prescaler register.
  3. If you set Prop_Seg = 3 TQ, PS1 = 6 TQ, and PS2 = 6 TQ, where is the sample point located as a percentage of the total bit time?
Show answer
  1. Calculate :

  2. Calculate BRP Register Value:

    This is not an integer, so our initial choice of parameters is invalid! This is a common real-world problem. Let's adjust. If we choose 20 TQ per bit:


    • This works. You would write 1 to the BRP register.
  3. Calculate Sample Point: The sample point is after Sync_Seg, Prop_Seg, and PS1.


    A sample point between 60% and 80% is generally considered robust.


3. Driver Implementation: Core Functionality

With timing configured, we can implement the main driver functions. We'll use the STM32 tutorial as a practical guide for the code structure, focusing on a loopback test which is an excellent way to validate a driver without external hardware.

Initialization and Filtering

Your CAN_Init() function will configure GPIOs, clocks, and the CAN peripheral's operating mode. A critical part of initialization is setting up the acceptance filters.

STM32 Tutorial #48 - HOWTO use CAN Peripheral

This section of the STM32 tutorial demonstrates how to configure the peripheral for loopback mode and, most importantly, how to set up the hardware acceptance filters. This is a core competency for writing an efficient CAN driver.

First, watch the configuration part from 13:13 to 14:15 to see the operating mode and baud rate being set. Then, jump to the detailed filtering section from 23:18 to 34:04. Focus on the concepts of filter banks, IDs, and masks. The demonstration of how a mask can be used to accept a range of IDs or all IDs is particularly valuable.

A filter configuration function, CAN_ConfigFilter(), would look something like this conceptually:

// Example of configuring a filter in ID/Mask mode
void CAN_ConfigFilter(void) {
    CAN_FilterTypeDef filter_config;

    filter_config.FilterBank = 0; // Use filter bank 0
    filter_config.FilterMode = CAN_FILTERMODE_IDMASK; // Use ID/Mask mode
    filter_config.FilterScale = CAN_FILTERSCALE_32BIT; // 32-bit scale for standard/extended IDs

    // To receive ID 0x123 and 0x124, but not 0x125
    // ID:   0x123 -> ... 001 0010 0011
    // ID:   0x124 -> ... 001 0010 0100
    // Mask:          ... 111 1111 1110 (ignore the last bit)
    filter_config.FilterIdHigh = (0x123 << 5); // Set the ID (register layout dependent)
    filter_config.FilterIdLow = 0x0000;
    filter_config.FilterMaskIdHigh = (0x7FE << 5); // Set the mask (0x7FF = all bits matter)
    filter_config.FilterMaskIdLow = 0x0000;

    filter_config.FilterFIFOAssignment = CAN_RX_FIFO0; // Assign to FIFO 0
    filter_config.FilterActivation = ENABLE;
    
    // In a real implementation, you would write these values to the
    // CAN filter registers (e.g., CAN_F0R1, CAN_FMR1 on STM32)
    // HAL_CAN_ConfigFilter(&hcan, &filter_config); 
}

Transmission and Reception

Transmission in CAN is simpler than in I2C because the hardware handles the complex parts. Your CAN_Transmit() function's job is to:

  1. Check if a transmit mailbox is free.
  2. Populate a transmit header structure with the ID, DLC (Data Length Code), and frame type (Data/Remote).
  3. Copy the payload data into a buffer.
  4. Request transmission. The hardware then takes over, waits for bus access, and handles arbitration.

Reception is best handled with interrupts. When a message passes the filter, it's placed in a receive FIFO, and a "message pending" interrupt is generated. The ISR for this interrupt must:

  1. Identify which FIFO has the message.
  2. Read the header (ID, DLC) and the data payload from the FIFO.
  3. Copy the message to an application-level buffer or queue for processing in the main context.
  4. Clear the interrupt pending flag.

STM32 Tutorial #48 - HOWTO use CAN Peripheral

Let's see how transmission and reception are implemented in code. This part of the tutorial shows how to prepare a message, request transmission, and how to implement the interrupt callback to process received messages.

Watch the section on transmission from 18:08 to 22:07, and the section on setting up receive callbacks from 16:51 to 18:08. Notice how the transmit function is non-blocking—it just queues the message in a mailbox. The reception is handled asynchronously in the callback.


4. Interview Preparation

Your deep AUTOSAR experience is a huge asset. An interviewer will want to see that you can connect that high-level knowledge to the metal.

  • Question: "Walk me through how you would configure the CAN bit timing for 1 Mbps, given an 80 MHz peripheral clock."

    • Your Answer: "First, I'd choose the total number of Time Quanta (TQ) per bit. A good starting point is 16 or 20 TQ. Let's use 16. The TQ frequency would be 1 MHz * 16 = 16 MHz. Next, I'd calculate the Baud Rate Prescaler. Assuming the formula is BRP = F_pclk / (2 * F_tq), the BRP value would be 80 / (2 * 16) = 2.5. Since this isn't an integer, 16 TQ is not a valid choice. I'd try 20 TQ: F_tq is 20 MHz, so BRP = 80 / (2 * 20) = 2. This works. I'd write 1 (BRP-1) to the prescaler register. Finally, I'd partition the remaining 19 TQ (20 total - 1 for Sync) among the Prop_Seg, PS1, and PS2 to place the sample point correctly, for example, at 80% (16 TQ), which is very common for CAN-FD and robust for classical CAN."
  • Question: "Your system is flooded with CAN messages, and performance is suffering. What's the first thing you would check in the CAN driver?"

    • Your Answer: "The first thing I would check is the hardware acceptance filter configuration. If the filters are too permissive or disabled, the CPU is being interrupted for every single message on the bus, most of which might be irrelevant to the node. By configuring the hardware filters to accept only the specific message IDs this ECU needs to process, we can offload the entire filtering task to the CAN peripheral and ensure the CPU only wakes up for useful data. This is the most effective way to reduce CPU load from a high-traffic CAN bus."
  • Question: "Describe the architecture of your CAN driver. How would you handle Tx and Rx?"

    • Your Answer: "My driver would have a layered API. An Init function would handle clock, GPIO, and mode configuration, including bit timing and filters. For transmission, I'd implement a non-blocking CAN_Transmit function. It would find an empty hardware transmit mailbox, load it with the message ID, DLC, and data, and then trigger the transmission. The hardware handles the rest. For reception, I'd use an interrupt-driven approach. A 'message pending' interrupt would fire when a valid message is received in a hardware FIFO. The ISR would read the message from the FIFO, place it into a thread-safe circular buffer or an RTOS queue, and signal a higher-level task to process it. This decouples the ISR from the application logic and keeps the ISR short and efficient."
  • Question: "How would you test your CAN driver without any other hardware?"

    • Your Answer: "I would use the CAN peripheral's built-in loopback mode. In this mode, the peripheral's transmitter output is internally connected to its receiver input. This allows me to transmit a message and immediately receive it through my own driver's interrupt path. It's a perfect way to verify the entire software stack—bit-timing, framing, filtering, Tx path, and Rx path—on a single device without needing a physical bus, a second node, or even a transceiver."

Conclusion

In this lesson, we've dissected the three essential components of a bare-metal CAN driver: precise bit-timing calculation, efficient hardware message filtering, and the logic for transmission and interrupt-driven reception. You have moved from the abstract world of the AUTOSAR COM stack down to the register level, which is precisely the expertise required for this role.

Key Takeaways:

  • CAN bit timing is a multi-segment calculation involving TQ, BRP, and segment lengths, crucial for bus synchronization.
  • Hardware acceptance filters are the most powerful tool for managing bus traffic and reducing CPU load.
  • The driver architecture should leverage the hardware: a "fire-and-forget" approach for transmission mailboxes and an interrupt-driven model for receive FIFOs.
  • Loopback mode is an indispensable tool for unit testing a CAN driver in isolation.

Next Lesson Preview:
You have now built drivers for SPI, I2C, and CAN. You are well-versed in managing hardware on the bare metal. The next logical step is to manage software. We will now begin Module 5: Real-Time Operating Systems (RTOS) Concepts. Our first lesson will introduce the core components of an RTOS, including the scheduler, tasks, and kernel objects, laying the groundwork for building complex, multitasking embedded systems.