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.
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.





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
- Vorbereitung
Der Steg wird in die Gabellichtschranke gesteckt und blockiert den Lichtstrahl.
Das Spiel wartet auf den Start. - Spielstart
Nach dem Drücken des Starttasters beginnt eine zufällige Wartezeit.
Diese Zufälligkeit verhindert, dass man „auf Verdacht“ reagiert. - Startsignal
Die LED leuchtet auf (optional zusätzlich ein Piezo-Ton).
Ab diesem Moment zählt jede Millisekunde. - Reaktion
Der Spieler zieht den Steg so schnell wie möglich aus der Gabellichtschranke.
Der Lichtstrahl ist wieder frei – das Signal ändert sich. - Zeitmessung
Der Arduino misst die Zeit zwischen Startsignal und dem Moment, in dem der Steg gezogen wurde. - 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)
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.
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).




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
| Board | Benutzbare Digitalpins für Interrupts |
|---|---|
| UNO, Nano, Mini (ATmega328-basierte Boards) | 2, 3 |
| Mega, Mega 2560, Mega ADK | 2, 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.
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.
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.
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.
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.
| Bauteil | Funktion | Arduino-Pin |
|---|---|---|
| Gabellichtschranke | DO | D2 (Interrupt-Pin) |
| VCC | 5 V | |
| GND | GND | |
| LED rot | Status „Warten / Sperre“ | D10 |
| LED gelb | Status „Vorbereitung“ | D9 |
| LED grün | Startsignal | D8 |
| Piezo-Buzzer | Signal | D11 |
| GND | GND | |
| Start-Taster | Signal | D12 |
| GND | GND | |
| OLED-Display (I2C) | VCC | 5 V |
| GND | GND | |
| SDA | SDA / A4 | |
| SCL | SCL / 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.


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:
- Arduino IDE öffnen
- Links den Bibliotheksverwalter auswählen
- nach „adafruit ssd1306“ suchen
- Schaltfläche „INSTALL“ klicken
- 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
lichtschrankePinvon 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 → LOWCHANGE
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








