GPIO beim Raspberry Pi

Prof. Jürgen Plate

GPIO beim Raspberry Pi per Kommando ansteuern

Zusätzlich zu USB, Ethernet, Audio, Video und HDMI bietet hat der Raspberry Pi verschiedene digitale Ein-/Ausgabeleitungen, die GPIOs (General Purpose Input/Output). Sie sind über die Stiftleiste P1 zugänglich. Beachten Sie, dass alle Signale der GPIO-Pins mit einem Logikpegel von 3,3 V arbeiten und nicht 5-V-tolerant sind. Einige Pins können auch als SPI-, I2C- oder UART-Schnittstelle verwendet werden.

Eine genaue Beschreibung der GPIO-Pins finden Sie in der GPIO-Einführung. Gerade für Anfänger sind die ersten Schritte oft schwierig und wer noch nicht viel Erfahrung mit Linux gemacht hat, findet oft nicht die einfachsten Lösungen. (Mehr zu UNIX/Linux finden Sie im UNIX/Linux-Skript.)

Bei UNIX galt das Prinzip schon immer und beim Linux des Raspberry Pi gilt es natürlich auch: Alles ist Datei! Mit anderen Worten, es werden alle Schnittstellen des Computers auf das Dateisystem abgebildet - und nicht nur das, am /proc-Verzeichnis kann man studieren, wie sich auch Informationen des Betriebssystem-Kerns auf ein Pseudo-Dateisystem (sogenannte virtuelle Dateien) abbinden lassen.

Auch die GPIOs können direkt über ein System virtueller Dateien angesprochen werden. Für die GPIO-Pins werden die Dateien in /sys/class/gpio/ angesprochen. Dies kann jedoch nur mit root-Rechten erledigt werden. Nach dem Booten befinden sich dort nur zwei Dateien, export und unexport. Alle anderen Dateien müssen Sie erst erzeugen.

Zuerst müssen die Files via export zugänglich gemacht werden. Dies geschieht, indem man die Nummer des gewünschten GPIO (nicht etwa die Pinnummer der Steckerleiste!) in export schreibt, z. B.:

echo "23" > /sys/class/gpio/export
Mit dem Kommando ls /sys/class/gpio können Sie nun ein neues Verzeichnis mit dem Namen 'gpio23' sehen. In diesem Verzeichnis befinden sich u. a. zwei Dateien direction und value. Diese Dateien dienen zum Steuern des GPIO-Pins.

Nun wird entschieden, ob der GPIO-Port Eingang oder Ausgang sein soll, z.B.:

echo "in" > /sys/class/gpio/gpio23/direction
oder
echo "out" > /sys/class/gpio/gpio23/direction
echo "0" > /sys/class/gpio/gpio23/value
Bei einem Ausgang ist es sinnvoll, diesem gleich mit einem Initialwert zu setzen. Im Beispiel oben wird er ausgeschaltet. Normalerweise sind alle (Pseudo-)Dateien unterhalb von /sys/class/gpio/ dem Benutzer 'root' und der Gruppe 'root' zugeordnet. Damit man mit dem GPIO auch spielen kann, wenn man nicht 'root' ist, setzt man auch gleich noch die Zugriffsrechte für alle:
chmod 660 /sys/class/gpio/gpio23/direction
chmod 660 /sys/class/gpio/gpio23/value

Hat der Raspberry Pi eine feste Aufgabe, kann die Initialisierung aller benötigten GPIOs in ein Shellscript geschrieben werden, das dann von /etc/rc.local beim Booten aufgerufen wird. Auf diese Weise können alle weiteren Aktionen vom "normalen" Useraccount aus erledigt werden und man muss keine programmtechnische Verrenkungen machen.

Bei mehreren Ports lohnt sich sowieso ein Script. Für die weiter unten beschriebene Testplatine mit zwei Tastern und vier LEDs sieht das Script 'led-init.sh' folgendermaßen aus. Nur dieses Script muss mit root-Rechten ausgeführt werden:

#!/bin/sh
# Input-Ports (Taster)
for Port in  17 27
  do
  echo "$Port" > /sys/class/gpio/export
  echo "in" >/sys/class/gpio/gpio${Port}/direction
  chmod 660 /sys/class/gpio/gpio${Port}/direction
  chmod 660 /sys/class/gpio/gpio${Port}/value
done

# Output-Ports (LED)
for Port in 22 23 24 25
  do
  echo "$Port" > /sys/class/gpio/export
  echo "out" >/sys/class/gpio/gpio${Port}/direction
  echo "0" >/sys/class/gpio/gpio${Port}/value
  chmod 660 /sys/class/gpio/gpio${Port}/direction
  chmod 660 /sys/class/gpio/gpio${Port}/value
