Wo liegt das Problem?

Hier ein interessanter Stolperstein in Balena, der einem das Leben ganz schön schwer machen kann, wenn man die eingebauten UARTs des Raspberry Pi für die serielle Kommunikation nutzen will. Fallbeispiele könnten hier die Anbindung von Sensoren mit serieller Schnittstelle oder die Kommunikation mit anderen Microcontrollern wie einem Arduino sein. Überträgt man hierfür einen unter Raspbian fehlerfrei laufenden Quellcode in die Balena-Welt, kann es sein, dass die serielle Kommunikation plötzlich nicht mehr funktioniert. Folgende zwei Ursachen konnte ich heute hierfür herausfinden:

  • Die symbolischen Links /dev/serial0 und /dev/serial1, die unter Raspbian verwendet werden sollten, um den richtigen UART anzusprechen, existieren zwar im Balena Host OS, werden standardmäßig aber nicht an die Container durchgereicht.
  • Die Development-Version des Belana OS startet zu Debuggingzwecken eine serielle Konsole, welche die UARTs belegt.

Um diese zu verstehen muss man wissen, dass der Raspberry Pi zwei UARTs besitzt: Den vollwertigen UART-Baustein unter /dev/ttyAMA0 und einen „Mini UART“ genannten, abgespeckten Baustein unter /dev/ttyS0. Ab dem Raspberry Pi 3 ist ersterer mit dem WiFi/Bluetooth-Baustein verbunden (ältere Pis hatten noch kein eingebautes WiFi oder Bluetooth), der zweite mit den GPIO-Pins:7

UART GPIO Pins

Der Mini UART hat allerdings den Nachteil, an den GPU-Takt gekoppelt zu sein, wodurch die Geschwindigkeit der seriellen Kommunikation den Gerüchten im Internet nach wohl ziemlich schwanken kann. (Zumindest wird dies immer wieder behauptet, geprüft hat es wohl noch niemand :-)). Da ab dem Raspberry Pi 3 /dev/ttyS0 anstelle von /dev/ttyAMA0 mit den GPIO-Pins verbunden ist, wurde in Rasbpian der symbolische Link /dev/serial0 eingeführt. Dieser zeigt immer auf den UART, der am GPIO-Header hängt. Der symbolische Link wurde in Balena OS übernommen, allerdings vergessen an die Container durchzureichen. Innerhalb der Docker-Container gibt es in Balena daher nur /dev/ttyAMA0 und /dev/ttyS0, wobei anscheinend beide mit den GPIOs verbunden sind. Konkret heißt das: Um die serielle Schnittstelle des Pis zu nutzen, muss in Balena daher immer /dev/ttyAMA0 oder /dev/ttyS0 angesprochen werden.

Das zweite Problem ist leider etwas hartnäckiger, betrifft dafür aber nur die Development-Version von Balena OS. Dort wird in bester Embedded-Tradition eine Login-Konsole über die seriellen Ports verfügbar gemacht, was einerseits zwar ganz nett ist, andererseits aber dazu führt, dass die UARTs nicht mehr anderweitig genutzt werden können. Dummerweise lässt sich das Verhalten in der Balena Cloud nirgends deaktivieren. Stattdessen empfehlen die Balena-Macher einfach, eine Produktivversion von Balena OS zu nutzen. Blöderweise unterstützt dieser aber den „Local Mode” für die Entwicklung nicht. Insbesondere während der Entwicklung ist dies also auch keine Option. Stattdessen muss während der Entwicklung bei jedem Neustart des Pi der Service serial-getty@serial0 wie folgt deaktiviert werden:

Entwicklungsrechner: balena ssh 569e406.local
Raspberry Pi: systemctl stop serial-getty@serial0
Raspberry Pi: exit

Dadurch werden die serielle Konsole beendet und die UARTs wieder freigegeben. Leider gibt es in der Balena Cloud keine Option, diese Änderung dauerhaft vorzunehmen. Zwar kann man mit folgenden Befehlen das Root-Filesystem in den Lese/Schreib-Modus versetzen und die Einstellung ändern, beim nächsten Upgrade von Balena OS geht sie aber wieder verloren:

Raspberry Pi: mount -o remount,rw /
Raspberry Pi: systemctl mask serial-getty@serial0
Raspberry Pi: systemctl stop serial-getty@serial0
Raspberry Pi: mount -o remount,ro /

Wie kann die serielle Kommunikation getestet werden?

Zum Test kann man einfach ein die GPIO-Pins 14 und 15 mit einem Kabel verbinden und folgendes Testprogramm auf dem Pi laufen lassen. Wenn alles funktioniert, sollte in der Konsole folgende Meldungen protokolliert werden:

[Logs]    [6/18/2021, 4:19:19 PM] [main] 1 Hallo, Serial Port!
[Logs]    [6/18/2021, 4:19:19 PM] [main] 1 Hallo, Serial Port!
[Logs]    [6/18/2021, 4:19:20 PM] [main] 2 Hallo, Serial Port!
[Logs]    [6/18/2021, 4:19:21 PM] [main] 3 Hallo, Serial Port!
...

Erhält man stattdessen folgende Exception, sind die UARTS noch durch die serielle Konsole belegt:

serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?)

Hier der Quellcode des Dockerfile.template:

FROM balenalib/%%BALENA_MACHINE_NAME%%-debian-python:latest-run

WORKDIR /usr/src/app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY ./src .
CMD ["python", "app.py"]

Hier die requiremeints.txt:

pyserial

Und hier die src/app.py:

#! /bin/env/python3
import serial, time

if __name__ == "__main__":
    seqnr = 0

    with serial.Serial("/dev/ttyS0", timeout=0.1) as sp:
        while True:
            seqnr += 1
            msg = "%s Hallo, Serial Port!" % seqnr
            sp.write(msg.encode())
            sp.flush()

            read_buffer = sp.read(50)
            print(read_buffer.decode())

            time.sleep(1)

Die Dateien müssen auf dem Entwicklungsrechner in einem leeren Verzeichnis angelegt werden. Alle Dateien befinden sich im selben Verzeichnis, mit ausnahme der app.py, die im Unterverzeichnis src liegen muss. Mit folgendem Befehl kann die Anwendung dann auf ein Device im Local Mode gepusht werden:

balena push 569e406.local
Serielle Schnittstelle im Balena Local Mode nutzen

Ein Kommentar zu „Serielle Schnittstelle im Balena Local Mode nutzen

Schreibe einen Kommentar