The SPI interface on Raspberry Pi uses a 4-wire synchronous serial bus (MOSI, MISO, SCLK, CE) capable of practical throughput up to 50 MHz on the Pi 5. It is the definitive choice for high-speed, short-distance peripherals like TFT displays, external ADCs (e.g., MCP3008), and flash memory. Unlike I2C, SPI does not rely on software addressing or open-drain pull-ups for data lines, but it demands strict attention to clock phase, chip select routing, and logic-level voltage translation.

The Physical Layer: Wiring the SPI Bus on Raspberry Pi

Before writing a single line of code, you must map the physical pins. The Raspberry Pi exposes two hardware SPI buses (SPI0 and SPI1), but SPI0 is the primary, fully-featured bus tied to the BCM2711/2712 SoC's dedicated SPI controller.

SPI0 Pinout (BCM Numbering):
  • MOSI (Master Out Slave In): GPIO 10 (Pin 19)
  • MISO (Master In Slave Out): GPIO 9 (Pin 21)
  • SCLK (Serial Clock): GPIO 11 (Pin 23)
  • CE0 (Chip Enable 0): GPIO 8 (Pin 24)
  • CE1 (Chip Enable 1): GPIO 7 (Pin 26)

Reference the official Raspberry Pi Pinout for physical board layout.

Pull-Up Requirements and Tri-State Logic

A common mistake is treating SPI like I2C. SPI data lines (MOSI, MISO, SCLK) do not require external pull-up resistors. They are push-pull driven. However, the Chip Enable (CE/CS) lines must be pulled high (10kΩ to 3.3V) at the peripheral end if the master might boot with the GPIO pin floating. A floating CE line during Pi boot can cause the peripheral to interpret SoC boot noise as clock signals, leading to phantom activations or locked-up sensors.

Furthermore, if you place multiple devices on the same SPI0 bus, every peripheral's MISO pin must support tri-state (high-impedance) output. When a device's CE pin is HIGH (inactive), its MISO pin must disconnect from the bus. If a cheap sensor module lacks tri-state MISO logic, it will back-feed voltage onto the bus and corrupt data from the active device. Use a 74HC125 tri-state buffer if your breakout board lacks this feature.

The 3.3V vs 5V Logic Hazard

Raspberry Pi GPIOs are strictly 3.3V tolerant. Feeding a 5V MISO line from an Arduino-style peripheral directly into GPIO 9 will permanently destroy the SoC's input pad. If your peripheral is 5V, use a bidirectional logic level shifter like the TXB0104 or a discrete MOSFET-based shifter (BSS138). Avoid resistor voltage dividers for SPI; the parasitic capacitance of the resistors will round off the square waves at anything above 1 MHz, causing bit errors.

Bus Mechanics: Protocol Comparison Matrix

To understand where SPI fits, compare its physical constraints against I2C and UART. This matrix answers the fundamental question of which protocol fits your distance, speed, and device count requirements.

Feature SPI I2C UART / RS-485
Wires Required 4 (shared) + 1 CE per device 2 (SDA, SCL) 2 (TX, RX) + GND
Max Practical Speed 50 MHz (Pi 5 hardware limit) 400 kHz (Fast) / 3.4 MHz (High-speed) 1 Mbps (UART) / 10 Mbps (RS-485)
Addressing Hardware CE pins (1 per device) 7-bit / 10-bit software address None (Point-to-Point) or Software
Max Distance < 1 meter (highly capacitance-sensitive) < 1 meter (requires strong pull-ups) < 15m (RS-485 differential pair)
Device Count Scaling Poor (requires a CE wire for every target) Excellent (up to 127 on 2 wires) Poor (requires multiplexing/networking)

The Decision Path: Selecting Your Interface

Use this decision tree to terminate your protocol selection. Do not default to SPI just because it is fast; the wiring overhead scales poorly.

