Community project
Arduino Reaction-Time Game

Build a reaction-time game that tests how quickly you can press a button after an LED lights up. The ESP32 microcontroller runs the game logic, measuring the time between the visual cue and your button press, then displaying your result with a blinking LED pattern.
This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions. You'll install the LED with its current-limiting resistor, connect the momentary pushbutton, wire the USB-C power supply, and upload the firmware to your ESP32. Once assembled, the device is ready to play—press the button to start each round and see how fast your reflexes are.
Wiring diagram
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| LEDRed, 220 Ω series resistor | 1 | Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically. |
| Momentary pushbuttonNormally-open tactile switch | 1 | Normally-open tactile pushbutton for the reaction input, read with the Arduino internal pull-up resistor. |
| USB-C 5V Adapter5 V USB-C wall adapter | 1 | USB-C wall adapter delivering regulated 5 V to the board's USB or VBUS rail. Default wired power source for desktop / stationary projects. |
| Resistor220 Ω | 1 | Through-hole resistor (current-limiting in series with an LED) |
Assembly
4 stepsInstall the LED and its resistor
Place the red LED on the breadboard. Connect Metro GPIO 13 to one end of the separate 220 Ω resistor. Connect the resistor’s other end to the LED long leg (anode). Connect the LED short leg (cathode, usually beside the flat edge) to a Metro GND pin.
- Tip: The Metro's 3.3 V GPIO output is suitable for a red LED with a 220 Ω resistor.
- Tip: The board's built-in LED may also indicate the cue if it is tied to the same GPIO.
- ⚠ The 220 Ω resistor must be in series with the LED; never wire the LED directly from GPIO 13 to GND.
Wire the reaction button
Mount the normally-open tactile button across the breadboard center gap. Connect one side of the switch to Metro GPIO 2 and the opposite side to GND. The firmware enables the internal pull-up, so no external resistor is needed.
- Tip: The input is normally HIGH and reads LOW only while the button is pressed.
- Tip: On a four-leg tactile switch, use terminals from opposite internally connected pairs.
- ⚠ Do not connect the button to 5 V; this project expects the button to connect GPIO 2 to GND.
Connect the wall power supply
Use a certified regulated 5 V USB-C wall adapter and USB-C cable to plug the wall_adapter directly into the Metro ESP32-S3 USB-C port. The board regulates this input for its own electronics.
- Tip: Choose a quality adapter rated for at least 0.5 A; the game normally draws about 100 mA.
- Tip: Keep the USB-C cable connected while operating the game.
- ⚠ Use only a regulated 5 V USB-C power adapter. Do not connect an unregulated adapter, mains wiring, or a higher-voltage supply to the board USB-C port.
Final connection check
Before powering the board, verify: GPIO 13 reaches the LED only through the 220 Ω resistor; the LED cathode is at GND; GPIO 2 reaches one button side; and the opposite button side is at GND. Then apply wall power.
- Tip: After using Schematik's Deploy button, view the Serial output at 115200 baud for round messages, false starts, and reaction times.
- Tip: Release the button after the three LED result blinks so the next round can arm.
- ⚠ Do not power the Metro from two different sources at once unless they are designed to share USB power.
Pin assignments
Board wiring reference| Pin | Connection | Type |
|---|---|---|
| GPIO 13 | led_resistor P1 | digital |
| EXT | led_resistor P2 → LED ANODE | digital |
| GND | red_led GND | ground |
| GPIO 2 | reaction_button SW1 | digital |
| GND | reaction_button SW2 | ground |
| EXT | wall_adapter +5V → Adafruit Metro ESP32-S3 USB-C power port | power |
| EXT | wall_adapter GND → Adafruit Metro ESP32-S3 USB-C power return | ground |
Firmware
ESP32#include <Arduino.h>
// Hoisted type definitions
enum GameState {
WAIT_FOR_RELEASE,
WAITING_FOR_CUE,
TIMING_REACTION,
BLINKING_RESULT
};
// Forward declarations
bool buttonPressedEvent();
void startWaitingRound();
void startResultBlink();
constexpr uint8_t LED_PIN = 13;
constexpr uint8_t BUTTON_PIN = 2;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t MIN_WAIT_MS = 2000;
constexpr uint32_t MAX_WAIT_MS = 5000;
constexpr uint32_t BLINK_INTERVAL_MS = 200;
GameState gameState = WAIT_FOR_RELEASE;
bool rawButtonState = HIGH;
bool stableButtonState = HIGH;
uint32_t lastRawChangeMs = 0;
uint32_t waitStartedMs = 0;
uint32_t randomDelayMs = 0;
uint32_t cueStartedMs = 0;
uint32_t blinkChangedMs = 0;
uint8_t blinkTransitions = 0;
bool buttonPressedEvent() {
const bool reading = digitalRead(BUTTON_PIN);
const uint32_t now = millis();
if (reading != rawButtonState) {
rawButtonState = reading;
lastRawChangeMs = now;
}
if ((now - lastRawChangeMs) >= DEBOUNCE_MS && stableButtonState != rawButtonState) {
stableButtonState = rawButtonState;
return stableButtonState == LOW;
}
return false;
}
void startWaitingRound() {
digitalWrite(LED_PIN, LOW);
randomDelayMs = random(MIN_WAIT_MS, MAX_WAIT_MS + 1);
waitStartedMs = millis();
gameState = WAITING_FOR_CUE;
Serial.println(F("Round started: wait for the LED."));
}
void startResultBlink() {
digitalWrite(LED_PIN, LOW);
blinkTransitions = 0;
blinkChangedMs = millis();
gameState = BLINKING_RESULT;
}
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.begin(115200);
randomSeed(micros());
Serial.println(F("Reaction-time game ready. Release the button to begin."));
}
void loop() {
const uint32_t now = millis();
const bool pressed = buttonPressedEvent();
switch (gameState) {
case WAIT_FOR_RELEASE:
digitalWrite(LED_PIN, LOW);
if (stableButtonState == HIGH) {
startWaitingRound();
}
break;
case WAITING_FOR_CUE:
if (pressed) {
Serial.println(F("False start! You pressed before the LED."));
startResultBlink();
} else if ((now - waitStartedMs) >= randomDelayMs) {
digitalWrite(LED_PIN, HIGH);
cueStartedMs = now;
gameState = TIMING_REACTION;
Serial.println(F("GO!"));
}
break;
case TIMING_REACTION:
if (pressed) {
Serial.print(F("Reaction time: "));
Serial.print(now - cueStartedMs);
Serial.println(F(" ms"));
startResultBlink();
}
break;
case BLINKING_RESULT:
if ((now - blinkChangedMs) >= BLINK_INTERVAL_MS) {
blinkChangedMs = now;
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
if (++blinkTransitions >= 6) {
digitalWrite(LED_PIN, LOW);
gameState = WAIT_FOR_RELEASE;
Serial.println(F("Release the button for the next round."));
}
}
break;
}
}“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.