done
Achtung: Auch wenn das Kommando chmod 660 ... durch chmod 666 ... (Zugriffsrecht fuer alle) wird kein Zugriffsrecht für others zugelassen. Wenn andere Benutzeraccounts als der Standarduser "pi" auf die GPIO-Pins zugreifen sollen, müssen Sie zusätzlich in die Gruppe gpio eingetragen werden:
sudo usermod -a -G gpio <Username>
Die Änderungen der Gruppe werden erst nach dem nächsten Login des Users wirksam.

Wenn Ihnen das Gefrickel mit den Gruppen- und Zugriffrechten zu aufwendig erscheint, gibt es noch einen zweiten Weg über die systemweite Konfiguration im Verzeichnis /etc/udev/. Für den GPIO-Zugriff legen Sie im darunter befindlichen Regel-Verzeichnis rules.d eine neue Regeldatei namens

/etc/udev/rules.d/80-gpio-noroot.rules
an. Wichtig ist eigentlich nur die Endung ".rules". Die Zahl am Anfang legt die Reihenfolge fest, in der die Regeldateien abgearbeitet werden. 80 ist da ein guter Kompromiss. Für das Beispiel verwende ich die oben schon erwähnte Gruppe "gpio", es wäre im Prinzip auch jede andere Gruppe möglich. In den Regeln wird nun festgelegt, dass für die beiden dem GPIO zugeordneten Verzeichnisse die Gruppe auf "gpio" gesetzt wird und die Zugriffsrechte auf Lesen und Schreiben. Zusätzlich wird auch noch das Sticky-Bit für die Gruppe gesetzt, was bedeutet, dass neue Dateien oder Unterverzeichnisse auch automatisch die Gruppe "gpio" erhalten:
# /etc/udev/rules.d/80-gpio-noroot.rules
# Zugriff auf GPIO ohne root-Rechte ermoeglichen
#
# Gruppe aendern
SUBSYSTEM=="gpio", RUN+="/bin/chown -R root.gpio /sys/class/gpio"
SUBSYSTEM=="gpio", RUN+="/bin/chown -R root.gpio /sys/devices/virtual/gpio"
# Sticky-Bit setzen
SUBSYSTEM=="gpio", RUN+="/bin/chmod g+s /sys/class/gpio"
SUBSYSTEM=="gpio", RUN+="/bin/chmod g+s /sys/devices/virtual/gpio"
# Zugriffsrechte setzen
SUBSYSTEM=="gpio", RUN+="/bin/chmod -R ug+rw /sys/class/gpio"
SUBSYSTEM=="gpio", RUN+="/bin/chmod -R ug+rw /sys/devices/virtual/gpio"
Jetzt müssen Sie nur noch den udev-Daemon von den Änderungen wissen lassen (beim nächsten Reboot passiert das dann automatisch):
sudo service udev restart
sudo udevadm trigger --subsystem-match=gpio

Durch Schreiben von "0" oder "1" in die virtuelle Datei value eines GPIO wird der entsprechende Pin der Pfostenleiste auf 0 V oder 3,3 V gesetzt. Das Lesen eines Eingangsports erfolgt auf ähnliche Art und Weise - hier liefert der GPIO "0" oder "1", je nachdem welcher Logikpegel am entsprechenden Pin anliegt. In einer Shell-Variablen läßt sich der Wert durch folgendes Kommando speichern:

Val=$(cat /sys/class/gpio/gpio23/value)

In der Praxis kann es durchaus vorkommen, dass die Signale invertiert anliegen. Um im Programm nicht mit invertierter Logik arbeiten zu müssen, kann man die GPIOs direkt das Eingangssignal invertieren lassen. Um die Pinlogik umzukehren, reicht das Kommando:

echo 1 >/sys/class/gpio/gpio23/active_low
Wenn active_low auf 1 gesetzt ist, liegt am Pin "0" an, wenn in value "1" steht, und umgekehrt.

Shell-Programmierung

Das folgende Shell-Script haben Sie eventuell schon im Kapitel GPIO beim Raspberry Pi gesehen; es arbeitet mit dem dort beschriebenen Testboard. Das Script fragt zum einen die beiden Taster ab und gibt deren Wert in der Shell aus und blinkt mit allen vier LEDs. Für das Blinken gibt es eine Shell-Funktion, die den Wert toggelt und eine Kontrollausgabe erzeugt.

#!/bin/sh

toggle()
  {
  # Zustand des Pins einlesen
  LedVal=$(cat /sys/class/gpio/gpio$1/value)
  # LED toggeln
  if [ $LedVal -eq "0" ]; then
    LedVal="1"
  else
    LedVal="0"
  fi
  # Kontrollausgabe auf der Konsole
  echo "Port $1 := $LedVal"
  # Pin auf 0 oder 1 setzen
  echo $LedVal > /sys/class/gpio/gpio$1/value
  }


