Mission 15 · Stage 2

Reaction Timer

millis() with if conditions

Measure button reaction time with millis() after a random GO signal.

Reaction Timer circuit diagram

Pin connections

Part 1Part 2

Arduino

pin 2

Button

pin 1

Button

pin 2

Arduino

GND

Arduino

pin 13

Resistor

pin 1

Resistor

pin 2

GO LED

anode (+)

GO LED

cathode (-)

Arduino

GND

See it

Let's time how fast you are!

Wait… wait… GO! Press as fast as you can and see your speed in numbers.

Game timers and sports clocks use this same trick to count time.

The story

The problem

If we used delay to wait, the program would freeze. We need a clock that keeps ticking.

Think of it like

millis() is like a stopwatch that never stops counting.

Meet the parts

It runs the clock and times your press.

Arduino

The brain

Loading part…

Press it the second the GO light turns on.

Button

The button

Loading part…

Slows the power down so the GO light does not get hurt.

Resistor

The protector

Loading part…

It tells you the exact moment to press.

GO LED

The GO light

Loading part…

How it works

1

Wait a surprise time

Instead of freezing, we just check the clock until it reaches the GO time.

if (state == WAIT && millis() >= waitUntil)
2

Show GO

The light turns on and we save the exact moment in goTime.

digitalWrite(GO_LED, HIGH);
goTime = millis();
3

Measure your speed

Take the time now and subtract goTime to find how fast you pressed.

reaction = millis() - goTime;

Then loop back to step 1

Build the circuit

Follow these steps in order. Match the wires to the colors shown.

  1. 1

    Place Arduino

    Place the Arduino (uno) on the breadboard.

    Arduino placed — ready to build!

    Loading part…
  2. 2

    Place Button

    Place the Button (btn1) on the breadboard.

    Loading part…
  3. 3

    Place Resistor

    Place the Resistor (r1) on the breadboard.

    Loading part…
  4. 4

    Place GO LED

    Place the GO LED (led1) on the breadboard.

    Loading part…
  5. 5

    Connect Arduino pin 2 to Button (btn1) 1.l

    Connect Arduino pin 2 to Button (btn1) 1.l.

  6. 6

    Connect Button (btn1) 2.l to Arduino GND

    Connect Button (btn1) 2.l to Arduino GND.

  7. 7

    Connect Arduino pin 13 to Resistor (r1) 1

    Connect Arduino pin 13 to Resistor (r1) 1.

  8. 8

    Connect Resistor (r1) 2 to GO LED (led1) anode (+)

    Connect Resistor (r1) 2 to GO LED (led1) anode (+).

  9. 9

    Connect GO LED (led1) cathode (-) to Arduino GND

    Connect GO LED (led1) cathode (-) to Arduino GND.

Try it

  • Open the Serial Monitor, wait for GO!, then press fast.
  • Read your reaction time in milliseconds — smaller is faster!

Peek at code

The game modes

enum State { WAIT, GO, SCORED };
State state = WAIT;
unsigned long goTime = 0;
unsigned long waitUntil = 0;

The game has three modes: WAIT, GO, and SCORED. The program moves between them.

scheduleWait()

void scheduleWait() {
  digitalWrite(GO_LED, LOW);
  waitUntil = millis() + random(2000, 5000);
  state = WAIT;
}

This sets a surprise future time for GO, without freezing the program.

Timing your press

void loop() {
  if (state == WAIT && millis() >= waitUntil) {
    digitalWrite(GO_LED, HIGH);
    goTime = millis();
    state = GO;
    Serial.println("GO! Press the button!");
  }
  if (state == GO && digitalRead(BUTTON_PIN) == LOW) {
    unsigned long reaction = millis() - goTime;
    Serial.print("Reaction: ");
    Serial.print(reaction);
    Serial.println(" ms");
    state = SCORED;
    delay(2000);
    scheduleWait();
  }
  delay(10);

loop() watches the clock to start GO, then times how fast you pressed.

Show full sketch (reaction-timer.ino)
const int BUTTON_PIN = 2;
const int GO_LED = 13;
enum State { WAIT, GO, SCORED };
State state = WAIT;
unsigned long goTime = 0;
unsigned long waitUntil = 0;
void scheduleWait() {
  digitalWrite(GO_LED, LOW);
  waitUntil = millis() + random(2000, 5000);
  state = WAIT;
}
void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(GO_LED, OUTPUT);
  Serial.begin(9600);
  randomSeed(analogRead(0));
  scheduleWait();
  Serial.println("Reaction Timer — wait for GO!");
}
void loop() {
  if (state == WAIT && millis() >= waitUntil) {
    digitalWrite(GO_LED, HIGH);
    goTime = millis();
    state = GO;
    Serial.println("GO! Press the button!");
  }
  if (state == GO && digitalRead(BUTTON_PIN) == LOW) {
    unsigned long reaction = millis() - goTime;
    Serial.print("Reaction: ");
    Serial.print(reaction);
    Serial.println(" ms");
    state = SCORED;
    delay(2000);
    scheduleWait();
  }
  delay(10);
}

Quick quiz

Q1. Where does work that repeats go?

  • A. loop()
  • B. setup()
  • C. pinMode only
Why: Yes! loop() runs again and again.

Q2. Why use millis() instead of delay() while waiting?

  • A. millis() lets the program keep checking things
  • B. millis() is louder
  • C. delay() reads buttons faster
Why: Yes! millis() counts time without freezing the program.

Code lab — try on your own

  1. Make the waits shorter — change random(2000, 5000) to random(1000, 3000).

    Hint: It's inside scheduleWait(), line 9.

  2. Add a note (comment) on line 28 that says this is the time since GO.

    Hint: That's the reaction = millis() - goTime line.

Code walkthrough

A line-by-line tour of the sketch — the same steps as in Robot Gurukul Studio.

Program overview

Big idea

Every Arduino program has a top part, a setup() part that runs once, and a loop() part that runs again and again.

In this project

The light waits, then says GO. You press fast, and it shows how quick you were.

Tip

Read from the top to the bottom. Tap any word or line if you need help!

const int BUTTON_PIN = 2;
const int GO_LED = 13;
enum State { WAIT, GO, SCORED };
State state = WAIT;
unsigned long goTime = 0;
unsigned long waitUntil = 0;
void scheduleWait() {
  digitalWrite(GO_LED, LOW);
  waitUntil = millis() + random(2000, 5000);
  state = WAIT;
}

setup()

Big idea

setup() runs one time when the board turns on.

In this project

It gets the button and GO light ready, mixes up the randomness, and starts the first wait.

Why here

Things we do only once go inside setup().

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(GO_LED, OUTPUT);
  Serial.begin(9600);
  randomSeed(analogRead(0));
  scheduleWait();
  Serial.println("Reaction Timer — wait for GO!");
}

loop()

Big idea

loop() runs again and again, forever.

In this project

It checks the clock, shows GO at the right time, and times your press.

Why here

Things that repeat go inside loop().

void loop() {
  if (state == WAIT && millis() >= waitUntil) {
    digitalWrite(GO_LED, HIGH);
    goTime = millis();
    state = GO;
    Serial.println("GO! Press the button!");
  }
  if (state == GO && digitalRead(BUTTON_PIN) == LOW) {
    unsigned long reaction = millis() - goTime;
    Serial.print("Reaction: ");
    Serial.print(reaction);
    Serial.println(" ms");
    state = SCORED;
    delay(2000);
    scheduleWait();
  }
  delay(10);
}

Try this: Change a number inside loop(), then press Run to see what happens.

pinMode

Big idea

pinMode tells a pin if it will listen or push power out.

In this project

It makes pin 2 listen to the button and pin 13 push power to the GO light.

Why here

It goes in setup() because we only set it once.

  pinMode(BUTTON_PIN, INPUT_PULLUP);

digitalWrite

Big idea

digitalWrite turns a pin ON or OFF.

In this project

It turns the GO light off again to get ready for the next round.

Why here

It is used when we set up the next wait.

  digitalWrite(GO_LED, LOW);

digitalRead

Big idea

digitalRead checks if a pin is ON or OFF.

In this project

It checks if you pressed the button after GO.

Why here

It goes in loop() so we can catch your press.

  if (state == GO && digitalRead(BUTTON_PIN) == LOW) {

analogRead

Big idea

analogRead reads a number from a pin, from 0 up to 1023.

In this project

It reads a wiggly number from an empty pin to mix up the randomness.

Why here

In setup() so each game has a different GO time.

  randomSeed(analogRead(0));

delay

Big idea

delay means wait. Nothing else happens while it waits.

In this project

It pauses for a moment after showing your score, before the next round.

Why here

Right after we print your reaction time.

    delay(2000);

millis

Big idea

millis counts how many milliseconds the board has been on, like a stopwatch.

In this project

It marks when GO appears and when you press, so we can find the difference.

Why here

It lets us time things without stopping the whole program.

  waitUntil = millis() + random(2000, 5000);

random

Big idea

random picks a surprise number for you.

In this project

It picks how long to wait before GO, so you cannot guess it.

Why here

When we set up each new round.

  waitUntil = millis() + random(2000, 5000);

begin

Big idea

Serial.begin opens a way for the board to send words to your computer screen.

In this project

It lets the program print GO and your reaction time.

Why here

It goes in setup() once, before we print anything.

  Serial.begin(9600);

print

Big idea

print sends words to the screen but stays on the same line.

In this project

It prints "Reaction: " right before your number.

Why here

In loop() so the label and number sit together.

    Serial.print("Reaction: ");

println

Big idea

println sends a line of words to the screen.

In this project

It prints the GO message and starts a new line.

Why here

In setup() so you see the start message.

  Serial.println("Reaction Timer — wait for GO!");