Raspberry Pi IO

Aus Hobbyelektronik.org
Raspberry Pi mit angeschlossenen LEDs

Ich war einer der glücklichen, die einen der ersten Raspberry Pis ergattern konnte. Zu meiner Schande muss ich gestehen, dass ich relativ lange gezögert habe, mich intensiver mit dem Computerchen zu beschäftigen.

Interessant für Bastler ist auf jeden Fall die anschließbare Peripherie. Neben UART, SPI und I²C (für letztere existieren noch keine Treiber und ich bin leider noch zu blöd welche zu schreiben) existieren auch frei belegbare GPIOs auf dem Board.

Erster Test

Das Beispielprogramm erfüllt seinen Zweck sehr gut. Nach ein wenig Herumspielen wollte ich wissen, wie schnell man denn die Bits wackeln lassen kann.

Eine kleinere Änderung im Source lässt GPIO17 (bzw. "GPIO 0" an der Steckerbelegung - ich möchte nicht wissen, wer auf diese missverständliche Namensgebung gekommen ist) mit nahezu maximaler Frequenz 1000 Mal ein- und ausschalten.

for (rep=0; rep<1000; rep++)
{
	GPIO_SET = 1<<17;
	GPIO_CLR = 1<<17;
}

Mit den Shellbefehlen

gcc io.c
sudo ./a.out

Lässt sich der Code kompilieren und anschließend ausführen.

Der Logic-Analyzer misst knapp 77ns Periodendauer, also 13MHz wobei ab und zu geringfügig größere Pausen entstehen, die die Frequenz auf 11MHz absinken lassen. Das ist trotzdem ganz respektabel!

Das Problem mit root

Das Beispielprogramm funktioniert also super, solange man es als root ausführt.

Hinsichtlich der Sicherheit ist das jedoch etwas ungeschickt - wer möchte seine Programme dauerhaft als root ausführen?

Besonders für mein Vorhaben eine ganz schlechte Idee: ich möchte als kleine Demonstration LEDs per Browser schalten lassen. Gleichzeitig verbietet der gesunde Menschenverstand, einen Webserver oder gar PHP als root-User laufen zu lassen (besonders in Hinblick auf die schlechte Presse in den letzten Tagen).

Auch das /dev/mem-Interface möchte man nicht unbedingt fürs Userland freigeben (was zudem auch nicht funktioniert).

Glücklicherweise kann man auf die GPIOs einzeln über virtuelle Dateien zugreifen. Das ist zwar nicht ganz so schnell, dafür kann man die virtuellen Dateien "normalen" Benutzern zugänglich machen.

Dieses Zugänglichmachen findet für jeden Pin in 4 Schritten statt (hier am Beispiel von GPIO17):

sudo echo "17" > /sys/class/gpio/export
sudo echo "out" > /sys/class/gpio/gpio17/direction
sudo chmod 666 /sys/class/gpio/gpio17/value
sudo chmod 666 /sys/class/gpio/gpio17/direction

Die letzten beiden Zeilen erlauben Lese- und Schreibzugriff für Besitzer (root), Gruppe (root) und jeden anderen. Alternativ kann man auch mit chown den Besitzer bzw. die Gruppe ändern, sodass man keinen Zugriff für jeden geben muss. In meinem Fall ist es aber (noch) egal.

Um alle Ports auf einmal "freizugeben" habe ich eine kleines Shell-Script geschrieben, die dies erledigt:

#!/bin/sh

echo "17" > /sys/class/gpio/export
echo "18" > /sys/class/gpio/export
echo "21" > /sys/class/gpio/export
echo "22" > /sys/class/gpio/export
echo "23" > /sys/class/gpio/export
echo "24" > /sys/class/gpio/export
echo "25" > /sys/class/gpio/export

echo "out" > /sys/class/gpio/gpio17/direction
echo "out" > /sys/class/gpio/gpio18/direction
echo "out" > /sys/class/gpio/gpio21/direction
echo "out" > /sys/class/gpio/gpio22/direction
echo "out" > /sys/class/gpio/gpio23/direction
echo "out" > /sys/class/gpio/gpio24/direction
echo "out" > /sys/class/gpio/gpio25/direction

