Teil 1: Alle Infos rund um die GPIO des Raspberry Pi 2 und die Installation von Java
Teil 1: Alle Infos rund um die GPIO des Raspberry Pi 2 und die Installation von Java
Der Raspberry Pi wird als Mediacenter oder auch als Miniserver im Heimnetzwerk eingesetzt. Ein weiterer interessanter Aspekt des Raspberry Pi ist seine GPIO. Mit ihr ist es möglich, die unterschiedlichste Hardware an den Raspi anzubinden. Wie man die GPIO mit Java optimal ausreizt, erklärt diese Artikelserie. In Teil 1 beleuchten wir zunächst die GPIO im Detail und installieren Java auf dem Raspberry Pi.
Mit dem Raspberry Pi Typ 2 ist Anfang 2015 ein wirklich sehr leistungsfähiger Vertreter der Pi-Familie auf den Markt gekommen. Mit seinem ARM-Cortex-A7-Chip ist er seinen Vorgängern um Längen überlegen. Auf dem Raspberry Pi 2 kann man ohne Probleme Java als Programmiersprache verwenden. Diese Artikelserie wird Schritt für Schritt erklären, was zu tun ist, um mit Java die GPIO des Raspberry Pi zu verwenden. Wir zeigen am Beispiel einer Dachfenstersteuerung, was alles mit der GPIO und Java möglich ist. Im ersten Teil widmen wir uns zunächst den Details der GPIO und installieren Java auf dem Raspi. Im zweiten Teil geht es jetzt an das Programmieren mit Java. In Teil 3 beginnen wir ein echtes Projekt und bauen eine Dachfenstersteuerung. Dazu blicken wir zunächst auf die benötigte Hardware wie Taster und Temperatursensor und erläutern im vierten Teil die Programmierung von Servomotor und Display.
Der Rasperry Pi ist von Haus aus mit einer flexiblen und leistungsfähigen IO-Schnittstelle ausgestattet, die GPIO (General Purpose Input/Output). Wie wir in Tabelle 1 sehen, sind die einzelnen Anschlüsse der GPIO nicht nur einfache IOs – es gibt auch einige Anschlüsse, die für Spezialaufgaben wie I2C- und SPI-Bus vorgesehen sind. I2C und SPI sind serielle Bussysteme, an die zusätzliche IO-Bausteine angeschlossen werden können. Diese Bausteine müssen keine einfachen IOs sein; es können sehr komplexe A/D-, D/A-Wandler, digitale Sensoren, Timer oder auch Speicher sein. Für unsere Dachfenstersteuerung werden wir I2C, SPI, PWM und natürlich auch die einfache IO verwenden. Wer nicht mit diesen Schnittstellen vertraut ist, sollte sich einen Moment gedulden – wir werden gleich einen Blick darauf werfen.
Um den Raspberry Pi verwenden zu können, benötigen wir zu allererst ein Betriebssystem – Raspbian ist hier die erste Wahl. Wie der Name schon vermuten lässt, handelt es sich dabei um eine Debian-Portierung. Man kann sich einfach die aktuelle Version herunterladen. In der Zip-Datei steckt ein Image, das man mit einem geeigneten Tool auf eine mindestens 4 GB große SD-Karte schreiben muss:
sudo dd if=2015-05-05-raspbian-wheezy.img of=/dev/sdf
6400000+0 Datensätze ein
6400000+0 Datensätze aus
3276800000 Bytes (3,3 GB) kopiert, 1209,78 s, 2,7 MB/s
Danach kann man die Karte in seinen Raspberry Pi stecken und booten. Nach dem Booten landet man direkt in der raspi-config (Abb. 1). Hier kann man alle nötigen Einstellungen vornehmen, damit der Raspberry sein volles Potenzial entfalten kann. Falls Sie später eine Einstellung noch einmal ändern wollen, ist das kein Problem: Mit sudo raspi-config kann man das Programm einfach noch einmal aufrufen.
Kommen wir jetzt aber zu den wichtigen Einstellungen. Auf jeden Fall mit Punkt 1 das Filesystem erweitern, sonst verwendet der Raspi nur einen kleinen Teil der Speicherkapazität Ihrer SD-Karte. Unter Punkt 2 sollten Sie auf jeden Fall das Passwort für den User pi neu setzen. Wenn man auf dem Raspberry Pi eher remote arbeiten möchte, sollte man unter den Advanced Options (Punkt 8) den SSH Deamon starten. Möchte man direkt auf dem Raspberry arbeiten, ist es sinnvoll, im Punkt 4 (Internationalisation Options) die Umgebung richtig einzustellen. Das waren erst einmal die Einstellungen, die unbedingt gemacht werden sollten, damit wir später nicht in Probleme laufen. Wenn wir jetzt die raspi-config verlassen, möchte der Raspberry Pi rebooten. Ohne den Reboot sind unsere eben gemachten Änderungen wirkungslos. Nach dem Reboot zeigt uns der Raspberry seine aktuelle IP. Wir können uns jetzt bequem von unseren PC aus per SSH mit dem Raspberry Pi verbinden und remote auf ihm arbeiten. Das war es erst einmal mit Pi from scratch.
Wie bereits angesprochen, verfügt der Raspberry Pi über verschiedene Schnittstellen, die wir uns jetzt etwas genauer ansehen wollen.
I2C-Bus: Der I2C-Bus (ausgesprochen: I Quadrat C) ist ein serieller Datenbus, der für die Kommunikation über kurze Distanzen gedacht ist, das heißt nur innerhalb von Platinen oder Geräten. Die Datenübertragung erfolgt synchron über zwei Leitungen: Eine Datenleitung SDA und eine Taktleitung SCL. Beide Leitungen werden über Pull-up-Widerstände auf ein positives Potenzial gezogen. Die Kommunikation wird dabei immer vom Busmaster aus initiiert. Der Bus verfügt über einen 7-Bit-Adressbereich, das entspricht 128 Teilnehmern. Sechzehn Adressen sind schon reserviert, also bleiben netto 112 freie Adressen. Üblicherweise kann man nur die unteren drei Bits der Adressen an den Clients frei wählen, die oberen Bits sind fest verdrahtet.
Die möglichen Adressen für einen Baustein können dem Datenblatt entnommen werden. Hier einige Beispiele:
Die Daten werden Byte- oder Wortweise übertragen. Die Übertragungsgeschwindigkeit beträgt zwischen 100 KBit/s bidirektional (Standard Mode) bis zu 5 MBit/s unidirektional (Ultra Fast Mode). Es gibt verschiedene Möglichkeiten, die Reichweite des I2C-Busses zu erhöhen. Sie reichen vom Verringern der Pull-up-Widerstände bis zum Tunneln durch CAN-Bus-Treiber.
SPI-Schnittstelle: Die SPI-Schnittstelle (Serial Peripheral Interface ) wurde von Motorola entwickelt und ist dazu gedacht, unterschiedliche Peripherie-Bausteine (Slaves) an einen Busmaster anzuschließen. Die Kommunikation erfolgt synchron; das bedeutet, der Busmaster gibt einen festen Takt für die Datenübertragung vor. Dies erfolgt über die mit „CLK“ bezeichnete Leitung. Neben der Taktleitung gibt es noch die Leitungen MOSI (Master Out Slave In) und MISO (Master In Slave Out), über welche die Daten übertragen werden. Es gibt bei SPI keine festen Adressen für die einzelnen Busteilnehmer; die Daten werden von einem Teilnehmer heraus in den nächsten Teilnehmer hinein geschoben. Man kann sich den SPI-Bus wie ein langes Schieberegister vorstellen. Damit die Slaves wissen, dass der Zustand in den Schieberegistern konsistent ist, gibt es noch eine Leitung CE (Chip Enable). Wenn der Master alle Daten an die richtigen Stellen geschoben hat, setzt er das CE-Signal, damit die Slaves wissen, dass sie jetzt den Bus lesen oder schreiben können. Damit dieses Verfahren fehlerfrei funktionieren kann, muss der Busmaster die komplette Topologie des Busses kennen. Um dem letzten Teilnehmer am Bus ein Datenpaket zu schicken, muss dieses Paket durch alle davor liegenden Teilnehmer hindurch geschoben werden. Normalerweise verfügen die Slaves über eine NOOP-Funktion (No Operation), die den Slave dazu veranlasst, die Daten auf dem Bus nicht zu verarbeiten. Wenn man den SPI-Bus einsetzen möchte, muss man die Software zur Steuerung exakt an die Hardware anpassen. Dazu ist es notwendig, sich die Datenblätter der einzelnen Busteilnehmer sehr genau anzusehen.
PWM: PWM ist die Abkürzung für Pulsweitenmodulation. Bei diesem Verfahren wird ein Rechtecksignal mit konstanter Frequenz erzeugt. Es wird nur das Verhältnis zwischen Puls- und Pausenzeiten variiert (Abb. 2). Mit diesem Verfahren ist es möglich, die Leistung, die an einem Verbraucher ankommt, genau einzustellen. Der Grund, warum es so gemacht wird, ist recht einfach zu erklären: Eine Transistorendstufe erzeugt im gesperrten und vollständig leitenden Zustand kaum Verlustleistung, es wird also keine überschüssige Energie in Abwärme verwandelt. Stellt man einen Arbeitspunkt ein, der einer bestimmten Leistung entspricht, würde man im ungünstigsten Fall fast die Maximalleistung aus Verlustleistung produzieren – alte Netzteile mit linearer Regelung arbeiten auf diese Weise. Moderne Schaltnetzteile verwenden ein ähnliches Verfahren und kommen so auf einen Wirkungsgrad über 90 Prozent. Oft wird am Ausgang einer PWM-Stufe ein Tiefpassfilter installiert, um wieder eine saubere Gleichspannung zu bekommen. Das PWM-Verfahren wird auch bei Servomotoren verwendet, dort entspricht das Verhältnis zwischen Puls und Pause dem Winkel, den der Servo annimmt.
Die „Götter“ haben vor das Java-Programmieren der GPIO einen echten Installationsmarathon gestellt. Als Allererstes müssen wir uns natürlich erst einmal ein Betriebssystem für unseren Raspberry Pi besorgen und installieren. Die dazu nötigen Schritte sind im Abschnitt „Pi from scratch“ beschrieben. Dort wird auch beschrieben, was zu tun ist, um die Treiber, die I2C und die SPI-Schnittstelle zu laden.
Um auf die GPIO zugreifen zu können, müssen wir als Basis die WiringPi Library installieren; Sie stellt uns ein C-API und ein komfortables Kommandozeilentool zur Verfügung. Die Installation der WiringPi ist im Kasten „WiringPi installieren“ beschrieben. Nachdem sie installiert wurde, können wir die ersten kleinen Experimente mit der GPIO durchzuführen. Abbildung 3 zeigt einen Versuchsaufbau mit einer Sieben-Segment-Anzeige und zwei Tastern, die über die GPIO angesteuert werden können. In Schaltplan eins sehen wir genau, wie wir die einzelnen Komponenten anschließen müssen, damit wir ein erstes Versuchsfeld bekommen. Der Schaltplan (Abb. 4) kann im gEDA- und EPS-Format von der Website zu dieser Ausgabe heruntergeladen werden.
WiringPi installieren
WiringPi ist eine Bibliothek, die uns einen einfachen Zugriff auf die GPIO des Raspberry Pi ermöglicht. Sie wurde von Gordon Henderson entwickelt und wird von ihm weiter gepflegt. Sie stellt uns einfache Kommandos und ein C-API zur Verfügung, um die IO-Ports zu lesen und zu schreiben. Unter anderem bringt sie auch Treiber für den I2C- und den SPI-Bus mit. Die Installation kann mit folgenden Schritten durchgeführt werden:
sudo apt-get update
sudo apt-get upgrade
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build
cd ..
Nach dem Build können wir mit gpio readall testen, ob die Installation erfolgreich war. Das Ergebnis sollte in etwa wie in Listing 1 aussehen.
Listing 1
+-----+-----+---------+------+---+---Pi 2---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5V | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 1 | ALT0 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT0 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 2---+---+------+---------+-----+-----+
Für weiterführende Informationen zu WiringPi möchte ich auf die Homepage des Projekts verweisen.
Um einen neuen Wert in die Sieben-Segment-Anzeige zu schreiben, müssen wir einfach die entsprechenden Anschlüsse der GPIO als Ausgänge definieren und beschreiben. Das machen wir mit dem Kommandozeilentool GPIO, das mit WiringPi installiert wurde. Es ist zwar etwas umständlich, die einzelnen Bits von Hand zu setzen, gibt einem aber die Möglichkeit, jeden Anschluss einzeln zu testen:
pi@raspberrypi ~ $ gpio mode 0 out
pi@raspberrypi ~ $ gpio mode 1 out
pi@raspberrypi ~ $ gpio mode 2 out
pi@raspberrypi ~ $ gpio mode 3 out
pi@raspberrypi ~ $ gpio write 0 0
pi@raspberrypi ~ $ gpio write 0 1
pi@raspberrypi ~ $ gpio write 1 1
pi@raspberrypi ~ $ gpio write 0 0
Um die zwei Taster einzulesen, müssen wir erst die Ports als Eingänge definieren und dann einfach abfragen:
pi@raspberrypi ~ $ gpio mode 4 in
pi@raspberrypi ~ $ gpio mode 5 in
pi@raspberrypi ~ $ watch -n 0.5 'gpio read 4 ; gpio read 5'
Wenn die ersten Tests soweit funktioniert haben, können wir die Installation von Java und der Pi4J-Bibliothek starten.
Um die GPIO mit Java anzusteuern, verwenden wir die Library des Pi4J-Projekts. Damit diese fehlerfrei arbeitet, müssen vor der Installation einige Vorarbeiten erledigt werden: Als Erstes muss die WiringPi Library installiert werden, was wir ja bereits erledigt haben. Als Nächstes müssen wir das Oracle Java Developmet Kit installieren. Es kann direkt von der Oracle-Homepage heruntergeladen werden (Abb. 5).
Mit SFTP lässt sich das JDK am einfachsten auf den Raspberry übertragen:
sftp pi@192.168.2.6
pi@192.168.2.6's password:
Connected to 192.168.2.6.
sftp> put jdk-8u51-linux-arm-vfp-hflt.tar.gz
Uploading jdk-8u51-linux-arm-vfp-hflt.tar.gz to /home/pi/jdk-8u51-linux-arm-vfp-hflt.tar.gz
jdk-8u51-linux-arm-vfp-hflt.tar.gz 100% 77MB 8.5MB/s 00:09
sftp>
Anwender, die kein Linux verwenden, können diesen Schritt mit WinSCP erledigen. Auf dem Raspberry sind jetzt folgende Schritte zu erledigen, um das JDK zu installieren. Als Erstes legen wir eine neues Verzeichnis an. Im Unix-Filesystem Hierarchy Standard (FHS) ist das Verzeichnis /opt/ für zusätzliche Software vorgesehen. Daher speichern wir unser JDK genau dort ab und nicht etwa im Verzeichnis /home/ vom User Pi: sudo mkdir -p -v /opt/java. Nun wird das JDK in das Verzeichnis /opt/java/ entpackt:
gunzip jdk-8u51-linux-arm-vfp-hflt.tar.gz
sudo tar -xvf jdk-8u51-linux-arm-vfp-hflt.tar -C /opt/java/
Jetzt müssen wir dem System noch mitteilen, wo die neue Java-Version zu finden ist:
sudo update-alternatives --install "/usr/bin/java" "java"
"/opt/java/jdk1.8.0_51/bin/java" 1
Und zu guter Letzt teilen wir dem System noch mit, dass wir ab jetzt diese Java-Version als Default verwenden möchten: sudo update-alternatives –set java /opt/java/jdk1.8.0_51/bin/java.
Jetzt können wir das Oracle-JDK verwenden. Da einige Programme auf eine gesetzte JAVA_HOME-Umgebungsvariable angewiesen sind, setzen wir sie sicherheitshalber gleich mit. Möchte man die Variable systemweit setzen, muss man sie als root in die Datei /etc/environment eintragen. Für unsere Versuche reicht es, die Variable mit in die usereigene .bashrc aufzunehmen:
echo 'export JAVA_HOME="/opt/java/jdk1.8.0_51/bin"' >> ./.bashrc
Nach dem nächsten Login des Users pi hat er die Variable in seiner Umgebung. Um direkt weitermachen zu können, setzen wir uns die Variable nur für die aktuelle Session manuell: export JAVA_HOME=“/opt/java/jdk1.8.0_51/bin„. Die Installation nimmt etwas Zeit in Anspruch, aber das Warten hat sich gelohnt. Nun können wir einen kleinen Test starten, um zu sehen, ob unsere Java-Installation fehlerfrei arbeitet:
$ java -version
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b07)
Java HotSpot(TM) Client VM (build 25.51-b07, mixed mode)
$ javac -version
javac 1.8.0
Jetzt kommen wir zur Installation der Pi4J Library. Der schnellste Weg, die Library direkt aus dem Internet zu laden ist curl: curl -s get.pi4j.com | sudo bash.
Die Entwickler der Pi4J-Bibliothek haben, um uns das Leben etwas leichter zu machen, ein kleines Skript mitinstalliert. Das Pi4J Utility Script (Listing 2) setzt den Klassenpfad richtig, damit es möglich ist, schnell und einfach kleine Java-Programme zu kompilieren und zu testen.
Listing 2
pi4j --?
------------------------------
Pi4J Utility Script
------------------------------
USAGE: pi4j [OPTION]... (<FILE|CLASS|JAVA-ARGS>)
(if no option is specified, the '--run' option is assumed)
OPTIONS:
--------
?, --help : display this 'help' content
-v, --version : display Pi4J version
-u, --update : check for & install Pi4J updates
-U, --uninstall : uninstall Pi4J
-c, --compile <FILE> : exec javac with pi4j in classpath
-r, --run <CLASS> : exec java with pi4j in classpath
EXAMPLES:
---------
pi4j --version (display Pi4J version)
pi4j --update (update Pi4J installation)
pi4j --uninstall (uninstall Pi4J package)
pi4j --compile HelloWorld.java (compile 'HelloWorld.java' sources)
pi4j -c HelloWorld.java (compile 'HelloWorld.java' sources)
pi4j --run HelloWorld (run compiled 'HelloWorld' program)
pi4j -r HelloWorld (run compiled 'HelloWorld' program)
pi4j HelloWorld (run compiled 'HelloWorld' program)
Mit dem Befehl pi4j -c … .java kompilieren wir unsere Java-Programme. Wenn keine Fehler zurückgemeldet werden, wird eine gleichnamige .class-Datei im aktuellen Verzeichnis erstellt. Diese Java-Klasse können wir dann auch mit folgendem Befehl ausführen lassen: pi@i2c ~ $ pi4j -r …
Jetzt können wir unser erstes kleines Java-Testprogramm kompilieren und starten. Das machen wir mit folgenden zwei Befehlen:
pi4j -c count.java
pi4j -r count
Das Programm realisiert einen Zähler, den man über die Taster bedienen kann. Nach 30 Sekunden wird er wieder beendet (Listing 3).
Listing 3
import com.pi4j.wiringpi.Spi;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import java.lang.Thread;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin;
public class count
{
public static byte counter= 0x00;
public static GpioPinDigitalOutput bit0;
public static GpioPinDigitalOutput bit1;
public static GpioPinDigitalOutput bit2;
public static GpioPinDigitalOutput bit3;
public static void main(String args[])throws InterruptedException {
GpioController gpio = GpioFactory.getInstance();
GpioPinDigitalInput up = gpio.provisionDigitalInputPin(RaspiPin.GPIO_04, PinPullResistance.PULL_DOWN);
GpioPinDigitalInput down = gpio.provisionDigitalInputPin(RaspiPin.GPIO_05, PinPullResistance.PULL_DOWN);
bit0 = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_00, "bit0", PinState.LOW);
bit1 = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_01, "bit1", PinState.LOW);
bit2 = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_02, "bit2", PinState.LOW);
bit3 = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_03, "bit3", PinState.LOW);
up.addListener(new GpioPinListenerDigital() {
@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
if((event.getState()==PinState.HIGH) ) {
if (counter<9){
counter++;
}
countOut();
}
}
});
down.addListener(new GpioPinListenerDigital() {
@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
if ((event.getState() == PinState.HIGH)) {
if (counter>0){
counter--;
}
countOut();
}
}
});
Thread.sleep(10000);
} //main
public static void countOut () {
if ((counter&0x01)==1) { count.bit0.high(); } else { count.bit0.low(); }
if ((counter&0x02)==2) { count.bit1.high(); } else { count.bit1.low(); }
if ((counter&0x04)==4) { count.bit2.high(); } else { count.bit2.low(); }
if ((counter&0x08)==8) { count.bit3.high(); } else { count.bit3.low(); }
}
} //class
Im zweiten Teil der Artikelserie geht es an das Programmieren des Raspberry Pi 2 mit Java.