Zum Inhalt

SPI-Protokoll-Architektur & Hochgeschwindigkeits-Peripherie

Wenn die Bandbreitenbegrenzung des 2-Draht-I2C-Busses (üblicherweise 100 kHz bis 400 kHz) für eine Anwendung nicht mehr ausreicht, bietet das Serial Peripheral Interface (SPI) synchrone Vollduplex-Datenübertragung im Multi-Megahertz-Bereich. Ursprünglich von Motorola entwickelt, ist SPI der Standard für datenintensive Peripherie: Analog-Digital-Wandler (ADCs), schnelle Flash-Speicher, hochauflösende TFT- und E-Paper-Displays, Ethernet-Controller (ENC28J60) und CAN-Bus-Controller (MCP2515).

Da SPI mit aktiven Push-Pull-Treibern und dedizierten Chip-Select-Leitungen (CE) arbeitet, entfallen die verlangsamenden RC-Ladezeiten offener Leitungen sowie der Adress-Overhead im Datenstrom.


1. SPI-Bus-Topologie & Signalleitungen

Standard-SPI setzt auf eine 4-Draht-Topologie in einer Controller/Target-Konfiguration (früher Master/Slave):

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph Controller["Raspberry Pi (Host-Controller)"]
        direction TB
        BUS_LINES["Gemeinsame SPI-Leitungen:<br/>SCLK (GPIO 11) + MOSI (GPIO 10) + MISO (GPIO 9)"]
        CE_LINES["Getrennte Chip-Selects:<br/>CE0 (GPIO 8) & CE1 (GPIO 7)"]
        BUS_LINES --- CE_LINES
    end

    subgraph Dev0["Peripherie 0: MCP3008 ADC (Aktiv bei CE0 LOW)"]
        direction TB
        P0_IO["CLK + DIN + DOUT"]
        P0_CS["CS0 (Select-Leitung)"]
        P0_IO --- P0_CS
    end

    subgraph Dev1["Peripherie 1: ST7789 Display (Aktiv bei CE1 LOW)"]
        direction TB
        P1_IO["SCK + MOSI (Dateneingang)"]
        P1_CS["CS1 (Select-Leitung)"]
        P1_IO --- P1_CS
    end

    Controller ==> Dev0
    Dev0 ==> Dev1

Signalleitungen & Pinbelegung

Signalleitung BCM-Name Physischer Pin Funktion
SCLK GPIO 11 Pin 23 Serieller Takt, wird ausschließlich vom Raspberry Pi erzeugt
MOSI (COPI) GPIO 10 Pin 19 Controller Output, Peripheral Input (Daten zum Sensor)
MISO (CIPO) GPIO 9 Pin 21 Controller Input, Peripheral Output (Daten vom Sensor)
CE0 (SS0) GPIO 8 Pin 24 Aktiv-LOW Chip-Select für erstes SPI-Gerät
CE1 (SS1) GPIO 7 Pin 26 Aktiv-LOW Chip-Select für zweites SPI-Gerät

Da die MISO-Leitung nur von dem Baustein aktiv getrieben wird, dessen Chip-Select-Leitung auf LOW gezogen ist (alle inaktiven Bauteile schalten MISO auf hochohmig/High-Z), können mehrere Peripheriegeräte dieselben Daten- und Taktleitungen kollisionsfrei teilen.


2. Die vier SPI-Modi: CPOL und CPHA

SPI besitzt keinen einheitlich festgeschriebenen Taktstandard. Das zeitliche Zusammenspiel wird über zwei Parameter festgelegt: - CPOL (Clock Polarity): Bestimmt, ob der Takt im Ruhezustand LOW (0) oder HIGH (1) ist. - CPHA (Clock Phase): Bestimmt, ob die Datenbits auf der ersten (führenden) oder zweiten Flanke abgetastet werden.

Aus diesen Parametern ergeben sich die vier Standard-SPI-Modi:

SPI-Modus CPOL (Ruhetakt) CPHA (Abtastflanke) Beschreibung
Modus 0 0 (Low) 0 (Steigende Flanke) Takt ruht Low; Abtastung bei steigender Flanke (Am weitesten verbreitet: MCP3008, SD-Karten)
Modus 1 0 (Low) 1 (Fallende Flanke) Takt ruht Low; Abtastung bei fallender Flanke
Modus 2 1 (High) 0 (Fallende Flanke) Takt ruht High; Abtastung bei fallender Flanke
Modus 3 1 (High) 1 (Steigende Flanke) Takt ruht High; Abtastung bei steigender Flanke (Häufig bei Industrie-Flash/FRAM)