chmod 666 /sys/class/gpio/gpio17/value
chmod 666 /sys/class/gpio/gpio18/value
chmod 666 /sys/class/gpio/gpio21/value
chmod 666 /sys/class/gpio/gpio22/value
chmod 666 /sys/class/gpio/gpio23/value
chmod 666 /sys/class/gpio/gpio24/value
chmod 666 /sys/class/gpio/gpio25/value

chmod 666 /sys/class/gpio/gpio17/direction
chmod 666 /sys/class/gpio/gpio18/direction
chmod 666 /sys/class/gpio/gpio21/direction
chmod 666 /sys/class/gpio/gpio22/direction
chmod 666 /sys/class/gpio/gpio23/direction
chmod 666 /sys/class/gpio/gpio24/direction
chmod 666 /sys/class/gpio/gpio25/direction

Für alle, die Linux nicht kennen: das "#!/bin/sh" am Anfang ist kein gewöhnlicher Kommentar, sondern ein Shebang, das der Bash (also der Konsole) sagt, mit was die Datei ausgeführt werden möchte. Wichtig ist auch, dass die Zeilenenden auf Linefeed (\n) und nicht auf Carrier Return+Linefeed eingestellt sind. Jeder gute Editor (z. B. Notepad++ oder SciTE) kann das. (Noch ein kleiner Hinweis: das 666 ist nichts satanisches, sondern die Berechtigung rw-rw-rw-, die es jedem Benutzer erlaubt, die virtuellen Dateien zu lesen und schreiben. Einen großen Dank an Y.T., der mich auf einen kleinen aber eklatanten Fehler hingewiesen hat)

Wichtig ist anschließend, dass die Datei vor dem Start Ausführungsrechte bekommt:

chmod 770 gpio.sh

Anschließend kann man sie mit dem Befehl folgendem Befehl ausführen:

sudo ./gpio

Achtung: Die Freigabe der GPIOs ist nicht persistent. Nach einem Neustart muss das Script erneut ausgeführt werden!

Webserver

Ein grundlegendes Element fehlt noch: der Webserver, den man wahrscheinlich am einfachsten mit PuTTY installiert.

Hier die Befehle, die ich zur Installation von lighttpd mit PHP5 verwendet habe (keine Garantie auf Funktionalität, Vollständigkeit und Korrektheit):

sudo groupadd www-data
sudo aptitude install lighttpd
sudo aptitude install php5-cgi
sudo lighty-enable-mod fastcgi
sudo adduser pi www-data

sudo chown -R www-data:www-data /var/www
sudo chmod -R 775 /var/www

Lighttpd habe ich deswegen gewählt, weil er (wie der Name suggeriert) etwas leichter und schneller als der mächtigere (aber etwas schwerfällige) Apache ist.

Damit PHP funktioniert, muss in der Datei /etc/lighttpd/lighttpd.conf (wenn noch nicht vorhanden) durch das Ausführen von

sudo nano /etc/lighttpd/lighttpd.conf

folgender Text hinzugefügt werden:

fastcgi.server = ( ".php" => ((
"bin-path" => "/usr/bin/php5-cgi",
"socket" => "/tmp/php.socket"
))) 

Nach einem Server-Neustart mit

sudo /etc/init.d/lighttpd force-reload

sollte der Raspberry Pi per HTTP erreichbar sein.

Software

Server

Wie schon erwähnt läuft auf dem Server (oder sollte zumindest) PHP laufen. Da ich es nicht mag, Seiten wegen Kleinigkeiten neu zu laden, kommt ein Ajax/JSON-Interface zum Einsatz.

Dementsprechend fällt die Serversoftware minimalistisch aus.

Beispielsweise übernimmt read.php das Lesen der Ports und deren Richtung. Die Informationen werden in ein assoziatives (Name => Wert) Array geschrieben, ins JSON-Format umgewandelt und an den Client ausgegeben:

