Functional Driver Bring-up: From Register Access to CAN Frame Communication on AM335x D-CAN

Overview

Driver bring-up is often an iterative process.

Instead of implementing a complete driver immediately, I gradually verified every layer of the hardware and software stack until a complete CAN communication path was established.

This post summarizes the bring-up methodology used for the AM335x D-CAN controller on BeagleBone Black.


Step 1. Verify Register Access

The first goal is not sending CAN frames.

It is simply verifying that the CPU can access the controller registers.

Example:

printf("DCAN1 base: 0x%08lx\n", SOC_DCAN_1_REGS);
printf("CTL = 0x%08lx\n", REG32(SOC_DCAN_1_REGS + DCAN_CTL));

If register values can be read consistently, the MMIO mapping is working.


Step 2. Enable Peripheral Clock

Many peripherals are inaccessible until their clocks are enabled.

Configure:

CM_PER_DCAN1_CLKCTRL

Verify:

MODULEMODE = ENABLE
IDLEST = Functional

Only after this step can the controller operate normally.


Step 3. Configure Pin Multiplexing

Even if the controller is initialized correctly, communication will fail if the pins are still configured as UART or GPIO.

Configure:

P9.24 -> DCAN1_TX
P9.26 -> DCAN1_RX

Verify pad configuration registers.


Step 4. Enter Initialization Mode

Before modifying controller configuration:

INIT = 1
CCE = 1

This allows:

  • Bit timing configuration
  • Message Object configuration

without active CAN communication.


Step 5. Configure Bit Timing

Determine:

  • Controller input clock
  • Desired bitrate
  • BRP
  • TSEG1
  • TSEG2

Verify:

24 MHz
↓

125 kbps

↓

BTR = 0x00000517

Step 6. Configure TX Message Object

Configure:

  • Message ID
  • DLC
  • Data
  • MSGVAL
  • DIR

Transfer configuration through IF registers into Message Object memory.

Issue:

TX Request

Step 7. Validate Hardware Independently

Before debugging RTEMS software, validate the physical CAN path using Linux.

Topology:

BBB Linux
    ↓
DCAN1
    ↓
CAN Transceiver
    ↓
CAN Bus
    ↓
CANable
    ↓
UTM Linux SocketCAN

Successfully receiving frames with candump confirms:

  • Controller
  • Transceiver
  • Wiring
  • Termination
  • USB adapter
  • SocketCAN

are all functioning correctly.


Step 8. Validate RTEMS Transmission

Switch to RTEMS.

Topology:

RTEMS BBB
    ↓
DCAN1
    ↓
CAN Bus
    ↓
CANable
    ↓
UTM Linux

Verify:

candump can0

Frame received:

123 [8] 11 22 33 44 55 66 77 88

This proves the TX path is functional.


Step 9. Configure RX Message Object

Create a receive Message Object.

Configure:

  • MSGVAL
  • RXIE
  • UMASK
  • ID filtering

Transfer configuration into Message Object memory.

Verify configuration by reading it back.


Step 10. Validate RTEMS Reception

Send from Linux:

cansend can0 123#1122334455667788

Initially:

NWDAT = 0

but

RXOK = 1

Using RXOK as the trigger to force a Message Object read successfully returned:

ID  = 0x123

DLC = 8

Data:
11 22 33 44 55 66 77 88

This confirmed that:

  • CAN controller received the frame
  • Message Object stored the frame correctly
  • IF register transfer works
  • Frame decoding works

Step 11. Compare with Previous Driver

Study the previous implementation:

dcan_init_rxobj()

dcan_read_obj()

dcan_save_msg()

dcan_isr()

instead of immediately integrating into the RTEMS CAN stack.

Understanding the receive flow first makes future integration much easier.


Bring-up Philosophy

The bring-up process can be summarized as:

Register Access
        ↓
Clock
        ↓
Pinmux
        ↓
Bit Timing
        ↓
TX Message Object
        ↓
Hardware Validation
        ↓
TX Functional
        ↓
RX Message Object
        ↓
RX Functional
        ↓
Interrupt Handling
        ↓
RTEMS CAN Stack Integration

Each layer builds upon the previous one.

If one layer is verified independently, debugging becomes significantly easier.


Lessons Learned

A functional driver should not be developed by implementing everything at once.

Instead:

  • verify hardware first,
  • validate each register operation,
  • prove TX independently,
  • prove RX independently,
  • then integrate with the operating system framework.

This incremental bring-up methodology greatly reduces debugging complexity and provides clear checkpoints throughout driver development.