Vorsicht bei Modus-Konflikten

Wird der falsche SPI-Modus gewählt, verschieben sich alle gelesenen Bits um einen halben Taktzyklus oder die Werte werden komplett invertiert. Prüfe immer das Datenblatt des jeweiligen Sensors.


3. Kernel-Aktivierung & Linux Spidev-Geräte

Unter Raspberry Pi OS Bookworm wird SPI in /boot/firmware/config.txt aktiviert:

sudo nano /boot/firmware/config.txt

Folgenden Eintrag prüfen oder ergänzen:

# Primären SPI0-Bus aktivieren (CE0 und CE1)
dtparam=spi=on

Werden mehr als zwei SPI-Geräte benötigt, lässt sich der sekundäre SPI1-Controller zuschalten:

# Optional: SPI1 mit 3 Chip-Selects aktivieren (GPIO 18, 17, 16)
dtoverlay=spi1-3cs

Datei speichern und neu starten:

sudo reboot

Prüfen, ob die Linux-Gerätedateien vorhanden sind:

ls -l /dev/spidev*

Ausgabe:

crw-rw---- 1 root spi 153, 0 /dev/spidev0.0
crw-rw---- 1 root spi 153, 1 /dev/spidev0.1

/dev/spidev0.0 steuert das Gerät an CE0 (GPIO 8), /dev/spidev0.1 das Gerät an CE1 (GPIO 7).


4. Hardware-Labor: Analoge Sensoren mit MCP3008 ADC auslesen

Der Raspberry Pi besitzt auf seiner Stiftleiste keine analogen Eingänge (ADC). Um analoge Spannungen (von Potentiometern, Fotowiderständen, NTC-Temperatursensoren oder 4–20-mA-Stromschleifen) einzulesen, ist ein externer Analog-Digital-Wandler wie der Microchip MCP3008 (10 Bit Auflösung, 8 Kanäle, SPI-Interface) der Standard.

Schaltplan & Verdrahtung

Verbindung des 16-Pin DIP MCP3008 mit dem Raspberry Pi:

MCP3008-Pin Signal Verbindung Raspberry Pi Hinweis
Pin 1 CH0 Analoges Messsignal Spannung von 0,0 V bis 3,3 V
Pin 2–8 CH1–CH7 Analoge Eingänge Weitere Sensoren
Pin 9 DGND Pin 6 (GND) Digitale Masse
Pin 10 CS Pin 24 (GPIO 8 / CE0) Chip-Select
Pin 11 DIN Pin 19 (GPIO 10 / MOSI) Daten-Eingang
Pin 12 DOUT Pin 21 (GPIO 9 / MISO) Daten-Ausgang
Pin 13 CLK Pin 23 (GPIO 11 / SCLK) Takt
Pin 14 AGND Pin 14 (GND) Analoge Masse
Pin 15 VREF Pin 1 (3,3 V) Referenzspannung (3,30 V)
Pin 16 VDD Pin 1 (3,3 V) Versorgungsspannung (3,3 V)

Störunterdrückung & Abblockkondensator

Schalte einen 100-nF-Keramikkondensator direkt zwischen Pin 16 (VDD) und Pin 9 (DGND) unmittelbar am Baustein, um Spannungseinbrüche abzufangen. Für präzise Messungen gehört ein weiterer 100-nF-Kondensator zwischen VREF und AGND.


5. Vollduplex-Programmierung mit Python und spidev

Der MCP3008 erwartet ein 3-Byte-Befehlspaket zur Kanalabfrage. Da SPI stets im Vollduplex-Betrieb läuft, empfängt der Raspberry Pi während des Sendens der 3 Bytes simultan 3 Antwort-Bytes:

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph TX["Gesendet vom Raspberry Pi (MOSI)"]
        direction TB
        T1["Byte 1: Start-Bit (0x01)"]
        T2["Byte 2: Single-Ended Konfig (0x80 | Ch<<4)"]
        T3["Byte 3: Füllbyte (0x00)"]
        T1 --> T2 --> T3
    end

    subgraph RX["Empfangen vom MCP3008 (MISO)"]
        direction TB
        R1["Byte 1: High-Z Unbekannt"]
        R2["Byte 2: Null-Bit + Bits 9 & 8"]
        R3["Byte 3: Untere 8 Datenbits"]
        R1 --> R2 --> R3
    end

    TX ==>|"Vollduplex-Übertragung"| RX

