Java auf dem Raspberry Pi - Teil 1: GPIOs
Kurze Einführung in Java
Java ist eine der Programmiersprachen, die es schon sehr lange gibt. Die erste Version wurde 1995 veröffentlicht, im selben Jahr, in dem auch JavaScript, PHP und Qt geboren wurden. Python ist ein wenig älter, die erste Version wurde 1991 veröffentlicht. Während die Marke Java von Sun Microsystems zu Oracle übergegangen ist, wird die Entwicklung der Programmiersprache als Open-Source-Projekt betrieben, das 2020 zu GitHub umgezogen ist. Seit 2018 werden jährlich zwei neue Java-Releases zur Verfügung gestellt, was dazu führt, dass Fehlerkorrekturen und neu entwickelte Funktionen regelmäßig auf die Rechner gelangen. Zu den neuen Funktionen gehören auch Verbesserungen, die auf eingebettete Plattformen wie den Raspberry Pi abzielen.Es gibt die Ansicht, dass „Java tot ist“, aber die zahlreichen Konferenzen - auch wenn sie in Zeiten von Corona alle virtuell geworden sind - und viele Java-bezogenen Projekte sind ein Beweis dafür, dass die Sprache immer noch quicklebendig ist. Für die Weiterentwicklung von Java ist eine Vielzahl namhafter Unternehmen verantwortlich, darunter Oracle, Microsoft, SAP und Google. Es wird auch gemunkelt, dass die Hälfte der Microsoft Azure Cloud auf Java läuft! Heute gibt es Java-Distributionen von verschiedenen Open-Source- (jdk.java.net, adoptopenjdk.net) und kommerziellen Anbietern (Azul, BellSoft, Oracle und andere).
Java auf dem Raspberry Pi?!
Wurde der Raspberry Pi nicht dafür entwickelt, um mit Python programmiert zu werden? Das mag ja so sein, aber das bedeutet nicht, dass man nicht auch andere Sprachen verwenden kann. Und, obwohl dies ein Java-Artikel ist, wollen wir nicht behaupten, dass diese Sprache gegenüber Python, C oder einer anderen Sprache vorzuziehen ist! Jedes Projekt hat seine eigenen Anforderungen, für die eine bestimmte Sprache perfekt geeignet sein könnte, oder in der das Entwicklerteam die besten Kenntnisse hat.
Als mein erster Versuch, ein Python-basiertes Pong-Spiel zu bauen, erfolglos endete und sich die Benutzeroberfläche als enttäuschend erwies, wollte ich als Java-Entwickler erkunden, ob ich mein Fachwissen nicht vielleicht auf den Raspberry PI anwenden könnte. Glücklicherweise lassen sich Benutzeroberflächen mit JavaFX erzeugen, einem unabhängigen Projekt [4, 5], das Java um ein Framework zur Erstellung von GUIs (Graphical User Interface) erweitert. Es bietet alle typischen Basiskomponenten (Schaltflächen, Labels, Tabellen, Diagramme) und es gibt eine Vielzahl von freien und Open-Source-Bibliotheken, die diese Liste noch erweitert.
Hier fand ich auch die perfekte Kombination von Java mit dem Raspberry Pi, da ich eine Touchscreen-Oberfläche für das Drum-Booth meines Sohnes bauen wollte (Bild 1 und Bild 2).
Kombiniert mit einer Relaisplatine, einem Arduino und einigen LED-Streifen sollte dies die Basis für meine Experimente in der neuen Welt der Embedded-Java-Programmierung sein.
Vorbereitungen
Für die Experimente, die in diesem Artikel beschrieben werden, benötigen Sie einen aktuellen Raspberry Pi mit einem ARMv7- oder ARMv8-Prozessor. Die älteren Boards mit einem ARMv6 haben ein anderes internes Layout, für das Standard-Java nicht funktioniert. Wenn Sie Java auf einem solchen ARMv6-Board verwenden wollen, gibt es eine Lösung, die das Azul Zulu JDK verwendet.
Sie müssen keine in der Artikelreihe verwendeten Code-Beispiele abtippen, sie sind in einem GitHub-Repository verfügbar. Beginnen Sie mit der Vorbereitung der SD-Karte für den Raspberry Pi. Seit einiger Zeit gibt es ein praktisches „Imager“-Tool, in dem Sie „Raspberry Pi OS Full (32-bit)“ wählen (Bild 3).
Sobald die SD-Karte fertig ist und Sie Ihren Raspberry Pi gestartet haben, geben Sie am Terminal java -version ein, um zu überprüfen, ob es die Version 11 ist. Wie Sie sehen werden, ist OpenJDK in dieser Vollversion des Betriebssystems vorinstalliert:
$ java -version
openjdk version "11.0.9" 2020-10-20
OpenJDK Runtime Environment (build 11.0.9+11-post-Raspbian-1deb10u1)
OpenJDK Server VM (build 11.0.9+11-post-Raspbian-1deb10u1, mixed mode)
Visual-Studio-Code
Sie können Ihren Java-Code auf einem PC entwickeln, testen und ausführen, bevor Sie ihn nach Fertigstellung auf den Raspberry Pi übertragen. Aber es gibt noch einen anderen Ansatz: Visual Studio Code. Diese kostenlose IDE (Integrated Development Environment) von Microsoft hat eine lange Liste von Erweiterungen, die sie zum perfekten Begleiter für jedes Programmierprojekt machen. Es gibt eine Version für 32-Bit-ARM-Systeme (zum Beispiel das Raspberry-Pi-OS) und 64-Bit-ARM-Systeme (das kommende Raspberry-Pi-OS, das sich noch in der Entwicklung befindet) oder auch Ubuntu Desktop.
Es gibt auch ein „Java Extension Pack“, das der IDE mehrere Java-Erweiterungen hinzufügt, um sie zu einer vollständigen IDE für Java-Entwickler zu machen (Bild 4)!
Experimentieren mit Hello World!
Lassen Sie uns unser erstes Java-Programm auf dem Raspberry Pi ausprobieren. Schreiben Sie in Visual Studio Code, in einem Texteditor oder im Terminal eine neue Textdatei namens „HelloWorld.java“ mit folgendem Inhalt:
public class HelloWorld {
public static void main(String args[]) {
System.out.println("Hello World!");
}
}
Unser gesamter Code ist Teil der Klasse HelloWorld, die per Konvention gleich dem Dateinamen ist. Ein Java-Programm beginnt mit der Methode public static void main(String args[]). Das einzige, was wir hier tun müssen, ist „Hello World!“ als Ausgabe des Programms anzugeben.
Mit Java 11 können wir dieses einfache Programm ohne Kompilierung des Codes ausführen. Geben Sie im Terminal im Verzeichniss der Java-Datei den Befehl java HelloWorld.java ein, um das folgende Ergebnis zu erhalten
pi@raspberrypi:~/elektor $ java HelloWorld.java
Hello World!
Sicherlich ist print() in Python kürzer im Vergleich zu System.out.println() in Java, aber wir verzeihen Java dies als Jugendsünde.
Auslesen der CPU-Temperatur
Auf viele der Systemwerte, Ein- und Ausgaben kann über das Verzeichnis /sys/ des Linux-Dateisystems zugegriffen werden. Die dort gespeicherten Werte können einfach als Textdatei ausgelesen werden. Versuchen wir einmal, die Temperatur des Raspberry-Pi-Prozessors aus einer der Dateien in diesem sys-Verzeichnis zu ermitteln. Die Namensgebung in der Verzeichnisstruktur ist nicht immer sehr eindeutig und auch die Interpretation der Werte ist nicht immer einfach, aber es ist ein guter Ausgangspunkt, um auch ein wenig das Linux-System kennenzulernen. Führen Sie den folgenden Befehl aus:
$ ls -l /sys/
total 0
drwxr-xr-x 2 root root 0 Dec 2 15:44 block
drwxr-xr-x 29 root root 0 Feb 14 2019 bus
drwxr-xr-x 64 root root 0 Feb 14 2019 class
drwxr-xr-x 4 root root 0 Feb 14 2019 dev
drwxr-xr-x 10 root root 0 Feb 14 2019 devices
drwxr-xr-x 3 root root 0 Feb 14 2019 firmware
drwxr-xr-x 8 root root 0 Jan 1 1970 fs
drwxr-xr-x 12 root root 0 Jan 1 1970 kernel
drwxr-xr-x 122 root root 0 Feb 14 2019 module
drwxr-xr-x 2 root root 0 Dec 15 11:39 power
Es scheint, dass wir hier eine Menge Informationen finden können! Betrachten einige davon etwas genauer:
$ cat /sys/firmware/devicetree/base/cpus/cpu@0/compatible
arm,cortex-a72
$ cat /sys/class/net/wlan0/address
dc:a6:32:c5:b7:9d
$ cat /sys/class/bluetooth/hci0/uevent
DEVTYPE=host
$ cat /sys/class/thermal/thermal_zone0/temp
30667
Die letzte Angabe sieht tatsächlich wie eine Temperatur in Grad Celsius aus, wenn wir den Wert durch 1000 teilen würden! Lassen Sie uns ein einfaches Programm schreiben, das diesen Wert jede Sekunde ausliest und die Durchschnittstemperatur berechnet.
Der Code in Listing 1 verwendet einige zusätzliche Java-Methoden, die zu Beginn importiert werden. Die io-Methoden lesen eine sys-Datei als Textdatei. List und ArrayList erzeugen eine Liste aller Messwerte. In der main()-Methode rufen wir eine separate Methode checkTemp() auf, die die Temperatur erfasst und in der Liste speichert. Es ist gute Praxis, jede Funktionalität in einer eigenen Methode zu isolieren, um den Code sauber zu halten.
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.List;
import java.util.ArrayList;
public class CPUTemp {
private static final String FILE = "/sys/class/thermal/thermal_zone0/temp";
private static final List<Integer> values = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
while(true) {
checkTemp();
Thread.sleep(1000);
}
}
private static void checkTemp() {
try (BufferedReader br = new BufferedReader(new FileReader(FILE))) {
int value = Integer.valueOf(br.readLine());
values.add(value);
int total = values.stream().mapToInt(Integer::valueOf).sum();
System.out.println("Now: " + value
+ " - Average: " + (total / values.size())
+ " - Number of measurements: " + values.size());
} catch (Exception ex) {
System.err.println("Error during temperature check: "
+ ex.getMessage());
}
}
}
Thread.sleep(1000) sorgt dafür, dass zwischen jeder Temperaturermittlung eine Sekunde verstreicht. Innerhalb der Methode wird der Durchschnittswert berechnet, indem die Gesamtsumme der Werteliste mit Hilfe eines Streams berechnet wird. Streams wurden in Java 8 eingeführt und sind eine sehr leistungsfähige Möglichkeit, mit Listen von Elementen zu arbeiten. Auch hier ist der Dateiname auch der Name der Klasse „CPUTemp.java“.
Wie zuvor ist dies eine einfache Java-Klasse ohne zusätzliche Abhängigkeiten, so dass wir sie ohne Kompilierung ausführen können. Die Ausgabe kann mit „Strg+c“ angehalten werden:
pi@raspberrypi:~/elektor $ java CPUTemp.java
Now: 36998 - Average: 36998 - Number of measurements: 1
Now: 34563 - Average: 35780 - Number of measurements: 2
Now: 35537 - Average: 35699 - Number of measurements: 3
Now: 36024 - Average: 35780 - Number of measurements: 4
Now: 35537 - Average: 35731 - Number of measurements: 5
Ansteuern einer LED und Auslesen eines Tasters
Lassen Sie uns eine weitere Single-File-Java-Anwendung erstellen, die zwei LEDs umschaltet und den Zustand eines Tasters auslesen kann. Der Schaltung, um zwei LEDs und einen Taster an separate GPIOs anzuschließen, ist recht einfach. Der Taster benötigt 3,3 V, nicht 5,0 V, verwenden Sie also den richtigen Power-Pin des Raspberry Pi! In diesem Aufbau sind die LEDs an die Pins BCM 14 und BCM 15 angeschlossen, der Taster an BCM 18 (Bild 5).
Testen am Terminal
Mit den im Raspberry-Pi-OS integrierten Befehlen können wir vom Terminal aus auf die GPIOs zugreifen. Dies nutzen wir, um unsere Verdrahtung zu testen. Führen Sie die folgenden Befehle aus, um Pin 14 als Ausgangspin (op) zu konfigurieren und ihn auf High (dh) oder Low (dl) zu schalten. Versuchen Sie das Gleiche für die andere LED an BCM 15:
$ raspi-gpio set 14 op
$ raspi-gpio set 14 dh
$ raspi-gpio set 14 dl
Wir können einen ähnlichen Ansatz verwenden, um die Taste zu testen, indem wir den Pin als Eingangspin konfigurieren und den Zustand des Eingangs abfragen:
$ raspi-gpio set 18 ip
$ raspi-gpio get 18
GPIO 18: level=0 fsel=0 func=INPUT pull=DOWN
$ raspi-gpio get 18
GPIO 18: level=1 fsel=0 func=INPUT pull=DOWN
Wenn die Taste gedrückt wird, ändert sich der Wert „level“ auf 1.
Doing It in Java
Mach es in Java!
Wir verwenden den gleichen grundlegenden Ansatz wie in „CPUTemp.java“ und nutzen die Terminalbefehle in einem Java-Programm, indem wir Runtime.getRuntime().exec(cmd) angeben. Wenn wir die Benutzereingabeklasse Scanner hinzufügen, können wir auch das Ergebnis des Befehls erfassen und es zur Überprüfung des Zustands der Schaltfläche verwenden. Dies wird in Listing 2 gezeigt.
Der Code nutzt ein Action-Enum, um die Verarbeitung des Befehls zu vereinfachen. In seiner einfachsten Form ist ein Enum nur eine Liste von vordefinierten Namen, aber in diesem Beispiel fügen wir jedem Namen einen Wert hinzu. Dies ist ein starker Pluspunkt von Enums, da sie es ermöglichen, den Code viel lesbarer zu machen und ihn meist gleichzeitig auch zu vereinfachen. Wie in der doAction-Methode zu sehen ist, wird das Action-Enum als Parameter genutzt und sein Wert verwendet, um den auszuführenden Befehl zu generieren.
Die Kommentare im Code sollten alles klar machen, da dies nur einfaches Java ist, um die Verwendung von Eingangs- und Ausgangs-GPIOs zu illustrieren. Der komplexeste Teil ist die runCommand-Methode, die einige kombinierte Methoden verwendet, um den Befehl auszuführen, das Ergebnis zu lesen und mögliche Fehler zu behandeln. Falls Ihnen dies im Moment noch nicht klar ist, keine Sorge - das wird es, sobald der Code läuft!
Da dieses Beispiel wiederum keine Abhängigkeiten aufweist, können wir es ohne Kompilierung ausführen. Die Ausgabe zeigt, was vor sich geht, Sie sollten also sehen, wie Ihre LEDs in verschiedenen Intervallen ein- und ausschalten. Wenn die LEDs aufhören zu blinken, können Sie die Taste drücken, die zehnmal im Abstand von einer Sekunde überprüft wird. Die erwartete Ausgabe ist wie folgt:
$ java RaspiGpio.java
Executing: raspi-gpio set 14 op
Executing: raspi-gpio set 15 op
Executing: raspi-gpio set 18 ip
Blink loop: 0
Executing: raspi-gpio set 14 dh
Executing: raspi-gpio set 15 dh
Blink loop: 1
Executing: raspi-gpio set 14 dl
Executing: raspi-gpio set 15 dl
...
Executing: raspi-gpio get 18
Button check 0: GPIO 18: level=0 fsel=0 func=INPUT pull=DOWN
- PUSHED: false
...
Button check 3: GPIO 18: level=1 fsel=0 func=INPUT pull=DOWN
- PUSHED: true
...
Hausaufgaben für’s nächste Mal!
Mit einigen Grundlagen haben wir eine kleine Einführung in Java auf dem Raspberry Pi gegeben. Sie können gerne mit dem Beispielcode herumspielen und in den angegebenen Links die Hintergründe nachlesen, um Ihr Verständnis weiter zu vertiefen. Dies wird hoffentlich die nächsten Schritte im nächsten Artikel einfacher machen, wenn wir eine vollständige Java-Anwendung entwickeln und unsere GPIOs mit einer Webseite verknüpfen.
(200617-02)
Haben Sie Fragen oder Kommentare?
Haben Sie technische Fragen oder Kommentare zu diesem Artikel? Dann kontaktieren Sie den Autor direkt über javaonraspberrypi@webtechie.be oder die Redaktion über ditor@elektor.com.