AI Arduino

Wir programmieren den Maixduino
Keine Kommentare

Mit dem Maixduino steht eine kostengünstige Platine zur Verfügung, die bei der Mobilisierung von Algorithmen der künstlichen Intelligenz hilft. Dieser Artikel zeigt die Möglichkeiten zur Programmierung des Einplatinencomputers auf.

Dank Python und diversen Frameworks ist es heute kein großes Problem mehr, AI-Algorithmen auf einer Workstation zum Laufen zu bringen. Schwieriger wird es, wenn man eine Appliance mit den Algorithmen und Prozessen ausstatten möchte. Neben den Problemen bei der Findung eines ausreichend starken Einplatinencomputers bekommt man es dabei nämlich mit der Frage zu tun, welche „sonstige“ Hardware man für die Datenerfassung einsetzt. Der in Europa beispielsweise bei Elektor für rund 30 Euro erhältliche Maixduino (Abb. 1) umgeht dieses Problem auf eine bequeme Art und Weise. Die sowohl in C als auch in Python programmierbare Platine setzt dabei nicht besonders viel Programmierkenntnisse voraus. Wir wollen einen Blick auf die Möglichkeiten werfen.

Abb. 1: Der Maixduino – des AI-Mobilisierers bester Freund

Abb. 1: Der Maixduino – des AI-Mobilisierers bester Freund

Unter der Haube

Für Mess-, Steuer- und Regelungsaufgaben vorgesehene Einplatinenrechner haben im Allgemeinen wenig leistungsstarke Prozessoren. Der Arduino Uno wird mit einem Achtbitter betrieben. Am Maixduino findet sich ein als Kendryte K210 bezeichneter Prozessor, dessen Aufbaudiagramm in Abbildung 2 zu sehen ist.

Abb. 2: Der Kendryte K210 bringt diverse Funktionseinheiten mit

Abb. 2: Der Kendryte K210 bringt diverse Funktionseinheiten mit

Kern des Systems sind zwei mit 400 Megahertz getaktete 64-Bit-Prozessoren. Interessanterweise entscheidet man sich hierbei nicht für die ARM-Architektur, sondern setzt auf die quelloffene ISA RISC-V. Die Platine taugt nicht nur für Experimente mit künstlicher Intelligenz, sondern auch als Demo-Board für Gehversuche mit der neuartigen Architektur. Neben Engines für Hardwarebusse finden sich mit KPU und APU zwei Funktionsblöcke, die für die Verarbeitung von in neuronalen Netzwerken häufig auftretenden mathematischen Operationen und für Audioverarbeitung optimiert sind. Ein weiteres Modul hört im Blockdiagramm auf den Namen FFT – eine Engine, die schnelle Fourier-Transformationen in Hardware bzw. mit Hardwarebeschleunigung durchführen kann.

Daraus ergibt sich eine extrem schnelle Abarbeitung der FFT. In vielen Fällen – die in Abbildung 3 gezeigte Beispielanwendung von Elektor ist eine gute Demonstration dafür – ist es so, dass der Aufwand für die Bereitstellung und Aberntung der Informationen größer ist als die für die FFT verbrauchte Zeit.

Abb. 3: Das integrierte MEMS-Mikrofon zeichnet Umgebungsgeräusche auf

Abb. 3: Das integrierte MEMS-Mikrofon zeichnet Umgebungsgeräusche auf

Wie programmieren?

Für die Entwicklung von Software stehen drei Methoden zur Verfügung. Die erste ist die auf MicroPython basierte MaixPy. Dabei handelt es sich um eine vom ESP32 bekannte Python Runtime, die dem Evaluations-Board ein mehr oder weniger komplettes Python-Laufzeitsystem implantiert. Möchte man stattdessen mit C programmieren, so steht sowohl die Arduino-basierte IDE als auch das offizielle SDK zur Verfügung. Insbesondere mit Letzterem, das unter [1] vollumfänglich dokumentiert ist, lässt sich die Leistungsfähigkeit der Hardware maximal ausnutzen. Angemerkt sei zudem, dass die dortige Dokumentation mehr über die Hardware verrät und auch für Nutzer der Python-Schnittstelle lesenswert ist.

ML Summit Munich 2020

Market Segmentation in the Era of Big Data

mit Özge Sahin (SkyScry)

Deep Learning mit TensorFlow

Mit Jakob Karalus (Deloitte Consulting)

Eine weitere interessante Neuerung ist, dass die Kommunikation mit der Hostworkstation über ein USB-C-Kabel zu erfolgen hat. Wer eine frisch von Elektor bezogene Platine mit dem Rechner verbindet, stellt fest, dass der Bildschirm rot aufleuchtet und eine Willkommensmeldung angezeigt wird: Der Anbieter installiert von Haus aus ein kleines Softwaremodul, das neu angekommene Benutzer begrüßt.

Im Auslieferungszustand veraltete Software ist für Embedded-Entwickler ein mehr als bekanntes Ärgernis. Beim Maixduino ist die Situation insofern kritisch, als der Anbieter permanent Firmwareaktualisierungen vornimmt. Unter [2] findet sich ein Repository, das wöchentlich um einen Eintrag reicher wird. In den folgenden Schritten arbeiten wir mit der Version 0.5.0_19 und laden die Datei maixpy_v0.5.0_19_g64e411a.bin auf unsere Workstation herunter. Die Python-Firmware wird von Seiten des Anbieters in verschiedenen Ausbaustufen angeboten, die sich im Bereich der unterstützten Frameworks unterscheiden. Die hier verwendete Version ist ein Komplettpaket, das bis auf einen speziellen GUI-Stack alle Funktionen mitbringt. Auf der Sollseite steht allerdings, dass die Dateigröße von 1,7 MB doch vergleichsweise viel Speicher auf dem Prozessrechner frisst. Bei der Auswahl von schlankeren Versionen muss man darauf achten, dass nicht alle Varianten des Betriebssystems auch mit der MaixPyIDE-Entwicklungsumgebung zusammenarbeiten.

Wir arbeiten unter Ubuntu 18.04 LTS und geben im ersten Schritt folgendes Kommando ein, um die Benutzer zur hardwarezugriffsberechtigten Gruppe dialout hinzuzufügen:

tamhan@TAMHAN18:~$ sudo usermod -a -G dialout $(whoami)

Im nächsten Schritt öffnen wir durch Eingabe des Kommandos sudo gedit /etc/udev/rules.d/50-usbuart.rules den grafischen Editor, wo wir folgende Zeile einfügen:

SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="0666"

Danach müssen wir Linux nur noch zum Aktualisieren der udev-Datenstruktur animieren. Auch das lässt sich unter Ubuntu auf der Kommandozeile erledigen, was Neustarts des Systems erspart:

tamhan@TAMHAN18:~$ sudo udevadm control --reload-rules

Zum Abschluss der Einrichtung müssen wir feststellen, ob bzw. wo sich der nun anzuschließende Prozessrechner im /dev-Baum des Betriebssystems eingepflegt hat. Hierzu dient wie immer das Kommando dmesg. Die für uns relevante Zeile ist nach dem Schema „[11308.281635] usb 1-6.4: FTDI USB Serial Device converter now attached to ttyUSB0“ aufgebaut. Beachten Sie, dass der Maixduino so gut wie immer zwei TTY-Schnittstellen mit direkt aufeinanderfolgenden Zahlen belegt – am Rechner des Autors waren das die Schnittstellen ttyUSB0 und ttyUSB1. Für die Kommandoübermittlung ist immer die niederwertigere der beiden Schnittstellen verantwortlich. Für das eigentliche Ausliefern der Binär-Images steht neben einem grafischen Tool unter Linux auch ein Kommandozeilenwerkzeug zur Verfügung. Seine Verwendung ist insofern kommoder, als es sich über den Paketmanager PIP3 herunterladen lässt. Die vollständige Auslieferung sieht dann folgendermaßen aus:

tamhan@TAMHAN18:~/Downloads$ pip3 install kflash
tamhan@TAMHAN18:~/Downloads$ kflash -p /dev/ttyUSB0 -b 1500000 -B goE maixpy_v0.5.0_19_g64e411a.bin

Nach dem erfolgreichen Durchlaufen des rund zwei Minuten dauernden Flash-Prozesses muss der Prozessrechner durch Abstecken der Energieversorgung neu gestartet werden. Die Firmware begrüßt Sie daraufhin mit einem roten Bildschirm, der Sie in der Welt von Python willkommen heißt.

Zu beachten ist, dass das Deployment einer C-basierten Applikation die Python-Firmware vom Controller löscht. Will man wieder zur Arbeit mit Python zurückkehren, so ist ein weiterer Besuch bei kflash notwendig.

Eine Frage des Programmierwerkzeugs

Da MaixPy auf MicroPython basiert, implementiert es am Evaluations-Board eine vollwertige serielle Konsole. Es mag auf den ersten Blick unwahrscheinlich klingen, ist aber so: Wer mit einem Terminalemulator Kontakt aufnimmt, kann bei bestehender Internetverbindung sogar Bibliotheken aus dezidierten Repositories herunterladen. Wirklich bequem ist die Arbeit allerdings nur, wenn man die Verarbeitung des Python-Codes an eine IDE abtritt. Für unsere Firmwareversion steht diese unter [3] bereit. Das für Ubuntu Linux vorgesehene Binärfile hört auf den Namen maixpy-ide-linux-x86_64-0.2.4.run. In ihm verbirgt sich ein vollwertiger Installationsassistent, dem wir im ersten Schritt per chmod Ausführbarkeitsrechte zuweisen und ihn danach zur Ausführung freigeben:

tamhan@TAMHAN18:~/Downloads/MaixPy-IDE$ chmod +x maixpy-ide-linux-x86_64-0.2.4.run
tamhan@TAMHAN18:~/Downloads/MaixPy-IDE$ ./maixpy-ide-linux-x86_64-0.2.4.run

Nach der erstmaligen Installation startet das Set-up-Programm die IDE auf Wunsch automatisch. Spätere Aufrufe lassen sich aus dem Startmenü bewerkstelligen. Wenn Sie MaixPyIDE erstmals öffnen, spendiert der Anbieter des Entwicklungssystems das folgende Hello-World-Programm. Wir drucken es an dieser Stelle absichtlich ohne Kommentare ab, da eine kurze Erklärung im Rahmen des Artikels erfolgt:

import sensor, image, time, lcd
lcd.init(freq=15000000)
sensor.reset()

MicroPython stellt Entwicklern eine Gruppe von Hardwareabstraktionsklassen zur Verfügung, über die man mit den diversen Peripheriegeräten kommuniziert. Wir importieren hier eine Gruppe, um sie danach durch Aufruf der jeweiligen Initialisierungsmethoden für den Betrieb bereitzustellen. Für die LCD-Schnittstelle ist im Rahmen der Einrichtung ein Geschwindigkeitsparameter notwendig, der die Frequenz des Datenaustauschs zwischen Controller und Display festlegt. Bei der Einrichtung des über das Sensorobjekt anzusprechenden Kamera-CCDs müssen wir die Framegröße und einige andere Parameter einschreiben:

sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
clock = time.clock()

Zu guter Letzt findet sich dann noch eine Endlosschleife, die den Kamerasensor im ersten Schritt nach den angelieferten Informationen befragt und diese danach am LCD-Display ausgibt:

while(True):
clock.tick()
img = sensor.snapshot()
lcd.display(img)
print(clock.fps())

Das Hantieren mit den Klassen clock und time ermöglicht dann die Realisierung eines kleinen FPS-Counters. Zum Anweisen eines Probelaufs entscheiden wir uns in der Rubrik Tools | Board für den Maixduino, um danach das unten links eingeblendete grüne Verbindungssymbol anzuklicken. Die IDE fragt nun nach einem seriellen Port, wir entscheiden uns für denjenigen, den wir weiter oben zum Firmwareausliefern verwendet haben. Nach dem erfolgreichen Verbindungsaufbau wird das Verbindungssymbol rot, und der darunter befindliche Playbutton aktiviert sich selbst. Zur Fehlersuche ist es empfehlenswert, MaixPyIDE aus einem Terminalfenster heraus zu starten, weil das Programm während seiner Tätigkeit Statusinformationen in die Konsole wirft.

Wir starten unser Beispielprogramm durch Klick auf Play. Am Bildschirm erscheinen die für den Sensor sichtbaren Szenen. Besonders lustig ist, dass die Daten auch in Richtung der IDE wandern – sie bereitet diese wie in Abbildung 4 gezeigt auf.

Abb. 4: MaixPyIDE berechnet auf Wunsch sogar Livehistogrammdaten

Abb. 4: MaixPyIDE berechnet auf Wunsch sogar Livehistogrammdaten

Beachten Sie, dass der Maixduino auch bei Verbindung mit USB-2.0-Ports oder Hubs funktioniert. In diesem Fall erreicht er normalerweise aber nicht seine optimale Leistung.

Experimente in F

Bei der Vorführung bzw. Inbetriebnahme neuer Embedded-Hardware ist es empfehlenswert, frei nach Diomidis Spinellis Klassiker „Code Reading“ [4] vorzugehen. Greifen Sie sich ein ausführliches Programmbeispiel, um es danach so lange abzuspecken, bis es den Bedürfnissen Ihrer Applikationssituation entspricht. Ein guter Kandidat ist die Demonstration der weiter oben angesprochenen FFT Engine. Ganz analog zu unserem ersten primitiven Beispiel beginnt die Arbeit auch hier mit der Inklusion einer Gruppe von Abstraktionsklassen:

from Maix import GPIO
from Maix import I2S
from Maix import FFT
import image
import lcd
from fpioa_manager import fm, board_info

So gut wie alle modernen Mikrocontroller und SOM-Module erlauben dem Entwickler die Umkonfiguration von Pins dynamisch bzw. durch Software. Im Fall des Maixduino erfolgt das über das FM-Modul, mit dem wir im ersten Schritt eine Gruppe von GPIO-Pins mit der für die Tonentgegennahme verantwortlichen I2S-Engine verdrahten:

lcd.init()
fm.register(20,fm.fpioa.I2S0_IN_D0)
fm.register(19,fm.fpioa.I2S0_WS)
fm.register(18,fm.fpioa.I2S0_SCLK)

Im nächsten Schritt verwenden wir den I2S-Konstruktor, um ein für die Kommunikation mit dem MEMS-Mikrofon vorgesehenes Objekt zu generieren:

rx = I2S(I2S.DEVICE_0)
rx.channel_config(rx.CHANNEL_0, rx.RECEIVER, align_mode = I2S.STANDARD_MODE)
sample_rate = 38640
rx.set_sample_rate(sample_rate)

Danach folgt, wie üblich, die Deklaration einer Gruppe von Konstanten. Sie erlauben die Anpassung des Verhaltens der FFT Engine, was insbesondere bei numerischen Einsätzen hilfreich ist:

img = image.Image()
sample_points = 1024
FFT_points = 512
lcd_width = 320
lcd_height = 240
hist_num = FFT_points #changeable
if hist_num > 320:
hist_num = 320
hist_width = int(320 / hist_num) #changeable
x_shift = 0

Nach dem erfolgreichen Abschluss der Einrichtungshandlungen können wir den eigentlichen FFT-Prozess anwerfen. Er kommt – wie sollte es anders sein – in einer Endlosschleife unter, die im ersten Schritt durch den Aufruf der Record-Methode die Aufzeichnung von Toninformationen befiehlt:

while True:
audio = rx.record(sample_points)

Im nächsten Schritt folgt ein Aufruf der FFT-Methode, um die eigentliche Fourier-Transformation durchzuführen. Zu guter Letzt kümmert sich dann eine for-Schleife darum, die Amplitudeninformationen ans Display auszuliefern (Listing 1).

FFT_res = FFT.run(audio.to_bytes(),FFT_points)
FFT_amp = FFT.amplitude(FFT_res)
img = img.clear()
x_shift = 0
for i in range(hist_num):
  if FFT_amp[i] > 240:
    hist_height = 240
  else:
     hist_height = FFT_amp[i]
  img = img.draw_rectangle((x_shift,240-hist_height,hist_width,hist_height),[255,255,255],2,True)
  x_shift = x_shift + hist_width
lcd.display(img)

Der Code für das Zeichnen des Histogramms ist lang, aber nicht kompliziert: Die draw_rectangle-Klasse wird pro Bin einmal aufgerufen und bringt die angelieferten Informationen auf den Bildschirm. Über die Laufvariable x_shift verschiebt der Code das Rechteck, um am Ende ein fertiges Histogramm zu erhalten. Speichern Sie das Programm danach und schicken Sie es durch einen Klick auf das Play-Symbol in Richtung des Maixduinos. Abbildung 5 zeigt ein beispielhaftes Resultat.

Abb. 5: Die FFT-Spektren erscheinen am Bildschirm der Platine

Abb. 5: Die FFT-Spektren erscheinen am Bildschirm der Platine

Aufmerksamen Beobachtern fällt auf, dass sich die Spektralinformationen ab der Mitte des Displays spiegeln. Hierbei handelt es sich um eine Besonderheit der Fourier-Transformation realer Signale, auf die wir an dieser Stelle aber nicht näher eingehen können. Interessierte finden sowohl im Internet als auch in mathematischer Literatur weitergehende Informationen zum Thema.

Hit a Roadblock

In der Praxis kommt es allerdings während der Programmausführung bzw. insbesondere beim Programmstart zu Fehlermeldungen der Bauart „MemoryError: Out of normal MicroPython Heap Memory.“ Das ist bei allen MicroPython-Derivaten gleichermaßen der Fall. Von MicroPython abgeleitete Engines implementieren auf ihren Ziel-Mikrocontrollern ja prinzipiell eine vollwertige Python Runtime: Aus der Logik folgt, dass diese mit Speicherlecks behaftet ist und dass der am Mikrocontroller doch vergleichsweise eng begrenzte Speicher irgendwann ausgeht.

Erfreulicherweise ist die Lösung des Problems meist einfach: Stecken Sie den Controller kurz ab, um ihm die Stromversorgung zu entziehen. Dadurch wird der Arbeitsspeicher, nicht aber der remanente Speicher genullt, und der Zyklus kann wieder von vorne beginnen. Bei der produktiven Arbeit mit MicroPython empfiehlt es sich, einen Hub mit Schalter zu kaufen – auf diese Art und Weise lassen sich die oft alle paar Minuten erforderlichen Reboots wesentlich bequemer bewerkstelligen.

Und jetzt mit Bildern

Die FFT von Audio- oder sonstigen Informationen ist eine sehr wertvolle Datenquelle. Wer das nicht glaubt, findet bei HP bzw. Keysight Literatur zum Thema. FFT-Messgeräte taugen unter anderem zur Drehzahlerfassung, wenn man sie mit Turbinengeräuschen versorgt.

Als letzte Aufgabe in diesem Artikel wollen wir uns der Königsklasse des maschinellen Lernens zuwenden: Ja, es geht um die Klassifizierung von Bildern. Eine alte Weisheit besagt, dass die Qualität eines Machine-Learning-Prozesses vor allem von der Qualität des Modells abhängig ist. Da die Erzeugung eines eigenen Modells den Rahmen dieses Artikels sprengen würde, wollen wir stattdessen auf eines in den bereitgestellten Modelldateien zurückgreifen.

Rufen Sie den URL http://dl.sipeed.com/MAIX/MaixPy/model/face_model_at_0x300000.kfpkg auf, um das Modell herunterzuladen. Die zur Auslieferung von Maixduino-Software verwendeten kfpkg-Dateien bringen Informationen darüber mit, wo sie im Speicher des Controllers zu platzieren sind. Wir können das Flashen deshalb nach dem folgenden Schema auslösen – der Parameter – B ist für die Festlegung der Baudrate verantwortlich:

tamhan@TAMHAN18:~/Downloads$ kflash -p /dev/ttyUSB0 -b 1500000 -B goE face_model_at_0x300000.kfpkg

Abb. 6: kflash weiß, wo die Informationen zu platzieren sind

Abb. 6: kflash weiß, wo die Informationen zu platzieren sind

Mehrfache Aufrufe dieses Kommandos führen nicht dazu, dass die eigentliche Firmware beschädigt wird – sie liegt ja in einem anderen Bereich des Speichers (Abb. 6). Auch dieses Programm beginnt mit der Importierung von Abstraktionsklassen und der Armierung des Sensors:

import sensor
import image
import lcd
import KPU as kpu

lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)

Danach beginnt die Initialisierung des Beschleunigers. Kpu.load erwartet hier eine Adresse im Speicher, an der ein für die Engine verständliches Modell beginnt. Das restliche Einlesen erfolgt automatisiert.

Auf YOLO2 basierende Modelle erwarten eine Gruppe weiterer numerischer Parameter. Sie werden im Allgemeinen vom Modellentwickler festgelegt. Die hier gezeigte Konfiguration führt zu einem lauffähigen Modell:

task = kpu.load(0x300000)
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
a = kpu.init_yolo2(task, 0.5, 0.3, 5, anchor)
[/code]

Zu guter Letzt findet sich hier eine weitere Endlosschleife, die vom Sensor angelieferte Informationen über Aufruf von run_yolo1 in Richtung des neuronalen Netzwerkbeschleunigers schreibt:

while(True):
img = sensor.snapshot()
code = kpu.run_yolo2(task, img)
if code:
for i in code:
print(i)
a = img.draw_rectangle(i.rect())
a = lcd.display(img)
a = kpu.deinit(task)

Nach getaner Arbeit nehmen wir die im code-Objekt angelieferten Werte entgegen, um sie zur Anreicherung des Schirmbilds einzuspannen. Die eigentliche Auslieferung erfolgt dann über die weiter oben bekannte Methode display. Damit ist auch diese Version des Programms einsatzbereit. Lassen Sie die Kamera ein Gesicht sehen, um sich an der Anzeige eines Rahmens zu erfreuen. Neben dem hier realisierten Erkennen von Personen gibt es auch fortgeschrittene Applikationen, die mehrere (verschiedene) Klassen erkennen. Die Programmiervorgehensweise ist dabei allerdings identisch zur hier verwendeten.

Fazit

Der Maixduino mag mit Sicherheit nicht der Weisheit letzter Schluss sein – für die korrekte Positionierung der Kamera greift man auf einen 3D-gedruckten Spacer zurück. Unterm Strich ist das Board allerdings eine sehr bequeme Möglichkeit, um sowohl hauseigene Machine-Learning-Experimente durchzuführen als auch um Machine Learning Appliances zu mobilisieren und so für Dogfooding-Tests ansprechbar zu machen. Wer sich schon immer von ML und Co. angezogen gefühlt hat, wird die Investitionen von rund 30 Euro mit Sicherheit nicht bereuen.

Links & Literatur

[1] https://kendryte.com/downloads/

[2] http://dl.sipeed.com/MAIX/MaixPy/release/master/

[3] http://dl.sipeed.com/MAIX/MaixPy/ide/_/v0.2.4

[4] Spinellis, Diomidis: „Code Reading“, Addison-Wesley Professional, 2003

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -