A second mode, and when it will not light · 07 of 8

Flashing yellow at night

Many signals switch to a flashing yellow late at night. Added to the state machine, it is one more variable and a few lines that read the serial monitor. Added to the delay() sketch, the same command waits for the current delay to end, up to five seconds late.

A second mode

Many signals are switched to a flashing yellow late at night, when traffic is light. Whether and when that happens, and what it asks of drivers, is decided locally, so treat this as an example of a second mode rather than a rule of the road.

For the sketch it means a second piece of state. phase and phaseStart still say where the day cycle is. A new variable, night, says which mode is running, and flashStart times the flashing the same way phaseStart times a phase.

Asked for in the middle of a red

Night mode, asked for mid-red
Time
0.0 s
delay() answered
-
millis() answered
-
Both sketches run the same cycle and look identical. The difference only shows when something asks them to do something else.

Both sketches run the same cycle. One second into the red, somebody types n. The state machine reads the serial port on every pass of loop(), so it sees the n almost at once and starts flashing.

A sketch built from delay() can only read the port between delays. It is inside delay(5000) for the red, so it answers four seconds late, when the red ends. Typed just as a red began, the same command would wait the whole five seconds. No amount of care in the rest of the sketch fixes that; the wait is the design.

Reading a command without waiting for one

Serial.available() says how many characters have arrived and returns at once. If it is zero, the sketch carries on. Only when something is there does Serial.read() take it. That is the same shape as the phase check: ask, and if the answer is no, move on.

In MicroPython, select.poll does the same job for Thonny's shell. keys.poll(0) returns straight away, empty if nothing is waiting.

What you should see

The day cycle, as before. Type n and send it, and within a moment the red, green or yellow goes out and the yellow starts flashing, half a second on and half a second off. Type d and the cycle restarts on a full red. The serial monitor prints each change:

red
night: flashing yellow
red
green

A button could switch the mode just as well as the serial monitor. Read it in the same place, once per pass, and it is seen however briefly it is pressed.

The code

The state machine from the last article, plus a mode. Type n in the serial monitor for night mode, flashing yellow; type d for day mode, which restarts the cycle on red. No library.

traffic_light_night_mode.ino
/*
  Traffic Light - night mode, from the serial monitor   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)
    Tools > Serial Monitor        115200 baud. Type n or d and Send.
    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;

struct Phase {
  bool red, yellow, green;
  unsigned long ms;
  const char *name;
};

const Phase PHASES[] = {
  // RED   YELLOW GREEN  ms
  { true,  false, false, 5000, "red" },
  { false, false, true,  5000, "green" },
  { false, true,  false, 2000, "yellow" },
};
const int PHASE_COUNT = sizeof(PHASES) / sizeof(PHASES[0]);
const unsigned long FLASH_MS = 500;  // night: 500 ms on, 500 ms off

int phase = 0;
unsigned long phaseStart = 0;
bool night = false;                  // the second piece of state
unsigned long flashStart = 0;

void lights(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 show(int p) {
  lights(PHASES[p].red, PHASES[p].yellow, PHASES[p].green);
  Serial.println(PHASES[p].name);
}

void setup() {
  Serial.begin(115200);
  pinMode(RED_PIN, OUTPUT);
  pinMode(YELLOW_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  show(phase);
  phaseStart = millis();
}

void loop() {
  unsigned long now = millis();

  // A command, if one has arrived. Never waits for one.
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'n' && !night) {
      night = true;
      flashStart = now;
      Serial.println("night: flashing yellow");
    } else if (c == 'd' && night) {
      night = false;
      phase = 0;                     // day always restarts on red
      phaseStart = now;
      show(phase);
    }
  }

  if (night) {
    // Yellow on for the first FLASH_MS of every two, off for the second.
    bool on = ((now - flashStart) / FLASH_MS) % 2 == 0;
    lights(false, on, false);
  } else if (now - phaseStart >= PHASES[phase].ms) {
    phase = (phase + 1) % PHASE_COUNT;
    phaseStart = now;
    show(phase);
  }
}

The serial port is read on every pass of loop(), so a command takes effect at once, whatever phase the light is in. Night mode's flashing is timed from flashStart with the same subtraction as the phases, so it never waits either.

When it does not work

Typing n does nothing.

Check the serial monitor is at 115200 and that you pressed Send or Enter; nothing reaches the board until you do. Then check the letter is lower case: the sketch compares with 'n' and 'd' exactly. The line ending setting does not matter, since other characters are ignored.

In Thonny, typing n does nothing.

Type it in the shell while the program is running and press Enter. The program reads one character at a time from the shell, and Thonny only sends a line once Enter is pressed. If the program has stopped with an error, the shell is back at the prompt instead.

Coming back from night mode starts on red, not where it left off.

That is deliberate. d sets phase to 0 and phaseStart to now, so day mode always starts on a full red. Carrying on mid-cycle would mean a green or yellow with part of its time already gone, which is harder to read and to test.

Could a button switch the mode instead?

Yes. Read the button in the same place as the serial port, once per pass of loop(), and treat a press like the letter n. Because loop() never waits, a press is seen within a fraction of a millisecond, however short it is.

Where this goes next

The short list of reasons, in the order they are usually the answer.

When a light stays dark

Edit this page — content/books/traffic-light/flashing-yellow-at-night.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