Decision Matrix:
  • IF you need to stream continuous high-bandwidth data (e.g., 320x240 TFT display, 1MSPS ADC audio sampling) AND distance is under 50cm Pick SPI.
  • IF you have 5+ low-bandwidth environmental sensors (temperature, humidity) on the same bus Pick I2C. (SPI would require 5+ separate CE wires and GPIO pins).
  • IF the peripheral is located more than 2 meters away from the Pi Pick RS-485 (UART). SPI and I2C will fail due to wire capacitance and noise pickup.
  • IF you are daisy-chaining addressable LEDs (WS2812B) Pick SPI0 (using the Pi's hardware SPI to bitbang the WS2812 protocol via the rpi_ws281x library is vastly more reliable than CPU PWM).

Concrete Default Pick: For 90% of high-speed Pi sensor and display projects, configure SPI0 at 10 MHz, Mode 0, using a 74LVC1T45 level shifter if your peripheral operates at 5V.

Minimal Working Exchange: Python spidev

Before running code, enable the SPI kernel overlay via the terminal: sudo raspi-config → Interface Options → SPI → Enable. Reboot the Pi.

The standard user-space library is spidev. Install it via pip3 install spidev. Below is a minimal, robust exchange script configured for an MCP3008 10-bit ADC. Note the explicit wiring context in the comments.

import spidev
import time
import sys

# WIRING CONTEXT:
# Pi GPIO 11 (SCLK) -> MCP3008 CLK
# Pi GPIO 10 (MOSI) -> MCP3008 DIN
# Pi GPIO 9  (MISO) -> MCP3008 DOUT
# Pi GPIO 8  (CE0)  -> MCP3008 CS/SHDN
# Pi 3.3V           -> MCP3008 VDD & VREF
# Pi GND            -> MCP3008 AGND & DGND

def setup_spi():
    spi = spidev.SpiDev()
    try:
        spi.open(0, 0)  # Bus 0, Chip Enable 0
        # 10 MHz is safe for breadboards; 20+ MHz requires short, direct PCB traces
        spi.max_speed_hz = 10000000 
        spi.mode = 0    # CPOL=0, CPHA=0 (Clock idle low, sample on leading edge)
        spi.bits_per_word = 8
    except Exception as e:
        print(f"SPI Init Failed: {e}. Did you enable SPI in raspi-config?")
        sys.exit(1)
    return spi

def read_adc_channel(spi, channel):
    if channel < 0 or channel > 7:
        raise ValueError("MCP3008 channel must be 0-7")
    
    # MCP3008 requires a 3-byte transaction
    # Byte 1: Start bit (1), Single-ended (1), Channel (3 bits)
    cmd = [1, (8 + channel) << 4, 0]
    
    # Perform the exchange
    response = spi.xfer2(cmd)
    
    # Parse the 10-bit result from the response bytes
    # See kernel spidev docs: https://www.kernel.org/doc/html/latest/spi/spidev.html
    adc_value = ((response[1] & 3) << 8) + response[2]
    return adc_value

if __name__ == '__main__':
    spi_bus = setup_spi()
    try:
        while True:
            val = read_adc_channel(spi_bus, 0)
            voltage = (val * 3.3) / 1023.0
            print(f"CH0 Raw: {val:04d} | Voltage: {voltage:.3f} V")
            time.sleep(0.5)
    except KeyboardInterrupt:
        spi_bus.close()
        print("\nSPI bus closed.")

Classic Failures and Bus Debugging

SPI lacks the built-in ACK/NACK handshaking of I2C. If the wiring is wrong, the Pi will simply read zeros, ones, or garbage without throwing an I/O error. Here are the three classic failures and how to sniff them out.

1. Clock Polarity and Phase Mismatch (CPOL/CPHA)

The Symptom: The Pi communicates with the device, but the returned data is scrambled, shifted by one bit, or consistently reads as 0xFF / 0x00.
The Cause: SPI has four "Modes" (0, 1, 2, 3) defining whether the clock idles HIGH or LOW (CPOL) and whether data is sampled on the rising or falling edge (CPHA). The spidev default is Mode 0. Many Bosch sensors (BME280) use Mode 0, but some Texas Instruments ADCs require Mode 1 or 3.
The Fix: Check the peripheral's datasheet timing diagram. Change spi.mode = 1 (or 2, or 3) in your Python script.

2. Baud Rate vs. Parasitic Capacitance

The Symptom: Code works on a short 10cm jumper, but fails when you move the sensor 50cm away, or when you add a logic level shifter.
The Cause: Breadboards and long jumper wires add parasitic capacitance (often 10-50pF). At 20 MHz, the RC time constant of the GPIO's drive strength and the wire capacitance rounds the square wave into a triangle wave. The peripheral fails to register the clock edge.
The Fix: Drop spi.max_speed_hz from 20000000 to 2000000 (2 MHz). If you absolutely need high speed over distance, use a dedicated bus buffer IC like the PCA9600 or route the SPI traces on a custom PCB with a ground plane.

3. Address Clash and Floating CE Lines

The Symptom: Reading Device A accidentally triggers Device B, or the bus locks up entirely.
The Cause: Unlike I2C address clashes, SPI clashes happen when two devices share the same CE pin, or when an unselected device's MISO line fails to go high-impedance and fights the active device's MISO line.
The Fix: Ensure every peripheral has a dedicated CE wire back to the Pi (or use a 74HC138 decoder to expand CE lines). Verify MISO tri-state behavior with a multimeter (should read floating/OL when CE is HIGH).

How to Sniff and Debug the SPI Bus

When software configuration fails, you must look at the physical signals. Do not guess; measure.

  1. Software Loopback Test: Before connecting a sensor, connect a jumper wire directly from Pi MOSI (GPIO 10) to Pi MISO (GPIO 9). Send a known byte array via spidev. If the received array matches the sent array, the Pi's hardware and kernel drivers are healthy.
  2. Software Logic Analyzer (piscope): For speeds under 5 MHz, use piscope. It leverages the pigpio daemon to sample the Pi's GPIO registers directly and displays a timing diagram on your desktop via X11 forwarding. It is invaluable for verifying CE assertion timing.
  3. Hardware Logic Analyzer: For speeds above 5 MHz, software sampling drops packets. Buy a $15 24MHz 8-channel USB logic analyzer (Saleae Logic clone). Connect CH0 to SCLK, CH1 to MOSI, CH2 to MISO, and CH3 to CE. Use PulseView (sigrok) to decode the SPI protocol layer. This will instantly reveal if your clock is idling high when it should be low, or if your CE line is dropping out mid-transaction due to a loose Dupont connector.

Mastering the SPI interface on Raspberry Pi requires respecting the physical layer. Keep your 3.3V logic protected, match your clock phase to the datasheet, and terminate your CE lines properly. When in doubt, drop the baud rate and hook up the logic analyzer.