Skip to content

Electronics

Arduino traffic light project with a crossing button

An Arduino pedestrian crossing is a state machine: the lights are always in one of three states, GREEN, YELLOW or RED, and a few rules move them from one to the next. A button press sets a request flag, and millis() times each state, so the button is read on every pass and an early press is remembered, not missed.

The circuit

  • Green LED on pin 9, yellow on pin 10, red on pin 11, each through 220 Ω to GND
  • A push button from pin 2 to GND, read with INPUT_PULLUP, so a press reads LOW

Each LED takes about (5 − 2) ÷ (220 + 25) ≈ 12 mA, counting the pin's own 25 Ω: safely under a pin's 20 mA.

The rules

  1. GREEN → YELLOW when a press is waiting and green has lasted 5 s
  2. YELLOW → RED after 2 s
  3. RED → GREEN after 5 s, clearing the request

The 5 s minimum keeps the traffic moving when people press over and over.

Live experiment

Help Pip cross

A pedestrian crossing running the program on this page: green for the cars until someone presses and green has had its 5 s, then yellow for 2 s and red for 5 s while Pip crosses. Press early and watch the request flag hold the press until it's served.

Try it yourself: free lesson

“Project: Traffic Light” is the project that ends The Core, Electronics World 8. Free with an account. Lessons unlock in order, so you’ll do the 55 before it first.

The sketch

#define GREEN 0
#define YELLOW 1
#define RED 2

int state = GREEN;
unsigned long since = 0;   // when this state began
bool request = false;      // a press is waiting

void show(int s) {
  digitalWrite(9, s == GREEN);
  digitalWrite(10, s == YELLOW);
  digitalWrite(11, s == RED);
  state = s;
  since = millis();
}

void setup() {
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  show(GREEN);
}

void loop() {
  if (digitalRead(2) == LOW) request = true;
  unsigned long t = millis() - since;

  if (state == GREEN && request && t >= 5000) {
    show(YELLOW);
  } else if (state == YELLOW && t >= 2000) {
    show(RED);
  } else if (state == RED && t >= 5000) {
    request = false;
    show(GREEN);
  }
}

show() lights one LED, stores the new state and notes the time. else if makes sure only one rule fires per pass.

Check the timeline

Press at 2 s, early in green. The flag waits, and at 5 s green has had its time: yellow until 7 s, red until 12 s, then green again. Press at 13 s and green holds until 17 s, its own 5 s, before the next yellow. Nobody presses after that, so green stays green.

Free lesson

Now build it yourself

Each lesson comes in three short parts: see the idea, try it on a live board, then build it yourself, with quick checks as you go.

Try it yourself: free lesson

“Project: Traffic Light” is the project that ends The Core, Electronics World 8. Free with an account. Lessons unlock in order, so you’ll do the 55 before it first.

Or start from the very first lesson, no account needed

Practice questions

Three quick questions from the lessons. Pick an answer to check it.

  1. Question 1 of 3In GREEN, what must be true before the lights move on?
    Show the answer

    Answer: A press is waiting and green has lasted 5 s. Both. Someone must want to cross (request) and green must have had its 5 s. In code, request && t >= 5000.

    • Why not “5 s have passed, nothing else”? With time alone, the lights would change every few seconds even with nobody waiting. A press must be waiting too.
    • Why not “A press, nothing else”? With a press alone, someone pressing just as green returns would stop the cars at once. Green must get its 5 s first.
  2. Question 2 of 3How does the program remember a press that comes early in green?
    Show the answer

    Answer: It sets a bool, the request flag. A flag remembers an event until it's served. request becomes true on a press and is cleared when red ends.

    • Why not “It ignores the press”? Ignoring an early press would make people press again and again. The rule is to remember it and serve it when green's 5 s are up.
    • Why not “It waits with delay(5000) first”? delay(5000) freezes loop(), so nothing else is read or timed while it waits. A flag remembers the press without stopping anything.
  3. Question 3 of 3Why use millis(), not delay(), for the minimum green?
    Show the answer

    Answer: So the button is still read while green waits. During delay(5000) nothing else runs, so a press would be missed. With millis(), loop() keeps reading the button while green times out.

    • Why not “millis() saves power”? millis() doesn't save power: loop() keeps running flat out. It's used so the button is still read.
    • Why not “delay() isn't accurate”? delay() is accurate enough. The trouble is that nothing else runs while it waits.
Keep practising: free lesson

“Project: Traffic Light” is the project that ends The Core, Electronics World 8. Free with an account. Lessons unlock in order, so you’ll do the 55 before it first.

Questions people ask

Why not write the traffic light with delay()?

delay(5000) freezes the whole program, so the button isn't read while the light waits and a press can be missed. With millis(), loop() keeps going round, reading the button on every pass while it times each state.

Does the crossing button need debouncing?

Not here. A press only sets a flag to true, and setting it true four times in a few milliseconds is the same as setting it once. Debouncing matters when you count presses or toggle something.

How do I add a flashing 'don't cross' stage?

Add a fourth state. Give it a number, a rule for entering it and a rule for leaving it, and inside it switch the LED on and off with millis(). Each new behaviour is a state and its arrows, not a new tangle of ifs.

Every topic