Raspberry Pi SPI Basics
SPI is a fast four-wire bus that uses a dedicated chip-select line per device, making it well suited to high-speed peripherals like ADCs and displays.
What Is SPI?
SPI, short for Serial Peripheral Interface, is a synchronous communication protocol that the Raspberry Pi uses to talk to fast peripherals. Unlike I2C, which shares one pair of wires among every device through unique addresses, SPI dedicates one clock line, two data lines, and a separate chip-select (CS) line to each device. The Pi drives the clock and picks which peripheral is 'listening' at any moment by pulling that peripheral's chip-select line low, so devices don't need an address at all — they simply respond whenever their own CS line is active.
- MOSI (Master Out, Slave In): data sent from the Pi to the peripheral
- MISO (Master In, Slave Out): data sent from the peripheral back to the Pi
- SCLK: the clock signal that keeps both sides in sync
- CE0 / CE1 (chip enable, also called chip select): one dedicated line per connected device
- Full-duplex operation — data can be sent and received in the same clock cycle
- Typical clock speeds run from a few hundred kHz up to tens of MHz, well beyond I2C's usual 100-400 kHz
SPI vs I2C
Wiring and Enabling SPI
The Raspberry Pi's primary SPI bus (SPI0) uses physical pin 19 for MOSI (GPIO10), pin 21 for MISO (GPIO9), pin 23 for SCLK (GPIO11), and two chip-select lines: pin 24 for CE0 (GPIO8) and pin 26 for CE1 (GPIO7) — enough to wire up two independent SPI devices without extra hardware. SPI is disabled by default; enable it with sudo raspi-config under Interface Options > SPI > Yes, or non-interactively with sudo raspi-config nonint do_spi 0, then reboot. Finally install the Python library with sudo apt install -y python3-spidev.
Talking to an SPI Device in Python
In Python, the spidev library gives you an SpiDev object representing one bus/device pair. Call .open(bus, device) where bus is almost always 0 and device is 0 or 1 depending on which CE line you wired up. Set max_speed_hz to something your peripheral's datasheet supports, and set mode (0-3) to match the clock polarity and phase it expects. The core method, xfer2(list_of_bytes), shifts your bytes out on MOSI while simultaneously shifting bytes in on MISO, returning a list the same length as what you sent — even if you only care about the reply, you still need to send filler bytes to generate the clock pulses that pull the response out.
Example
import spidev
spi = spidev.SpiDev()
spi.open(0, 0) # bus 0, chip-select CE0
spi.max_speed_hz = 1350000
spi.mode = 0
def read_channel(channel):
# MCP3008 command: start bit, single-ended mode, channel number
command = [1, (8 + channel) << 4, 0]
reply = spi.xfer2(command)
value = ((reply[1] & 3) << 8) | reply[2]
return value # 0-1023
try:
raw = read_channel(0)
voltage = raw / 1023 * 3.3
print(f"Raw: {raw} Voltage: {voltage:.2f}V")
finally:
spi.close()Example
import spidev
spi = spidev.SpiDev()
spi.open(0, 1) # bus 0, chip-select CE1
spi.max_speed_hz = 500000
spi.mode = 0
READ_STATUS_REGISTER = 0x05
reply = spi.xfer2([READ_STATUS_REGISTER, 0x00])
status_register = reply[1]
print(f"Status register: {status_register:#010b}")
spi.close()Exercise: Raspberry Pi I2C and SPI
Which statement correctly compares the wiring of I2C and SPI on a Raspberry Pi?