Der 10-Bit-Messwert setzt sich aus den niederwertigsten 2 Bits von Byte 2 und den 8 Bits von Byte 3 zusammen:

10-Bit ADC Zusammensetzung:
Rohwert = ((Byte[1] & 0x03) · 256) + Byte[2]
Spannung = (Rohwert ÷ 1023) · VREF

Produktionsreife Python-Implementierung

Paket python3-spidev installieren:

sudo apt update
sudo apt install -y python3-spidev

Skript mcp3008_monitor.py anlegen:

#!/usr/bin/env python3
"""
Hochleistungs-SPI-ADC-Treiber fuer Microchip MCP3008
Liest analoge Messwerte mit bis zu 1,35 MHz Taktfrequenz ein.
"""

import time
import sys
import spidev

class MCP3008ADC:
    def __init__(self, bus: int = 0, device: int = 0, speed_hz: int = 1350000, vref: float = 3.3):
        """
        Initialisiert die SPI-Verbindung zum MCP3008.
        Maximal zulaessiger SPI-Takt bei 3,3V Versorgungsspannung ist 1,35 MHz.
        """
        self.vref = vref
        self.spi = spidev.SpiDev()
        self.spi.open(bus, device)
        self.spi.max_speed_hz = speed_hz
        self.spi.mode = 0b00  # SPI Modus 0 (CPOL=0, CPHA=0)
        self.spi.bits_per_word = 8

    def read_channel(self, channel: int) -> tuple[int, float]:
        """
        Liest einen 10-Bit Rohwert (0..1023) und berechnet die Spannung.
        Kanal muss eine Zahl von 0 bis 7 sein.
        """
        if not 0 <= channel <= 7:
            raise ValueError(f"Ungueltiger Kanal {channel}: Erlaubt ist 0 bis 7.")

        # Befehlspaket: Start-Bit (1), Single-Ended (1), Kanal-ID
        tx_buffer = [1, (8 + channel) << 4, 0]

        # Vollduplex-Uebertragung durchfuehren
        rx_buffer = self.spi.xfer2(tx_buffer)

        # 10-Bit Ergebnis zusammensetzen
        raw_val = ((rx_buffer[1] & 3) << 8) | rx_buffer[2]
        voltage = (raw_val / 1023.0) * self.vref

        return raw_val, voltage

    def close(self) -> None:
        self.spi.close()

def main() -> None:
    print("Initialisiere MCP3008 ADC...")
    adc = MCP3008ADC(bus=0, device=0, speed_hz=1350000, vref=3.3)

    try:
        print("Lese Kanal 0 aus (Potentiometer / Analogsensor)...")
        print("--------------------------------------------------")
        while True:
            raw, volts = adc.read_channel(0)
            percentage = (raw / 1023.0) * 100.0

            sys.stdout.write(f"\rCH0 Rohwert: {raw:4d} | Spannung: {volts:5.3f} V | Skala: {percentage:5.1f}%")
            sys.stdout.flush()
            time.sleep(0.1)

    except KeyboardInterrupt:
        print("\n\nMessung beendet.")
    finally:
        adc.close()

if __name__ == "__main__":
    main()

6. Signalintegrität bei hohen Taktfrequenzen

Wird der SPI-Takt auf 10 MHz bis 32 MHz hochgeschraubt, treten Übertragungseffekte auf:

  1. Leitungsreflexionen (Ringing): Schnelle Flanken erzeugen Überschwinger. Bei Signalverzerrungen auf SCLK empfiehlt sich ein serieller 33-Ω- bis 47-Ω-Bedämpfungswiderstand direkt am Ausgang des Raspberry Pi.
  2. Übersprechen: Verlege in Flachbandkabeln niemals SCLK direkt neben MISO ohne dazwischenliegende Masseleitung.
  3. Leitungslänge: Standard-SPI ist für kurze Platinenverbindungen (< 20 cm) ausgelegt. Müssen Distanzen über 1 Meter überbrückt werden, muss der Takt unter 500 kHz gesenkt werden oder es müssen differentielle Bustreiber (RS-485 / LVDS) eingesetzt werden.

Nächste Schritte

Vom synchronen SPI-Bus geht es weiter zur differentiellen Industrie-Kommunikation: