Auf Instagram bin ich auf das interessante Projekt „Color Memory“ von Sunflower gestoßen. Dieses kleine Spiel überzeugt durch seinen einfachen Aufbau und eignet sich hervorragend für Einsteiger, die grundlegende Programmierkonzepte und den Umgang mit dem Arduino kennenlernen möchten. Es bietet eine praktische Möglichkeit, Abläufe im Code zu verstehen und eine Schaltung mit LEDs und einem Piezo-Buzzer zu erstellen. Dabei lernst du, wie du if-Bedingungen verwendest, Arrays einsetzt und die Steuerung von Hardwarekomponenten wie LEDs und Buzzern programmierst. Zusätzlich ermöglicht das Projekt, erste Erfahrungen in der Strukturierung und Erweiterung von Code zu sammeln – perfekt, um die Grundlagen der Mikrocontroller-Programmierung zu vertiefen.
Disclaimer: Die Idee für das Projekt „Color Memory“ stammt vom Instagram-Account Sunflower. Ich habe mich von diesem Beitrag inspirieren lassen und das Spiel für meinen Technikblog aufgegriffen. Die Umsetzung und Beschreibung basieren auf meinen eigenen Anpassungen und Interpretationen des ursprünglichen Konzepts. Ein herzliches Dankeschön an Sunflower für die kreative Idee und die Inspiration!


Inhaltsverzeichnis
- Ablauf des Spieles – Color Memory
- Was lernt man bei der Programmierung?
- Was wird für den Aufbau der Schaltung benötigt?
- Aufbau der Schaltung
- Programmieren des Spieles – Color Memory in der Arduino IDE
- Abschluss & Fazit
Ablauf des Spieles – Color Memory
Das Spiel beginnt mit dem zufälligen Aufleuchten einer Sequenz von LEDs, die vom Mikrocontroller vorgegeben wird. Sobald die Sequenz beendet ist, ertönt ein Signalton vom Piezo-Buzzer, der den Spieler auffordert, die Reihenfolge der LEDs korrekt nachzubilden. Der Spieler gibt die Sequenz ein, indem er beispielsweise Tasten drückt, die den jeweiligen LEDs zugeordnet sind. Wird die Reihenfolge korrekt wiederholt, erhält der Spieler einen Punkt, und die Sequenz wird in der nächsten Runde um eine LED erweitert. Mit jedem Level steigt die Schwierigkeit, da die Reihenfolge länger und komplexer wird. Ein Fehler in der Eingabe beendet das Spiel. Ziel ist es, so viele Runden wie möglich zu meistern!
Was lernt man bei der Programmierung?
Du lernst, wie du Zufallszahlen erzeugst, um die Reihenfolge der LEDs zu bestimmen, Arrays anlegst und dynamisch befüllst, um die Sequenz zu speichern, sowie digitale Pins programmierst, um LEDs und einen Piezo-Buzzer anzusteuern. Dieses Projekt kombiniert spielerischen Spaß mit praktischen Lernerfahrungen in der Mikrocontroller-Programmierung und eignet sich hervorragend, um deine Fähigkeiten gezielt auszubauen.
Was wird für den Aufbau der Schaltung benötigt?
In meinem Fall verwende ich für die Schaltung:
- einen Arduino UNO R3*, oder
- einen Arduino Nano V3
- vier, 5 mm LEDs* inkl. 220 Ohm Vorwiderstand*
- vier Taster mit farbigen Tastknöpfen*
- eine I2C, 4fach 14 Segmentanzeige*
- diverse Breadboardkabel*
- ein 830 Pin Breadboard*
Statt einem Arduino UNO R3 kannst du ebenso ein Arduino Nano oder ESP32 verwenden. Der UNO R3 hat sich in diesen Schaltungen etabliert, da dieser bei diversen Kits enthalten ist.
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Aufbau der Schaltung
Wie erwähnt kannst du die Schaltung am Arduino UNO und Nano aufbauen (selbstverständlich auch am Leonardo sowie Mega 2560 R3).
Programmieren des Spieles – Color Memory in der Arduino IDE
Wie eingangs erwähnt, bietet dir die Programmierung dieses Spiels eine wertvolle Grundlage für viele weitere Spiele und Projekte.
Im Laufe der Entwicklung wirst du wichtige Techniken und Konzepte kennenlernen, die dir auch bei zukünftigen Vorhaben nützlich sein werden. In den folgenden Abschnitten zeige ich dir Schritt für Schritt, wie du dieses kleine, aber spannende Spiel selbst programmieren kannst.
Schritt 1 – Abfragen von Taster & aufleuchten von LEDs
Im ersten Schritt fragen wir die Taster ab und lassen die LEDs aufleuchten. Die Taster müssen entprellt werden, dazu kannst du entweder nach jedem Tastendruck eine Zeit x warten (Funktion delay) oder du nutzt die Bibliothek Bounce2. Der Weg über die Bibliothek ist deutlich komfortabler, da du hier zusätzlich abfragen kannst, ob der Taster gedrückt gehalten wird oder losgelassen wurde.
#include <Bounce2.h> #define tasterBlauPin 3 #define tasterGruenPin 5 #define tasterGelbPin 7 #define tasterRotPin 9 Bounce tasterBlau = Bounce(); Bounce tasterGruen = Bounce(); Bounce tasterGelb = Bounce(); Bounce tasterRot = Bounce(); const int BOUNCE_INTERVAL = 5; void setup() { Serial.begin(9600); tasterBlau.attach(tasterBlauPin, INPUT_PULLUP); tasterBlau.interval(BOUNCE_INTERVAL); tasterGruen.attach(tasterGruenPin, INPUT_PULLUP); tasterGruen.interval(BOUNCE_INTERVAL); tasterGelb.attach(tasterGelbPin, INPUT_PULLUP); tasterGelb.interval(BOUNCE_INTERVAL); tasterRot.attach(tasterRotPin, INPUT_PULLUP); tasterRot.interval(BOUNCE_INTERVAL); } void loop() { tasterBlau.update(); tasterGruen.update(); tasterGelb.update(); tasterRot.update(); if (tasterBlau.rose()) { Serial.println("Taster BLAU betätigt!"); } if (tasterGruen.rose()) { Serial.println("Taster GRUEN betätigt!"); } if (tasterGelb.rose()) { Serial.println("Taster GELB betätigt!"); } if (tasterRot.rose()) { Serial.println("Taster ROT betätigt!"); } }
Auf der seriellen Schnittstelle wird beim betätigen eines der Taster jeweils der entsprechende Text „Taster <Farbe> betätigt!“ ausgegeben.
Die LEDs sollen jeweils nur kurz aufleuchten, dazu erstellen wir eine Funktion, welcher wir den Pin der LED übergeben und dort einfach diese aktiviert, eine Pause eingelegt und anschließend deaktiviert wird.
#include <Bounce2.h> #define ledBlau 2 #define ledGruen 4 #define ledGelb 6 #define ledRot 8 void setup() { Serial.begin(9600); pinMode(ledBlau, OUTPUT); pinMode(ledGruen, OUTPUT); pinMode(ledGelb, OUTPUT); pinMode(ledRot, OUTPUT); } void blinkLed(int ledPin) { digitalWrite(ledPin, HIGH); delay(500); digitalWrite(ledPin, LOW); } void loop() { blinkLed(ledBlau); blinkLed(ledGruen); blinkLed(ledGelb); blinkLed(ledRot); }
Schritt 2 – Erzeugen von Zufallszahlen
Die Erzeugung echter Zufallszahlen ist für Computer eine komplexe Aufgabe, da sie dazu neigen, wiederkehrende Muster zu erzeugen oder Werte auf Basis mathematischer Funktionen zu berechnen. Diese Werte sind somit oft vorhersagbar und nicht wirklich zufällig. Mikrocontroller wie der Arduino bieten jedoch eine clevere Lösung für dieses Problem: Durch das Abgreifen eines Startwertes (Seed) von einem ungenutzten analogen Pin – zum Beispiel A0 – können wir eine unvorhersehbare Datenquelle nutzen. Der analoge Pin liefert durch elektrische Rauscheffekte stets leicht variierende Werte, die wir als Grundlage für echte Zufallszahlen in einer definierten Range (Bereich) verwenden können. Auf diese Weise wird das Verhalten des Spiels jedes Mal einzigartig und authentisch zufällig.
Da wir in diesem Spiel lediglich eine Range von 0 bis 4 verwenden (entsprechend der Farben Blau, Grün, Gelb und Rot), besteht die Möglichkeit, dass eine Zahl mehrfach hintereinander generiert wird. Um dies zu verhindern, habe ich die Funktion generateUniqueRandomNumber
erstellt. Diese überprüft, ob die neu generierte Zahl bereits zuvor verwendet wurde. Falls dies der Fall ist, ruft sich die Funktion selbst erneut auf, bis eine eindeutige, noch nicht genutzte Zahl gefunden wird. So bleibt die Sequenz abwechslungsreich und die Herausforderung für den Spieler erhalten.
int lastRandomNumber = -1; void setup() { Serial.begin(9600); randomSeed(analogRead(0)); } int generateUniqueRandomNumber() { int randNumber = random(0, 4); if (randNumber != lastRandomNumber) { lastRandomNumber = randNumber; return randNumber; } return generateUniqueRandomNumber(); } void loop() { Serial.println(generateUniqueRandomNumber()); delay(500); }
Die ermittelten Zufallszahlen werden in diesem kleinen Sketch im seriellen Monitor der Arduino IDE ausgegeben.
Schritt 3 – Abspeichern von Zufallszahlen in Arrays
In Arrays kannst du gleichartige Daten speichern, die Größe des Arrays ist am Mikrocontroller nur durch den vorhandenen Speicher begrenzt. In meinem Fall lege ich eine größe von 10 fest. Damit kann der Spieler bist maximal Level 10 spielen.
Derzeit belegt das gesamte Spiel auf einem Arduino UNO R3 31% des Speicherplatzes für Globale Variablen du kannst also diesen Wert von 10 auf 60 erhöhen! (Angenommen der Benutzer schafft es dann auch 60 folgen von LEDs sich zu merken 🙂 )
Der Sketch verwendet 9382 Bytes (29%) des Programmspeicherplatzes. Das Maximum sind 32256 Bytes. Globale Variablen verwenden 640 Bytes (31%) des dynamischen Speichers, 1408 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
Das derzeitige Level wird über eine 4fach 14 Segmentanzeige visualisiert. Für diese Anzeige verwende ich die Bibliothek Adafruit LED Backpack welche ich bereits im Beitrag Arduino Lektion #108: 14 Segmentanzeige verwendet habe.
#include <Adafruit_GFX.h> #include "Adafruit_LEDBackpack.h" #define ledBlau 2 #define ledGruen 4 #define ledGelb 6 #define ledRot 8 int lastRandomNumber = -1; int leds[] = { ledBlau, ledGruen, ledGelb, ledRot }; int level = 1; int sequenz[10] = {}; Adafruit_AlphaNum4 alpha4 = Adafruit_AlphaNum4(); void setup() { Serial.begin(9600); randomSeed(analogRead(0)); pinMode(ledBlau, OUTPUT); pinMode(ledGruen, OUTPUT); pinMode(ledGelb, OUTPUT); pinMode(ledRot, OUTPUT); alpha4.begin(0x70); alpha4.setBrightness(15); } void generateSequenz() { Serial.println("generateSequenz"); for (int index = 0; index < level; index++) { sequenz[index] = leds[generateUniqueRandomNumber()]; } } void playSequenz() { Serial.println("playSequenz"); for (int index = 0; index < level; index++) { int ledPin = sequenz[index]; blinkLed(ledPin); } } void blinkLed(int ledPin) { digitalWrite(ledPin, HIGH); delay(150); digitalWrite(ledPin, LOW); } int generateUniqueRandomNumber() { int randNumber = random(0, 4); if (randNumber != lastRandomNumber) { lastRandomNumber = randNumber; return randNumber; } return generateUniqueRandomNumber(); } void displayLevel() { String msg = (level < 10 ? "000" : "00") + String(level, DEC); alpha4.writeDigitAscii(0, msg.charAt(0)); alpha4.writeDigitAscii(1, msg.charAt(1)); alpha4.writeDigitAscii(2, msg.charAt(2)); alpha4.writeDigitAscii(3, msg.charAt(3)); alpha4.writeDisplay(); } void loop() { displayLevel(); generateSequenz(); playSequenz(); delay(150); level++; if (level > 10) { level = 1; } }
Schritt 4 – Erzeugen von Tönen mit einem Piezo Buzzer
Über den Piezo Buzzer kannst du nicht nur einfache Töne sondern auch kleine Lieder abspielen (siehe Weihnachtsevent 2019 im JFZ Schöningen – “Löten eines Weihnachtsbaums mit Sound”). In diesem Spiel nutzen wir diesen als Startsignal für den Spieler damit dieser die zuvor abgespielte Sequenz wiederholt.
#define piezoBuzzerPin 13 void setup() { pinMode(piezoBuzzerPin, OUTPUT); } void loop() { tone(piezoBuzzerPin, 1400, 75); delay(500); tone(piezoBuzzerPin, 500, 75); delay(500); }
Das kleine Beispiel spielt auf dem Piezo Buzzer zwei Töne ab, einer mit der Frequenz von 1400 Herz und ein anderer mit 500 Herz. (Je höher die Herz desto höher ist der Ton.) Zwischen diesen beiden Tönen wird eine Pause von 500 Millisekunden eingelegt.
Das fertige Spiel
Nachfolgend findest du das fertige kleine Spiel als ZIP-Datei zum download.
Hier der komplette Quellcode
#include <Bounce2.h> #include <Adafruit_GFX.h> #include "Adafruit_LEDBackpack.h" #define ledBlau 2 #define tasterBlauPin 3 #define ledGruen 4 #define tasterGruenPin 5 #define ledGelb 6 #define tasterGelbPin 7 #define ledRot 8 #define tasterRotPin 9 #define piezoBuzzerPin 13 Bounce tasterBlau = Bounce(); Bounce tasterGruen = Bounce(); Bounce tasterGelb = Bounce(); Bounce tasterRot = Bounce(); const int BOUNCE_INTERVAL = 5; int leds[] = { ledBlau, ledGruen, ledGelb, ledRot }; int level = 1; bool generateRandomSequenz = false; int sequenz[10] = {}; int userValues[10] = {}; int btnCounter = 0; Adafruit_AlphaNum4 alpha4 = Adafruit_AlphaNum4(); void setup() { Serial.begin(9600); pinMode(ledBlau, OUTPUT); pinMode(ledGruen, OUTPUT); pinMode(ledGelb, OUTPUT); pinMode(ledRot, OUTPUT); tasterBlau.attach(tasterBlauPin, INPUT_PULLUP); tasterBlau.interval(BOUNCE_INTERVAL); tasterGruen.attach(tasterGruenPin, INPUT_PULLUP); tasterGruen.interval(BOUNCE_INTERVAL); tasterGelb.attach(tasterGelbPin, INPUT_PULLUP); tasterGelb.interval(BOUNCE_INTERVAL); tasterRot.attach(tasterRotPin, INPUT_PULLUP); tasterRot.interval(BOUNCE_INTERVAL); pinMode(piezoBuzzerPin, OUTPUT); randomSeed(analogRead(0)); alpha4.begin(0x70); alpha4.setBrightness(15); generateRandomSequenz = true; } int lastRandomNumber = -1; int generateUnqueRandomNumber() { int randNumber = random(0, 4); if (randNumber != lastRandomNumber) { lastRandomNumber = randNumber; return randNumber; } return generateUnqueRandomNumber(); } void generateSequenz() { Serial.println("generateSequenz"); for (int index = 0; index < level; index++) { sequenz[index] = leds[generateUnqueRandomNumber()]; } } void playSequenz() { Serial.println("playSequenz"); for (int index = 0; index < level; index++) { int ledPin = sequenz[index]; blinkLed(ledPin); tone(piezoBuzzerPin, 1400, 75); } } void blinkLed(int ledPin) { digitalWrite(ledPin, HIGH); delay(500); digitalWrite(ledPin, LOW); } void recordUserSequenz() { tasterBlau.update(); tasterGruen.update(); tasterGelb.update(); tasterRot.update(); if (tasterBlau.rose()) { blinkLed(ledBlau); userValues[btnCounter] = ledBlau; btnCounter++; } if (tasterGruen.rose()) { blinkLed(ledGruen); userValues[btnCounter] = ledGruen; btnCounter++; } if (tasterGelb.rose()) { blinkLed(ledGelb); userValues[btnCounter] = ledGelb; btnCounter++; } if (tasterRot.rose()) { blinkLed(ledRot); userValues[btnCounter] = ledRot; btnCounter++; } } void blinkAll() { for (int index = 0; index < 5; index++) { digitalWrite(ledBlau, HIGH); digitalWrite(ledGruen, HIGH); digitalWrite(ledGelb, HIGH); digitalWrite(ledRot, HIGH); delay(175); digitalWrite(ledBlau, LOW); digitalWrite(ledGruen, LOW); digitalWrite(ledGelb, LOW); digitalWrite(ledRot, LOW); delay(175); } } void displayLevel() { String msg = "000" + String(level, DEC); alpha4.writeDigitAscii(0, msg.charAt(0)); alpha4.writeDigitAscii(1, msg.charAt(1)); alpha4.writeDigitAscii(2, msg.charAt(2)); alpha4.writeDigitAscii(3, msg.charAt(3)); alpha4.writeDisplay(); } void loop() { displayLevel(); if (generateRandomSequenz) { if (level == 10) { level = 0; } generateSequenz(); playSequenz(); generateRandomSequenz = false; } else { recordUserSequenz(); } if (btnCounter == level) { btnCounter = 0; generateRandomSequenz = true; for (int index = 0; index < level; index++) { int seqValue = sequenz[index]; int userValue = userValues[index]; if (seqValue != userValue) { level = 1; blinkAll(); tone(piezoBuzzerPin, 500, 125); Serial.println("Verloren!"); return; } } Serial.println("Gewonnen!"); level++; } }
Abschluss & Fazit
Das Spiel „Color Memory“ und der Weg über seine Programmierung bieten eine hervorragende Möglichkeit, die Grundlagen der Arduino-Programmierung anschaulich und praxisnah zu erlernen. Es zeigt nicht nur, wie man verschiedene Hardwarekomponenten wie LEDs und Piezo-Buzzer effizient steuert, sondern verdeutlicht auch die Anwendung von zentralen Programmierkonzepten wie if-Bedingungen, Arrays und die Erzeugung von Zufallszahlen.
Die schrittweise Entwicklung des Spiels schult sowohl das Verständnis für logische Abläufe als auch die Fähigkeit, Code strukturiert und funktional zu schreiben. Dadurch eignet sich dieses Projekt nicht nur für Anfänger, sondern auch für alle, die ihre Kenntnisse in der Mikrocontroller-Programmierung erweitern möchten. Es beweist, wie viel Spaß man haben kann, während man etwas Neues lernt und dabei ein funktionierendes Ergebnis in den Händen hält.
Ich hoffe, dieser Beitrag hat dir geholfen, einen spannenden Einstieg in die Welt des Arduino zu finden, und inspiriert dich, weitere kreative Projekte umzusetzen. Viel Spaß beim Programmieren!