A smart stop light Arduino project uses a microcontroller to sequence red, yellow, and green LEDs, enhanced here with an HC-SR04 ultrasonic sensor to detect approaching vehicles and a tactile pushbutton for pedestrian crossing requests. By utilizing non-blocking millis() timing instead of delay(), this build ensures the pedestrian button remains instantly responsive regardless of the traffic light's current state. Below, you will find the exact hardware specifications, resistor calculations, a fully compilable state-machine codebase, and a dedicated debugging guide to get your intersection running flawlessly.
Project Overview & Difficulty Rating
Estimated Build Time: 45 minutes
Core Concepts: State machines, non-blocking timing (
millis()), ultrasonic distance measurement, LED current limiting.Target Board Variant: Arduino Uno R4 Minima (ARM Cortex-M4 based, 5V logic).
While a basic blinking LED sequence is a standard day-one exercise, adding real-world sensor inputs introduces timing conflicts. If you use delay() to hold a red light for 5 seconds, a pedestrian pressing the crosswalk button during that window will be ignored. This project solves that by implementing a finite state machine (FSM) that constantly polls inputs while managing light transitions in the background.
Hardware Spec Sheet & Pin Mapping
Selecting the right components prevents voltage drops and logic errors. The Arduino Uno R4 Minima is used here for its modern architecture, but the 5V logic levels remain compatible with classic AVR boards.
| Component | Exact Variant / Spec | Est. Price (2026) | Notes |
|---|---|---|---|
| Microcontroller | Arduino Uno R4 Minima | $20.00 | RA4M1 chip, native 5V logic, USB-C |
| Distance Sensor | HC-SR04 Ultrasonic | $2.50 | Requires 5V VCC for stable 3.3V logic triggers |
| Red LED | 10mm Diffused (2.0Vf, 20mA) | $0.20 | Requires 150Ω current-limiting resistor |
| Yellow LED | 10mm Diffused (2.2Vf, 20mA) | $0.20 | Requires 150Ω current-limiting resistor |
| Green LED | 10mm Diffused (3.2Vf, 20mA) | $0.25 | Requires 100Ω current-limiting resistor |
| Pushbutton | 6x6mm Tactile Switch (4-pin) | $0.10 | Used with internal pull-up resistor |
Pin Mapping Table
| Arduino Uno R4 Pin | Component | Function |
|---|---|---|
| Digital 8 | Red LED (via 150Ω) | Stop signal output |
| Digital 9 | Yellow LED (via 150Ω) | Caution signal output |
| Digital 10 | Green LED (via 100Ω) | Go signal output |
| Digital 6 | HC-SR04 Trig | Ultrasonic pulse trigger |
| Digital 7 | HC-SR04 Echo | Ultrasonic return read |
| Digital 2 | Pushbutton | Pedestrian cross request (Active LOW) |
| 5V | HC-SR04 VCC, Button | Power rail |
| GND | All Components | Common ground |
Step-by-Step Assembly & Wiring
- Calculate and Install Resistors: Never wire LEDs directly to a 5V microcontroller pin. Using Ohm's Law (R = (V_source - V_forward) / I), we calculate the resistors. For the Red LED: (5V - 2.0V) / 0.02A = 150Ω. For the Green LED: (5V - 3.2V) / 0.02A = 90Ω (use the next standard size up: 100Ω). Insert the LEDs and resistors into the breadboard in series.
- Wire the HC-SR04: Connect the VCC pin of the ultrasonic sensor strictly to the 5V pin on the Arduino. Warning: Powering the HC-SR04 from the 3.3V pin will result in erratic distance readings and failure to trigger.
- Wire the Pedestrian Button: Connect one leg of the tactile switch to Digital Pin 2, and the other leg to GND. We will use the microcontroller's internal pull-up resistor in the code, eliminating the need for an external 10kΩ resistor.
- Verify Common Ground: Ensure the GND rail on your breadboard is tied to the Arduino's GND pin. A floating ground on the sensor will cause the Echo pin to hang high, freezing your code.
Complete Compilable Code (Targets Uno R4 Minima)
The following C++ code is written for the Arduino IDE (2.x). It uses a finite state machine to manage the light sequence and millis() to prevent blocking delays. It also includes error handling for the ultrasonic sensor timeout.
// Smart Stop Light Arduino Project
// Target Board: Arduino Uno R4 Minima (Compatible with Uno R3/Nano)
// Author: ElectricalFlux
// --- PIN DEFINITIONS ---
constexpr int PIN_RED_LED = 8;
constexpr int PIN_YELLOW_LED = 9;
constexpr int PIN_GREEN_LED = 10;
constexpr int PIN_TRIG = 6;
constexpr int PIN_ECHO = 7;
constexpr int PIN_BUTTON = 2;
// --- TIMING CONSTANTS (milliseconds) ---
constexpr unsigned long YELLOW_DURATION = 2000; // 2 seconds
constexpr unsigned long RED_DURATION = 5000; // 5 seconds
constexpr unsigned long PEDESTRIAN_EXTRA = 3000; // +3 seconds for pedestrians
constexpr unsigned long SENSOR_POLL_INTERVAL = 50; // Poll sensor every 50ms
// --- SENSOR THRESHOLDS ---
constexpr int CAR_DETECTION_CM = 40; // Trigger light change if car is within 40cm
constexpr int SENSOR_TIMEOUT_US = 30000; // 30ms timeout for pulseIn (~5 meters)
// --- STATE MACHINE ENUM ---
enum TrafficState {
STATE_GREEN,
STATE_YELLOW,
STATE_RED
};
TrafficState currentState = STATE_GREEN;
unsigned long stateStartTime = 0;
unsigned long lastSensorPoll = 0;
bool pedestrianRequested = false;
void setup() {
Serial.begin(115200);
// Initialize LED pins
pinMode(PIN_RED_LED, OUTPUT);
pinMode(PIN_YELLOW_LED, OUTPUT);
pinMode(PIN_GREEN_LED, OUTPUT);
// Initialize Sensor pins
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
// Initialize Button with internal pull-up (Active LOW)
pinMode(PIN_BUTTON, INPUT_PULLUP);
// Set initial state
setLights(HIGH, LOW, LOW); // Start on Green (assuming standard traffic flow)
currentState = STATE_GREEN;
stateStartTime = millis();
Serial.println("Stop Light Arduino System Initialized.");
}
void loop() {
unsigned long currentMillis = millis();
// 1. Poll Pedestrian Button (Non-blocking)
if (digitalRead(PIN_BUTTON) == LOW) {
pedestrianRequested = true;
}
// 2. Poll Ultrasonic Sensor (Non-blocking interval)
if (currentMillis - lastSensorPoll >= SENSOR_POLL_INTERVAL) {
lastSensorPoll = currentMillis;
int distance = readDistanceCM();
// If car detected and we are currently GREEN, trigger state change
if (distance > 0 && distance < CAR_DETECTION_CM && currentState == STATE_GREEN) {
transitionToYellow(currentMillis);
}
}
// 3. State Machine Logic
switch (currentState) {
case STATE_GREEN:
// Stays green indefinitely until sensor or button triggers
if (pedestrianRequested) {
transitionToYellow(currentMillis);
}
break;
case STATE_YELLOW:
if (currentMillis - stateStartTime >= YELLOW_DURATION) {
transitionToRed(currentMillis);
}
break;
case STATE_RED:
unsigned long requiredRedTime = RED_DURATION;
if (pedestrianRequested) {
requiredRedTime += PEDESTRIAN_EXTRA;
}
if (currentMillis - stateStartTime >= requiredRedTime) {
transitionToGreen(currentMillis);
}
break;
}
}
// --- HELPER FUNCTIONS ---
void setLights(bool red, bool yellow, bool green) {
digitalWrite(PIN_RED_LED, red);
digitalWrite(PIN_YELLOW_LED, yellow);
digitalWrite(PIN_GREEN_LED, green);
}
void transitionToYellow(unsigned long currentTime) {
currentState = STATE_YELLOW;
stateStartTime = currentTime;
setLights(LOW, HIGH, LOW);
pedestrianRequested = false; // Clear request, it will be honored in RED state
Serial.println("State: YELLOW");
}
void transitionToRed(unsigned long currentTime) {
currentState = STATE_RED;
stateStartTime = currentTime;
setLights(HIGH, LOW, LOW);
Serial.println("State: RED");
}
void transitionToGreen(unsigned long currentTime) {
currentState = STATE_GREEN;
stateStartTime = currentTime;
setLights(LOW, LOW, HIGH);
pedestrianRequested = false;
Serial.println("State: GREEN");
}
int readDistanceCM() {
// Clear trigger pin
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
// Send 10us pulse
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
// Read echo with timeout to prevent infinite hanging
long duration = pulseIn(PIN_ECHO, HIGH, SENSOR_TIMEOUT_US);
if (duration == 0) {
// Timeout occurred, sensor might be disconnected or out of range
return -1;
}
// Calculate distance (speed of sound = 343 m/s -> 29.1 us per cm / 2 for round trip)
int distance = duration / 58.2;
return distance;
}
Debugging: Exact Errors & The "First Three" Checks
When moving from basic tutorials to sensor-integrated projects, you will inevitably hit roadblocks. Here is how to diagnose the most common hardware and software failures in this specific build.
Compilation Error: fatal error: NewPing.h: No such file or directory
If you attempted to adapt third-party code using the NewPing library instead of the native pulseIn() function used in our code above, the IDE will throw this exact string.
The Fix: Go to Sketch > Include Library > Manage Libraries, search for "NewPing by Tim Eckel", and install it. However, for a simple stop light arduino build, the native pulseIn() function with a timeout parameter (as written in our code) is lighter on memory and avoids library dependency bloat.
Runtime Failure: The First Three Things to Check
If the code uploads successfully but the stop light stays stuck on green, or the sensor reads a constant 0cm or -1, run through these three diagnostics:
- Verify HC-SR04 VCC Voltage: The most common mistake is wiring the ultrasonic sensor's VCC pin to the Arduino's 3.3V output. The HC-SR04 requires a 5V supply to generate a strong enough acoustic pulse. Measure the VCC pin with a multimeter; it must read between 4.8V and 5.2V.
- Check Trigger/Echo Pin Swaps: The physical pins on the HC-SR04 are grouped tightly. It is incredibly easy to swap the Trig and Echo wires. Ensure Trig is on Pin 6 (Output) and Echo is on Pin 7 (Input). If swapped, the
pulseIn()function will time out immediately, returning 0. - Test for Blocking Delays: If the pedestrian button only works when the light is green, but is ignored when the light is red or yellow, you have accidentally left a
delay()function inside your loop. Review the code to ensure all timing relies on themillis()subtraction method shown above.
Extending and Simplifying the Build
delay() loop that cycles Green (5s) -> Yellow (2s) -> Red (5s). This isolates the wiring and basic digitalWrite() concepts before introducing timing logic.
How to Extend: To turn this into an IoT intersection, swap the Arduino Uno R4 Minima for an ESP32 DevKit V1. Add an MQTT client library to publish the traffic state and pedestrian request counts to a local Home Assistant dashboard. You will need to add logic level shifters (like the BSS138) because the ESP32 operates at 3.3V logic, while the HC-SR04 expects 5V triggers.
Stop Light Arduino FAQ
How do I power a stop light arduino project without a USB cable?
For permanent installations or dioramas, use the Arduino's DC power jack or the VIN pin. Supply between 7V and 12V DC using a regulated wall adapter (a 9V 1A switching supply is ideal, costing around $8). The onboard linear regulator will step this down to 5V for the microcontroller and LEDs. Avoid 9V alkaline batteries; they lack the current capacity (amp-hours) to sustain three 10mm LEDs and a sensor for more than a few hours, and they suffer from severe voltage sag under load.
Why do my 10mm LEDs dim when the stop light arduino switches states?
If your LEDs dim or flicker when the ultrasonic sensor fires, you are experiencing a voltage brownout on the 5V rail. The HC-SR04 draws a spike of current (up to 15mA) when transmitting the acoustic burst, while 10mm LEDs draw 20mA each. If you are powering the Arduino via a weak USB port (limited to 500mA) or a failing battery, the voltage drops. To fix this, add a 100µF electrolytic decoupling capacitor across the 5V and GND rails on your breadboard to smooth out transient current spikes.
Can I use an ESP32 instead of an Uno for this stop light arduino build?
Yes, but you must account for logic level differences. The ESP32 is a 3.3V device. While the HC-SR04's Echo pin outputs 5V (which can damage an ESP32 GPIO pin if fed directly), you must use a voltage divider (e.g., a 1kΩ and 2kΩ resistor network) on the Echo pin to step the 5V return signal down to a safe 3.3V. Furthermore, update the pin definitions in the code, as ESP32 pins like GPIO 34-39 are input-only and cannot be used for the LED outputs.






