Zum Inhalt

UART-Seriell-Architektur & Industrielles RS-485 / Modbus RTU

Asynchrone serielle Schnittstellen (Universal Asynchronous Receiver-Transmitter - UART) gehören zu den langlebigsten Standards der Computertechnik. Auf Einplatinencomputern erfüllt UART zwei wesentliche Aufgaben: als hardwarenahe Debug-Konsole für frühe Boot-Meldungen des Linux-Kernels und als Schnittstelle für industrielle Sensoren und Steuerungen.

In der Gebäudeautomation, Industriefertigung und bei Photovoltaik-Messungen halten einfache 3,3-V-Logiksignale langen Kabelstrecken und elektromagnetischen Störfeldern großer Elektromotoren nicht stand. Durch die Kopplung der Hardware-UART des Raspberry Pi mit einem differentiellen RS-485-Transceiver lassen sich Strecken von bis zu 1.200 Metern mit dem bewährten Modbus RTU-Industrieprotokoll störungssicher überbrücken.


1. Die Raspberry Pi UART-Hardware-Matrix (PL011 vs. Mini-UART)

Im SoC des Raspberry Pi arbeiten zwei grundlegend verschiedene UART-Controller:

  1. PL011 UART (/dev/ttyAMA0): Ein vollwertiger ARM-Standard-UART mit separaten Taktgeneratoren und 32-Byte-FIFO-Puffern. Sein Takt ist vollkommen unabhängig von der CPU-Taktung oder thermischem Throttling.
  2. Mini-UART (/dev/ttyS0): Ein reduzierter Hilfs-UART. Bei älteren Modellen (Pi 3/4) leitet sich sein Takt direkt vom VPU-Kerntakt des VideoCore-Grafikprozessors ab. Taktet die CPU bei Hitze herunter, verschiebt sich der Kerntakt – die serielle Baudrate gerät aus dem Takt und Datenpakete werden zerstört.
%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph SoC["Raspberry Pi SoC Hardware"]
        PL011["PL011 Hardware-UART<br/>(Hohe Stabilitaet, unabhaengig)"]
        MiniUART["Mini-UART<br/>(Abhaengiger Kerntakt,<br/>fehleranfaellig)"]
    end

    subgraph Routing["Linux Pin-Routing (/dev/serial0)"]
        BT["Onboard Bluetooth-Modul"]
        Header["40-Pin-Leiste:<br/>GPIO 14 (TX) & 15 (RX)"]
    end

    PL011 -.->|Standard auf Pi 3/4| BT
    MiniUART -.->|Standard auf Pi 3/4| Header

    PL011 ==>|Enterprise: disable-bt| Header

Den vollwertigen PL011-UART für die Stiftleiste freigeben

Standardmäßig verbindet Raspberry Pi OS den präzisen PL011-UART mit dem Bluetooth-Chip und legt den jitteranfälligen Mini-UART auf Pin 8 (GPIO 14 / TX) und Pin 10 (GPIO 15 / RX). Für industrielle Sensorbusse muss Bluetooth deaktiviert und der PL011-Controller auf die Stiftleiste gelegt werden.

Schritt 1: Firmware-Konfiguration anpassen

Datei /boot/firmware/config.txt öffnen:

sudo nano /boot/firmware/config.txt

Folgende Direktiven am Dateiende ergänzen:

# Primaeren UART auf Stiftleiste aktivieren
enable_uart=1

# Bluetooth deaktivieren und PL011-UART auf GPIO 14/15 routen
dtoverlay=disable-bt

Schritt 2: Serielle Kernel-Konsole entfernen

Der Linux-Kernel startet ab Werk eine interaktive Login-Konsole auf /dev/serial0 mit 115200 Baud. Sendet ein Sensor während des Systemstarts Daten, interpretiert systemd dies als Tastatureingaben, was den Bootvorgang blockieren kann.

Datei /boot/firmware/cmdline.txt bearbeiten:

sudo nano /boot/firmware/cmdline.txt

