PyRaspBeta

I²C, SPI, UART and 1-Wire

A bus is a small set of shared wires with rules on top. Instead of one GPIO per signal, the Pi and its parts exchange bytes — a whole conversation over two to four pins. Nearly every sensor, display and module in the shop uses one of four buses.

I²C — two wires, many devices

I²C uses SDA (data) and SCL (clock). Every device on the bus has a 7-bit address, and they all share the two wires: the controller names an address and only that device answers. Standard speed is 100 kHz, fast mode 400 kHz — slow, but enough for sensors.

On the Pi, bus 1 is on GPIO2 (SDA, physical pin 3) and GPIO3 (SCL, physical pin 5). These pins carry fixed 1.8 kΩ pull-ups, so you don't add any. Typical addresses: the BME280 at 0x76 or 0x77, the ADS1115 at 0x48–0x4B, an I²C LCD at 0x27.

Reading a register with smbus2:

from smbus2 import SMBus

with SMBus(1) as bus:
    chip_id = bus.read_byte_data(0x76, 0xD0)   # BME280 chip id register
    print(f"chip id: 0x{chip_id:02x}")          # 0x60

If no device acknowledges the address, the call raises OSError: [Errno 121] Remote I/O error — the same error a real Pi gives for a missing or miswired chip.

SPI — four wires, point to point

SPI is faster and simpler electrically: SCLK (clock), MOSI and MISO (data out and in), and one CS (chip select) per device. The controller holds one device's CS low and clocks bytes out; every byte out brings a byte in at the same time (full duplex). Devices must agree on a mode (0–3), which sets the clock edges used to sample the data.

On the Pi, SPI0 uses SCLK GPIO11 (pin 23), MOSI GPIO10 (pin 19), MISO GPIO9 (pin 21), and has two chip selects: CE0 GPIO8 (pin 24) and CE1 GPIO7 (pin 26).

Reading channel 0 of an MCP3008 with spidev:

from spidev import SpiDev

spi = SpiDev()
spi.open(0, 0)                    # bus 0, CE0
spi.mode = 0

reply = spi.xfer2([1, 0x80, 0])   # start bit, single-ended, channel 0
value = ((reply[1] & 3) << 8) | reply[2]
print(f"{value} counts = {value * 3.3 / 1024:.2f} V")

UART — a serial conversation

UART is the oldest of the four: two wires (TXD transmit, RXD receive), no clock, and bytes framed by start and stop bits. Because there is no clock wire, both ends must agree on the baud rate in advance — 9600 for a GPS module, 115200 for many others.

On the Pi they are GPIO14 (TXD, pin 8) and GPIO15 (RXD, pin 10), exposed as /dev/serial0 (the simulator also answers to /dev/ttyS0 and /dev/ttyAMA0). With pyserial:

import serial

port = serial.Serial("/dev/serial0", baudrate=9600, timeout=1)

while True:
    line = port.readline()
    if line:
        print(line.decode(errors="replace").strip())

Send or receive at the wrong baud rate and the bytes arrive scrambled — framing errors, exactly as on real hardware.

1-Wire — one data wire, many sensors

1-Wire trades speed for wiring: a single data wire (plus ground) carries both signalling and data, and every device on it has a unique 64-bit ROM code. The Pi's default 1-Wire pin is GPIO4 (physical pin 7), and it needs a 4.7 kΩ pull-up to 3V3. The kernel exposes each device as a folder under /sys/bus/w1/devices.

A DS18B20 thermometer appears as 28-*, and Python reads it as a file:

import glob

device = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")[0]
for line in open(device):
    print(line.strip())

The first line ends crc=.. YES or NO; the second holds t= plus the temperature in thousandths of a degree. A 12-bit conversion takes 750 ms, and reading the file blocks for it.

Which bus should you use?

BusWiresAddressingGood for
I²C27-bit addressesmany slow sensors and displays
SPI4one CS per deviceADCs, displays, fast or streaming data
UART2point to pointGPS, modems, another microcontroller
1-Wire1 (+ pull-up)unique ROM codeschains of cheap temperature sensors

Going deeper

  • The simulator works at the transaction level. A program asks for an I²C read, an SPI transfer, UART bytes or a 1-Wire reading; the bus finds the parts wired to the board's bus pins and lets their behaviour answer at register level. That is why unmodified drivers work: the interesting mistakes are wiring mistakes, and those behave like hardware — a missing I²C device NACKs with Errno 121, an unwired MISO returns zeros, a wrong SPI mode rotates every bit of the reply and warns, a baud mismatch garbles the bytes.
  • I²C in the simulator is bus 1 only. Two devices answering the same address raise a warning, and Lab mode's console can scan the bus with i2cdetect -y 1. The BME280 sits at 0x76 unless its SDO pin is tied to VIN (0x77).
  • Clock speed is not simulated. Long wires, capacitance and 100 vs 400 kHz matter on real hardware; here every transaction either succeeds or fails electrically.
  • UART details. Opening /dev/serial0 sets the board's baud rate; the serial monitor in the workspace injects bytes as if you had a USB–serial adapter on TXD/RXD; a device transmitting at one rate to a board listening at another has its bytes dropped and transformed.
  • 1-Wire details. The /sys folders are created when the program starts — the wiring cannot change mid-run — and every read re-samples the sensor and takes the conversion time (750 ms at 12 bits). The simulator occasionally returns a bad CRC (crc=.. NO) so retry logic gets exercised.
  • The bus sniffer. Every transaction the simulator services (I²C, SPI, UART, 1-Wire, DHT, NeoPixel) is recorded with its address, direction, bytes and success flag — in Lab mode you can watch your driver's traffic byte by byte.
  • The canonical docs for the three libraries are smbus2, spidev and pySerial.