while :
do
  # Taster einlesen und anzeigen
  for Port in  17 27
  do
    LED=$(cat /sys/class/gpio/gpio${Port}/value)
    echo "Port $Port = $LED"
  done

  # mit den LEDs blinken
  for Port in 22 23 24 25
  do
    toggle $Port
  done
  echo ""
  sleep 1
done

Wenn Sie nicht wollen, dass die LEDs eventuell eingeschaltet sind, wenn Sie das Script (bzw. die darin befindliche Endlosschleife) abbrechen, etwa mit der Tastenkombination [Strg]-[C], spendieren Sie dem Script noch ein Trap-Kommando:
trap 'GPIO-aus.sh ; exit' 0 1 2 3 15
Dabei sorgt das Script GPIO-aus.sh für das Abschalten aller LEDS:
#!/bin/sh
for Port in 22 23 24 25
  do
  echo "0" > /sys/class/gpio/gpio${Port}/value
done

Durch Schreiben der GPIO-Nummer in die virtuelle Datei /sys/class/gpio/unexport kann der GPIO wieder deaktiviert werden. Auch diese Aktion darf nur 'root' ausführen. Das folgende Script deaktiviert die verwendeten Ports vollständig:

#!/bin/sh
for Port in 22 23 24 25
  do
  echo "0" > /sys/class/gpio/gpio${Port}/value
  echo "$Port" > /sys/class/gpio/unexport
done

Zum Schluss noch ein Script für das Testboard, diesmal ein Lauflicht:

#!/bin/sh
trap 'echo "0" > /sys/class/gpio/gpio${Port}/value; exit' 0 1 2 3 15

Port="22"
echo "1" > /sys/class/gpio/gpio22/value


while :
  do
  echo "0" > /sys/class/gpio/gpio${Port}/value
  Port=$(( $Port + 1 ))
  if [ $Port -eq "26" ]; then
    Port="22"
  fi
  echo "1" > /sys/class/gpio/gpio${Port}/value
  sleep 1
done

Noch übersichtlicher geht es, wenn man Arrays verwendet, wie sie die Bash bietet. Zum einen kann man die verwendeten GPIO-Ports übersichtlich definieren und zum anderen auch die Kommandozeilenparameter mit wenigen Befehlen verarbeiten. Das folgende Programm besitzt noch ein weiteres Feature: Falls einer der verwendeten Ports noch nicht initialisiert wurde, holt das Programm dies nach. Dabei wird geprüft, ob die (Pseudo-)Datei /sys/class/gpio/gpio${Port}/direction schon existiert. Ist dies der Fall, wird nichts gemacht. Andernfalls erfolgt die Initialisierung des angegebenen Ports.

#!/bin/bash

# Schaltet die LEDs an oder aus
# Programmaufruf: anzeige.sh <LED 1> <LED 2> <LED 3> <LED4>
# Fuer die LEDS sind drei Werte moeglich:
# - 0: LED aus
# - 1: LED an
# - x: alten Wert beibehalten
# Beispiele:
#    anzeige.sh 0 1 0 0 schaltet LED 2 an und die anderen aus
#    anzeige.sh 1 x x 0 schaltet LED 1 an, LED 4 aus und laesst
#                       LED 2 und LED 3 unveraendert
#

# Port-Zuordnung zu LED 1, LED 2, LED 3 und LED 4
GPIO=(22 23 24 25)

if [ $USER != "root" ]; then
  echo "Das Programm muss als root-User gestartet werden!"
  exit 1
fi

# alle Parameter vorhanden?
if [ $# -lt 3 ] ; then
  echo "usage: $0 LED1 LED2 LED3 LED4 (0 oder 1; x fuer alten Wert behalten)"
  exit 1
fi

# Parameter in einem Array speichern
PARAM=($@)

# Ports initialisieren; aber nur, wenn noch nicht erfolgt
for Port in $(echo ${GPIO[@]})
do
  if [ ! -f /sys/class/gpio/gpio${Port}/direction ] ; then
    echo "Initialisiere GPIO $Port"
    echo "$Port" > /sys/class/gpio/unexport
    echo "$Port" > /sys/class/gpio/export
    echo "out" >/sys/class/gpio/gpio${Port}/direction
    echo "0" >/sys/class/gpio/gpio${Port}/value
    chmod 666 /sys/class/gpio/gpio${Port}/direction
    chmod 666 /sys/class/gpio/gpio${Port}/value
  fi
done

for N in 0 1 2 3
do
  PA=${PARAM[$N]}
  GP=${GPIO[$N]}
  if [ $PA = "0" -o $PA = "1" ] ; then
    echo "$PA" >/sys/class/gpio/gpio${GP}/value
    echo "GPIO ${GP} auf $PA gesetzt"
  fi
done

Siehe auch


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate
Letzte Aktualisierung: