Als Vater, Programmierer und Maker hat man es manchmal echt nicht leicht. Gerade wenn die Kinder Hilfe in der Schule brauchen und der Arbeitstag recht lang war, ist Grundschulmathe natürlich eine gelungene Abwechslung, aber manchmal möchte man auch einfach seine Ruhe. Wie gut, dass Papa Programmierer und Maker ist und einige Bauteile in seinem Fundus hat, die ein einfaches Rechenspiel ermöglichen. Mir ist bewusst, dass ich auch spezielle Taschenrechner dafür kaufen kann, die solche Übungsaufgaben produzieren, aber warum Geld ausgeben, wenn man einfach nur etwas Zeit investieren muss?
In dem heutigen Beitrag zeige ich dir ein einfaches Grundrechenspiel für Kinder bis zur 4. Klasse mit allen gängigen Grundrechenarten: Addition, Subtraktion, Multiplikation und Division. Die Hardware ist dabei überschaubar und ich gehe auf das Thema State Machine näher ein. Interesse geweckt oder wollen das Kind/die Kinder auch Rechenaufgaben lösen? Dann legen wir los!
Hard- und Software für dieses Projekt
Wie bereits angekündigt, braucht es für dieses kleine Projekt nicht viel. Insgesamt braucht es gerade mal vier elektronische Komponenten, ein Steckbrett und ein paar Jumperkabel. Die ausführliche Liste kannst du Tabelle 1 entnehmen.
Tabelle 1: Benötigte Bauteile
Für meine Software nutze ich statt der Arduino IDE wieder Visual Studio Code mit PlatformIO, da ich so unabhängig vom Betriebssystem den Quellcode zur Verfügung stellen kann. Kleiner Bonus: Alle Abhängigkeiten werden noch vor dem Kompilieren von PlatformIO direkt runtergeladen.
Für das Projekt werden insgesamt drei Bibliotheken benötigt:
- LiquidCrystal_I2C: Ansteuerung des Displays über die I2C-Schnittstelle
- Encoder: Eine einfache Bibliothek, die einem die Arbeit mit den Encoder-Ticks abnimmt
- Bounce2: Ebenfalls eine Bibliothek zur Vereinfachung des Quellcodes, in diesem Fall für den Taster am Encoder
Grundfrage: Was ist eine State Machine?
Ohne jetzt wie ein Lehrer in einer Informatikklasse klingen zu wollen, ist die erste Frage für dieses Projekt: „Was ist eine State Machine?“. Solltest du nun gerade gegoogelt haben, kann ich dich schon einmal beruhigen, es ist keine schwedische Band. Für eine tiefere Erklärung ist der entsprechende Wikipedia-Eintrag lesenswert, wobei die kurze Antwort wahrscheinlich an der Stelle erst einmal reicht.
Im Prinzip reagiert der Controller auf Eingangssignale bzw. Statusänderungen und springt in einen anderen Zustand. Dieser führt eine entsprechende Operation aus und gibt der Außenwelt entsprechendes Feedback.
Vergleichen könnte man das im Grunde mit einem Kochrezept für ein Gericht. Hier wird meist detailliert erklärt, was zu tun ist, und erst wenn der aktuelle Schritt durchgeführt ist, geht man zum nächsten über. Gerade bei diesem Beispiel möchte ich nicht verschweigen, dass beim Kochen eines Gerichts natürlich die Fehlerbehandlung fehlt. Wenn etwas Unerwartetes im Programmablauf passiert, muss die State Machine entsprechend auch auf einen solchen Zustand reagieren. Um beim Beispiel vom Kochen zu bleiben: Wenn ich beim Anschwitzen der Zwiebeln vergessen habe, Öl oder Butter in die Pfanne zu geben, muss ich diesen Schritt nachträglich ausführen.
Jetzt die zweite Frage, die sich wahrscheinlich aufdrängen wird: „Wie wird eine State Machine programmiert?“.
Auch diese Frage ist leicht zu beantworten, am einfachsten über eine Switch-Case-Anweisung mit definierten Enums. Diese Anweisung bildet zusammen mit wenigen weiteren Funktionsaufrufen den Inhalt meiner loop()-Funktion. Wie du wahrscheinlich schon weißt, wird die loop()-Funktion zyklisch abgearbeitet, also von oben nach unten, und am Ende wird direkt wieder zum Anfang gesprungen.
Guck dir in diesem Fall einfach mal die loop()-Funktion in meinem Code an. Es gibt zwar nur wenige Kommentare in dem Code, aber die Funktionsnamen, Variablen und Enums sollten selbsterklärend sein, sofern grundlegendes Englisch vorhanden ist. Mit knapp 120 Zeilen Code in der loop()-Funktion ist diese State Machine recht klein. Ohne diese Struktur würde der Code in diesem Projekt schnell unübersichtlicher werden und sich an mehreren Stellen wiederholen. Ein weiterer Pluspunkt für die State Machine, zumindest für dieses Projekt.
Die Verkabelung
Damit das Projekt auch gelingen kann, müssen alle Hardwareteile miteinander verbunden werden. Egal ob jetzt auf einer selbst gemachten Platine oder auf einem Breadboard, die Anschlüsse sind erst einmal gleich, siehe Abbildung 1.

An der Stelle haben wir es recht einfach, weil wir alle Komponenten mit 5 Volt betreiben können und bis auf die I2C-Verbindung nur der Encoder korrekt angeschlossen werden muss. Mit dem Anlöten des I2C-Boards an das LCD-Display, damit dieses die Kommunikation zwischen Arduino Nano und Display übernimmt, sollte der komplette Aufbau in unter 20 Minuten erledigt sein, siehe Abbildung 2.

Abbildung 2: Verkabelung mit realen Bauteilen
Der Code und seine Funktion
Den Quellcode kannst du hier auf GitHub herunterladen: https://github.com/M3taKn1ght/Childrens_Basic_Arithmetic. Der Code wird, sofern ich Fehler von meinen Kindern gemeldet bekomme, aktualisiert.
Wie in meinen letzten Projekten nutze ich PlatformIO, was mir die Definition der Mikrocontroller, Bibliotheksabhängigkeiten und sogenannten Buildflags erleichtert. Letztere nutze ich gerne, um die Pinbelegung festzulegen. Will ich andere Mikrocontroller verwenden, kann ich z. B. die Pinbelegung anpassen, ohne den gesamten Quellcode überarbeiten zu müssen oder #defines in den Quellcode einzubauen. Das einzige #define in dem Code ist für die Release- oder Debug-Version, was sich direkt auf die Größe der fertig kompilierten Firmware auswirkt.
Ich nutze an der Stelle einen Arduino Nano mit ATmega328P und neuerem Bootloader. PlatformIO verwendet dafür die Board-Definition nanoatmega328new; für die Variante mit älterem Bootloader wird nanoatmega328 verwendet. In meinem Projekt können beide Varianten als Debug- oder Release-Version gebaut werden. Die Debug-Version ist nur dafür gedacht, den Quellcode besser zu verstehen und Fehler zu finden. Im Normalfall solltest du eine der beiden Release-Versionen verwenden. Sollte die eine Board-Definition bei deinem Nano nicht funktionieren, nutze die andere. Je nach Bootloader unterscheidet sich die Konfiguration für das Hochladen der Firmware.
Kommen wir zu dem eigentlichen Quellcode. In der setup()-Funktion wird erst einmal die Hardware initialisiert und eine erste Ausgabe erfolgt. Ist die Debug-Version ausgewählt, gibt es auch noch ein paar Ausgaben auf dem seriellen Monitor.
Wie schon in dem Kapitel „Grundfrage: Was ist eine State Machine?“ erläutert, passiert in der loop()-Funktion die eigentliche Magie. Es wird geprüft, ob der Encoder gedreht, der Taster gedrückt oder der Status geändert wurde. Entsprechend reagiert die State Machine darauf und führt die nötigen Schritte dafür aus.
Damit ich sich wiederholende Aufgaben nicht immer wieder schreiben muss, sind diese in Funktionen ausgelagert. PlatformIO bzw. Visual Studio Code sollte dir bei jeder Funktion zeigen, was die Funktion macht und welche Parameter die Funktion braucht.
Das Grundprinzip des Spiels ist wie folgt:

- Der Arduino Nano wird gestartet und initialisiert alles
- Initiale Ausgabe anzeigen (Dauer: 5 Sekunden)
- Auswahl, welche Rechenarten abgefragt werden sollen
- Zahlenraum definieren
- Spiel wird gestartet
Die passende Antwort wird mit dem Drehencoder ausgewählt und durch Drücken des Tasters bestätigt.
Solange der Spieler richtig antwortet, wird immer eine neue Aufgabe generiert und der Spieler muss antworten. Sollte die Antwort falsch sein, erscheint ein „Game Over“-Screen und die State Machine springt zu dem Punkt „Auswahl der Rechenarten“.
Wie schon oben geschrieben, nutze ich viele Funktionen, damit ich Code nicht wiederholen muss. Gerade die Anzeige einer Matheaufgabe, möglicher Lösungen oder des Zeigers, der die gewählte Antwort markiert, muss immer wieder aktualisiert werden. Teilweise musste ich einige Funktionen überladen, was in Programmierkreisen so viel heißt wie: Die Funktionen haben dieselbe Aufgabe, einer oder mehrere Parameter haben aber einen anderen Datentyp.
Ein Beispiel dafür ist die Funktion printLCDRight(…). Der Parameter für den angezeigten Text auf dem LCD-Display kann entweder ein const char* oder ein unsigned long sein. Die eigentliche Ausgabe erfolgt in beiden Fällen über die Variante mit const char*; bei einem unsigned long übernimmt die überladene Funktion zuvor die Umwandlung. Gerade in C++ wird dieser Kniff gerne genommen.
Zusammenfassung
Dieses Projekt ist noch nicht 100 % fehlerfrei und es gibt noch einige Punkte, die man hinzufügen könnte. Ein Highscore wäre eine nette Idee, womit der Wiederspielwert noch höher wäre. Natürlich fehlt auch noch ein passendes Gehäuse, was aber aus Mangel an Zeit einfach nicht umsetzbar war. Meine Kinder lieben das Spiel, jedoch sind offene Anschlüsse oder leicht zu entfernende Verbindungen bei Kinderhänden keine gute Idee. Die optimale Lösung an der Stelle wäre eine eigene Platine mit festen Anschlüssen und ein selbst konstruiertes und gedrucktes Gehäuse. Als Maker hat man natürlich eine kleine CNC-Fräse und einen 3D-Drucker in seinem Hobbykeller.

Die Schaltung arbeitet zwar nur mit 5 Volt, dennoch sollte sie wegen der offenen Kontakte und kleinen Bauteile nicht unbeaufsichtigt von Kindern verwendet werden. Ein Kurzschluss kann außerdem den Arduino Nano oder andere Komponenten beschädigen.

































