Building a Smart Traffic Light System With ESP32 and IR Sensors

by ElectroScope Archive in Circuits > Electronics

9 Views, 0 Favorites, 0 Comments

Building a Smart Traffic Light System With ESP32 and IR Sensors

IoT-Based-Smart-Traffic-System.jpg

I've been tinkering with IoT projects for a while now, and this smart traffic management system turned out way cooler than I expected. It's basically a miniature traffic control system that actually thinks for itself using IR sensors and an ESP32. Instead of just cycling through lights on a timer like those old-school setups, this one counts cars waiting at red lights and gives priority to congested lanes. Plus you can monitor everything through a web dashboard on your phone or laptop.

The whole thing runs on an ESP32 microcontroller, which handles the sensors, controls the LEDs, and hosts a little web server. I threw this together on breadboards first to test it out, and honestly, watching it automatically switch signals based on "traffic" (I used toy cars) was pretty satisfying.

Supplies

Here's everything I used for this build:

Hardware:

  1. ESP32 development board (1)
  2. IR sensors - the kind with both transmitter and receiver (4)
  3. Red LEDs (4)
  4. Yellow LEDs (4)
  5. Green LEDs (4)
  6. 220-ohm resistors (12)
  7. Breadboards (however many you need to fit everything)
  8. Jumper wires (bunch of male-to-male ones)
  9. USB cable for programming the ESP32
  10. 5V power supply

Software:

  1. Arduino IDE installed on your computer
  2. A web browser for checking the dashboard

The IR sensors are the key here. Each one watches a lane and detects when vehicles pass by. I got mine for a couple bucks each online and they work surprisingly well once you get them calibrated right.

How the System Actually Works

Before jumping into wiring, let me explain what this thing does. The system runs in three different modes that it switches between automatically.

In normal mode, it acts like a regular traffic light. Each lane gets green for 5 seconds, then yellow for 5 seconds, then red while the next lane goes green. Pretty standard stuff.

But when a lane is red, the IR sensor starts counting vehicles. Every time a car blocks the sensor, the count goes up. The ESP32 is constantly checking these counts.

Here's where it gets interesting. If any lane hits 2 or more vehicles waiting, the system kicks into preemption mode. It immediately turns all lanes red for 2 seconds (safety delay so nobody runs into each other), then gives the congested lane a green light. After that lane clears and the count resets to zero, everything goes back to normal cycling.

The web dashboard updates every second showing which lanes are green/yellow/red and how many vehicles each lane has counted. It's genuinely useful for watching the system work.

Wiring Everything Up

Circuit-Diagram-of-the-Smart-Traffic-System_0.jpg

I'm going to walk through the connections step by step. The pin numbers matter here, so double-check as you go.

IR Sensor Connections:

Each IR sensor has three pins usually labeled VCC, GND, and OUT. The OUT pin goes LOW when something blocks the sensor. Connect them like this:

  1. IR Sensor 1 → ESP32 pin 13
  2. IR Sensor 2 → ESP32 pin 12
  3. IR Sensor 3 → ESP32 pin 14
  4. IR Sensor 4 → ESP32 pin 15

All four sensors need power (VCC to 5V) and ground (GND to GND on the ESP32). I used the breadboard power rails to distribute power to all of them.

LED Connections for Lane 1:

Each LED needs a 220-ohm resistor in series to limit current. I connected the resistor to the LED's anode (long leg), then that goes to the ESP32 pin. The cathode (short leg) goes to ground.

  1. Red LED → 220Ω resistor → ESP32 pin 32 → Ground
  2. Yellow LED → 220Ω resistor → ESP32 pin 21 → Ground
  3. Green LED → 220Ω resistor → ESP32 pin 16 → Ground

LED Connections for Lane 2:

  1. Red LED → 220Ω resistor → ESP32 pin 33 → Ground
  2. Yellow LED → 220Ω resistor → ESP32 pin 22 → Ground
  3. Green LED → 220Ω resistor → ESP32 pin 17 → Ground

LED Connections for Lane 3:

  1. Red LED → 220Ω resistor → ESP32 pin 27 → Ground
  2. Yellow LED → 220Ω resistor → ESP32 pin 23 → Ground
  3. Green LED → 220Ω resistor → ESP32 pin 18 → Ground

