Der Weg zum eigenen Kampfroboter ist lang und beschwerlich. Warum also nicht mit einer Lego-Variante beginnen, die sich auch noch über einen Raspberry Pi 3 steuern lässt? Linux-Magazin-Autor Konstantin Agouros hat sich die entsprechende Hardware besorgt und die Kombination getestet.
Programmierbares Lego ist nicht neu. Mindstorms [1] erscheint bereits in dritter Generation, und auch die hat bereits fünf Jahre auf dem Buckel. In der Version ohne programmierbaren Baustein (Brick) brachte Lego zuletzt Lego Boost heraus, um jungen (und älteren) Fans das Programmieren näher zu bringen.
Für den Brick im Zentrum normaler Mindstorms-Projekte bietet die Firma Dexter Industries eine Alternative in Form des Brick Pi 3 [2]. Die lässt sich auf den Pi aufstecken und nimmt dann Verbindung zu den Sensoren und Aktoren auf. Das ist nicht nur für verspielte Entwickler interessant, sondern lässt sich im Prinzip auch für ernsthaftere Szenarien einsetzen. So kann der Roboter-Admin recht unkompliziert eine fahrende Kamera oder eine bewegliche Sensorstation konstruieren.
Dexter Industries ist ein amerikanisches Unternehmen, das Roboter zu Lehrzwecken entwickelt und vertreibt. Es wurde 2010 von John Cole gegründet und startete “auf dem Küchentisch”. Das Produktportfolio umfasst inzwischen einige Bausätze, von denen auch manche mit Raspberry Pis interagieren.
Dieser Artikel beschreibt den Brick Pi 3, die neue Generation des Brick Pi. Dieser erlaubt es, zum Steuern von Lego-Mindstorms-Bausätzen einen Pi als Ersatz des Lego Brick zu verwenden.
Anschlüsse
Beim Brick Pi 3 handelt es sich um eine Aufsteckplatine für die GPIO-Ports eines Raspberry Pi 3. Diese verfügt über je vier Lego-Anschlüsse für Sensoren und vier für Motoren.
Die Sensoren und vor allem die Motoren benötigen Strom. Zwar stecken im Lego Brick sechs AA-Batterien, die den Brick und alle angeschlossenen Motoren antreiben, doch ist dieser Strom schnell verbraucht. Ein Pi kann über seine GPIO-Ports zwar Strom liefern (3,3 Volt), aber der reicht bei Weitem nicht aus, um die Lego-Komponenten zu versorgen.
Daher bringt auch die Brick-Pi-3-Platine eine Stromversorgung mit, an die sich ein Batteriepack oder Netzteil anschließen lassen. Daneben besitzt die Platine einen Schalter zum An- und Ausschalten.
Die Brick-Pi-Platine reicht die GPIO-Ports nach oben durch. Das ermöglicht es, an die nicht benutzten Ports andere Sensoren anzuschließen. Die technischen Details des Geräts finden sich auf Github [3], darunter auch die Belegung der GPIO-Ports.
Testbereit
Der Hersteller bietet die Platine entweder als Base Kit für 100 US-Dollar an oder als Starter Kit inklusive Raspberry Pi 3, Speicherkarte und eines zusätzlichen Sensors für rund 180 US-Dollar [2]. Für den Test bestellte der Linux-Magazin-Autor das Base Kit, das außer der Platine noch ein zusammenschraubbares Gehäuse für den Raspberry Pi 3 und den Brick Pi mitbringt, außerdem einen Batteriepack mit acht AA-Batterien. Abbildung 1 zeigt alle notwendigen Einzelteile vor der Montage, Abbildung 2 veranschaulicht den Zustand danach.

Abbildung 2: Das montierte Ergebnis sieht schon etwas ansehnlicher aus, auch wenn der Batteriepack recht groß ausfällt.
Im Gegensatz zum ersten Modell des Brick Pi (ohne “3” dahinter) kommuniziert der Pi inzwischen nicht mehr über die serielle Schnittstelle auf den GPIO-Ports, sondern über das SPI-Protokoll [4], was die Kommunikation optimiert. Eine Transaktion (Lesen eines Sensorwerts, Starten eines Motors und so weiter) dauert laut Aussage des Dexter-Supports ungefähr 0,5 Millisekunden. In der Theorie ermöglicht das etwa 2000 Transaktionen pro Sekunde.
Unter Strom
Die Webseite von Dexter beschreibt verschiedene Möglichkeiten zur Stromversorgung. Steckt der Pi an der normalen USB-Stromversorgung, startet das Gerät normal, aber der Brick Pi lässt sich nicht nutzen. Möchte der Anwender auf dem Pi direkt programmieren, ist diese Betriebsart aber eine Option.
Die acht AA-Batterien liefern 12 Volt, was für den Pi und den Brick Pi genügt. Auch der Betrieb mit einem 9-Volt-Block ist möglich, aber das Vergnügen ist dann eher von kurzer Dauer. Zudem ist es möglich, den Pi bei Batteriebetrieb gleichzeitig an die USB-Stromversorgung anzuschließen. Dann fließt der Batteriestrom nur in den Brick Pi und die Lego-Komponenten. Entwickelt der Lego-Ingenieur etwas, das nicht mobil ist, verlängert diese Variante die Batterielaufzeit während der Fehlersuche.
Robo-Linux
Dexter Industries liefert alle Treiber und Module auch auf Github [5]. Wer sich das Übersetzen ersparen möchte, für den gibt es ein Raspbian for Robots als fertiges SD-Karten-Image [6], bei dem alle notwendigen Treiber, Programme sowie Beispielcode vorinstalliert sind. Den Zugriff auf diese Distribution erhält der Entwickler entweder via SSH (der Benutzername lautet standardmäßig »pi«, das Passwort »robots1234«) oder per VNC auf Port 5901 (Passwort genau wie bei SSH).
In der Distribution steckt die Software für alle Roboter von Dexter. Es empfiehlt sich, bei der ersten Inbetriebnahme des Brick Pi ein Firmware-Update zu starten. Dafür gibt es beim Einsatz von VNC ein Icon auf dem Raspbian-for-Robots-Desktop oder wahlweise das Kommando
sudo bash /home/pi/Dexter/BrickPi3/Firmware/brickpi3samd_flash_firmware.sh
das ein entsprechendes Bashskript zur Firmware-Aktualisierung startet.
Die Programmierung
Für die Programmierung des Brick Pi 3 stellt Dexter Bibliotheken für Python, C sowie die Sprache Scratch bereit. Auf Github findet sich zudem ein Verweis auf eine Bibliothek für Windows 10 IoT, falls der Pi auf Microsofts Betriebssystem läuft. Unter [7] wartet außerdem eine Java-Anbindung, die nicht in das Robo-Linux integriert ist.
Im Homeverzeichnis des Pi-Benutzers findet der Entwickler in dem Ordner »Dexter/BrickPi3/Software/Sprache/Examples« Beispielprogramme in verschiedenen Sprachen. Die größte Auswahl bietet Python. Listing 1 zeigt ein einfaches Programm in dieser Sprache, es dreht einen Motor vor und zurück.
Listing 1
Einfaches Python-Beispiel
01 import brickpi3 02 import time 03 04 BP = brickpi3.BrickPi3() 05 06 try: 07 BP.offset_motor_encoder(BP.PORT_A, BP.get_motor_encoder(BP.PORT_A)) 08 except IOError as error: 09 print(error) 10 11 t = 1 12 13 BP.set_motor_power( BP.PORT_A, 10) 14 time.sleep(t) 15 BP.set_motor_power( BP.PORT_A, 0) 16 time.sleep(t) 17 BP.set_motor_power( BP.PORT_A, -10) 18 time.sleep(t) 19 BP.set_motor_power( BP.PORT_A, 0)
Zeile 4 erzeugt eine Instanz der angeschlossenen Brick-Pi-3-Platine. Wie auch beim Lego Brick muss der Programmierer wissen, an welchen Port er welchen Motor oder Sensor anschließen muss. Aktoren-Ports sind mit A bis D gekennzeichnet, Sensoren-Ports tragen die Nummern 1 bis 4. Das Beispiel initialisiert Port A auf »Motor«, was allerdings schiefgehen kann, wenn etwa ein Motor fehlt. Diesen Fall fängt der Programmierer über den »try«-Block ab.
Zeile 13 setzt den Motor auf Tempo »10«. Das Programm wartet 1 Sekunde, bevor es den Motor wieder abstellt. Nach einer weiteren Sekunde ändert es das Tempo auf »-10«. Der Motor dreht sich nun genauso schnell wie zuvor, aber in die andere Richtung. Nach 1 Sekunde stoppt er dann erneut.
Mit der nötigen Distanz
Um Erfahrungen mit einer neuen Programmierumgebung zu sammeln, sucht sich der Autor am liebsten eine konkrete Aufgabe, die er testweise umsetzen kann. Um es am Anfang nicht gleich zu kompliziert zu machen, soll der Roboter im ersten Versuch mit Hilfe des Abstandssensors erkennen, wenn ihm jemand zu nahe kommt, und in diesem Fall einen Arm heben: Keinen Schritt weiter!
Abbildung 3 zeigt den – sehr einfachen – Roboter, ein Video gibt es im Videokanal des Linux-Magazins [8]. Listing 2 zeigt den nötigen Code, der den beschriebenen Ablauf realisiert.
Listing 2
Annäherungsversuche
01 import brickpi3
02 import time
03
04 BP = brickpi3.BrickPi3()
05 BP.reset_all()
06
07 try:
08 BP.offset_motor_encoder(BP.PORT_A, BP.get_motor_encoder(BP.PORT_A))
09 BP.set_sensor_type(BP.PORT_1, BP.SENSOR_TYPE.EV3_INFRARED_PROXIMITY)
10 except IOError as error:
11 print(error)
12
13 # sensor calibration
14 done = False
15 while (not done):
16 try:
17 BP.get_sensor(BP.PORT_1)
18 done = True
19 except brickpi3.SensorError as error:
20 print(error)
21
22 flagup = False
23
24 print("Entering EventLoop")
25 try:
26 while True:
27 time.sleep(0.2)
28 a = BP.get_sensor(BP.PORT_1)
29 if(a < 20):
30 if(not flagup):
31 BP.set_motor_position(BP.PORT_A, 90)
32 time.sleep(0.2)
33 BP.set_motor_power( BP.PORT_A, 0)
34 print"Motor:" , BP.get_motor_encoder(BP.PORT_A)
35 flagup = True
36 else:
37 if(flagup):
38 BP.set_motor_position(BP.PORT_A, 0)
39 time.sleep(0.2)
40 BP.set_motor_power( BP.PORT_A, 0)
41 flagup = False
42 print "Motor: ", BP.get_motor_encoder(BP.PORT_A)
43
44
45 except KeyboardInterrupt:
46 BP.reset_all()
Das Programm alloziert zuerst die Brick-Pi-3-Instanz. Sicherheitshalber versetzt es den Roboter anschließend in den Grundzustand. Der erste »try«-Block initialisiert den Motor auf Port A sowie den Abstandssensor auf Port 1. Dank der Schleife unterhalb des Kommentars »Sensor Calibration« wartet das Programm, bis der Sensor seinen Betrieb aufgenommen hat.
Auf der Konsole erscheint zunächst sehr oft die Meldung: »get_sensor error: Invalid sensor data«. Nach 1 bis 2 Sekunden verschwindet diese aber und der Sensor ist einsatzbereit. Zunächst ist “Fahne unten”. Da die Variable »flagup« sich den Zustand merken muss, bekommt sie den Wert »False« (Zeile 22).
Nun beginnt die eigentliche Schleife. Sie steckt in einem »try«-Block, damit ein [Strg]+[C] den Programmablauf unterbrechen kann. Das Skript resettet den Roboter, bevor es sich beendet. Das sollte der Entwickler grundsätzlich so handhaben, da sonst zum Beispiel ein eingeschalteter Motor endlos weiterläuft. In der Endlosschleife wartet das Programm 0,2 Sekunden und fragt dann den Wert des Abstandssensors ab. Der Zahlenwert, den die Bibliothek zurückliefert, ergibt mit 0,5 multipliziert den Abstand in Zentimeter (der Sensorwert 10 entspricht dabei 5 cm). Im Skript soll der Roboter reagieren, sobald sich der Abstand auf weniger als 10 cm verringert, dementsprechend prüft die If-Abfrage, ob der Sensorwert kleiner als 20 ist.
Ist dies der Fall und die Flagge ist unten, dreht das Skript den Motor um 90 Grad. Nun wartet das Skript 0,2 Sekunden und schaltet den Motor dann ab.
Listing 3
90-Grad-Drehung
01 Done = False 02 BP.offset_motor_encoder(BP.PORT_A, BP.get_motor_encoder(BP.PORT_A)) 03 BP.set_motor_power(BP.PORT_A, 5) 04 while (not Done): 05 pos = BP.get_motor_encoder(BP.PORT_A) 06 print "Motor: ",pos 07 if(pos == 90): 08 Done = True 09 time.sleep(0.02)
Für dieses etwas merkwürdige Verhalten gibt es eine Erklärung: Schaltet das Skript den Motor nicht ab, tänzelt der Roboter um die gewünschte Zahl herum, weil der Motor leider nicht (oder sehr selten) die exakte Gradzahl trifft. Den Tipp mit der Abschaltvorrichtung bekam der Autor im sehr schnell reagierenden Forum von Dexter.
Überschreitet der Messwert den Schwellenwert von 10 cm und ist die Fahne oben, senkt das Skript sie wieder (Zeile 41). Zur Kontrolle liest es am Ende der Schleife noch die Motorposition aus und zeigt sie an.
Den einzigen Weg, den der Autor fand, um eine exakte Drehung um 90 Grad zu erreichen, demonstriert Listing 3.
Ohne Zwischenlandung
Eine allgemeine Anmerkung: Im Vergleich zu anderen Roboter-APIs fällt auf, dass der Entwickler im Prinzip mit Busy Waits arbeiten muss, wenn das Programm beispielsweise auf eine Änderung des Abstands wartet. Das Board und das API unterstützen keine Interrupts.
Da ein gewöhnliches Linux kein Realtime OS ist, könnte dies zu Unfällen führen, wenn das Gesamtsystem unter hoher Last steht und deswegen den Wartezeitpunkt verpasst. Auch muss der Entwickler mit den Wartezyklen experimentieren. Im Beispiel der Drehung um 90 Grad war der erste Versuch mit 0,2 Sekunden und einem Motordrehtempo von 10 viel zu grob gewählt.
Grafische Programmierung mit Scratch
Scratch ist eine grafische Programmierumgebung des MIT und in erster Linie dazu gedacht, Programmieranfängern (insbesondere Kindern) die Grundbegriffe des Programmierens beizubringen. Die Basiseinheiten heißen Blöcke. Es gibt einige Erweiterungen, um zum Beispiel die GPIO-Ports am Raspberry Pi zu unterstützen.
Dexter verwendet die standardmäßig in Scratch eingebauten Broadcast-Blöcke, denen es die richtige Nachricht mitgeben muss, um zum Beispiel einen Motor zu starten oder einen Sensor zu initialisieren. Abbildung 4 zeigt den Scratch-Code, der dem Python-Code aus Listing 2 entspricht, allerdings mit einer ungenaueren Drehung. Die Dokumentation der Blöcke befindet sich grafisch auf einer Github-Seite [9].
Wichtig ist, im Broadcast-Block die richtige Nachricht für den Sensor zu senden. Im Test stellte sich heraus, dass »S1 EV3 US cm« mit Leerzeichen falsch ist und Entwickler die Leerzeichen weggelassen müssen. In dem blauen Sensorblock zum Auslesen des Wertes erscheint der richtige Wert im Listing erst dann, nachdem Scratch den Broadcast schon einmal gesendet hat.
Fazit
Wer die Lust an Robotik mit Lego kombinieren will, für den eignet sich der Brick Pi 3 durchaus, da er dank seiner APIs volle Flexibilität bietet. Einen Raspberry Pi mit Lego-Technik einfach fahrbar zu bekommen, hat auf jeden Fall seinen Reiz und kann auch über pure Spielerei hinausgehen.
Das API fällt im Vergleich zu anderen etwas rudimentär aus (das mit Reverse Engineering freigelegte Morse-API für die Dash-&-Dot-Roboter bietet hier mehr), aber das sollte den Spaß für Lego-Enthusiasten nicht schmälern.
Infos
-
Lego Mindstorms: https://de.wikipedia.org/wiki/Lego_Mindstorms
-
Brick Pi 3: https://www.dexterindustries.com/shop/brickpi-advanced-for-raspberry-pi/
-
Schaltpläne der Brick-Pi-3-Komponenten mit GPIO-Ports: https://github.com/DexterInd/BrickPi3/blob/master/Hardware/BrickPi%203.2.1.pdf
-
SPI-Protokoll: https://www.corelis.com/education/tutorials/spi-tutorial/
-
Brick-Pi-3-Treiber und -Protokolle: https://github.com/DexterInd/BrickPi3
-
Raspbian for Robots: https://www.dexterindustries.com/raspberry-pi-robot-software/
-
Java-API für Brick und Brick Pi: https://ev3dev-lang-java.github.io
-
Brick-Pi-Video im Videokanal des Linux-Magazins: https://www.linux-magazin.de/videos/
-
Github-Seite von Dexter Industries mit den in Scratch verwendeten Blöcken: https://github.com/DexterInd/BrickPi3/tree/master/Software/Scratch








