Every road crossing is a small computer program. It keeps the cars moving until someone presses the button, then switches the car lights through yellow to red, gives the pedestrians green, warns them before it ends and lets the cars go again. In this lesson you build the whole sequence with five LEDs, a push button and a buzzer, and you learn to write it as a state machine, the way real traffic controllers are programmed.
What you'll learn
- How to plan a program as a list of states
- How to time each state with
millis()instead ofdelay() - How to remember a button press until the program is ready for it
- How to make blinking and ticking without stopping the program
What you need
- UNO R3 board and USB cable (UNO R3 Starter Kit)
- Red, yellow and green LEDs for the cars, and a red and a green LED for the pedestrians (UNO R3 Starter Kit)
- 5 resistors, 220 Ω (UNO R3 Starter Kit)
- 1 push button (UNO R3 Starter Kit)
- Active buzzer (UNO R3 Starter Kit)
- Breadboard and jumper wires (UNO R3 Starter Kit)
Make it real: stand the car lights in a column on the breadboard, and draw a small road crossing on paper to put in front of it.
Wiring

| From | To |
|---|---|
| UNO pin 4 | 220 Ω resistor, then the car red LED's long leg |
| UNO pin 3 | 220 Ω resistor, then the car yellow LED's long leg |
| UNO pin 2 | 220 Ω resistor, then the car green LED's long leg |
| UNO pin 6 | 220 Ω resistor, then the walk red LED's long leg |
| UNO pin 5 | 220 Ω resistor, then the walk green LED's long leg |
| All LED short legs | GND |
| Push button, one leg | UNO pin 7 |
| Push button, the diagonally opposite leg | GND |
| Active buzzer + | UNO pin 8 |
| Active buzzer − | GND |
The five states
| State | Cars | Pedestrians | Ends when |
|---|---|---|---|
| Cars go | Green | Red | Button pressed, after at least 5 s of green |
| Cars slow down | Yellow | Red | After 2 s |
| Walk | Red | Green, slow ticking | After 6 s |
| Walk ends | Red | Green flashing, fast ticking | After 3 s |
| Cars get ready | Red and yellow | Red | After 1.5 s |
The sketch
const int CAR_RED = 4;
const int CAR_YELLOW = 3;
const int CAR_GREEN = 2;
const int WALK_RED = 6;
const int WALK_GREEN = 5;
const int BUTTON_PIN = 7;
const int BUZZER_PIN = 8;
const byte CARS_GO = 0, CARS_SLOW = 1, WALK = 2, WALK_ENDS = 3, CARS_READY = 4;
byte state = CARS_GO;
unsigned long stateStart = 0;
bool requested = false; // someone pressed the button
void enter(byte newState) {
state = newState;
stateStart = millis();
}
void lights(bool carRed, bool carYellow, bool carGreen, bool walkRed, bool walkGreen) {
digitalWrite(CAR_RED, carRed);
digitalWrite(CAR_YELLOW, carYellow);
digitalWrite(CAR_GREEN, carGreen);
digitalWrite(WALK_RED, walkRed);
digitalWrite(WALK_GREEN, walkGreen);
}
void setup() {
for (int pin = 2; pin <= 6; pin++) pinMode(pin, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
unsigned long now = millis();
unsigned long inState = now - stateStart;
if (digitalRead(BUTTON_PIN) == LOW && state == CARS_GO) requested = true;
switch (state) {
case CARS_GO:
lights(false, false, true, true, false);
digitalWrite(BUZZER_PIN, LOW);
if (requested && inState >= 5000) { // cars always get 5 s of green
requested = false;
enter(CARS_SLOW);
}
break;
case CARS_SLOW:
lights(false, true, false, true, false);
if (inState >= 2000) enter(WALK);
break;
case WALK:
lights(true, false, false, false, true);
digitalWrite(BUZZER_PIN, now % 600 < 40); // slow tick
if (inState >= 6000) enter(WALK_ENDS);
break;
case WALK_ENDS:
lights(true, false, false, false, (now / 250) % 2); // flashing green
digitalWrite(BUZZER_PIN, now % 200 < 30); // fast tick
if (inState >= 3000) enter(CARS_READY);
break;
case CARS_READY:
lights(true, true, false, true, false);
digitalWrite(BUZZER_PIN, LOW);
if (inState >= 1500) enter(CARS_GO);
break;
}
}
How it works
The variable state says which of the five steps the crossing is in, and switch runs only the code for that step. Each step sets its lights and checks one thing: has enough time passed, or has someone pressed the button? enter() moves to the next step and notes the time, so inState always says how long the current step has lasted.
Because nothing in loop() waits, the button is checked thousands of times per second. A press is stored in requested and handled only when the cars have had at least 5 seconds of green, just like a real crossing. The ticking comes from the clock: now % 600 < 40 is true for 40 ms out of every 600, which makes a short click. Many real crossings tick like this so that blind pedestrians know when to cross. The red-and-yellow step before green is how traffic lights work in many European countries; leave it out if yours go straight from red to green.
Try this next
- Light a small "Wait" LED as soon as the button has been pressed.
- Count the pedestrians who pressed the button and print the number in the Serial Monitor.
- At night (ask a photoresistor), let the car lights flash yellow and skip the pedestrian cycle.
Troubleshooting
- One LED never lights: check that its long leg goes to the resistor and its short leg to GND.
- The button does nothing: the two legs you use may always be connected. Turn the button a quarter turn.
- The buzzer clicks all the time: it must be the active buzzer, with its + leg on pin 8.