GUIDE #04 — CAN Bus · SocketCAN · Linux

CAN Bus Setup on
BeagleBone Black

From a 2015 Debian 7.8 eMMC image to a fully working SocketCAN interface on Debian 13.5 — every step documented, including the ones that didn't go as planned.

BeagleBone BlackBoard
Debian 13.5OS
6.18.32-bone35Kernel
SN65HVD230Transceiver

A Note Before You Start

This guide documents the full journey — not just the happy path. The BBB's onboard eMMC came with Debian 7.8 from 2015, which has no SocketCAN support whatsoever. Getting CAN working required a newer OS, and that meant choosing how to deliver it.

Throughout this project we explored three approaches, in order of increasing permanence:

PATH A
microSD boot (with S2 button)
Run the new system from a microSD card. Works well in theory — but requires holding the S2 button every time the board powers up, and the card can fail unexpectedly.
PATH B
Persistent SD boot (uEnv.txt trick)
Modify the eMMC's uEnv.txt so the board always prefers the SD card without S2. Simpler day-to-day, but still dependent on the SD card.
PATH C
Write new Debian directly to eMMC (recommended)
The cleanest solution — no SD card needed, S2 never required again. Covered in Section 10.

If you just want CAN working as quickly and permanently as possible, scroll to Section 10. Otherwise, read from the top.

What We Did — At a Glance

  • Identified that Debian 7.8 / kernel 3.8.x lacks SocketCAN support entirely
  • Chose Debian 13.5 IoT (v6.18.x) — most stable CAN-capable image for AM335x
  • Flashed the image to microSD using Balena Etcher
  • Set credentials in sysconf.txt via Notepad before first boot
  • Booted from microSD by holding the S2 button while applying power
  • Installed UsbNcm Host Device driver manually on Windows 10 via Device Manager
  • Verified USB network connectivity with ping to 192.168.7.2
  • Connected via PuTTY over SSH and confirmed Debian 13.5 + kernel 6.18.x
  • Loaded SocketCAN kernel modules: can, can_raw, c_can, c_can_platform
  • Enabled BB-CAN0-00A0.dtbo overlay in /boot/uEnv.txt
  • Verified can0 interface with loopback test using candump + cansend
  • Wired SN65HVD230 transceiver to P9.19 (RX) and P9.20 (TX)
  • Permanently flashed Debian 13.5 to eMMC via USB stick — SD card no longer required
  • Controlled a BBB LED over CAN bus from ESP32 serial monitor commands

Why the Stock Image Won't Work

BeagleBone Black boards often run a very old Debian image stored on the onboard eMMC. In our case the board was running Debian 7.8 with kernel 3.8.13-bone72, released in 2015.

⚠ Problem Kernel 3.x does not include the c_can / c_can_platform SocketCAN drivers. Running modprobe c_can fails — the module simply does not exist in this kernel.

The solution is to run a modern Debian image. We chose Debian 13.5 with kernel v6.18.x — the most stable CAN-capable branch for AM335x at the time of writing.

Choosing & Flashing the Image

STEP 1

Download the correct image

https://files.beagle.cc/file/beagleboard-public-2021/images/
am335x-debian-13.5-iot-armhf-2026-05-19-4gb.img.xz
ℹ Why v6.18.x and not v6.18.x?v6.18 is the version used in this guide — confirmed working with SocketCAN and device tree overlays on AM335x.
STEP 2

Flash with Balena Etcher

  • Click Flash from file → select the .img.xz file
  • Click Select target → select your microSD card
  • Click Flash and wait for verification to complete
⚠ Multiple partitions errorIf Balena Etcher fails, open Disk Management, delete all partitions on the SD card until only Unallocated space remains, then retry. Alternatively use Win32DiskImager after extracting with 7-Zip.
STEP 3

Set credentials in sysconf.txt

⚠ Use Notepad, not WordPadWordPad adds CRLF line endings which can corrupt the file.
# Remove the # and set your values:
user_name=beagle
user_password=beagle
root_password=debian

Booting from microSD

STEP 4

Boot sequence

  • Disconnect all power from the BBB
  • Insert the microSD card
  • Hold the S2 button (small button next to the microSD slot)
  • Connect the USB cable while keeping S2 held
  • Release S2 after 5–6 seconds
  • Wait 3–5 minutes for first boot to complete
ℹ First boot is slowsysconf.txt is processed during first boot — user accounts and SSH keys are created. Be patient before attempting to connect.

Making SD Boot Persistent (No S2 Required)

STEP 4B

Mount the eMMC and edit its uEnv.txt

sudo su
mkdir -p /mnt/emmc2
mount /dev/mmcblk1p2 /mnt/emmc2
echo "mmcdev=0" >> /mnt/emmc2/boot/uEnv.txt
sync && reboot
✓ ResultThe board now boots from SD automatically without S2. Falls back to eMMC if SD is absent.
⚠ CaveatThis setting lives on the eMMC. If you flash a new image to eMMC later, redo this step — or go straight to Section 10.

Installing the USB Network Driver on Windows

STEP 5

Assign UsbNcm driver via Device Manager

  • Open Device Manager — Win + X → Device Manager
  • Find "Unknown USB Device" or "CDC NCM" under Other Devices
  • Right-click → Update Driver → Browse → Let me pick
  • Network Adapters → uncheck "Show compatible hardware"
  • Manufacturer: Microsoft → Model: UsbNcm Host Device
✓ SuccessYellow warning icon disappears and a new network adapter appears.
STEP 6

Verify connectivity

c:\windows\system32\ping.exe 192.168.7.2
Reply from 192.168.7.2: bytes=32 time=1ms TTL=64 Packets: Sent = 4, Received = 4, Lost = 0 (0% loss)

Connecting via PuTTY

STEP 7

PuTTY connection settings

FieldValue
Host Name192.168.7.2
Port22
Connection TypeSSH
Loginbeagle
Passwordbeagle
sudo su
cat /etc/debian_version  → 13.5
uname -r               → 6.18.32-bone35

Loading CAN Kernel Modules

STEP 8

Load the SocketCAN stack

modprobe can && modprobe can_raw && modprobe c_can && modprobe c_can_platform
lsmod | grep can
c_can_platform 12288 0 c_can 24576 1 c_can_platform can_raw 16384 0 can 20480 1 can_raw

Enabling CAN0 via Device Tree Overlay

STEP 9

Confirm overlay file exists

find / -name "*CAN0*" 2>/dev/null
/boot/dtbs/6.18.32-bone35/overlays/BB-CAN0-00A0.dtbo
STEP 10

Edit /boot/uEnv.txt

nano /boot/uEnv.txt
# Find and change:
#uboot_overlay_addr0=<file0>.dtbo
# To:
uboot_overlay_addr0=BB-CAN0-00A0.dtbo
# Ctrl+X → Y → Enter → reboot
STEP 11

Verify can0 after reboot

ip link show can0
4: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can
✓ can0 existsDOWN is expected — no transceiver yet. The interface is present and ready.

Testing with Loopback Mode

STEP 12

Bring up can0 in loopback mode

ip link set can0 down
ip link set can0 up type can bitrate 500000 loopback on
STEP 13

Send and receive a test frame

Terminal 1:

candump can0

Terminal 2:

cansend can0 123#DEADBEEF
can0 123 [4] DE AD BE EF can0 123 [4] DE AD BE EF
✓ CAN Bus is workingThe SocketCAN stack is fully operational.

Connecting the CAN Transceiver

BBB PinSN65HVD230Description
P9.20TXTransmit data to bus
P9.19RXReceive data from bus
P9.01GNDGround — must be connected
P9.03VCC3.3V power supply
⚠ Termination resistorEach end of the CAN bus requires a 120 Ω resistor between CANH and CANL. The SN65HVD230 module includes a built-in resistor — on most modules: no jumper = resistor active.
⚠ GND pin is criticalDuring this project the transceiver GND pin (P9.01) was accidentally left disconnected. The system appeared to work but signal quality was unreliable. Always verify P9.01 is connected before debugging CAN issues.
ip link set can0 down
ip link set can0 up type can bitrate 500000
ip link show can0
# state UP when bus is active

Auto-starting can0 on Boot

STEP 14

Create the init script and systemd service

cat > /usr/local/bin/can-init.sh << 'EOF'
#!/bin/bash
ip link set can0 down 2>/dev/null || true
ip link set can0 type can bitrate 500000
ip link set can0 up
EOF
chmod +x /usr/local/bin/can-init.sh

cat > /etc/systemd/system/can-init.service << 'EOF'
[Unit]
Description=CAN bus init
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/can-init.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable can-init.service
systemctl start can-init.service
✓ Resultcan0 is brought up automatically at every boot. No manual commands needed.

Writing Debian Directly to eMMC

Why we ended up here

Running from a microSD card worked — but SD cards can corrupt unexpectedly, require S2 on every boot, and add one more point of failure. After one SD card became unreadable mid-session, we wrote the new Debian image directly onto the eMMC. The result: the board boots to Debian 13.5 with full CAN support from power-up, every time, with no SD card and no button-holding ritual.

STEP A

Boot from eMMC (old Debian 7.8) as a staging environment

Remove the SD card and power up normally — the board boots into the old eMMC system. Connect via PuTTY (192.168.7.2). This gives us a running Linux environment to write the new image from.

STEP B

Put the image file on a USB stick and mount it

sudo su
mkdir -p /mnt/usb
mount /dev/sda1 /mnt/usb
ls /mnt/usb
# Confirm: am335x-debian-13.5-base-v6.18-armhf-2026-05-19-4gb.img
STEP C

Identify the eMMC block device

lsblk
mmcblk0 3.6G ← eMMC (target) mmcblk1 14.7G ← SD card (if present) sda 15.1G ← USB stick
⚠ Double-check before writingConfirm mmcblk0 is the eMMC (3.6G) and sda is the USB stick. Writing to the wrong device will overwrite it permanently.
STEP D

Write the image to eMMC

dd if=/mnt/usb/am335x-debian-13.5-base-v6.18-armhf-2026-05-19-4gb.img \
   of=/dev/mmcblk0 bs=4M status=progress
⚠ Do not interrupt this commandThe write takes ~10–15 minutes. PuTTY session must stay open throughout. If interrupted, eMMC will be left in an unbootable state. Run sync when done.
3774873600 bytes (3.8 GB) copied, 411 s, 9.2 MB/s
sync
STEP E

Remove SD card, reboot, reconnect

umount /mnt/usb && reboot

After ~3 minutes, reconnect via PuTTY. On first boot you'll be prompted to change the password.

sudo su
cat /etc/debian_version  → 13.5
ip link show can0        → state DOWN (ready)
✓ eMMC now runs Debian 13.5The board boots to a modern CAN-capable system every time — no SD card, no button. Re-apply can-init.service from Section 09 to restore automatic can0 startup.

Controlling a BBB LED over CAN Bus

With the CAN bus working, this section adds a real-world output: an LED connected to a BBB GPIO pin, controlled by commands sent from an ESP32 over the CAN bus. Type 1 in the ESP32 serial monitor — the LED lights up. Type 0 — it turns off.

STEP 15

Hardware — wiring the LED to BBB

ComponentBBB PinNotes
LED anode (+)P9_12 (GPIO 540)Long leg of the LED — connects to GPIO output
330 Ω resistorBetween LED cathode and GND220–470 Ω range all work. Limits current.
LED cathode (−)P9_1 (GND)Short leg — through the resistor to GND
P9_12 (GPIO 540) ──────────────── LED anode (+) │ LED │ LED cathode (−) [330Ω] P9_1 (GND) ────────────────
⚠ LED polarityThe long leg is the anode (+) — this connects to P9_12. The short leg is the cathode (−) and goes through the resistor to GND.
STEP 16

Finding the correct GPIO number for P9_12

On kernel 6.x, GPIO numbers are offset from the gpiochip base — they are not the same as the physical pin number. The correct number must be calculated before export.

Step 1 — Find P9_12's line number:

gpioinfo 2>/dev/null | grep P9_12
line  28:  "P9_12"  input
# P9_12 is line 28

Step 2 — Find the gpiochip base:

ls /sys/class/gpio/ | grep gpiochip
gpiochip512
gpiochip544
gpiochip576
gpiochip608
# The first chip starts at 512

Step 3 — Calculate the GPIO number:

# GPIO number = gpiochip base + line number
# GPIO number = 512 + 28 = 540
ℹ Why 512?On Debian 13.5 with kernel 6.x the AM335x GPIO controller registers its first chip at base 512. Older kernels started at 0 — this is why GPIO numbers differ across kernel versions.
STEP 17

Export, verify, then control

Always verify the pin state before writing to it — confirm the direction and value are consistent with what you expect.

Export the GPIO:

echo 540 > /sys/class/gpio/export

This file is a special kernel interface — writing a GPIO number to it causes the kernel to create a new directory with all the files needed to control that pin:

# Before export:
/sys/class/gpio/
├── export
├── unexport
├── gpiochip512/
└── gpiochip544/

# After:  echo 540 > /sys/class/gpio/export
/sys/class/gpio/
├── export
├── unexport
├── gpio540/              ← this directory was created
│   ├── direction         ← set "in" or "out"
│   ├── value             ← read or write 0 / 1
│   ├── active_low        ← logic polarity
│   └── edge
├── gpiochip512/
└── gpiochip544/
echo out > /sys/class/gpio/gpio540/direction

Verify — check direction and current value:

cat /sys/class/gpio/gpio540/direction
out                          ← must be "out" before writing

cat /sys/class/gpio/gpio540/value
0                            ← 0 = LOW = LED off (expected at start)

cat /sys/class/gpio/gpio540/active_low
0                            ← 0 = normal logic (1=HIGH=LED on)
ℹ What these values mean direction: out — pin is configured as output, we can write to it.
value: 0 — pin is currently LOW, LED should be off.
active_low: 0 — normal logic: writing 1 = HIGH = LED on, writing 0 = LOW = LED off.

Check can0 is UP before proceeding:

ip link show can0
4: can0: <NOARP,ECHO> mtu 16 ... state DOWN ...  ← DOWN = not ready
4: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 ... state UP ...  ← UP = ready

If state is DOWN, bring it up:

ip link set can0 type can bitrate 500000
ip link set can0 up
ip link show can0
4: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 ... state UP ...  ← now UP
ℹ can-init.serviceIf you set up the systemd service from Section 09, can0 comes up automatically on every boot and this step is not needed.

Now turn the LED on and off:

echo 1 > /sys/class/gpio/gpio540/value   # LED ON  — verify visually
cat /sys/class/gpio/gpio540/value
1                                         ← confirms HIGH

echo 0 > /sys/class/gpio/gpio540/value   # LED OFF — verify visually
cat /sys/class/gpio/gpio540/value
0                                         ← confirms LOW
✓ LED responds correctlyvalue reads 1 when LED is on, 0 when off — logic is consistent. can0 is UP. Wiring is correct. Proceed to CAN integration.
STEP 18

Transfer can_led.c to BBB

First, find the correct home directory on your BBB — it may be /home/beagle or /home/debian depending on the image:

ls /home/
debian   ← use this name in the path below

Option A — scp over USB network (from Windows Command Prompt):

scp can_led.c debian@192.168.7.2:/home/debian/can_led.c
ℹ scp not found?On Windows 10/11 scp is included by default. If missing, use WinSCP or PuTTY's pscp.exe.

Option B — paste directly via PuTTY:

cat > /home/debian/can_led.c << 'EOF'
// paste the full can_led.c source code here (from Step 19)
EOF

