Tricks zum Programmieren der GPIO-Schnittstelle

© vska, 123RF

Rein und raus

Sobald Sie komplexere Aufgaben anpeilen, brauchen Sie auch ein erweitertes Repertoire an Programmiertechniken für den Zugriff auf die GPIO-Pins des Raspberry Pi, wie zum Beispiel die Verarbeitung von Interrupts.

Das Kürzel GPIO steht für General Purpose Input/Output, was sich in etwa als Mehrzweck-Ein/Ausgabe ins Deutsche übersetzen lässt. Damit bezeichnet man im Allgemeinen einen Kontakt, dessen Verhalten als Eingabe- respektive Ausgabeschnittstelle sich über einen dahinter hängenden integrierten Schaltkreis beliebig programmieren lässt. Der Raspberry Pi verfügt über ein GPIO-Interface mit insgesamt 26 Pins mit, von denen sich 17 wahlweise als Ein- oder Ausgang ansteuern lassen (Abbildung 1).

Abbildung 1: Das GPIO-Interface des Raspberry Pi. Die grün gekennzeichneten Pins lassen sich wahlweise als Ein- oder Ausgänge ansteuern. Dazu gehört beispielsweise auch der Port GPIO23 auf Pin 16 der Schnittstelle.

Linux bringt ein eigenes GPIO-Subsystem als Schnittstelle für Anwendungen mit, schützt die durch Treiber wie I2C oder SPI verwendeten Ressourcen und stellt einen Pin-genauen Zugriff sicher, sodass sich kein Programm darum zu kümmern braucht, was andere Software gerade mit anderen GPIO-Kontakten anstellt. Dies bietet den angenehmen Vorteil, dass man sich bei der Programmierung keine Gedanken um sogenannte Race Conditions zu machen braucht, bei der ein Programm das andere beim Zugriff auf die Pins blockiert. Auch komplizierte Softwaremechanismen wie Locks oder ein Interrupt-Management können dadurch entfallen.

Das GPIO-System von Linux legt seine Dateien im Verzeichnis /sys/class/gpio/ ab. Wie bei vielen Konfigurations- und Kontrolldateien erhält nur Root hier Zugriff. Die Anwendungen auf dem Raspberry Pi kommunizieren mit dem GPIO-System mittels dieser Dateien, über die das System Informationen zwischen Hard- und Software übermittelt.

Dabei kommt oft das Kommando echo der Shell zum Einsatz. Normalerweise benutzt man es, um Texte auf die Standard-Ausgabe (in der Regel der Bildschirm) zu schreiben, etwa mit echo "Hallo!". Zusammen mit einer Ausgabeumleitung kann man damit aber auch in Dateien schreiben: So legt echo "Hallo!" > hallo.txt den Text in der Datei hallo.txt im aktuellen Verzeichnis ab.

Nehmen wir einmal an, Sie möchten den Pin 16 der GPIO-Schnittstelle ansprechen, der dem Port GPIO23 entspricht. Im Schaltplan des RasPi (Abbildung 2) bezeichnet diesen Port auch als GPIO_GEN4 [1]. Um ein Benutzerinterface für diesen Port zu erzeugen, führen Sie folgenden Befehl aus:

$ sudo echo 23 >/sys/class/gpio/export
Abbildung 2: Der Raspberry-Pi-Schaltplan bezeichnet Pin 16 (GPIO23) auch als GPIO_GEN4. Auf diesem Schema erkennen Sie auch die anderen 16 programmierbaren Pins.

Der Kernel erzeugt daraufhin das Verzeichnis /sys/class/gpio/gpio23, das vier im weiteren Verlauf wichtige Dateien enthält: active_low, direction, edge und value. Sie enthalten die in der Tabelle "Dateien in /sys/class/gpio/gpio23" gezeigten Ausgangswerte, zumindest, solange am Pin noch nichts hängt.

Dateien in

Datei

Ausgangswert

active_low

0

direction

in

edge

none

value

0

Über die vier Dateien erfolgt die gesamte Ansteuerung des Ports GPIO23. Die dazu notwendigen Kommandos fasst Listing 1 zusammen. Um den Port als Ein- oder Ausgang zu definieren, beschreiben Sie beispielsweise die Datei direction mit dem entsprechenden Wert (Zeile 2 und 3). Das gleiche gilt für das Initialisieren eines Ausgangswerts (Zeile 5 und 6). Um die Pin-Ausgabe an- beziehungsweise abzuschalten, schreiben Sie in die Datei value (Zeile 8 und 9). Über das File active_low invertieren Sie bei Bedarf die Pin-Logik (Zeile 11 und 12). Das müssen Sie vor dem Lesen einer Eingabe beziehungsweise dem Setzen eines Ausgabewerts erledigen.

Listing 1

 

# Port als Ein- oder Ausgang verwenden
$ sudo echo in >/sys/class/gpio/gpio23/direction
$ sudo echo out >/sys/class/gpio/gpio23/direction
# Ausgabewert initialisieren
$ sudo echo low >/sys/class/gpio/gpio23/direction
$ sudo echo high >/sys/class/gpio/gpio23/direction
# Pin-Ausgabe ein/aus
$ sudo echo 1 >/sys/class/gpio/gpio23/value
$ sudo echo 0 >/sys/class/gpio/gpio23/value
# Pin-Logik invertieren
$ sudo echo 0 >/sys/class/gpio/gpio23/active_low
$ sudo echo 1 >/sys/class/gpio/gpio23/active_low

Wie schnell lassen sich nun mittels dieser Mechanismen die GPIO-Pin-Werte modifizieren? Das in Listing 2 gezeigte einfache Python-Programm generiert Pulse mit 18 kHz. Die Entsprechung in C [2] schafft dagegen rund 120 kHz. Die tatsächliche Umschaltfrequenz variiert, den der RasPi beschäftigt sich zwischendurch auch noch mit anderen Dingen, wie etwa Netzwerkzugriffen, dem aktualisieren der Zeit und anderen System- und Benutzerprozessen.

Listing 2

 

#!/usr/bin/python
#
# GPIO-Pin umschalten
# Ausgabefrequenz: ~18 kHz
# (je nach anderen Aktivitaeten)
pin_path = '/sys/class/gpio/gpio23'
def write_once(path, value):
  f = open(path, 'w')
  f.write(value)
  f.close()
  return
# Ausgabe setzen, Initialwert "low"
write_once(pin_path + '/direction', 'out\n')
f = open(pin_path + '/value', 'w')
# Schnellstmoeglich blinken
while 1:
  f.write('1')
  f.flush()
  f.write('0')
  f.flush()
f.close()

GPIO ohne Root?

Bisher haben wir als Root auf die GPIO-Schnittstelle zugegriffen, was aus verschiedenen Gründen in der Praxis als nicht unbedingt ratsam erscheint, sich hauptsächlich aber aus Gründen des Sicherheit und Systemstabilität verbietet. Nur allzu leicht bringen Sie mit als Root abgesetzten fehlerhaften Kommandos das System aus dem Tritt oder gar zum Absturz.

Komplett lässt sich dieses Dilemma nicht umgehen, denn Zugriffe auf das GPIO-Interface klappen nun einmal nur mit Root-Rechten. Einen gewissen Schutz bietet das Zwischenschalten einer Kontrollinstanz, wie beispielsweise des C-Programms gpio_control.c, das Sie von der Website Ryniker.ods.org herunterladen können [3]. Die Kommentare am Anfang des Quelltexts beschreiben, wie Sie es übersetzen und auf dem RasPi (als Root) einrichten. Via setuid läuft es nach einem Aufruf durch einen normalen Benutzer mit der effektiven UID von root, sodass es mit allen zum Ansteuern der GPIO-Schnittstelle und ihrer Kontrolldateien notwendigen Rechten agieren kann.

Der meiste Code in gpio_control.c dient ausschließlich dazu, sicherzustellen, dass die übergebenen Argumente sinnvoll sind und keinen Schaden anrichten können. Außerdem informiert Sie das Programm, sobald ein ungewöhnlicher Betriebszustand auftritt. Um den bereits im vorigen Beispiel verwendeten Pin 23 mit Gpio_control anzusteuern, tippen Sie als einfacher Benutzer:

$ gpio_control 23 export

Bei Bedarf konfigurieren Sie gpio_control.c vor dem Übersetzen so, dass es lediglich den Anwendern einer bestimmten Gruppe Zugriff auf das GPIO-Interface einräumt, anstatt allen Benutzern. Für jeden einzelnen GPIO-Port können Sie zudem den Export explizit gestatten oder unterbinden.

Über den Pin 16 (GPIO23) steuert der Raspberry Pi die grüne Status-LED für OK an. Versuchen Sie, diesen Pin anzusteuern, dann scheitert die Operation, weil der Kernel diese Ressource bereits mit Beschlag belegt (Listing 3). Auch andere Systemkomponenten wie etwa der I2C-Kernel-Treiber, können einzelne GPIO-Ports für normale Nutzer blockieren.

Listing 3

 

$ gpio_control 16 export
export failed: Device or resource busy

Der Kernel speichert den Zustand der GPIO-Pins. Exportieren Sie beispielsweise einen Ausgabe-Pin und geben ihn hinterher wieder frei, dann verschwinden zwar die entsprechenden Kontrolldateien, aber der Pin bleibt ein Ausgabe-Pin mit dem letzten gesetzten Wert. Exportieren Sie ihn später erneut, stellt der Kernel die Kontrolldateien und damit den gespeicherten Zustand wieder her.

Interrupts

Viele einfache Programme lesen in einer Endlosschleife den Wert eines Eingabesignals aus und reagieren, sobald sich dessen Wert verändert. Bei einer einzelnen Signalquelle mag das noch angehen – allerdings lastet eine solche Schleife trotzdem die CPU voll aus und bremst damit alle anderen Vorgänge auf dem RasPi.

Eine mögliche Abhilfe wäre eine Verzögerung in der Schleife, indem Sie diese etwa mittels des Kommandos sleep 0.5 zwischen den Abfragen für eine halbe Sekunde pausieren lassen. Das schafft zwar Zeit für andere Aktionen, bedeutet aber auch, dass im Schnitt eine Viertelsekunde vergeht, bevor das Programm eine Veränderung des Eingabewerts mitbekommt.

Mit Interrupts gibt es eine wesentlich effektivere Methode, um direkt auf solche Signalveränderungen zu reagieren. Sie greift insbesondere dann bestens, wenn es gilt, Veränderungen an verschiedenen Pins im Auge zu behalten. Das Python-Programm aus Listing 4 illustriert den Einsatz des GPIO-Interrupt-Handlings.

Das Programm konfiguriert Port 23 als Eingabequelle und setzt die zugehörige Kontrolldatei edge auf den Wert both. Damit erreicht es das sowohl "fallende" als auch "steigende" Veränderungen des Werts einen Interrupt auslösen. Außerdem öffnet es die Datei value, in der das System die ausgelesenen Werte ablegt.

Der Aufruf von select.poll() erzeugt das Abfrageobjekt po, für das dann po.register() die Werte-Datei des Ports als eine der Quellen registriert, die eine folgenden po.poll()-Anfrage bedienen können.

Listing 4

 

#!/usr/bin/python3
# Test interrupts.
import select, time, sys
pin_base = '/sys/class/gpio/gpio23/'
def write_once(path, value):
  f = open(path, 'w')
  f.write(value)
  f.close()
  return
f = open(pin_base + 'value', 'r')
write_once(pin_base + 'direction', 'in')
write_once(pin_base + 'edge', 'both')
po = select.poll()
po.register(f, select.POLLPRI)
state_last = f.read(1)
t1 = time.time()
sys.stdout.write('Initial pin value = {}\n'.format(repr(state_last)))
while 1:
  events = po.poll(60000)
  t2 = time.time()
  f.seek(0)
  state_last = f.read(1)
  if len(events) == 0:
    sys.stdout.write('  timeout  delta = {:8.4f} seconds\n'.format(t2 - t1))
  else:
    sys.stdout.write('value = {}  delta ={:8.4f}\n'.format(state_last, t2 - t1))
    t1 = t2

