traffic light/The sequence/05. The first sequence
The sequence · 05 of 8

The first sequence

Red for five seconds, green for five, yellow for two, and round again. Written with delay() it is three steps and a function that sets all three pins, and it works. It also spends almost every moment of its life waiting.

Which sequence

Traffic lights do not work the same everywhere. This book models the plain three-step cycle many countries use: red, then green, then yellow as a warning that red is coming, then red again. The UK, among others, adds a step with red and yellow together before green. The next article shows how to add it.

The times are short so you can watch a whole cycle: five seconds of red, five of green and two of yellow. Real signals are much longer and set for each junction.

One cycle, line by line

The first sequence
Time
0.0 s
Showing
nothing yet
In this delay()
none
Each digitalWrite takes a few microseconds. Each delay() takes seconds. Run it and watch which line the sketch spends its life on.

The light on the left is whatever the last show() set. The highlighted line is where the sketch is. Setting the pins takes a few microseconds. Each delay() takes seconds. So for nearly the whole cycle, the sketch is inside a delay().

show() sets all three

show(HIGH, LOW, LOW) sets the red pin HIGH and the other two LOW in one call. Every step goes through it, and every step names all three lights, including the two that are off.

That is the whole defence against two lights at once. One pin, one light showed that the board lights whatever pins are HIGH. A sketch that only ever wrote digitalWrite(GREEN_PIN, HIGH) to go green would leave the red on from the step before, and the board would show both.

What you should see

Red for five seconds, green for five, yellow for two, and round again. The serial monitor at 115200 prints each name as it changes:

red
green
yellow
red

What delay() costs

The sketch works, and for a light that does nothing else it is fine. But try to add anything. A button that should switch the light to night mode can only be read between delays, so a press during the five-second red is not seen until the red ends, if it is still held then.

That is the same problem as blinking without stopping, made worse by longer waits. The XL LED fixed it by checking the clock. A traffic light needs one more thing as well: it has to remember which light it is on.

The code

No library. show() sets all three pins in one call, so no step can leave a light on by accident. loop() is three steps: set the lights, then wait.

traffic_light_first_sequence.ino
/*
  Traffic Light - first sequence, with delay()          TK03 / /p/tk03

  Wiring. Count from the square pad on the TinkerBlock board, LEDs up,
  header at the bottom:

    GND    -> GND
    NC     -> nothing   (both NC pins are unconnected on the board)
    RED    -> D9 on an Uno, GPIO 25 on an ESP32, GPIO 4 on an ESP32-S3,
              GP13 on a Raspberry Pi Pico
    YELLOW -> D10 on an Uno, GPIO 26 on an ESP32, GPIO 5 on an ESP32-S3,
              GP14 on a Pico
    GREEN  -> D11 on an Uno, GPIO 27 on an ESP32, GPIO 6 on an ESP32-S3,
              GP15 on a Pico

  Arduino IDE
    Tools > Board                 your board, e.g. ESP32S3 Dev Module
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    No library needed.
*/

// GPIO numbers. Uno: 9, 10, 11. ESP32: 25, 26, 27. ESP32-S3: 4, 5, 6.
// Pico: 13, 14, 15.
const int RED_PIN = 4;
const int YELLOW_PIN = 5;
const int GREEN_PIN = 6;

// Set all three at once, so no step can leave a light on by mistake.
void show(bool red, bool yellow, bool green) {
  digitalWrite(RED_PIN, red ? HIGH : LOW);
  digitalWrite(YELLOW_PIN, yellow ? HIGH : LOW);
  digitalWrite(GREEN_PIN, green ? HIGH : LOW);
}

void setup() {
  Serial.begin(115200);
  pinMode(RED_PIN, OUTPUT);
  pinMode(YELLOW_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
}

void loop() {
  show(HIGH, LOW, LOW);         // red: stop
  Serial.println("red");
  delay(5000);

  show(LOW, LOW, HIGH);         // green: go
  Serial.println("green");
  delay(5000);

  show(LOW, HIGH, LOW);         // yellow: about to turn red
  Serial.println("yellow");
  delay(2000);
}

Every delay() stops the whole sketch. That is fine for a light on its own, and it is exactly what goes wrong the moment the sketch should also notice a button or the serial port. The next article rewrites it so it never waits.

When it does not work

Two lights are on at the same time.

A step turned one light on without turning the others off. The show() function in this sketch sets all three pins every time, so as long as every step goes through it, only the lights you name can be lit.

Our traffic lights show red and yellow together before green.

Many countries do, the UK among them. Add a step after red: show(HIGH, HIGH, LOW) and a delay of about two seconds. The next article turns the sequence into a table, where that is one extra row.

Can I make the times realistic?

Change the numbers in the delay() calls; they are milliseconds. Real signals are timed by traffic engineers for each junction, often much longer than these. The short times here are so a whole cycle fits in the time it takes to watch it.

The sequence starts on red. Can it start on green?

Move the green step to the top of loop(). The order of the steps is the order of the lights, and loop() starts from its first line every time round. Starting on red is the usual choice because it is the safe state.

Where this goes next

The same light as a table and a clock, and a loop() that never waits.

A sketch that remembers

Edit this page — content/books/traffic-light/the-first-sequence.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Traffic Light

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum