Skip to content

Technik Blog

Programmieren | Arduino | ESP32 | MicroPython | Python | Raspberry Pi | Raspberry Pi Pico

Menu
  • Smarthome
  • Gartenautomation
  • Mikrocontroller
    • Arduino
    • ESP32 & Co.
    • Calliope Mini
    • Raspberry Pi & Pico
  • Solo Mining
  • Deutsch
  • English
Menu

„Zieh ab, wenn’s leuchtet!“ – Mini-Reaktionsspiel mit Arduino

Veröffentlicht am 16. Januar 202617. Januar 2026 von Stefan Draeger

Reaktionsspiele kennt jeder: Ein Signal erscheint – und man muss so schnell wie möglich reagieren.
Genau dieses einfache Prinzip greifen wir in diesem Projekt auf und machen daraus ein kleines Arduino-Spiel:

„Zieh ab, wenn’s leuchtet!“

Dabei steht nicht das Gewinnen im Vordergrund, sondern das spielerische Lernen.
Anhand eines einfachen Spiels lernst du, wie ein Arduino auf Sensordaten reagiert, wie sich Zeitspannen messen lassen und wie Ausgaben wie LEDs oder ein Piezo-Buzzer angesteuert werden.

Im Mittelpunkt steht eine Gabellichtschranke, deren Signal ausgewertet wird, um eine Reaktion exakt zu erfassen. Ohne komplizierte Schaltungen entsteht so ein Projekt, das sofort verständlich ist und gleichzeitig wichtige Grundlagen vermittelt.

„Zieh ab, wenn’s leuchtet!“ – Mini-Reaktionsspiel mit Arduino
Dieses Video auf YouTube ansehen.

Statt eines trockenen Beispiels entsteht ein kleines Spiel, das direkt Feedback liefert – sichtbar, hörbar und messbar. Ideal für Einsteiger, Workshops oder als kleines Projekt für zwischendurch.

Schaltung - Arduino Reaktionsspiel - Zieh ab wenns leuchtet
Arduino UNO als Schaltzentrale
Gabellichtschranke und Halterung
LEDs und Buzzer auf Breadboard
OLED Display und Starttaster

Inhaltsverzeichnis

  • Spielidee & Ablauf
    • Die Grundidee
    • So läuft eine Spielrunde ab
  • Was lernst du bei diesem Projekt?
    • Digitale Signale verstehen und auswerten
    • Sensoren sinnvoll einsetzen
    • Zeit messen am Arduino
    • LEDs und Piezo-Buzzer ansteuern
    • Programmabläufe logisch strukturieren
  • Benötigte Bauteile
    • Alternative Mikrocontroller
  • Die Gabellichtschranke verstehen – warum wir Interrupts nutzen
    • Welche Interrupt-Pins stehen zur Verfügung?
    • Übersicht: Interrupt-fähige Pins nach Board
  • LEDs und Piezo-Buzzer ansteuern – einfache digitale Ausgänge
    • LEDs als visuelles Feedback
    • Piezo-Buzzer ansteuern – tone() und noTone()
      • tone(pin, frequenz, dauer)
      • tone(pin, frequenz) + noTone(pin)
    • Warum wir D0 und D1 bewusst nicht verwenden
  • OLED-Display am Arduino – Ansteuerung über I2C
    • Benötigte Anschlüsse
    • I2C-Pins am Arduino UNO
  • Schaltung & Verdrahtung – alles zusammengeführt
  • 3D-Druck: Halter für die Gabellichtschranke
  • Programmierung – Schritt für Schritt zum fertigen Spiel
    • Benötigte Bibliotheken
    • Warum volatile wichtig ist
      • Was passiert ohne volatile?
      • Einsatz von volatile im Projekt
    • Wie eine Interrupt-Routine aufgerufen wird
      • Interrupts beim Arduino aktivieren
      • Beispiel aus dem Reaktionsspiel
      • Die Interrupt-Service-Routine (ISR)
      • Welche Interrupt-Modi gibt es?
      • Warum wir nicht CHANGE verwenden
      • Wichtige Regeln für Interrupt-Routinen
  • Fazit: Lernen durch Spielen

Spielidee & Ablauf

Das Spielprinzip von „Zieh ab, wenn’s leuchtet!“ ist bewusst einfach gehalten, damit der Fokus nicht auf komplizierter Technik liegt, sondern auf dem Zusammenspiel von Sensor, Signal und Reaktion.

Die Grundidee

Ein kleiner Steg steckt in einer Gabellichtschranke und unterbricht dort den Lichtstrahl. Solange der Steg steckt, weiß der Arduino: Das Spiel ist bereit.
Sobald der Lichtstrahl wieder frei ist, erkennt der Arduino sofort die Bewegung – ganz ohne mechanischen Kontakt.

Der Clou:
Der Spieler darf den Steg erst dann herausziehen, wenn das Startsignal erscheint.

So läuft eine Spielrunde ab

  1. Vorbereitung
    Der Steg wird in die Gabellichtschranke gesteckt und blockiert den Lichtstrahl.
    Das Spiel wartet auf den Start.
  2. Spielstart
    Nach dem Drücken des Starttasters beginnt eine zufällige Wartezeit.
    Diese Zufälligkeit verhindert, dass man „auf Verdacht“ reagiert.
  3. Startsignal
    Die LED leuchtet auf (optional zusätzlich ein Piezo-Ton).
    Ab diesem Moment zählt jede Millisekunde.
  4. Reaktion
    Der Spieler zieht den Steg so schnell wie möglich aus der Gabellichtschranke.
    Der Lichtstrahl ist wieder frei – das Signal ändert sich.
  5. Zeitmessung
    Der Arduino misst die Zeit zwischen Startsignal und dem Moment, in dem der Steg gezogen wurde.
  6. Ergebnis
    Die gemessene Reaktionszeit wird ausgegeben und kann mit vorherigen Runden verglichen werden.

Was lernst du bei diesem Projekt?

Auch wenn „Zieh ab, wenn’s leuchtet!“ auf den ersten Blick wie ein simples Spiel wirkt, stecken darin mehrere grundlegende Arduino-Konzepte, die in vielen Projekten immer wieder gebraucht werden.

Digitale Signale verstehen und auswerten

Du lernst, wie ein digitales Sensorsignal funktioniert und wie der Arduino erkennt, ob der Lichtstrahl der Gabellichtschranke unterbrochen ist oder nicht. Dabei wird klar, was HIGH und LOW bedeuten und warum Pull-up-Widerstände in der Praxis so wichtig sind.

Sensoren sinnvoll einsetzen

Die Gabellichtschranke dient hier nicht nur als „An/Aus-Schalter“, sondern als präziser Sensor, mit dem sich Bewegungen zuverlässig erfassen lassen – ganz ohne mechanischen Kontakt oder Prellen.

Zeit messen am Arduino

Ein zentraler Bestandteil des Spiels ist die Zeitmessung. Du lernst, wie der Arduino Zeitpunkte speichert und daraus Reaktionszeiten im Millisekunden- oder Mikrosekundenbereich berechnet.

LEDs und Piezo-Buzzer ansteuern

Das Projekt zeigt, wie Ausgaben richtig eingesetzt werden:
Eine LED signalisiert den Startzeitpunkt, ein Piezo-Buzzer kann diesen zusätzlich akustisch untermalen. So entsteht ein klares, für den Spieler verständliches Feedback.

Programmabläufe logisch strukturieren

Du baust eine einfache, aber saubere Spiellogik auf:

  • Warten auf ein Startsignal
  • Zufällige Verzögerung
  • Start der Zeitmessung
  • Auswertung des Sensors
  • Ausgabe des Ergebnisses

Diese Struktur lässt sich später auf viele andere Arduino-Projekte übertragen.

Benötigte Bauteile

Für das Reaktionsspiel „Zieh ab, wenn’s leuchtet!“ werden nur wenige, gut verfügbare Komponenten benötigt. Viele davon hast du vielleicht bereits in deiner Bastelkiste.

  • Arduino UNO*
    (alternativ Arduino Nano oder andere AVR-basierte Mikrocontroller)
  • Gabellichtschranke*
    (optischer Sensor zur Erkennung des herausgezogenen Stegs)
  • Drei LEDs mit Vorwiderständen*
    (rot, gelb, grün – z. B. jeweils 220 Ω)
  • Piezo-Buzzer*
    (für akustisches Feedback beim Startsignal)
  • I2C OLED Display*
    (z. B. 0,96″ mit SSD1306-Controller zur Anzeige der Reaktionszeit)
  • 400-Pin Breadboard*
  • Diverse Breadboardkabel* (Dupont-Kabel)
Bauteile - Arduino Reaktionsspiel

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!

Alternative Mikrocontroller

Statt dem klassischen Arduino UNO kannst du auch den Arduino Nano, oder einen UNO R4 Minima/WiFi/Nano verwenden. Alle diese Mikrocontroller sind kompatibel mit diesem Beitrag.

v.l.n.r. - Arduino UNO R4 Minima - Arduino Nano ESP32 - Arduino Nano V3 - Arduino Nano R4
v.l.n.r. – Arduino UNO R4 Minima – Arduino Nano ESP32 – Arduino Nano V3 – Arduino Nano R4

Die Gabellichtschranke verstehen – warum wir Interrupts nutzen

Die Gabellichtschranke liefert ein digitales Signal:
Ist der Lichtstrahl unterbrochen, ändert sich der Zustand am Ausgang (HIGH/LOW).

Gabellichtschranke F249 - Ansicht von der Seite 2
Gabellichtschranke F249 - Ansicht von der Seite 1
Gabellichtschranke F249 - Ansicht von Oben
Gabellichtschranke F249 - Ansicht von Unten

Man könnte dieses Signal grundsätzlich an einem normalen digitalen Pin abfragen. Für ein Reaktionsspiel ist das jedoch nicht zuverlässig genug. Der Grund: Der Arduino fragt solche Pins nur in bestimmten Programmabschnitten ab. Währenddessen können andere Aufgaben laufen – kurze Signaländerungen gehen verloren oder werden zeitlich ungenau erfasst.

Da es in diesem Projekt auf Millisekunden ankommt, nutzen wir einen Interrupt.

Interrupts sorgen dafür, dass der Arduino sofort reagiert, sobald sich das Signal der Gabellichtschranke ändert – unabhängig davon, was das Hauptprogramm gerade macht.

Beim Arduino UNO stehen dafür die Interrupt-Pins 2 und 3 zur Verfügung. Nur diese Pins können Signaländerungen hardwareseitig direkt erfassen.

Für das Spiel bedeutet das:

  • Der Moment, in dem der Steg gezogen wird, wird exakt erkannt
  • Die Zeitmessung ist präzise und reproduzierbar
  • Keine Reaktion geht verloren

Merksatz: Zeitkritische Sensorsignale gehören an Interrupt-Pins.

Welche Interrupt-Pins stehen zur Verfügung?

Nicht jeder Arduino stellt die gleichen Pins für Interrupts bereit.
Welche Pins genutzt werden können, hängt vom jeweiligen Mikrocontroller ab.

In diesem Projekt verwenden wir bewusst einen klassischen Arduino UNO, bei dem die Interrupt-Pins klar definiert sind. Wenn du jedoch ein anderes Board einsetzt, solltest du wissen, welche Pins Interrupts unterstützen.

Übersicht: Interrupt-fähige Pins nach Board

BoardBenutzbare Digitalpins für Interrupts
UNO, Nano, Mini (ATmega328-basierte Boards)2, 3
Mega, Mega 2560, Mega ADK2, 3, 18, 19, 20, 21
Micro, Leonardo (ATmega32u4)0, 1, 2, 3, 7

Weitere Informationen findest du in der offiziellen Dokumentation unter https://docs.arduino.cc/language-reference/de/funktionen/external-interrupts/attachInterrupt/

LEDs und Piezo-Buzzer ansteuern – einfache digitale Ausgänge

Nachdem wir uns mit der Gabellichtschranke und den Interrupts beschäftigt haben, kümmern wir uns nun um die Ausgabeseite des Spiels.
Dazu gehören in diesem Projekt drei LEDs und ein Piezo-Buzzer.

Im Gegensatz zur Lichtschranke sind diese Bauteile unkritisch:
Sie werden ganz normal an digitale Pins des Arduino angeschlossen und per Software ein- und ausgeschaltet.

LEDs als visuelles Feedback

Die LEDs dienen im Spiel als klare visuelle Signale:

  • Rot → Warten / Sperre
  • Gelb → Vorbereitung
  • Grün → Startsignal („Zieh ab!“)

Jede LED wird über einen Vorwiderstand (z. B. 220 Ω) an einen eigenen digitalen Pin angeschlossen.
Der Arduino schaltet die LEDs anschließend einfach mit HIGH oder LOW.

LEDs und Buzzer auf Breadboard

Technisch betrachtet passiert hier nichts Besonderes – und genau das ist der Vorteil:
Der Fokus liegt nicht auf der Schaltung, sondern auf der logischen Bedeutung der Signale im Spielablauf.

Piezo-Buzzer ansteuern – tone() und noTone()

Auch der Buzzer wird an einen normalen digitalen Pin angeschlossen.
Zur Ansteuerung stellt die Arduino-Umgebung die Funktion tone() zur Verfügung.

Piezo Buzzer
Piezo Buzzer

Dabei gibt es zwei gängige Varianten, die man kennen sollte:

tone(pin, frequenz, dauer)

Wird zusätzlich eine Dauer angegeben, erzeugt der Arduino einen Ton mit der gewünschten Frequenz für genau diesen Zeitraum.

tone(buzzerPin, 1000, 200);

In diesem Beispiel wird ein Ton mit 1000 Hz für 200 Millisekunden ausgegeben.
Nach Ablauf der Zeit stoppt der Ton automatisch – ein zusätzlicher Funktionsaufruf ist nicht notwendig.

Diese Variante eignet sich besonders für:

  • kurze Signal- oder Bestätigungstöne
  • Countdown- oder Startsignale
  • einfache Soundeffekte

tone(pin, frequenz) + noTone(pin)

Wird die Funktion tone() ohne Dauer aufgerufen, läuft der Ton endlos, bis er explizit gestoppt wird.

tone(buzzerPin, 800);
// ...
noTone(buzzerPin);

Hier beginnt der Ton mit 800 Hz und bleibt aktiv, bis noTone() aufgerufen wird.

Diese Variante ist sinnvoll, wenn:

  • der Ton von einer Bedingung abhängt
  • ein Signal aktiv bleiben soll, bis ein Ereignis eintritt
  • der Ton manuell beendet werden soll

Warum wir D0 und D1 bewusst nicht verwenden

Beim Arduino UNO (und kompatiblen Boards) sind die Pins D0 und D1 etwas Besonderes:

  • D0 → RX (Serielle Kommunikation)
  • D1 → TX (Serielle Kommunikation)

Diese Pins werden unter anderem:

  • beim Upload eines Programms
  • für den seriellen Monitor

verwendet.

RX & TX Pins am Arduino UNO
RX & TX Pins am Arduino UNO

Würden wir LEDs oder den Buzzer an diese Pins anschließen, könnte es:

  • zu Problemen beim Upload kommen
  • zu unerwartetem Flackern oder Tönen
  • zu gestörter serieller Ausgabe führen

Daher gilt als Faustregel: D0 und D1 bleiben für die serielle Kommunikation reserviert.

Für LEDs und Buzzer stehen mehr als genug andere digitale Pins zur Verfügung.

OLED-Display am Arduino – Ansteuerung über I2C

Für die Anzeige der Reaktionszeit verwenden wir ein OLED-Display mit I2C-Schnittstelle.
Der große Vorteil von I2C: Mit nur vier Anschlüssen lässt sich das Display vollständig betreiben.

Benötigte Anschlüsse

Ein I2C-OLED-Display benötigt lediglich:

  • VCC → Versorgungsspannung (5 V oder 3,3 V, je nach Modul)
  • GND → Masse
  • SDA → Datenleitung
  • SCL → Taktleitung

Damit ist das Display schnell angeschlossen und spart wertvolle Pins am Arduino.

I2C-Pins am Arduino UNO

Beim Arduino UNO sind die I2C-Pins besonders komfortabel herausgeführt: zum einen als separate, eindeutig beschriftete Buchsen neben AREF (oben links), zum anderen bei den analogen Pins A4 → SDA und A5 → SCL.

Arduino UNO SCL & SDA Pins
Arduino UNO SCL & SDA Pins

Das bedeutet:

  • Du kannst entweder die dedizierten SDA/SCL-Pins verwenden
  • oder alternativ A4 und A5, falls das besser zu deinem Aufbau passt

Beide Varianten funktionieren gleichwertig.

Schaltung & Verdrahtung – alles zusammengeführt

Nachdem wir alle verwendeten Komponenten einzeln betrachtet haben, setzen wir sie nun zu einer vollständigen Schaltung zusammen.
Der Aufbau erfolgt bewusst auf einem Breadboard, sodass keine Lötarbeiten notwendig sind und Änderungen jederzeit möglich bleiben.

Skizze - Arduino Reaktionsspiel - Zieh ab wenns leuchtet
BauteilFunktionArduino-Pin
GabellichtschrankeDOD2 (Interrupt-Pin)
VCC5 V
GNDGND
LED rotStatus „Warten / Sperre“D10
LED gelbStatus „Vorbereitung“D9
LED grünStartsignalD8
Piezo-BuzzerSignalD11
GNDGND
Start-TasterSignalD12
GNDGND
OLED-Display (I2C)VCC5 V
GNDGND
SDASDA / A4
SCLSCL / A5

3D-Druck: Halter für die Gabellichtschranke

Für das Reaktionsspiel „Zieh ab, wenn’s leuchtet!“ habe ich einen kleinen 3D-gedruckten Halter für die Gabellichtschranke entworfen.
Der Halter nimmt die Lichtschranke passgenau auf und stellt den herausziehbaren Steg bereit, der den Lichtstrahl zuverlässig unterbricht.

Halter - Gabellichtschranke mit Steg - Kontakt offen
Halter - Gabellichtschranke mit Steg - Kontakt geschlossen

Die STL-Dateien kannst du kostenlos auf Thingiverse herunterladen:

Download auf Thingiverse: https://www.thingiverse.com/thing:7269572

Der Halter ist bewusst minimal gehalten und eignet sich ideal für:

  • Steckbrett-Aufbauten
  • Tests und Prototypen
  • Workshops und Demonstrationen

Programmierung – Schritt für Schritt zum fertigen Spiel

Nachdem der Aufbau der Schaltung abgeschlossen ist, kümmern wir uns nun um die Programmierung des Arduino.
Der Sketch wird dabei nicht auf einmal, sondern bewusst Schritt für Schritt aufgebaut. So kannst du jeden Abschnitt testen und besser nachvollziehen, was im Hintergrund passiert.

Im Mittelpunkt stehen dabei:

  • das Auslesen der Gabellichtschranke
  • die Zeitmessung
  • die Ansteuerung von LEDs, Piezo-Buzzer und OLED-Display
  • sowie eine saubere Spiellogik

Benötigte Bibliotheken

Für dieses Projekt werden drei Bibliotheken verwendet:

  • Adafruit SSD1306 – Ansteuerung des OLED-Displays
  • Adafruit GFX Library – Grafikgrundlage für das Display
  • Bounce2 – Entprellung des Starttasters

Gerade mechanische Taster liefern beim Drücken kein sauberes Signal, sondern prellen.
Die Bounce2-Bibliothek übernimmt diese Entprellung zuverlässig in Software und verhindert Mehrfachauslösungen.

Die Installation ist unkompliziert und erfolgt direkt über den Bibliotheksverwalter der Arduino IDE:

  1. Arduino IDE öffnen
  2. Links den Bibliotheksverwalter auswählen
  3. nach „adafruit ssd1306“ suchen
  4. Schaltfläche „INSTALL“ klicken
  5. Bibliothek installieren (inkl. Abhängigkeit Adafruit GFX Library)

Nach der Installation kann die Bibliothek direkt im Sketch eingebunden werden.

Quellcode
/*******************************************************
 *  Zieh ab, wenn's leuchtet!
 *  Mini-Reaktionsspiel mit Arduino
 *
 *  Autor: Stefan Draeger
 *  Blog:  https://draeger-it.blog/zieh-ab-wenns-leuchtet-mini-reaktionsspiel-mit-arduino/
 *
 *  Hardware:
 *  - Arduino UNO (oder kompatibel)
 *  - Gabellichtschranke (Interrupt-Pin)
 *  - 3 LEDs (rot, gelb, grün)
 *  - Piezo-Buzzer
 *  - Start-Taster (entprellt mit Bounce2)
 *  - OLED Display (SSD1306, I2C)
 *******************************************************/

#include <Bounce2.h>

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSerif9pt7b.h>

/* ---------------- Pinbelegung ---------------- */

// LEDs
#define ledRotPin    10
#define ledGelbPin    9
#define ledGruenPin   8

// Buzzer
#define buzzerPin    11

// Sensor & Taster
#define lichtschrankePin 2    // Interrupt-Pin (UNO: 2 oder 3)
#define buttonPin       12

/* ---------------- Button-Objekt ---------------- */

// Bounce2 sorgt dafür, dass der Starttaster sauber entprellt wird
Bounce2::Button button = Bounce2::Button();

/* ---------------- OLED ---------------- */

#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(128, 32, &Wire, -1);

/* ---------------- Spielzustände ----------------
 * Alle Variablen, die im Interrupt verwendet oder
 * verändert werden, müssen 'volatile' sein.
 */

// Spiel läuft aktuell
volatile bool gameStarted = false;

// Ampelphase aktiv (Ziehen in dieser Phase = Fehler)
volatile bool ampelRunning = false;

// Spieler hat zu früh gezogen
volatile bool failed = false;

// Startzeit der Messung
unsigned long startTime = 0;

// Interrupt meldet: Zeitmessung stoppen
volatile bool stopRequested = false;

// Zeitpunkt, an dem der Steg gezogen wurde
volatile unsigned long endTime = 0;

// Schutz vor direktem erneuten Triggern nach Fehler
volatile bool back = false;

// Kleine Guard-Zeit im Interrupt (gegen Flattern)
volatile unsigned long lastIrqMs = 0;

/* ---------------- Setup ---------------- */

void setup() {
  Serial.begin(9600);

  // OLED initialisieren
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
  }

  // LEDs & Buzzer
  pinMode(ledRotPin, OUTPUT);
  pinMode(ledGelbPin, OUTPUT);
  pinMode(ledGruenPin, OUTPUT);
  pinMode(buzzerPin, OUTPUT);

  // Gabellichtschranke stabilisieren (Pull-up)
  pinMode(lichtschrankePin, INPUT_PULLUP);

  // Interrupt: reagiert nur auf eine Flanke
  attachInterrupt(
    digitalPinToInterrupt(lichtschrankePin),
    stoppTimer,
    RISING
  );

  // Starttaster (gegen GND, interner Pull-up)
  button.attach(buttonPin, INPUT_PULLUP);
  button.interval(5);
  button.setPressedState(LOW);

  // Startanzeige
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setFont(&FreeSerif9pt7b);
  display.setCursor(30, 13);
  display.println("Zieh ab,");
  display.setCursor(10, 26);
  display.println("wenn's leuchtet!");
  display.display();
}

/* ---------------- Interrupt-Routine ----------------
 * Wird aufgerufen, sobald der Steg aus der
 * Gabellichtschranke gezogen wird.
 * Wichtig: KEIN Display, kein Serial hier!
 */

void stoppTimer() {
  unsigned long now = millis();

  // Guard gegen Flattern / Mehrfachtrigger
  if (now - lastIrqMs < 25) return;
  lastIrqMs = now;

  // Ziehen während der Ampelphase = Fehler
  if (ampelRunning) {
    failed = true;
    gameStarted = false;
    return;
  }

  // Spiel läuft nicht → ignorieren
  if (!gameStarted) return;

  // Schutz nach Fehlstart
  if (back) {
    back = false;
    return;
  }

  // Zeitpunkt speichern, Auswertung im loop()
  endTime = now;
  stopRequested = true;
}

/* ---------------- Hilfsfunktionen ---------------- */

// Startzeit setzen
void startTimer() {
  startTime = millis();
}

// Ampel-Sequenz (rot → gelb → grün)
void ampel() {
  ampelRunning = true;

  digitalWrite(ledRotPin, HIGH);
  tone(buzzerPin, 1000, 125);
  delay(250);

  digitalWrite(ledGelbPin, HIGH);
  tone(buzzerPin, 800, 125);
  delay(250);

  digitalWrite(ledGruenPin, HIGH);
  tone(buzzerPin, 600, 125);

  ampelRunning = false;
}

// Alle LEDs aus
void resetLEDs() {
  digitalWrite(ledRotPin, LOW);
  digitalWrite(ledGelbPin, LOW);
  digitalWrite(ledGruenPin, LOW);
}

// Alle LEDs an (Fehleranzeige)
void allActiveLEDs() {
  digitalWrite(ledRotPin, HIGH);
  digitalWrite(ledGelbPin, HIGH);
  digitalWrite(ledGruenPin, HIGH);
}

/* ---------------- Hauptloop ---------------- */

void loop() {
  button.update();

  // Neue Runde starten
  if (button.rose() && !gameStarted) {

    // Display leeren
    display.clearDisplay();
    display.display();

    resetLEDs();

    // Interrupt-Flags sauber zurücksetzen
    noInterrupts();
    stopRequested = false;
    endTime = 0;
    back = false;
    interrupts();

    ampel();
    gameStarted = true;
    startTimer();
  }

  // Fehlstart behandeln
  if (failed) {
    failed = false;

    display.clearDisplay();
    display.setCursor(20, 23);
    display.setFont(&FreeSerif9pt7b);
    display.setTextColor(SSD1306_WHITE);
    display.print("!!! Failed !!!");
    display.display();

    tone(buzzerPin, 500, 250);

    for (int i = 0; i < 5; i++) {
      allActiveLEDs();
      delay(175);
      resetLEDs();
      delay(175);
    }

    back = true;
    gameStarted = false;
  }

  // Zeitmessung auswerten (atomar!)
  bool localStop = false;
  unsigned long localEnd = 0;

  noInterrupts();
  localStop = stopRequested;
  localEnd = endTime;
  if (localStop) stopRequested = false;
  interrupts();

  if (localStop && gameStarted) {
    gameStarted = false;

    unsigned long duration = localEnd - startTime;

    Serial.print("Zeit: ");
    Serial.print(duration);
    Serial.println(" ms");

    display.clearDisplay();
    display.setCursor(15, 23);
    display.setFont(&FreeSerif9pt7b);
    display.setTextColor(SSD1306_WHITE);
    display.print("Zeit: ");
    display.print(duration);
    display.print("ms");
    display.display();
  }
}

Warum volatile wichtig ist

In diesem Projekt arbeiten zwei Programmteile gleichzeitig:

  • das Hauptprogramm (loop())
  • die Interrupt-Routine (stoppTimer())

Beide greifen auf dieselben Variablen zu, zum Beispiel auf gameStarted, stopRequested oder endTime.
Damit diese Zusammenarbeit zuverlässig funktioniert, müssen diese Variablen als volatile deklariert werden.

volatile teilt dem Compiler mit, dass sich der Wert einer Variable jederzeit ändern kann – auch außerhalb des normalen Programmablaufs, etwa durch einen Interrupt.

Was passiert ohne volatile?

Ohne volatile darf der Compiler annehmen, dass sich eine Variable nur dort ändert, wo er es im Code „sieht“.
Er kann den Wert zwischenspeichern oder optimieren.

Bei Interrupts ist das problematisch:
Die Interrupt-Routine ändert Variablen asynchron, das Hauptprogramm bekommt diese Änderung sonst unter Umständen nie mit.

Typische Symptome sind:

  • das Spiel stoppt nicht
  • die Zeit wird nicht angezeigt
  • der Code funktioniert „manchmal“, aber nicht zuverlässig

Einsatz von volatile im Projekt

Alle Variablen, die im Interrupt gelesen oder geschrieben werden, sind deshalb als volatile deklariert:

volatile bool gameStarted = false;
volatile bool stopRequested = false;
volatile unsigned long endTime = 0;
volatile bool failed = false;

So wird sichergestellt, dass der Arduino bei jedem Zugriff den aktuellen Wert aus dem Speicher liest.

Wie eine Interrupt-Routine aufgerufen wird

Damit der Arduino auf ein externes Ereignis – wie das Signal der Gabellichtschranke – sofort reagieren kann, muss eine sogenannte Interrupt-Routine eingerichtet werden.
Diese wird automatisch vom Mikrocontroller aufgerufen, sobald sich das Signal an einem dafür vorgesehenen Pin ändert.

Interrupts beim Arduino aktivieren

Interrupts werden beim Arduino mit der Funktion attachInterrupt() eingerichtet.
Der Aufruf erfolgt in der Regel in der setup()-Funktion.

Die allgemeine Form lautet:

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

Dabei haben die Parameter folgende Bedeutung:

  • pin
    Der digitale Pin, an dem das Interrupt-Signal anliegt
    (z. B. Pin 2 beim Arduino UNO)
  • ISR
    Der Name der Interrupt-Service-Routine
    (eine normale Funktion ohne Parameter und ohne Rückgabewert)
  • mode
    Gibt an, bei welcher Signaländerung der Interrupt ausgelöst wird

Beispiel aus dem Reaktionsspiel

In diesem Projekt wird die Gabellichtschranke an Pin D2 angeschlossen.
Der Interrupt wird so eingerichtet:

attachInterrupt(
  digitalPinToInterrupt(lichtschrankePin),
  stoppTimer,
  RISING
);

Bedeutung:

  • Sobald das Signal an lichtschrankePin von LOW nach HIGH wechselt
  • ruft der Arduino automatisch die Funktion stoppTimer() auf
  • unabhängig davon, was gerade im loop() passiert

Die Interrupt-Service-Routine (ISR)

Die Interrupt-Routine selbst ist eine ganz normale Funktion – mit zwei wichtigen Einschränkungen:

void stoppTimer() {
  // Interrupt-Code
}
  • keine Parameter
  • kein Rückgabewert

Der Arduino springt bei einem Interrupt direkt in diese Funktion und kehrt danach wieder zum normalen Programmablauf zurück.

Welche Interrupt-Modi gibt es?

Der dritte Parameter (mode) legt fest, wann der Interrupt ausgelöst wird:

  • RISING
    LOW → HIGH (z. B. Steg wird gezogen)
  • FALLING
    HIGH → LOW
  • CHANGE
    Bei jeder Änderung (LOW ↔ HIGH)
  • LOW
    Solange der Pin LOW ist (selten sinnvoll)

Für Reaktionsspiele ist RISING oder FALLING ideal, da nur ein klarer Moment erfasst werden soll.

Warum wir nicht CHANGE verwenden

CHANGE löst den Interrupt bei jeder Flanke aus.
Bei Sensoren oder mechanischer Bewegung kann das zu:

  • mehrfachen Interrupts
  • falschen Zeitwerten
  • schwer reproduzierbaren Fehlern

führen.

Deshalb reagieren wir bewusst nur auf eine definierte Flanke.

Wichtige Regeln für Interrupt-Routinen

Eine Interrupt-Routine muss extrem kurz sein.
Erlaubt ist:

  • Flags setzen (bool, volatile)
  • Zeitstempel speichern (millis())

Nicht erlaubt (bzw. dringend zu vermeiden):

  • delay()
  • Serial.print()
  • Display-Ausgaben
  • komplexe Berechnungen

Im Reaktionsspiel macht die ISR deshalb nur eines:

Sie signalisiert dem Hauptprogramm, dass ein Ereignis passiert ist.

Die eigentliche Auswertung erfolgt später im loop().

Fazit: Lernen durch Spielen

„Zieh ab, wenn’s leuchtet!“ ist mehr als ein kleines Reaktionsspiel.
Es verbindet Sensorik, Interrupts, Zeitmessung und Benutzerfeedback zu einem Projekt, das sofort verständlich ist – und trotzdem wichtige Arduino-Grundlagen vermittelt.

Gerade für Einsteiger, Schulprojekte oder Workshops eignet sich dieses Spiel ideal, da Fehler sichtbar werden und der Lerneffekt unmittelbar ist.

Letzte Aktualisierung am: 17. Januar 2026

Foto von Stefan Draeger
Über den Autor

Stefan Draeger — Entwickler & Tech-Blogger

Ich zeige praxisnah, wie du Projekte mit Arduino, ESP32 und Smarthome-Komponenten umsetzt – Schritt für Schritt, mit Code und Schaltplänen.

Mehr Artikel von Stefan →

Schreibe einen Kommentar Antwort abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Fragen oder Feedback?

Du hast eine Idee, brauchst Hilfe oder möchtest Feedback loswerden?
Support-Ticket erstellen

Newsletter abonnieren

Bleib auf dem Laufenden: Erhalte regelmäßig Updates zu neuen Projekten, Tutorials und Tipps rund um Arduino, ESP32 und mehr – direkt in dein Postfach.

Jetzt Newsletter abonnieren

Unterstütze meinen Blog

Wenn dir meine Inhalte gefallen, freue ich mich über deine Unterstützung auf Tipeee.
So hilfst du mit, den Blog am Leben zu halten und neue Beiträge zu ermöglichen.

draeger-it.blog auf Tipeee unterstützen

Vielen Dank für deinen Support!
– Stefan Draeger

Kategorien

Tools

  • QR-Code Generator
  • Unix-Zeitstempel-Rechner
  • ASCII Tabelle
  • Spannung, Strom, Widerstand und Leistung berechnen
  • Widerstandsrechner
  • 8×8 LED Matrix Tool
  • 8×16 LED Matrix Modul von Keyestudio
  • 16×16 LED Matrix – Generator

Links

Blogverzeichnis Bloggerei.de TopBlogs.de das Original - Blogverzeichnis | Blog Top Liste Blogverzeichnis trusted-blogs.com

Stefan Draeger
Königsberger Str. 13
38364 Schöningen
Tel.: 015565432686
E-Mail: info@draeger-it.blog

Folge mir auf

link zu Fabook
link zu LinkedIn
link zu YouTube
link zu TikTok
link zu Pinterest
link zu Instagram
  • Impressum
  • Datenschutzerklärung
  • Disclaimer
  • Cookie-Richtlinie (EU)
©2026 Technik Blog | Built using WordPress and Responsive Blogily theme by Superb
Cookie-Zustimmung verwalten
Wir verwenden Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wir tun dies, um das Surferlebnis zu verbessern und um personalisierte Werbung anzuzeigen. Wenn Sie diesen Technologien zustimmen, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn Sie Ihre Zustimmung nicht erteilen oder zurückziehen, können bestimmte Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
  • Optionen verwalten
  • Dienste verwalten
  • Verwalten von {vendor_count}-Lieferanten
  • Lese mehr über diese Zwecke
Einstellungen anzeigen
  • {title}
  • {title}
  • {title}