Zwar nutzt das Programm aus Listing 3 nur eine einzelne Interrupt-Quelle, aber mit dem Poll-Objekt lassen sich bei Bedarf auch weitere GPIO-Ports sowie andere Interrupt-Quellen registrieren. So könnte etwa eine Pipe, die in Verbindung mit einem anderen Prozess steht oder Daten aus dem Netzwerk empfängt, ebenso als Auslöser von Interrupts dienen.

Der zweite Operand von po.register() gibt an, welcher von drei möglichen Zuständen als Interrupt erkannt wird. Der Wert select.POLLPRI sorgt dafür, dass dies bei priority data to read geschieht. Die beiden anderen möglichen Zustände, data available und ready for output, sind für einen GPIO-Pin ohnehin immer gegeben. Sie können aber als Auslöser für andere Interrupt-Quellen dienen.

In manchen Fällen ist auch das Ausbleiben eines erwarteten Signals von Interesse. Der Aufruf po.poll(60000) wartet lediglich 60:000 Millisekunden lang, also eine Minute, auf einen Interrupt. Dann gibt er eine leere Liste von Interrupt-Signalen zurück und signalisiert so einen Timeout.

Für einen GPIO-Pin enthält diese Datei value zwei Bytes Inhalt: Eine O oder 1 repräsentiert den Pin-Zustand, es folgt ein Zeilenvorschub. Der Aufruf f.seek(0) setzt die Positionsmarke für die Datei wieder auf den Anfang, sodass sich der abgespeicherte Wert noch einmal einlesen lässt.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 5 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Raspberry Pi Geek kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

Aktuelle Ausgabe

12/2019
RasPi-Alternativen

Diese Ausgabe als PDF kaufen

Preis € 9,99
(inkl. 19% MwSt.)

Stellenmarkt

Neuigkeiten

  • Grußbotschaften

    Mit Sonic Pi zaubern Sie komplexe Sounds, die Sie bei Bedarf sogar noch während des Abspielens modifizieren.

  • Das Runde und das Eckige

    Mit dem MCP4725 ergänzen Sie einen RasPi um einem D/A-Wandler, der bei Bedarf noch weitere Funktionen erfüllt.

  • Alles unter Kontrolle

    Schon ein einfaches Mikrocontrollerboard wie das CY8CKIT-049-42xx bietet erstaunlich viele Möglichkeiten beim Ansteuern von Hardware.

  • Viele Kerne

    Das Spresense Development Board von Sony lässt sich mit der Arduino IDE programmieren und bringt auch ein eigenes Entwickler-SDK mit.

  • Exotische Früchte

    Der aus China stammende Orange Pi positioniert sich mit einem guten Preis und interessanten Features gegen die RasPi-Truppe. Kann er sich auch behaupten?

  • Flexibler Surfer

    Mit dem neuen RasPi 4 setzen Sie einen öffentlichen Webkiosk schnell und kostengünstig auf.

  • Auskunftsfreudig

    Viele Devices, so auch der E.ON-Aura-Controller, verwenden eine Schnittstelle namens REST, um Zustandsdaten zu übermitteln. Mit ein wenig Bastelei lesen Sie diese auch über Ihr Smartphone aus.

  • Doppelt gemessen

    Mit wenig Aufwand und einem Pi Zero realisieren Sie einen mobilen Zweikanal-Spannungsprüfer.

  • Elegant zusammengeführt

    Tizonia streamt Musik von Online-Quellen und lokalen Sammlungen. Die schlanke Architektur macht den Player zur guten Wahl für den Einsatz auf dem RasPi.

  • Kommunikativ

    Nicht jeder traut sich zu, sein eigenes Smartphone zu bauen. Allerdings kann jeder Linux-Nutzer den Raspberry Pi im Handumdrehen zu einem VoIP-Telefon aufrüsten.