Den Eintrag console=serial0,115200 (oder console=ttyAMA0,115200) vollständig löschen.

Die restlichen Boot-Parameter müssen auf einer einzigen Zeile verbleiben:

console=tty1 root=PARTUUID=... rootfstype=ext4 fsck.repair=yes rootwait

Schritt 3: Bluetooth-Dienst deaktivieren & Neustart

sudo systemctl disable hciuart.service
sudo reboot

Nach dem Neustart kontrollieren, ob /dev/serial0 direkt auf ttyAMA0 verweist:

ls -l /dev/serial0
# Ausgabe: lrwxrwxrwx 1 root root 7 /dev/serial0 -> ttyAMA0

2. Differentielle Signalübertragung: Der RS-485 Physical Layer

Klassisches UART bezieht jedes Signal auf eine gemeinsame Masseleitung. Über weite Strecken führen Ausgleichsströme zu Potenzialunterschieden zwischen Gebäudeteilen, während parallele 400-V-Starkstromkabel Störimpulse induzieren.

RS-485 löst dieses Problem durch symmetrische, differentielle Signalübertragung über ein verdrilltes Adernpaar (Twisted Pair), bezeichnet als A (Data+) und B (Data-):

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph Host["Raspberry Pi + RS-485 Transceiver"]
        direction TB
        TX["TX-Pin (3,3 V Logik)"]
        RX["RX-Pin (3,3 V Logik)"]
    end

    subgraph Cable["Verdrilltes Adernpaar (Bis zu 1200 Meter)"]
        direction TB
        LineA["Leitung A (Data +)"]
        LineB["Leitung B (Data -)"]
    end

    subgraph PLC["Industrie-SPS / Modbus-Zaehler"]
        direction TB
        NodeA["Klemme A (+)"]
        NodeB["Klemme B (-)"]
    end

    Host --> Cable
    Cable --> PLC
  • Logische 1 (Ruhezustand / Mark): VA - VB > +200 mV
  • Logische 0 (Aktiv / Space): VA - VB < -200 mV

Da elektromagnetische Störfelder auf beide eng miteinander verdrillten Leiter exakt gleich wirken, hebt der Differenzverstärker des Empfängers das Gleichtakt-Rauschen vollständig auf:

Gleichtaktunterdrückung (Common Mode Rejection):
Vdiff = (VA + VStörung) - (VB + VStörung) = VA - VB

Bus-Terminierung & Abschlusswiderstände

  1. 120-Ω-Abschlusswiderstände: Bei langen Leitungen und hohen Baudraten reflektieren Signale an offenen Leitungsenden wie Wasserwellen an einer Betonwand. An den beiden äußersten Enden des Busses muss jeweils ein 120-Ω-Widerstand (0,25 W) parallel zwischen Leitung A und B geschaltet werden.
  2. Fail-Safe-Vorspannung: Wenn kein Sender aktiv ist, schweben die Leitungen. Ein 560-Ω-Pull-Up von Leitung A auf 3,3 V und ein 560-Ω-Pull-Down von Leitung B auf Masse verhindern fehlerhafte Geister-Startbits.

3. Hardware-Verdrahtung (SP3485 vs. MAX485)

Achtung bei 5V-Logikpegeln

Der klassische MAX485 benötigt 5 V Versorgungsspannung und gibt am Empfängerausgang (RO) 5 V TTL-Spannung aus. Wird ein 5V-Transceiver direkt mit GPIO 15 (RX) des Raspberry Pi verbunden, wird der Pi zerstört!

Verwende zwingend einen nativen 3,3-V-Transceiver wie den Exar/MaxLinear SP3485 oder TI SN65HVD72. Steht nur ein 5V-Modul zur Verfügung, muss zwingend ein Spannungsteiler oder bidirektionaler Level-Shifter vor den RX-Pin geschaltet werden.

Halbduplex-Richtungsumschaltung (DE/RE)

RS-485 arbeitet im Halbduplex-Betrieb: Über dasselbe Adernpaar wird abwechselnd gesendet und empfangen. Der Transceiver besitzt zwei Steuerpins: - DE (Driver Enable): Aktiv HIGH (schaltet den Sender ein). - RE (Receiver Enable): Aktiv LOW (schaltet den Empfänger ein).

Werden DE und RE miteinander verbunden und an einen freien GPIO-Pin (z. B. GPIO 18) angeschlossen, setzt der Raspberry Pi diesen Pin zum Senden auf HIGH und schaltet ihn sofort danach wieder auf LOW, um Antworten zu empfangen. Moderne Industrie-HATs nutzen automatische Hardware-Umschalter (z. B. MAX13487), die ohne manuelles Umschalten per GPIO auskommen.

Raspberry Pi Pin Transceiver-Pin (SP3485) Bedeutung
Pin 1 (3,3 V) VCC 3,3 V Betriebsspannung
Pin 6 (GND) GND Logikmasse
Pin 8 (GPIO 14 / TXD) DI (Driver In) Sendedaten vom Pi zum Bus
Pin 10 (GPIO 15 / RXD) RO (Receiver Out) Empfangsdaten vom Bus zum Pi
Pin 12 (GPIO 18) DE & RE (Verbunden) Richtungsumschaltung (HIGH=Senden, LOW=Empfangen)
Busklemmen Klemme A / Klemme B Anschluss an verdrilltes Adernpaar

4. Modbus RTU Protokollaufbau

Modbus RTU ist ein binäres Protokoll der Anwendungsschicht, das asynchron über RS-485 übertragen wird. Es basiert auf einem strikten Master-Slave-Prinzip: Der Raspberry Pi ist der Modbus Master (Client), während angeschlossene Sensoren oder Stromzähler als Slaves (Server) mit festen Geräteadressen (1 bis 247) arbeiten.

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph Frame["Modbus RTU Telegramm-Aufbau<br/>(Binaerstrom)"]
        direction TB
        F1["Slave-ID (1 Byte)"]
        F2["Funktionscode (1 Byte)"]
        F3["Nutzdaten / Register<br/>(0 - 252 Bytes)"]
        F4["CRC-16 Pruefsumme<br/>(2 Bytes)"]
    end

    F1 --> F2 --> F3 --> F4

Wichtigste Funktionscodes

  • 0x03 (Read Holding Registers): Liest konfigurierbare 16-Bit-Register aus (Spannung, Leistung, Frequenz).
  • 0x04 (Read Input Registers): Liest schreibgeschützte analoge Messwerte ein.
  • 0x06 (Write Single Register): Schreibt einen einzelnen 16-Bit-Sollwert.
  • 0x10 (Write Multiple Registers): Schreibt einen ganzen Block von Registern atomar.

5. Python-Praxis: Industriellen Stromzähler auslesen

Dieses Skript liest elektrische Messwerte (Spannung, Stromstärke, Wirkleistung, Netzfrequenz) von einem Hutschienen-Stromzähler (z. B. Eastron SDM630 / SDM120) über minimalmodbus ein:

sudo apt update
sudo apt install -y python3-serial python3-pip
pip3 install --break-system-packages minimalmodbus

Skript /usr/local/bin/modbus_energy_poller.py anlegen:

#!/usr/bin/env python3
"""
Industrieller Modbus RTU Energiemonitor
Liest Leistungswerte von einem Eastron Hutschienenzaehler ueber RS-485 (9600 8N1).
"""

import time
import sys
import logging
import minimalmodbus
import serial

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")

class IndustrialEnergyMeter:
    def __init__(self, port: str = "/dev/serial0", slave_address: int = 1, baudrate: int = 9600):
        self.slave_address = slave_address
        self.instrument = minimalmodbus.Instrument(port, slave_address)

        # Schnittstellen-Parameter konfigurieren
        self.instrument.serial.baudrate = baudrate
        self.instrument.serial.bytesize = 8
        self.instrument.serial.parity = serial.PARITY_NONE
        self.instrument.serial.stopbits = 1
        self.instrument.serial.timeout = 0.5  # 500 ms Timeout

        # RTU-Modus aktivieren
        self.instrument.mode = minimalmodbus.MODE_RTU
        self.instrument.clear_buffers_before_each_transaction = True
        self.instrument.close_port_after_each_call = False

        logging.info(f"Modbus RTU Geraet auf {port} (Slave-ID: {slave_address}) bereit.")

    def read_metrics(self) -> dict[str, float]:
        """
        Liest 32-Bit IEEE 754 Gleitkomma-Register vom Stromzaehler.
        Eastron Register-Zuordnung:
        - 0x0000: Netzspannung (Volt)
        - 0x0006: Stromstaerke (Ampere)
        - 0x000C: Wirkleistung (Watt)
        - 0x0046: Netzfrequenz (Hertz)
        """
        try:
            # Funktionscode 0x04 (Input Register), 2 Register je 32-Bit Float
            voltage = self.instrument.read_float(0x0000, functioncode=4, number_of_registers=2)
            current = self.instrument.read_float(0x0006, functioncode=4, number_of_registers=2)
            power = self.instrument.read_float(0x000C, functioncode=4, number_of_registers=2)
            freq = self.instrument.read_float(0x0046, functioncode=4, number_of_registers=2)

            return {
                "voltage_v": round(voltage, 2),
                "current_a": round(current, 3),
                "power_w": round(power, 1),
                "frequency_hz": round(freq, 2)
            }
        except minimalmodbus.NoResponseError:
            logging.error(f"Keine Antwort von Slave {self.slave_address}. Polung A/B pruefen!")
            raise
        except minimalmodbus.InvalidResponseError as err:
            logging.error(f"CRC-Pruefsummenfehler oder defektes Datenpaket: {err}")
            raise

    def close(self) -> None:
        if self.instrument.serial.is_open:
            self.instrument.serial.close()

def main() -> None:
    meter = IndustrialEnergyMeter(port="/dev/serial0", slave_address=1, baudrate=9600)

    try:
        logging.info("Starte kontinuierliche Messwerterfassung (Strg+C zum Abbrechen)...")
        while True:
            try:
                metrics = meter.read_metrics()
                logging.info(
                    f"Netz: {metrics['voltage_v']} V | "
                    f"Last: {metrics['current_a']} A | "
                    f"Leistung: {metrics['power_w']} W | "
                    f"Frequenz: {metrics['frequency_hz']} Hz"
                )
            except Exception:
                time.sleep(1.0)
                continue

            time.sleep(2.0)

    except KeyboardInterrupt:
        logging.info("Messung beendet.")
    finally:
        meter.close()

if __name__ == "__main__":
    main()

6. Fehleranalyse in der Praxis

Fehler 1: "NoResponseError" beim Leseversuch

  • Vertauschte A/B-Polarität: Klemme A und B am Transceiver tauschen. Es gibt herstellerübergreifend keine verbindliche Konvention: Einige Hersteller bezeichnen A als Data+ und B als Data-, andere genau umgekehrt. Das Tauschen zerstört nichts und löst das Problem meist sofort.
  • Falsche Slave-Adresse oder Baudrate: Industrie-Geräte werden ab Werk mit festen Adressen (häufig 1 oder 247) und Standard-Baudraten (9600 oder 19200) ausgeliefert. Prüfe das Handbuch oder das Display am Gerät.

Fehler 2: Paketverluste & CRC-Prüfsummenfehler

  • Fehlender Abschlusswiderstand: Bei Leitungslängen über 20 Meter müssen zwingend 120-Ω-Widerstände an beiden Endpunkten der Busleitung vorhanden sein.
  • Topologie-Verletzung (Sternverkabelung): RS-485 verlangt zwingend eine lineare Bus-Struktur (Daisy-Chain). Stichleitungen und Sternverteiler führen zu massiven Signalreflexionen, die Telegramme unlesbar machen.

Nächste Schritte

Vervollständige dein Wissen über serielle Busse und Signalgenerierung: