Schlauer Zwerg: Maschinelles Lernen mit dem Raspberry Pi Pico, Teil 1

Entweder ... oder

Inhaltsverzeichnis

Vor dem Blick auf das Modell steht zunächst ein Einblick in die Hardware und Schaltung an.

Der Aufbau ist bewusst einfach gehalten und kommt ohne Mikrofon, Kamera oder Sensoren aus. Er benötigt neben dem Raspberry Pi Pico lediglich ein paar Kabel, zwei Taster und eine Leuchtdiode mit einem Vorwiderstand.

Um das Modell zu trainieren verwendet der Autor wie bereits in seinem Artikel über ML in der mobilen App auf heise Developer ein XOR-Gatter. Die Taster dienen als Eingabe und die Leuchtdiode als Ausgabe. Das Exklusiv-Oder- beziehungsweise Entweder-Oder-Gatter schaltet die Ausgabe auf HIGH, wenn an einem der beiden Eingänge ein HIGH-Signal anliegt und an dem anderen ein LOW-Signal. Sind beide Eingänge auf LOW oder auf HIGH, schaltet der Ausgang auf LOW. Die Taster steuern die Eingabewerte, und den Ausgabewert zeigt die Leuchtdiode an.

Zunächst entsteht die Schaltung mit den zwei Tastern und der Leuchtdiode, und die XOR-Entscheidung übernimmt eine klassisch programmierte if-Abfrage: Die Leuchtdiode leuchtet lediglich, wenn der Status der beiden Taster unterschiedlich ist. Wenn beide Taster deaktiviert oder aktiviert sind, bleibt die Leuchtdiode aus. Gerade der Fall, dass beide Taster gedrückt sind, gestaltet das Training des Modells und die Berechnung der Gewichtung der einzelnen Knoten innerhalb des Modells interessant, aber das wird ebenso Bestandteil des zweiten Teils der Artikelserie wie das Umwandeln des ML-Modells in C-Code.

Das Foto zeigt den Aufbau der XOR-Schaltung (Abb. 1).

Die in Abbildung 1 gezeigten Taster lassen sich im Zweifel durch Kabelenden oder Jumper ersetzen. Die Farbe der Leuchtdiode (LED) spielt für die Anwendung grundsätzlich keine Rolle, aber eine blaue LED benötigt mehr als vier Volt, während der Raspberry Pi Pico nur 3,3 Volt liefert. Für den Nachbau lässt sich alternativ die auf dem Pico verbaute LED nutzen.

Für die externe LED ist ein Vorwiderstand erforderlich – im Beispielaufbau hat er 56 Ohm. An der Stelle soll eine kurze Auffrischung der Vorwiderstandsberechnung helfen: Der Pico liefert 3,3 Volt, und an der LED liegen 2,2 Volt an. Damit liegen die übrigen 1,1 Volt am Vorwiderstand an. Für die maximale Stromstärke, die durch die LED fließen kann, seien 20 mA (Milliampere) verwendet, die ebenso durch den mit der LED in Reihe geschalteten Widerstand fließen. Dadurch lässt sich der Widerstand nach dem Ohmschen Gesetz als Quotient aus Spannung und Stromstärke berechnen:

R = U / I = 1,1 V / 0,02 A = 55 Ohm.

Als handelsüblicher Widerstand steht der 56-Ohm-Widerstand in der sogenannten E12-Reihe zur Verfügung. Sollte er nicht vorhanden sein, lässt sich ein 100-Ohm- oder 120-Ohm-Widerstand verbauen, wodurch die LED etwas schwächer leuchtet. Das Weglassen des Vorwiderstands ist nicht zu empfehlen, da der Pico darauf empfindlich reagieren könnte.

Die Skizze der Schaltung ist mit dem Tool Fritzing erstellt worden (Abb. 2).

Eine Seite der beiden Taster ist jeweils mit 3,3 Volt am Pin 3V3 (OUT) des Picos verbunden (s. Abb. 2). Die andere Seite geht von dem einem Taster zu Pin GP11 und von dem anderen zu Pin GP12. Der Aufbau und somit die Beschaltung unterscheiden sich von Taster zu Taster. Bei der Leuchtdiode ist darauf zu achten, dass die Kathode, die an der LED mit der flachen Seite gekennzeichnet ist, mit Minus beziehungsweise GND verbunden ist. Der Vorwiderstand ist auf der einen Seite mit der Anode der LED verbunden und auf der anderen mit Pin GP16. Die interne LED des Pico ist mit Pin GP25 verbunden. Wer sie nutzen möchte, muss den Code entsprechend anpassen.