Option C — USB stick:

mkdir -p /mnt/usb && mount /dev/sda1 /mnt/usb
cp /mnt/usb/can_led.c /home/debian/can_led.c
umount /mnt/usb

Verify the file arrived:

ls -lh /home/debian/can_led.c
-rw-r--r-- 1 root root 1.1K Jun 11 10:00 /home/debian/can_led.c
← non-zero size confirms the file is complete
✓ File is on BBBProceed to compile.
STEP 19

Compile and run can_led.c on BBB

This program listens on can0 for any incoming frame. If DATA byte = 0x01, it turns the LED on. If 0x00, it turns it off.

// can_led.c — compile: gcc -o can_led can_led.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#define LED_GPIO "540"
#define GPIO_VAL "/sys/class/gpio/gpio540/value"

static void led_set(int on) {
    int fd = open(GPIO_VAL, O_WRONLY);
    if (fd < 0) { perror("gpio"); return; }
    write(fd, on ? "1" : "0", 1);
    close(fd);
}

static void gpio_init() {
    int fd = open("/sys/class/gpio/export", O_WRONLY);
    if (fd >= 0) { write(fd, LED_GPIO, strlen(LED_GPIO)); close(fd); }
    usleep(100000);
    fd = open("/sys/class/gpio/gpio540/direction", O_WRONLY);
    if (fd >= 0) { write(fd, "out", 3); close(fd); }
}

int main(int argc, char *argv[]) {
    const char *iface = argc > 1 ? argv[1] : "can0";
    gpio_init();
    led_set(0);

    int sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    struct ifreq ifr;
    strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
    ioctl(sock, SIOCGIFINDEX, &ifr);
    struct sockaddr_can addr = { .can_family = AF_CAN, .can_ifindex = ifr.ifr_ifindex };
    bind(sock, (struct sockaddr *)&addr, sizeof(addr));

    printf("Listening on %s — 0x01=ON  0x00=OFF\n", iface);
    struct can_frame f;
    while (1) {
        read(sock, &f, sizeof(f));
        if (f.can_dlc < 1) continue;
        printf("[RX] ID=0x%03X  DATA=0x%02X  → LED %s\n",
               f.can_id & CAN_SFF_MASK, f.data[0],
               f.data[0] ? "ON" : "OFF");
        led_set(f.data[0] ? 1 : 0);
    }
}
cd /home/debian
gcc -o can_led can_led.c
./can_led can0
STEP 20

ESP32 — main.cpp

ℹ Why no terminal commands for ESP32? BBB runs a full Linux operating system — you can type commands directly into the terminal and they execute instantly. ESP32 has no operating system, no terminal, and no file system. It is a bare-metal microcontroller: you write the code on your computer, compile it, and upload the binary to its flash memory (4MB of permanent storage that survives power loss). Every time the ESP32 powers up, it runs whatever is in flash — you cannot change its behaviour without re-uploading. This is why BBB uses gcc, echo, and ip link while ESP32 uses PlatformIO to upload pre-compiled code.

ESP32 memory at a glance:
RAM (520 KB) — temporary, cleared on power loss, holds variables and the running program
Flash (4 MB) — permanent, survives power loss, holds your uploaded code

Type 1 or 0 in the serial monitor. The ESP32 sends a 1-byte CAN frame to the bus; BBB receives it and controls the LED.

#include <Arduino.h>
#include <driver/twai.h>

#define TX_PIN GPIO_NUM_17
#define RX_PIN GPIO_NUM_16

void setup() {
  Serial.begin(115200);
  twai_general_config_t g = TWAI_GENERAL_CONFIG_DEFAULT(TX_PIN, RX_PIN, TWAI_MODE_NORMAL);
  twai_timing_config_t  t = TWAI_TIMING_CONFIG_500KBITS();
  twai_filter_config_t  f = TWAI_FILTER_CONFIG_ACCEPT_ALL();
  twai_driver_install(&g, &t, &f);
  twai_start();
  Serial.println("Ready — type 1 to turn LED ON, 0 to turn it OFF:");
}

