Jenseits des Smartphones: Android Things

Seite 2: Hardwarebeschleunigung

Inhaltsverzeichnis

Auch wenn Google Android Things mit der einen oder anderen Optimierung ausgestattet hat, kann es mit klassischen Embedded-Betriebssystemen schon deshalb nicht mithalten, weil es auf Java basiert und somit einige Bremsklötze wie die Garbage Collection mitbringt.

Mehr Infos

Pendel-Pendel!

Das zeitliche Verhalten von Prozessrechnersysteme lässt sich in der Modulationsdomäne besonders gut beschreiben. Ein englischsprachiges Video anhand des HP53310A findet sich auf YouTube. Wer keine gebrauchte Hardware kaufen möchte, kann bei SpectraCom mit dem CNT-91 auch neue Modulationsdomänen-Zähler erwerben.

Zur Optimierung bietet sich die Nutzung von hardwarebeschleunigten Bussystemen an. Es handelt sich dabei um Kommunikationsprotokolle, die eine Hardware-Engine im Prozessor des jeweiligen Host-Systems realisiert. Die Software hat dabei die Rolle des Beobachters, der Register schreibt und die zurĂĽckgelieferten Werte ausliest.

Eben Uptons Entscheidung, den Raspberry Pi auf einem Prozessor aus dem Hause Broadcom aufzubauen, stört ein wenig, da der BCM-Chip nur zwei PWM-Kanäle anbietet, die beim Raspberry Pi 3 immerhin alle mit Pins am GPIO-Port verbunden sind.

Zum Ermitteln der Pins hilft ein kleiner Trick: Ein via ADB (Android Debug Bridge) mit einer Workstation verbundenes Android-Things-Gerät bleibt aus Softwaresicht ein gewöhnliches Android-Smartphone. Zur Bestätigung dieses Verhaltens soll folgendes Code-Snippet dienen:

PeripheralManagerService manager =
new PeripheralManagerService();
List<String> portList = manager.getPwmList();
if (portList.isEmpty()) {
Log.i(TAG, "No PWM port available on this device.");
}
else {
Log.i(TAG, "List of available ports: " + portList);
}

Die Peripheral I/O API bietet Entwicklern die Möglichkeit, eine Liste aller auf dem jeweiligen Prozessrechnersystem vorhandenen Kanäle beziehungsweise Ports abzurufen. Das von Google vorgegebene Beispiel schreibt sie von Haus aus in das DeviceLog: Es spricht allerdings nichts dagegen, die Informationen wie in der folgenden Abbildung gezeigt durch Setzen eines Breakpoint auszulesen:

Der Breakpoint liefert die Erkenntnis, dass der Raspberry Pi tatsächlich nur über zwei PW-Kanäle verfügt.

Als nächste Aufgabe steht die Realisierung der eigentlichen PWM-Engine an, die für die Ausgabe einer charakteristischen Wellenform am Modulation-Domain-Analysator verantwortlich ist. Der Code beginnt erneut mit dem Anlegen einer Member-Variablen in MainActivity. PWM-Ports lassen sich dabei passend zum Namen über eine Instanz der Pwm-Klasse ansprechen, die folgendermaßen angelegt wird:

public class MainActivity extends Activity {
...
Pwm myPwm;

Auch in der eigentlichen Aktivierung findet sich auf den ersten Blick nichts Überraschendes. Der Namens-String kommt zum Erstellen einer Instanz der PWM-Klasse zum Einsatz, die anschließend aktiviert und mit einer Arbeitsfrequenz sowie einem Taktverhältnis ausgestattet wird:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PeripheralManagerService service =
new PeripheralManagerService();
try {
myPwm = service.openPwm("PWM0");
myPwm.setEnabled(true);
myPwm.setPwmFrequencyHz(2000);
myPwm.setPwmDutyCycle(50);
}
catch(Exception e){}

Interessant ist an dieser Stelle die Korrelation zwischen dem String und dem physischen Pin. Eine sympathische Übersicht ist auf der Pinout-Site verfügbar. Wer den Modulationsdomänenanalysator an den betreffenden Pin ansteckt und dem Gerät einige Zeit zum Samplen gibt, bekommt das in folgender Abbildung gezeigte Resultat:

Die Hardware-PWM-Engine ist unabhängig von Java-Eigenarten wie dem Garbage Collector.

An dieser Stelle sei noch vor einem potenziellen Anfängerfehler gewarnt: Die Funktion setPwmDutyCycle nimmt einen Double-Wert entgegen, der das Taktverhältnis des zu generierenden PWM-Signals beschreibt. Entwickler müssen dabei beachten, dass die gültigen Werte von 0 bis 100 laufen: Wer 0,5 übergibt, bekommt kein symmetrisches Taktverhältnis.

Die Vorteile der hardwarebeschleunigten Busse sind offensichtlich. Damit bleibt allerdings ein weiteres Problem ungelöst: Prozessrechner wie der Raspberry Pi verbrauchen – insbesondere im Vergleich zu einem dedizierten Mikrocontroller – ein Vielfaches an Energie, was für manche Szenarien untragbar ist. Oft reicht es aus, den Prozessrechner herunter- und bei Verfügbarkeit einer externen Energieversorgung wieder hochzufahren.

Der Artikel behandelt die Programmierung von 8-Bit-Microcontrollern nicht in der Tiefe wie der Blog von Michael Stal (interner Link), sondern soll stattdessen das in der folgenden Abbildung gezeigte Systemdiagramm annehmen, das sich eine Besonderheit vieler Schaltregler zu Nutze macht:

Dank des Enable-Ausgangs kann der Microcontroller den Prozessrechner gezielt abschalten.

Die Hersteller statten die Bauteile nämlich seit Langem mit einem Extraeingang aus, der das Federn der Spannungsversorgung und somit deren gezielte Abschaltung ermöglicht.

Ein ausfĂĽhrlicher Blick auf die Programmierung des verwendeten Microchip MCU PIC16F1503 wĂĽrde zu weit gehen. Interessant ist fĂĽr den konkreten Fall lediglich, dass er mit dem Raspberry Pi 3 ĂĽber das I2C-Protokoll (Inter-Integrated Circuit) kommunizieren wird. Das ursprĂĽnglich von Philips entwickelte Format ist ideal geeignet, um vergleichsweise langsame Clients an Prozessrechnersysteme anzuschlieĂźen. Es bietet im Vergleich zu SPI (Serial Peripheral Interface) zwar eine geringere Transferrate, vereinfacht aber das Anbinden zahlreicher Clients.

Controller lassen sich oft sowohl mit 5 als auch mit 3,3 V Signalspannung betreiben. Da der I2C-Bus des Raspberry Pi in der 3,3-Volt-Domäne lebt, ist es empfehlenswert, den Controller ebenfalls dort unterzubringen und sich somit den Aufwand für den Pegelwandler zu sparen.

Wie im Fall der PWM-Engines beginnt man beim I2C-Bus damit, eine Liste aller am Prozessrechner verfĂĽgbaren Busse zu beschaffen. Dazu bietet sich folgender Code an:

PeripheralManagerService manager =
new PeripheralManagerService();
List<String> deviceList = manager.getI2cBusList();
Mehr Infos

Achtung vor I2CDetect!

In der Praxis des Autors kam es beim Einsatz des unter Unix verbreiteten Kommandozeilenwerkzeugs i2cdetect immer wieder zu seltsamen Problemen: Manche Integrierte Schaltkreise reagieren geradezu allergisch auf den Scanprozess.

Google implementiert in Android Things kein I2C-Scansystem: Die folgenden Schritte erfolgen unter der Annahme, dass der Power Controller die Adresse 0x08 hat.

In Android Things kommt für jedes I2C-Gerät eine Instanz der Klasse I2cDevice zum Einsatz:

public class MainActivity extends Activity {
...
private I2cDevice myPWC;

Erwähnenswert ist für den folgenden Code, dass das Anlegen einer neuen I2C-Kommunikationsverbindung sowohl den Namen des Busses als auch die numerische Adresse des jeweiligen Geräts voraussetzt:


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PeripheralManagerService service =
new PeripheralManagerService();
try {
PeripheralManagerService manager =
new PeripheralManagerService();
myPWC = manager.openI2cDevice("I2C1", 0x08);
byte aByte=myPWC.readRegByte(0x01);
} catch (IOException e) {
Log.w(TAG, "Unable to access I2C device", e);
}

Nach dem erfolgreichen Anlegen der Geräteklasse erfolgt die Kommunikation wie erwartet. Der Beispielcode liest den Wert des Registers 0X01 aus, das beim verwendeten Power Controller Informationen über den aktuellen Systemzustand liefert.