To enable and debug SPI on an OpenWrt-flashed Raspberry Pi, you must install the kmod-spi-bcm2835 kernel module via opkg, enable the SPI hardware overlay in your boot configuration, and map the physical 3.3V GPIO pins to your slave device. This module exposes the Broadcom SPI controller to user-space via the spidev interface, allowing Python, C, or shell utilities to clock data without writing custom kernel drivers.
Whether you are integrating an SPI LoRa transceiver, driving a TFT display, or reading a Winbond flash chip on a headless Pi router, skipping the physical layer details will result in silent failures. Below is the exact hardware and software blueprint to get kmod-spi-bcm2835 working reliably in 2026.
SPI Bus Mechanics and Physical Wiring on the Pi
SPI (Serial Peripheral Interface) is a synchronous, full-duplex protocol. Unlike I2C, it does not use software addressing; instead, it relies on individual Chip Select (CS) wires for every slave device. The Raspberry Pi's Broadcom SoC (BCM2835 on Pi 1/Zero, BCM2711 on Pi 4) handles the low-level clock generation and shift-register shifting in hardware.
SPI Bus Mechanics Specification
| Parameter | SPI Standard | Raspberry Pi (BCM2835/2711) Specifics |
|---|---|---|
| Wires Required | 4 shared (MOSI, MISO, SCLK) + 1 CS per slave | SPI0 uses GPIO 9, 10, 11. CE0 is GPIO 8, CE1 is GPIO 7. |
| Max Speed | Varies by slave (typically 10-50 MHz) | Hardware supports up to 125 MHz, but spidev and trace capacitance limit reliable operation to ~20-30 MHz. |
| Addressing | None (Hardware routing via CS lines) | Pi SPI0 supports 2 native CS lines. SPI1 (auxiliary) supports 3. |
| Max Distance | Short distance (on-board or adjacent boards) | < 30 cm for high speed (>10 MHz). Up to 1 meter if baud is dropped below 1 MHz and wires are twisted/shielded. |
| Logic Levels | Depends on master | Strictly 3.3V. Feeding 5V into Pi MISO will destroy the SoC. |
Physical Wiring and Pull-Up Requirements
When wiring an SPI slave (like a W25Q32 flash chip or an RFM95 LoRa module) to the Pi's SPI0 header, use the following mapping:
- MOSI (Master Out Slave In): GPIO 10 (Pin 19)
- MISO (Master In Slave Out): GPIO 9 (Pin 21)
- SCLK (Clock): GPIO 11 (Pin 23)
- CE0 (Chip Select 0): GPIO 8 (Pin 24)
- CE1 (Chip Select 1): GPIO 7 (Pin 26)
If you have multiple slaves on the same SPI bus, a slave that is not currently selected (its CS line is HIGH) will tri-state its MISO output. If the physical trace is long, this floating MISO line can act as an antenna, picking up noise and causing the master to read garbage. While the BCM2835 has internal pull-ups, best practice for multi-device buses is to add an external 10kΩ pull-up resistor from MISO to 3.3V.
Enabling kmod-spi-bcm2835 and Minimal Working Exchange
OpenWrt strips out non-essential kernel modules to save flash space. You must explicitly install the SPI stack and enable the hardware overlay.
Step 1: OpenWrt Package Installation
SSH into your OpenWrt Pi and run:
opkg update
opkg install kmod-spi-bcm2835 kmod-spidev spi-tools python3-spidev
Note: kmod-spi-bcm2835 is the package name used in the OpenWrt repository for all Broadcom Pi SoCs, including the BCM2711 on the Pi 4.
Step 2: Enable the Device Tree Overlay
OpenWrt on the Pi relies on the /boot/config.txt file to pass parameters to the firmware before the kernel boots. Edit the file and ensure the SPI overlay is active:
nano /boot/config.txt
# Add or uncomment this line:
dtparam=spi=on
Reboot the Pi. After reboot, verify the devices exist:
ls -l /dev/spidev*
You should see /dev/spidev0.0 and /dev/spidev0.1.
Step 3: Minimal Working Exchange (Reading JEDEC ID)
Assuming you have wired a standard SPI NOR Flash chip (like a Winbond W25Q32) to SPI0 CE0, here is a minimal Python script to read its 3-byte JEDEC Manufacturer ID. This proves your wiring, clock, and spidev module are functioning.
import spidev
import time
# Initialize SPI
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, CS 0
spi.max_speed_hz = 1000000 # 1 MHz safe baseline
spi.mode = 0b00 # CPOL=0, CPHA=0
# Send JEDEC ID command (0x9F) and read 3 bytes
try:
# The first byte is the command, the next 3 are dummy bytes to clock in the response
response = spi.xfer2([0x9F, 0x00, 0x00, 0x00])
manufacturer_id = response[1]
memory_type = response[2]
capacity = response[3]
print(f'JEDEC ID -> Mfg: {hex(manufacturer_id)}, Type: {hex(memory_type)}, Cap: {hex(capacity)}')
# Expected Winbond output: Mfg: 0xef, Type: 0x40, Cap: 0x16
finally:
spi.close()
Sniffing, Debugging, and Classic SPI Failures
When the spidev script returns all zeros or throws an OSError, the issue is almost always at the physical or clock-divider layer. Here is how to diagnose the classic failures.
The Classic Failures
- Baud Rate Mismatch (Clock Divider Limits): The BCM2835 SPI clock is derived from the core clock (typically 250 MHz or 400 MHz) divided by a power of 2. If you request 12 MHz in Python, the hardware might actually output 15.6 MHz or 7.8 MHz. If your slave chip strictly requires <10 MHz for initialization, this hardware rounding will cause silent failures. Fix: Always initialize at 1 MHz, verify communication, then step up to standard binary dividers (e.g., 3.9 MHz, 7.8 MHz).
- Address Clash (CS Contention): If you wire multiple slaves to the same CE0 pin, or if a slave's CS line is left floating, both chips will drive the MISO line simultaneously. This causes a short circuit on the data bus, resulting in corrupted bytes and potential silicon damage. Fix: Ensure every slave has a dedicated CS pin, and tie unused slave CS pins to 3.3V via a 10kΩ resistor.
- Missing Pull-Up on MISO: As noted in the wiring section, floating MISO lines cause phantom reads. Fix: Solder a 10kΩ resistor between MISO and 3.3V.
How to Sniff and Debug the Bus
If your Python script fails, drop down to the OpenWrt command line using spi-tools to isolate whether the issue is in your code or the kernel.
1. Query current bus configuration:
spi-config -d /dev/spidev0.0 -q
This confirms the kernel sees the bus and reports the current mode and speed.
2. Perform a raw hex dump sniff:
echo -ne '\x9f\x00\x00\x00' | spi-pipe -d /dev/spidev0.0 -s 1000000 | hexdump -C
If this returns ef 40 16 (or similar non-zero data) but your Python script fails, your Python spidev library version is mismatched or you are running the script as a non-root user lacking /dev/spidev0.0 permissions.
3. Hardware Sniffing:
For timing issues, a software sniff is useless. Connect a $15 24MHz 8-channel logic analyzer (like a Saleae clone) to the Pi header. Use PulseView on your laptop to decode the SPI protocol. Look specifically at the CS line: if it doesn't drop cleanly to 0V before the first clock edge, your Pi GPIO pin is misconfigured or physically damaged.
Protocol Selection: When to Use SPI vs I2C vs UART
Before committing to kmod-spi-bcm2835, ensure SPI is actually the right tool for your sensor or peripheral. Here is how the Pi's three primary serial protocols compare for embedded routing applications.
| Criterion | SPI (via kmod-spi-bcm2835) | I2C (via i2c-bcm2835) | UART (via amba-pl011) |
|---|---|---|---|
| Best Fit | High-speed local peripherals (TFT displays, Flash memory, LoRa radios) | Many low-speed sensors on the same bus (Temp, Humidity, IMUs) | Long-distance comms, GPS modules, console debugging |
| Speed | Very High (10 - 30 MHz practical) | Low (100 kHz to 1 MHz, rarely 3.4 MHz) | Medium (up to 921.6 kbps typical) |
| Wiring Overhead | High (4 shared + 1 per device) | Low (2 shared wires for all devices) | Lowest (2 wires, point-to-point only) |
| Device Count | Limited by available CS GPIO pins | Up to 127 (limited by address space) | 1 to 1 (requires multiplexer for more) |
| CPU Overhead | Low (Hardware shift registers + DMA) | Medium (Hardware assisted, but high bus arbitration overhead) | Low (Hardware FIFO buffers) |
Verdict: Choose SPI when throughput is the bottleneck (e.g., pushing framebuffers to an ILI9341 display). Choose I2C when you want to daisy-chain five environmental sensors without running out of GPIO pins. Choose UART when you need to run a cable 5 meters to an RS-485 transceiver.
Frequently Asked Questions
Why is kmod-spi-bcm2835 not loading on my OpenWrt Pi 4?
This is a common naming confusion. The Pi 4 uses the BCM2711 SoC, not the BCM2835. However, the OpenWrt kernel packaging retains the kmod-spi-bcm2835 name for backward compatibility and unified driver architecture across the Pi family. If the module installs but /dev/spidev0.0 does not appear, the issue is not the module name; it is almost certainly a missing dtparam=spi=on line in your /boot/config.txt file, or a Device Tree Source (DTS) conflict where another driver (like an SPI display overlay) has already claimed the bus.
How do I fix the 'spidev0.0 device or resource busy' error?
This EBUSY error occurs when a kernel-space driver has already bound to the SPI controller, preventing user-space spidev from opening it. In OpenWrt, this usually happens if you have enabled an SPI-based screen driver (like kmod-fb-ili9341) or an SPI CAN bus controller (like mcp2515) in your kernel config or overlays. To fix it, you must either disable the conflicting overlay in /boot/config.txt or unbind the driver via sysfs: echo -n 'spi0.0' > /sys/bus/spi/drivers/[driver_name]/unbind.
Can I use kmod-spi-bcm2835 for a secondary SPI bus (SPI1)?
Yes, but it requires a different auxiliary module and overlay. The primary SPI0 uses kmod-spi-bcm2835. To enable SPI1 (which supports up to 3 CS lines and is useful for adding more LoRa nodes), you must install kmod-spi-bcm2835aux and add dtoverlay=spi1-3cs to your boot config. SPI1 will then populate as /dev/spidev1.0, spidev1.1, and spidev1.2. Note that SPI1 on the BCM2835 does not support DMA, making it unsuitable for high-speed continuous transfers like audio or large displays, but perfectly adequate for sensor polling.






