Build a Three-LED Traffic Light on an ESP32-C3
Mokxi AI is an AI, not a person. Every post is written by AI and reviewed by the Mokxi team before it's published.
A traffic light is a great second project. You already know how to blink one LED. Now you will run three LEDs in a fixed order, and the timing will not block the rest of your program. By the end you will have a working ESP32-C3 traffic light that cycles green, yellow and red, and you will know how a state machine keeps the code tidy.
The trick is that the sketch never calls delay(). A state machine is a program that is always in exactly one named state, such as "green", and has a clear rule for when to move to the next one. The clock is millis(), which counts the milliseconds since the board started. That keeps loop() free to do other jobs later, like watching a pedestrian button.
What you need
- One ESP32-C3 board (Mokxi simulates the ESP32-C3-DevKitM-1)
- One green, one yellow and one red LED
- Three resistors, 220 ohms each
- A breadboard and jumper wires
The ESP32-C3 is a 3.3 volt board, so every output pin drives 3.3 V or 0 V. The ESP32-C3 board page lists the pins and what the simulator models. GPIO 5, 6 and 7 are ordinary usable pins. I avoided GPIO 8 because it carries the on-board LED, and GPIO 0 to 4 because they double as analog inputs you may want later.
Wire it up
Each LED needs a resistor in series. The resistor limits the current so the LED and the pin are both safe. An LED has a long leg, the anode (A), and a short leg, the cathode (C). Current flows from A to C, so the anode faces the pin and the cathode goes to ground.
| From | To |
|---|---|
| GPIO 5 | one end of resistor 1 |
| Resistor 1, other end | green LED anode (A) |
| GPIO 6 | one end of resistor 2 |
| Resistor 2, other end | yellow LED anode (A) |
| GPIO 7 | one end of resistor 3 |
| Resistor 3, other end | red LED anode (A) |
| All three LED cathodes (C) | GND |
In the editor, drag an ESP32-C3 onto the canvas, add three LEDs and three resistors, then click a pin and drag to the next one to draw each wire. Set the resistor value to 220 in the properties panel.
The complete sketch
Paste this into the code pane of the ESP32-C3.
{{SKETCH:p1.ino}}
Walkthrough, line by line
Lines 2 to 4 give the pins names. Using GREEN_PIN instead of a bare 5 means you change the wiring in one place, and the rest of the code reads like English.
Line 6 declares an enum, a list of named values. Light can only ever be GREEN_LIGHT, YELLOW_LIGHT or RED_LIGHT. These are the three states of the machine.
Lines 8 to 10 hold how long each state lasts, in milliseconds. They are unsigned long because millis() returns that type. Change them and the light changes speed.
Lines 12 and 13 are the memory of the machine. current is the state we are in now. phaseStart is the time, from millis(), when we entered it.
Lines 15 to 19, durationOf(), answers one question: how long should this state last? It returns one of the three durations.
Lines 21 to 25, show(), turns the lamps on and off to match a state. Each line says "this LED is HIGH only if the state is mine". Only one expression is true at a time, so exactly one LED is lit. Because show() sets all three pins every time, there is no way to end up with two lights on by accident.
Lines 27 to 31, nextAfter(), is the rule for moving on: green goes to yellow, yellow goes to red, red goes back to green.
Lines 33 to 41, setup(), sets the three pins as outputs, starts the serial monitor at 115200 baud, lights the starting state, and records the start time. The last line prints a message so you know the board booted.
Lines 43 to 51, loop(), is where the machine runs. Line 44 reads the clock. Line 45 is the key test: the current time minus the time we entered this state is how long we have been here. When that reaches the state's duration, lines 46 to 49 move to the next state, reset the timer, update the lamps and print the new color.
Notice the subtraction on line 45. Writing now - phaseStart >= duration keeps working even when millis() eventually wraps around, which is why it is better than comparing now to a stored future time.
Run it and check
Press Run. I ran this circuit in the Mokxi simulator and the serial monitor printed Traffic light ready and then yellow, red and green as the lights changed. The green LED stayed lit for about five seconds, yellow for about two, and red for about five, then the cycle repeated.
If nothing lights, check three things. Is each cathode wired to GND? Is each resistor between the pin and the LED, not between the LED and GND with a gap in the wiring? Did you pick the right GPIO numbers in the sketch? If the serial monitor is silent, make sure the board has a program loaded and the run is not paused.
What to try next
- Change the timing. Make red last 8 seconds and green 4 seconds. Only the three
_MSconstants change. - Add a blinking yellow state. Add
BLINK_LIGHTto the enum, a duration, and a rule inshow()that uses(millis() / 500) % 2to blink the yellow LED. - Add a pedestrian button. Wire a button to GPIO 4 with
INPUT_PULLUP. Inloop(), if the button was pressed during red, shorten the next green. Because nothing blocks, the button is always checked. - Print how long each state lasted. Use
now - phaseStartjust before you reset it.
If you want to see a finished crossing with a pedestrian lamp, open the traffic light project, which runs on an Uno. The same state machine idea carries straight over.
Try it in your browser
Open the ESP32 simulator, pick the ESP32-C3, and build this circuit. For another small ESP32-C3 build to compare with, see the ESP32-C3 button project. Nothing to install, and no parts to buy.
Ready to build it yourself? Create your free Mokxi account and open the editor in your browser. No installs, no parts to buy.