<?php
$bits = array(17, 18, 21, 22, 23, 24, 25);
$retval = array();
for($x = 0; $x < count($bits); $x++) {
	$bit = $bits[$x];
	$val = trim(@shell_exec("cat /sys/class/gpio/gpio".$bit."/value"));
	$dir = trim(@shell_exec("cat /sys/class/gpio/gpio".$bit."/direction"));
	
	$val = $val == "1" ? 1 : 0;
	$dir = $dir == "out" ? "o" : "i";
	
	$retval[] = array("bit" => $bit, "val" => $val, "dir" => $dir);
}
echo json_encode($retval);

Client

Den Client habe ich relativ schnell (und etwas schlampig) in JavaScript heruntergerissen.

Wer etwas JavaScript und die vielen d.ebi, d.ac, d.ce und d.ctn irritiert - das sind Abkürzungen für häufig verwendete Befehle, die ich in common.js definiert habe:

var d = {
	d : document,
	w : window,
	ebi : function(i) { return document.getElementById(i); },
	ebn : function(n) { return document.getElementsByName(n); },
	febn : function(n) { return document.getElementsByName(n)[0]; },
	ce : function(n) { return document.createElement(n); },
	ctn : function(n) { return document.createTextNode(n); },
	ac : function(p, c) { return p.appendChild(c); },
	ac1 : function(p, c) { p.appendChild(c); return p; }
}

d.ebi steht zum Beispiel für document.getElementById

d.ac für document.appendChild

usw...

In diesem Sinne auch eine Entschuldigung an alle, die bei dem Klammer-Massaker einen Knoten im Hirn bekommen ;-)

Dadurch, dass nahezu alles in JavaScript gemacht wird, ist der HTML-Body gähnend leer. Erst beim Laden wird der Seite Leben eingehaucht:

Der Code erzeugt zunächst eine Tabelle, die dann durch Objekte von GpioPin gefüllt wird. Durch Klick auf die span-Elemente bzw. Bilder wird der Inhalt umgeschaltet und per XmlHttpRequest ein Kommando an den Server geschickt.

Beim Klick auf Lesen wird ebenfalls per XmlHttpRequest eine Anfrage an den Server gesendet, wie oben erwähnt, mit einem JSON-String antwortet. Dies sieht dann z. B. wie folgt aus:

[{"bit":17,"val":0,"dir":"o"},{"bit":18,"val":1,"dir":"o"},{"bit":21,"val":0,"dir":"o"},{"bit":22,"val":0,"dir":"i"},{"bit":23,"val":1,"dir":"i"},{"bit":24,"val":0,"dir":"o"},{"bit":25,"val":0,"dir":"o"}]

Aufgedröselt wird die Struktur deutlicher:

[
	{
		"bit" : 17,
		"val" : 0,
		"dir" : "o"
	},{
		"bit" : 18,
		"val" : 1,
		"dir" : "o"
	},{
		"bit" : 21,
		"val" : 0,
		"dir" : "o"
	},{
	...
	}
]

Diese wird durch den JSON-parser in ein Objekt umgewandelt, per Schleife zugeordnet und durch die Funktion GpioPin::SetVals() gesetzt.

Die Richtung und Wert kann man einfach durch Klick auf das jeweilige Element verändern. Durch einen Klick auf "Refresh" kann man die tatsächlichen Werte vom Server ermitteln, nach dem Aktivieren der Checkbox geschieht dies automatisch im Sekundentakt.

Getestet wurde die die Anwendung bis jetzt im Firefox, "Standard" Android-Browser und dem aktuellen IE, wobei in letzterem die Aktualisierung nicht richtig funktioniert.

Download

Datei:Raspberry Pi IO.zip

Hinweise

Der Inhalt der ZIP-Datei kann relativ einfach mit FileZilla auf den Raspberry (per SSH-Filetransfer) geschoben werden. Der Order IO muss unter /var/www/ liegen, gpio.sh an einem nahezu beliebigen Ort. Vor dem Ausführen der gpio.sh nicht vergessen, die Ausführungsrechte zu setzen!

Banana Pi

Ein freundlicher Hinweis von Oli:

Ich hatte das mal ausprobiert auf einen Banana PI mit Raspbian, Ging leider erst nachdem ich in der gpio.sh zum schluss das eingefügt habe.
Nur zur Ergänzung:
chgrp -R www-data /sys/class/gpio
chmod -R g+rw /sys/class/gpio