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:
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.
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
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
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
Set credentials in sysconf.txt
# Remove the # and set your values:
user_name=beagle
user_password=beagle
root_password=debian
Booting from microSD
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
Making SD Boot Persistent (No S2 Required)
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
Installing the USB Network Driver on Windows
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
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
PuTTY connection settings
| Field | Value |
|---|---|
| Host Name | 192.168.7.2 |
| Port | 22 |
| Connection Type | SSH |
| Login | beagle |
| Password | beagle |
sudo su
cat /etc/debian_version → 13.5
uname -r → 6.18.32-bone35
Loading CAN Kernel Modules
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_rawEnabling CAN0 via Device Tree Overlay
Confirm overlay file exists
find / -name "*CAN0*" 2>/dev/null
/boot/dtbs/6.18.32-bone35/overlays/BB-CAN0-00A0.dtbo
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
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/canTesting with Loopback Mode
Bring up can0 in loopback mode
ip link set can0 down
ip link set can0 up type can bitrate 500000 loopback on
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 EFConnecting the CAN Transceiver
| BBB Pin | SN65HVD230 | Description |
|---|---|---|
| P9.20 | TX | Transmit data to bus |
| P9.19 | RX | Receive data from bus |
| P9.01 | GND | Ground — must be connected |
| P9.03 | VCC | 3.3V power supply |
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
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
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.
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.
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
Identify the eMMC block device
lsblk
mmcblk0 3.6G ← eMMC (target)
mmcblk1 14.7G ← SD card (if present)
sda 15.1G ← USB stickWrite 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
3774873600 bytes (3.8 GB) copied, 411 s, 9.2 MB/s
sync
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)
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.
Hardware — wiring the LED to BBB
| Component | BBB Pin | Notes |
|---|---|---|
| LED anode (+) | P9_12 (GPIO 540) | Long leg of the LED — connects to GPIO output |
| 330 Ω resistor | Between LED cathode and GND | 220–470 Ω range all work. Limits current. |
| LED cathode (−) | P9_1 (GND) | Short leg — through the resistor to GND |
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
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)
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
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
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
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
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
ESP32 — main.cpp
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);
}
}
Run it — expected output
With ./can_led can0 running on BBB and the ESP32 flashed and connected:
Command Reference
Windows — Command Prompt
| Command | Where | What it does |
|---|---|---|
| c:\windows\system32\ping.exe 192.168.7.2 | CMD | Checks if BBB is reachable over USB network. |
BBB — PuTTY / SSH
| Command | Run as | What it does |
|---|---|---|
| sudo su | beagle | Switches to root. |
| cat /etc/debian_version | any | Shows installed Debian version. |
| uname -r | any | Shows running kernel version. |
| lsblk | any | Lists all block devices. Essential before any dd operation. |
| modprobe can / can_raw / c_can / c_can_platform | root | Loads SocketCAN kernel modules. |
| lsmod | grep can | root | Lists loaded CAN-related kernel modules. |
| find / -name "*CAN0*" 2>/dev/null | root | Finds device tree overlay files for CAN0. |
| nano /boot/uEnv.txt | root | Edits U-Boot environment — used to enable the CAN0 overlay. |
| ip link show can0 | root | Shows can0 interface state. |
| ip link set can0 up type can bitrate 500000 | root | Brings up can0 at 500 kbps. |
| ip link set can0 up type can bitrate 500000 loopback on | root | Same with loopback — for testing without hardware. |
| ip link set can0 down | root | Brings down can0. Required before changing parameters. |
| candump can0 | root | Listens on can0 and prints all received frames in real time. |
| cansend can0 123#DEADBEEF | root | Sends a CAN frame with ID 0x123 and 4 bytes of data. |
| gpioinfo 2>/dev/null | grep P9_12 | root | Finds P9_12's line number. Result: line 28. GPIO = gpiochip base (512) + line (28) = 540. |
| echo 540 > /sys/class/gpio/export | root | Exports GPIO 540 (P9_12) for userspace control — creates /sys/class/gpio/gpio540/. |
| echo out > /sys/class/gpio/gpio540/direction | root | Sets GPIO 540 as output. Must be done before writing value. |
| cat /sys/class/gpio/gpio540/direction | root | Reads current direction — should return "out" after setup. |
| cat /sys/class/gpio/gpio540/value | root | Reads current pin state — 0=LOW=LED off, 1=HIGH=LED on. |
| cat /sys/class/gpio/gpio540/active_low | root | Reads logic polarity — 0=normal (1=on), 1=inverted (0=on). |
| echo 1 > /sys/class/gpio/gpio540/value | root | Sets GPIO 540 HIGH — turns LED on. |
| echo 0 > /sys/class/gpio/gpio540/value | root | Sets GPIO 540 LOW — turns LED off. |
| gcc -o can_led can_led.c | root | Compiles the CAN LED control program. |
| ./can_led can0 | root | Runs the LED controller — listens for CAN frames and toggles GPIO. |
| dd if=<image> of=/dev/mmcblk0 bs=4M status=progress | root | Writes a disk image to eMMC. Irreversible — verify device name first. |
| sync | root | Flushes all pending writes to disk. Run after dd before rebooting. |
| reboot | root | Reboots 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.
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
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.
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.txt — filename only, no path prefix:
uboot_overlay_addr4=BB-CAN0-00A0.dtbo
/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.
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.
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