LED Connections for Lane 4:

  1. Red LED → 220Ω resistor → ESP32 pin 26 → Ground
  2. Yellow LED → 220Ω resistor → ESP32 pin 25 → Ground
  3. Green LED → 220Ω resistor → ESP32 pin 19 → Ground

Yeah, that's a lot of wires. Take your time and maybe use different colored jumper wires for red/yellow/green LEDs to keep things organized. I made a mess the first time and had to rewire half of it.

The circuit diagram shows how everything connects together. Notice how all the grounds are common and the 5V power is distributed to all sensors.

Programming the ESP32

Open up Arduino IDE and create a new sketch. You'll need to install the ESP32 board support if you haven't already (go to File → Preferences and add the ESP32 board manager URL, then install it through Tools → Board → Boards Manager).

Start with the basic setup - library includes and WiFi credentials:

#include <WiFi.h>
#include <WebServer.h>

const char* WIFI_SSID = "YourWiFiName";
const char* WIFI_PASSWORD = "YourPassword";

Replace those with your actual WiFi network name and password.

Next, define the timing constants. These control how long each light stays on:const unsigned long GREEN_DURATION_MS = 5000;
const unsigned long YELLOW_DURATION_MS = 5000;
const unsigned long ALL_RED_DELAY_MS = 2000;
const unsigned long DEBOUNCE_MS = 80;

The debounce time is important. Without it, sensor noise can cause false triggers and screw up the vehicle counts. 80 milliseconds works well for filtering out glitches while still catching actual vehicles.

Set up the pin arrays to match your wiring:

const uint8_t NUM_LANES = 4;
const uint8_t greenLedPins[NUM_LANES] = {16, 17, 18, 19};
const uint8_t yellowLedPins[NUM_LANES] = {21, 22, 23, 25};
const uint8_t redLedPins[NUM_LANES] = {32, 33, 27, 26};
const uint8_t irPins[NUM_LANES] = {13, 12, 14, 15};

In the setup function, configure all the pins:

void setupPins() {
for (uint8_t i = 0; i < NUM_LANES; i++) {
pinMode(greenLedPins[i], OUTPUT);
pinMode(yellowLedPins[i], OUTPUT);
pinMode(redLedPins[i], OUTPUT);
pinMode(irPins[i], INPUT_PULLUP);
}
allRed();
}

That INPUT_PULLUP is key for the IR sensors. It enables the internal pullup resistor so the pin reads HIGH normally and goes LOW when a vehicle is detected.

The vehicle detection code checks each sensor continuously:

void checkIR() {
unsigned long now = millis();
for (uint8_t i = 0; i < NUM_LANES; i++) {
if (laneState[i] != RED) continue;
int reading = digitalRead(irPins[i]);
if (reading == LOW && !irTriggered[i]) {
irTriggered[i] = true;
vehicleCount[i]++;
} else if (reading == HIGH && irTriggered[i]) {
irTriggered[i] = false;
}
}
}

This only counts vehicles when the lane is red, which makes sense - why count cars that can already go? The irTriggered flag prevents double-counting if a vehicle sits on the sensor.

The preemption logic runs every loop checking if any lane needs priority:

void checkPreemption() {
for (uint8_t i = 0; i < NUM_LANES; i++) {
if (laneState[i] == RED && vehicleCount[i] >= 2) {
allRed();
inPreemption = true;
preemptedLane = i;
stateStartMillis = millis();
stateDurationMs = ALL_RED_DELAY_MS;
preemptionReady = true;
break;
}
}
}

When it finds a lane with 2+ vehicles, it sets everything red for that 2-second safety delay, then the next function handles giving that lane the green:

void handlePreemption() {
if (inPreemption && preemptionReady && (millis() - stateStartMillis >= stateDurationMs)) {
for (uint8_t i = 0; i < NUM_LANES; i++)
setLaneState(i, (i == preemptedLane) ? GREEN : RED);
vehicleCount[preemptedLane] = 0;
currentGreenIndex = preemptedLane;
stateStartMillis = millis();
stateDurationMs = GREEN_DURATION_MS;
inPreemption = false;
preemptionReady = false;
}
}


Setting Up the Web Server

The web server code creates a simple HTML page that refreshes every second. In your setup function, add:

server.on("/", handleRoot);
server.begin();

Then define the handleRoot function to generate the HTML:

void handleRoot() {
String html = "<html><head><meta http-equiv='refresh' content='1'>";
html += "<style>body{font-family:Arial;margin:20px;}</style></head><body>";
html += "<h1>Smart Traffic System</h1>";
for (uint8_t i = 0; i < NUM_LANES; i++) {
html += "<h2>Lane " + String(i+1) + "</h2>";
html += "<p>Signal: ";
if (laneState[i] == GREEN) html += "GREEN";
else if (laneState[i] == YELLOW) html += "YELLOW";
else html += "RED";
html += "</p><p>Vehicles: " + String(vehicleCount[i]) + "</p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}

Upload the complete code to your ESP32. Open the Serial Monitor (set to 115200 baud) and you should see it connect to WiFi and print an IP address. Write that IP down.

Testing the System

Hardware-Connection-Smart-Traffic-Management-System-using-IoT.jpg
IoT-Based-Dashboard-Smart-Traffic.jpg

First test is simple - just power it on and watch the lights cycle. You should see each lane go green for 5 seconds, yellow for 5 seconds, then red while the next lane gets green. If the timing's off or lights don't turn on, check your wiring and timing constants.

To test the vehicle detection, wave your hand in front of an IR sensor while that lane is red. The vehicle count should increment. I used small toy cars on tracks to simulate actual traffic, which worked great.

For the preemption test, trigger a sensor twice while the lane is red. Both counts should register, and after a moment you should see all lanes turn red briefly, then that lane gets green immediately. The vehicle count for that lane should reset to zero once it goes green.

Open a web browser on any device connected to the same WiFi network. Type in the IP address the ESP32 printed. You should see the dashboard showing all four lanes with their current signal states and vehicle counts. Leave it open and watch it update as the system runs.

Common Issues I Ran Into

IR sensors not detecting anything: Check the sensor orientation and make sure you're using the right type of IR sensor. Some are reflective, others are transmit/receive pairs. I used transmit/receive pairs with the transmitter and receiver facing each other across the "lane."

Sensor triggering constantly: The sensitivity might be too high. Most IR sensor modules have a little potentiometer you can adjust. Turn it until the sensor only triggers when something actually blocks the beam. Also make sure ambient light isn't messing with it.

Wrong lights turning on: Triple-check your pin definitions in the code match your actual wiring. I mixed up lanes 3 and 4 my first attempt and it was confusing as hell to debug.

WiFi won't connect: Make sure your SSID and password are correct (watch for typos). Also, the ESP32 only works on 2.4GHz WiFi networks, not 5GHz. If your router has both, connect to the 2.4GHz one.

Vehicle counts don't reset: This usually means the lane isn't actually getting the green signal, or there's a logic error. Check that setLaneState is working correctly and the vehicle count reset happens in the right place in handlePreemption.

Erratic behavior after running for a while: The millis() timer overflows after about 50 days. For testing this doesn't matter, but if you run it long-term you might hit that. There are ways to handle millis overflow if you need this to run continuously.

Making It Better

Once you get the basic system working, there's a bunch of ways to improve it. You could add an LCD display to show the counts and signal states without needing the web dashboard. I've seen people mount these in little 3D printed intersection models which looks pretty cool.

The IR sensors work okay but they're not perfect. An ESP32-CAM module running object detection would be way more accurate and could even distinguish between cars, trucks, and motorcycles. That's a whole other project though.

You could also log the traffic data to an SD card or send it to a cloud platform like ThingSpeak. Then you could analyze traffic patterns over time, which would actually be useful data for real traffic planning.

Adding physical buttons to manually override the signals would be good for testing and could simulate emergency vehicle preemption in real life.

Final Thoughts

This Smart Traffic Management System using IoT project taught me a lot about how traffic systems actually work. The preemption logic is surprisingly simple but effective - just monitoring vehicle counts and dynamically adjusting timing makes a huge difference compared to fixed timers.

The ESP32 is perfect for this kind of thing. Having WiFi built-in meant I didn't need any extra hardware for the web dashboard, and it has plenty of GPIO pins for all the sensors and LEDs. Plus it's cheap enough that using it for a learning project doesn't hurt.

If you're building this, take your time with the wiring. It's tedious connecting 12 LEDs with resistors plus 4 sensors, but once it's done right it's solid. Use a breadboard first to test everything before committing to a permanent build.

The code's available on GitHub if you want the full version (link in the original article). I kept the snippets here short but there's more to it for the web server and state management.

Overall this was a fun weekend project that actually works. Watching it automatically detect congestion and adjust the signals never gets old. If you're into IoT or embedded systems, definitely give it a shot.