Die endgültige Anwendung wird aus dem TensorFlow-Micro-Code, dem Modell, dem Pico SDK und der Rahmenanwendung zum Ausführen bestehen. Zuvor dient ein einfaches Programm dazu, den Aufbau der Schaltung zu testen. Der Code ist bewusst einfach gehalten, aber das Programm dient als Grundgerüst, in das später der TinyML-Code und das trainierte Modell einfließen. Als Entwicklungsumgebung bietet sich das quelloffene und kostenlose Visual Studio Code an, wie Michael Stal in einem Blogbeitrag auf heise Developer erläutert hat.

Als erstes gilt es die Datei XOR.cpp anzulegen. Im Folgenden sind die darin einzufügenden Codepassagen aufgeschlüsselt.

#include "pico/stdlib.h"

const uint BUTTON1_PIN = 11;
const uint BUTTON2_PIN = 12;
const uint LED_PIN = 16; 
// const uint LED_PIN =  PICO_DEFAULT_LED_PIN;

Die eingebundene stdlib.h enthält das Software Development Kit (SDK) für den Raspberry Pi Pico mit den Grundfunktionen, die beispielsweise die Pins des Pico für die Ein- und Ausgabe festlegen. Die Pins für die Taster und Leuchtdiode sind anschließend als Konstanten definiert. Dabei ist darauf zu achten, dass es sich nicht um die physikalischen Werte des Pico-Boards handelt, sondern um die Werte der GPIO-Pins. GPIO steht für General Purpose Input / Output und kennzeichnet Pins, die für die Ein- oder Ausgabe konfigurierbar sind. Wer die interne LED nutzen möchte, verwendet statt des GPIO-Pins 16 den vordefinierten Wert PICO_DEFAULT_LED_PIN, der mit dem Wert 25 konfiguriert ist.

int main(int argc, char* argv[]) {
  setup();
  while (true) {
    loop();
  }
}

Die Aufteilung der main-Funktion in die Methoden setup und loop entspricht dem Vorgehen der TinyML-Beispiele. Wer bereits Erfahrung mit der Arduino-Entwicklung gesammelt hat, dürfte die Aufteilung ebenfalls wiedererkennen. Dadurch ist das Programm in zwei Bereiche aufgeteilt: Der setup-Part legt einmalig beim Start Werte und Konfigurationen fest. Später lädt er das trainierte Modell. Der loop-Aufruf hingegen läuft in einer Endlosschleife und führt immer wieder denselben Code aus.

void setup() {
  gpio_init(BUTTON1_PIN);
  gpio_set_dir(BUTTON1_PIN, GPIO_IN);
  gpio_init(BUTTON2_PIN);
  gpio_set_dir(BUTTON2_PIN, GPIO_IN);
  gpio_init(LED_PIN);
  gpio_set_dir(LED_PIN, GPIO_OUT);
}

Die setup-Methode definiert mit gpio_init und gpio_set_dir die Pins für die beiden Taster und die Leuchtdiode. Sie setzt die Taster als Eingaben und die Leuchtdiode als Ausgabe.

void loop() {
  int button1 = gpio_get(BUTTON1_PIN);
  int button2 = gpio_get(BUTTON2_PIN);

  if (button1 != button2) {
    gpio_put(LED_PIN, true);
  } else {
    gpio_put(LED_PIN, false);
  }
  sleep_ms(10);
}

Die loop-Methode liest den Status der jeweiligen Taster. Sollten deren Werte unterschiedlich sein, weil genau einer der beiden Taster gedrückt ist, setzt sie den Pin, an der sich die Leuchtdiode befindet, auf true: Die LED leuchtet. Sind die Tasterwerte gleich, weil beide oder keiner gedrückt ist, setzt sie den Pin auf false: Die LED bleibt dunkel. Am Ende wartet die Methode zehn Millisekunden, um zu verhindern, dass die mechanischen Taster während des Schaltvorgangs mehrmals auslösen.

Nach dem Kompilieren, Bauen und Hochladen des Programms auf den Raspberry Pi Pico, lässt sich die Schaltung und Anwendung testen.