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
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
greenA 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, 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.
The same two modes in MicroPython. select.poll asks whether a character is waiting in Thonny's shell without waiting for one, which is what Serial.available() does in the Arduino sketch.
"""
Traffic Light - night mode, MicroPython 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 -> GPIO 25 on an ESP32, GPIO 4 on an ESP32-S3, GP13 on a Pico
YELLOW -> GPIO 26 on an ESP32, GPIO 5 on an ESP32-S3, GP14 on a Pico
GREEN -> GPIO 27 on an ESP32, GPIO 6 on an ESP32-S3, GP15 on a Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Type n or d in the shell while it runs, then press Enter.
Nothing to install: machine, time, sys and select are built in.
"""
from machine import Pin
import select
import sys
import time
# GPIO numbers. ESP32: 25, 26, 27. ESP32-S3: 4, 5, 6. Pico: 13, 14, 15.
RED_PIN = 4
YELLOW_PIN = 5
GREEN_PIN = 6
red = Pin(RED_PIN, Pin.OUT, value=0)
yellow = Pin(YELLOW_PIN, Pin.OUT, value=0)
green = Pin(GREEN_PIN, Pin.OUT, value=0)
PHASES = [
# RED YELLOW GREEN ms name
(1, 0, 0, 5000, "red"),
(0, 0, 1, 5000, "green"),
(0, 1, 0, 2000, "yellow"),
]
FLASH_MS = 500 # night: 500 ms on, 500 ms off
def lights(r, y, g):
red.value(r)
yellow.value(y)
green.value(g)
def show(p):
r, y, g, _, name = PHASES[p]
lights(r, y, g)
print(name)
keys = select.poll()
keys.register(sys.stdin, select.POLLIN)
phase = 0
show(phase)
phase_start = time.ticks_ms()
night = False # the second piece of state
flash_start = 0
while True:
now = time.ticks_ms()
# A command, if one has arrived. poll(0) never waits for one.
if keys.poll(0):
c = sys.stdin.read(1)
if c == "n" and not night:
night = True
flash_start = now
print("night: flashing yellow")
elif c == "d" and night:
night = False
phase = 0 # day always restarts on red
phase_start = now
show(phase)
if night:
gone = time.ticks_diff(now, flash_start)
lights(0, 1 if (gone // FLASH_MS) % 2 == 0 else 0, 0)
elif time.ticks_diff(now, phase_start) >= PHASES[phase][3]:
phase = (phase + 1) % len(PHASES)
phase_start = now
show(phase)poll(0) returns at once: 0 means do not wait. A plain input() or sys.stdin.read() with nothing waiting would stop the loop until you typed, which is the delay() problem again. Type n or d in the shell and press Enter.
When it does not work
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.
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.
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.
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.
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
Questions about this product
See what other owners have asked, and read their solutions.
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.