static void send_led(uint8_t val) {
  twai_message_t msg = {};
  msg.identifier       = 0x123;
  msg.data_length_code = 1;
  msg.data[0]          = val;
  if (twai_transmit(&msg, pdMS_TO_TICKS(100)) == ESP_OK)
    Serial.printf("[TX] 0x%02X → LED %s\n", val, val ? "ON" : "OFF");
  else
    Serial.println("[TX] Failed");
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    if      (c == '1') send_led(0x01);
    else if (c == '0') send_led(0x00);
  }
}
STEP 21

Run it — expected output

With ./can_led can0 running on BBB and the ESP32 flashed and connected:

ESP32 serial monitor — type '1': [TX] 0x01 → LED ON BBB terminal: [RX] ID=0x123 DATA=0x01 → LED ON ← LED lights up ESP32 serial monitor — type '0': [TX] 0x00 → LED OFF BBB terminal: [RX] ID=0x123 DATA=0x00 → LED OFF ← LED turns off
✓ CAN-controlled LED workingA real GPIO output on a Linux board is now being toggled by a microcontroller over a differential bus. The same pattern scales directly to relays, motors, or any other actuator.

Command Reference

Windows — Command Prompt

CommandWhereWhat it does
c:\windows\system32\ping.exe 192.168.7.2CMDChecks if BBB is reachable over USB network.

BBB — PuTTY / SSH

CommandRun asWhat it does
sudo subeagleSwitches to root.
cat /etc/debian_versionanyShows installed Debian version.
uname -ranyShows running kernel version.
lsblkanyLists all block devices. Essential before any dd operation.
modprobe can / can_raw / c_can / c_can_platformrootLoads SocketCAN kernel modules.
lsmod | grep canrootLists loaded CAN-related kernel modules.
find / -name "*CAN0*" 2>/dev/nullrootFinds device tree overlay files for CAN0.
nano /boot/uEnv.txtrootEdits U-Boot environment — used to enable the CAN0 overlay.
ip link show can0rootShows can0 interface state.
ip link set can0 up type can bitrate 500000rootBrings up can0 at 500 kbps.
ip link set can0 up type can bitrate 500000 loopback onrootSame with loopback — for testing without hardware.
ip link set can0 downrootBrings down can0. Required before changing parameters.
candump can0rootListens on can0 and prints all received frames in real time.
cansend can0 123#DEADBEEFrootSends a CAN frame with ID 0x123 and 4 bytes of data.
gpioinfo 2>/dev/null | grep P9_12rootFinds P9_12's line number. Result: line 28. GPIO = gpiochip base (512) + line (28) = 540.
echo 540 > /sys/class/gpio/exportrootExports GPIO 540 (P9_12) for userspace control — creates /sys/class/gpio/gpio540/.
echo out > /sys/class/gpio/gpio540/directionrootSets GPIO 540 as output. Must be done before writing value.
cat /sys/class/gpio/gpio540/directionrootReads current direction — should return "out" after setup.
cat /sys/class/gpio/gpio540/valuerootReads current pin state — 0=LOW=LED off, 1=HIGH=LED on.
cat /sys/class/gpio/gpio540/active_lowrootReads logic polarity — 0=normal (1=on), 1=inverted (0=on).
echo 1 > /sys/class/gpio/gpio540/valuerootSets GPIO 540 HIGH — turns LED on.
echo 0 > /sys/class/gpio/gpio540/valuerootSets GPIO 540 LOW — turns LED off.
gcc -o can_led can_led.crootCompiles the CAN LED control program.
./can_led can0rootRuns the LED controller — listens for CAN frames and toggles GPIO.
dd if=<image> of=/dev/mmcblk0 bs=4M status=progressrootWrites a disk image to eMMC. Irreversible — verify device name first.
syncrootFlushes all pending writes to disk. Run after dd before rebooting.
rebootrootReboots the system.

CAN Bus Not Working — Three Layered Problems

During this project we encountered three separate issues, each masking the next. All three had to be resolved before CAN communication worked end-to-end.

Problem 1 — P9.19 / P9.20 Default to I2C Mode (Pinmux) ip link show can0 reports state UP and ERROR-ACTIVE, and cansend returns no error — but the SN65HVD230 transceiver receives nothing. The signal never reaches the physical pin.

On BBB running Debian 13.5 with kernel 6.x, pins P9.19 (DCAN0 RX) and P9.20 (DCAN0 TX) boot in I2C2 mode by default. The pinmux register value 0x30 means GPIO/I2C mode with the input receiver disabled.

cat /sys/kernel/debug/pinctrl/*/pins 2>/dev/null | grep -E "pin 116|pin 117"
pin 116 (PIN116)  44e109d0  0x00000030   ← I2C/GPIO mode, receiver DISABLED
pin 117 (PIN117)  44e109d4  0x00000030   ← I2C/GPIO mode, receiver DISABLED
Reference This exact issue is documented in the official BeagleBoard forum: Enabling CAN channels on BeagleBone Black (Nov 2025)
Key insight on kernel 6.x: the ocp node's P9_19_pinmux and P9_20_pinmux helpers must be explicitly disabled before the CAN overlay can claim those pins — otherwise I2C2 keeps them locked.
FIX

Load the system overlay — filename only, no path

The correct overlay is already compiled and present in the system:

find /boot/dtbs -name "BB-CAN0-00A0.dtbo" 2>/dev/null
/boot/dtbs/6.18.32-bone35/overlays/BB-CAN0-00A0.dtbo

Add to /boot/uEnv.txtfilename only, no path prefix:

uboot_overlay_addr4=BB-CAN0-00A0.dtbo
⊕ Warning Do not prefix with /lib/firmware/. On kernel 6.x, U-Boot looks in /boot/dtbs/ automatically. A path prefix causes silent load failure.

After reboot, verify the overlay loaded:

sudo beagle-version 2>/dev/null | grep "Loaded Overlay"
UBOOT: Loaded Overlay:[BB-CAN0-00A0.kernel]   ← must appear here

Note: the pinmux registers may still display 0x30 after reboot — this is a kernel 6.x debug readout quirk. The overlay is applied correctly; verify by testing with cansend.

Problem 2 — Missing Bypass Capacitor on SN65HVD230 Breakout ESP32 TWAI reports high RX error counter (128+) and enters bus-off state even when BBB is transmitting correctly. Frames appear corrupted or unreadable.

One SN65HVD230 breakout board had its bypass capacitor (C1, 100nF, VCC to GND) accidentally removed during rework. Without it, the 3.3V supply is noisy — the transceiver produces corrupted differential output and cannot reliably sample incoming signals.

Fix Solder a 100nF (0.1µF) ceramic capacitor between the VCC and GND pins of the SN65HVD230, as close to the chip as possible. A 10nF substitute works for basic testing but 100nF is the datasheet-recommended value.
Problem 3 — Extra Termination Resistor Lowering Bus Impedance Bus communication is unreliable or produces intermittent errors even with correct wiring.

CAN bus requires exactly two 120Ω termination resistors, one at each physical end of the cable. In parallel they produce the correct bus impedance of 60Ω:

120Ω ∥ 120Ω = 60Ω   ← correct bus impedance

Many SN65HVD230 breakout boards already have a 120Ω resistor soldered between CANH and CANL. If both boards have this resistor and an additional external resistor is added, three resistors appear in parallel:

120Ω ∥ 120Ω ∥ 120Ω = 40Ω   ← too low, causes signal integrity problems
Fix Count all termination resistors in the setup. With two SN65HVD230 breakout boards that each include an on-board 120Ω resistor, no additional external resistor is needed — the two on-board resistors already provide the correct 60Ω termination. If only one board has an on-board resistor, add a single 120Ω resistor at the opposite end of the bus.