![]() |
GPIO beim Raspberry PiProf. Jürgen Plate |
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/exportMit 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/directionoder
echo "out" > /sys/class/gpio/gpio23/direction echo "0" > /sys/class/gpio/gpio23/valueBei 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 doneAchtung: 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.rulesan. 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_lowWenn active_low auf 1 gesetzt ist, liegt am Pin "0" an, wenn in value "1" steht, und umgekehrt.
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
trap 'GPIO-aus.sh ; exit' 0 1 2 3 15Dabei 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/valuewhile : 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
Neulich schrieb mir Thomas Omerzu eine E-Mail zu einem Fehler, der beim Aktivieren der Ports auftritt und wohl nur manchmal anzutreffen ist. Er schrieb:
"Ich habe auf meinem RasPi B Rev. 2 den Effekt, dass das gpio_export bzw. ein echo ... > /sys/.../export sich im System erst mit einer kleinen Verzögerung auswirkt. Ein sofort nachfolgender Zugriff z. B. auf gpio_direction oder einem entsprechenden echo ins Filesystem schlägt mit 'Permission denied' fehl, und zwar nicht, weil man kein Schreibrecht hätte, sondern weil die Datei noch nicht existiert."Inzwischen konnte ich den Fehler auch bei meinen RasPis reproduzieren. Wenn Sie also auf ein unerklärliches Verhalten in diesem Zusammenhang stoßen, bauen Sie ein Delay nach dem Anlegen eines GPIO-Ports ein. Das sleep-Kommando von Linux erlaubt übrigens auch Sekundenbruchteile, bispielsweise den Aufruf sleep 0.1 .
Auch im C-Programm muss ein delay(100) zwischen gpio_export und gpio_direction verwendet werden, um den Fehler zu umgehen.