Revolutionizing Urban Flow: Building a Smart 4-Way Traffic System with Pedestrian Safety
An in-depth guide to coding an Arduino-based traffic controller using IR sensors for dynamic vehicle management and dedicated pedestrian features.
Creating an intelligent traffic light system for a four-way intersection involves managing vehicle flow efficiently while prioritizing pedestrian safety. This project leverages an Arduino microcontroller, four Infrared (IR) sensors for vehicle detection on each road, and pedestrian interaction mechanisms (push buttons and dedicated signals). The goal is to develop a system that dynamically adapts to traffic conditions and pedestrian needs, moving beyond simple timed cycles.
Key Project Highlights
- Dynamic Traffic Control: IR sensors detect vehicles, allowing the system to adjust green light durations or prioritize busier roads.
- Comprehensive Pedestrian Safety: Dedicated push buttons and signal lights for pedestrians on all four approaches, ensuring safe crossing times.
- Modular Arduino Programming: Structured code for managing multiple traffic lights, sensors, and pedestrian states, promoting clarity and scalability.
System Architecture and Components
The core of this system is an Arduino board (an Arduino Mega is recommended due to the number of I/O pins required for a full 4-way system with individual pedestrian controls). Each of the four roads is equipped with standard Red, Yellow, and Green LEDs for vehicles, an IR sensor to detect approaching vehicles, a push button for pedestrians to request crossing, and separate Red and Green LEDs for pedestrian signals.
Conceptual diagram of a sensor-based traffic management system.
Essential Hardware
- Arduino Board (Uno or Mega, Mega recommended for more pins)
- LEDs:
- 12x Vehicle Traffic LEDs (4 Red, 4 Yellow, 4 Green)
- 8x Pedestrian Signal LEDs (4 Red, 4 Green)
- 4x IR Proximity Sensors (for vehicle detection)
- 4x Push Buttons (for pedestrian crossing requests)
- Resistors:
- 20x 220Ω resistors (for all LEDs)
- 4x 10kΩ resistors (for push buttons, if not using internal pull-ups)
- Breadboard(s)
- Jumper Wires
- 5V Power Supply (can be from Arduino if load is not excessive)
Circuit Connections: Pin Configuration Table
Properly wiring the components to the Arduino is crucial. The following table outlines a suggested pin configuration. Note that IR sensors typically have VCC, GND, and OUT pins. The OUT pin connects to the Arduino. Push buttons are connected to an input pin and GND, often using an internal or external pull-up resistor.
| Component Group | Road | Vehicle Red LED | Vehicle Yellow LED | Vehicle Green LED | Pedestrian Red LED | Pedestrian Green LED | IR Sensor (Analog/Digital Pin) | Pedestrian Button (Digital Pin) |
|---|---|---|---|---|---|---|---|---|
| North | 1 | Pin 2 | Pin 3 | Pin 4 | Pin 22 | Pin 23 | Pin A0 | Pin 30 |
| East | 2 | Pin 5 | Pin 6 | Pin 7 | Pin 24 | Pin 25 | Pin A1 | Pin 31 |
| South | 3 | Pin 8 | Pin 9 | Pin 10 | Pin 26 | Pin 27 | Pin A2 | Pin 32 |
| West | 4 | Pin 11 | Pin 12 | Pin 13 | Pin 28 | Pin 29 | Pin A3 | Pin 33 |
Note: Ensure LEDs are connected with current-limiting resistors (e.g., 220Ω). Button pins are configured with `INPUT_PULLUP`, so connect the other terminal of the button to GND. IR sensor output pins are connected to the specified Arduino pins; their VCC and GND must also be connected. Pin numbers are examples and can be adjusted in the code.
Operational Logic and System Flow
The system operates on a cycle, granting green lights to different roads. IR sensors check for vehicles to potentially extend green times. Pedestrian buttons, when pressed, signal a request to cross. The system will then, at an appropriate point in the traffic cycle (e.g., after the current green phase completes with a yellow light), turn all vehicle lights red and activate the pedestrian "WALK" signal for the requesting road.
Example of pedestrian walk and don't walk signals.
Vehicle Detection
IR sensors are placed to detect vehicles waiting at each approach. A detection (typically a LOW signal from the sensor) can be used to inform the Arduino. The system might extend the green light duration for a road if vehicles are continuously detected or prioritize a road with waiting vehicles.
Pedestrian Crossing Sequence
- A pedestrian presses the button on one of the four roads.
- The system registers this request.
- Once the current vehicle green light cycle for the active road is complete (Green -> Yellow -> Red), the system initiates the pedestrian phase.
- All vehicle traffic lights turn RED.
- The pedestrian signal for the requesting road turns GREEN ("WALK"). Other pedestrian signals remain RED ("DON'T WALK").
- After a predefined safe crossing time, the pedestrian signal turns RED ("DON'T WALK"), possibly after a flashing warning phase.
- The system resumes normal vehicle traffic light cycles.
System Interaction Mindmap
This mindmap illustrates the key components of the 4-way smart traffic light system and their relationships, providing a high-level overview of its architecture.
Arduino Implementation: The Code
The following Arduino code provides a comprehensive structure for the 4-way traffic light controller with IR sensors and pedestrian features. It uses non-blocking delays (`millis()`) for responsive operation. Pin assignments match the table provided earlier. The IR sensors are assumed to pull their output LOW when an object is detected.
// Define a structure to hold pin numbers for each road's lights and sensors
struct TrafficLightPins {
int red; // Vehicle Red LED pin
int yellow; // Vehicle Yellow LED pin
int green; // Vehicle Green LED pin
int pedRed; // Pedestrian Red LED pin
int pedGreen; // Pedestrian Green LED pin
int irSensor; // IR Sensor pin
int pedButton;// Pedestrian Button pin
};
// Array of structures, one for each of the 4 roads (North, East, South, West)
TrafficLightPins roads[4] = {
// North (Road 0)
{2, 3, 4, 22, 23, A0, 30},
// East (Road 1)
{5, 6, 7, 24, 25, A1, 31},
// South (Road 2)
{8, 9, 10, 26, 27, A2, 32},
// West (Road 3)
{11, 12, 13, 28, 29, A3, 33}
};
// Timing constants (in milliseconds)
const unsigned long minGreenTime = 10000; // Minimum green time for vehicles (10s)
const unsigned long yellowTime = 3000; // Yellow light time (3s)
const unsigned long pedGreenTime = 7000; // Pedestrian green crossing time (7s)
const unsigned long allRedClearanceTime = 2000; // Brief all-red after yellow/before ped (2s)
// State tracking variables
unsigned long lastChangeTime = 0; // Stores time of the last major state change
int currentRoad = 0; // Index of the road currently having (or about to have) green light for vehicles
bool pedestrianWaiting[4] = {false, false, false, false}; // Flags if a pedestrian is waiting at road i
bool pedestrianCrossingActive = false; // True if a pedestrian crossing sequence is active
int roadForPedestrian = -1; // Index of the road where pedestrian crossing is happening
// Enum for main system states to manage traffic flow
enum SystemState { VEHICLE_GREEN, VEHICLE_YELLOW, ALL_RED_TRANSITION, PEDESTRIAN_CROSSING_START, PEDESTRIAN_WALK, PEDESTRIAN_DONT_WALK_CLEARANCE };
SystemState currentSystemState = VEHICLE_GREEN;
void setup() {
Serial.begin(9600); // Initialize serial communication for debugging
// Initialize all pins for LEDs as OUTPUTs and sensors/buttons as INPUT_PULLUPs
for (int i = 0; i < 4; i++) {
pinMode(roads[i].red, OUTPUT);
pinMode(roads[i].yellow, OUTPUT);
pinMode(roads[i].green, OUTPUT);
pinMode(roads[i].pedRed, OUTPUT);
pinMode(roads[i].pedGreen, OUTPUT);
pinMode(roads[i].irSensor, INPUT_PULLUP); // IR sensor: LOW means vehicle detected
pinMode(roads[i].pedButton, INPUT_PULLUP); // Pedestrian button: LOW means pressed
// Initial light states: All vehicle red, all pedestrian red
digitalWrite(roads[i].red, HIGH);
digitalWrite(roads[i].yellow, LOW);
digitalWrite(roads[i].green, LOW);
digitalWrite(roads[i].pedRed, HIGH);
digitalWrite(roads[i].pedGreen, LOW);
}
// Start with currentRoad (e.g., North) Green
digitalWrite(roads[currentRoad].red, LOW);
digitalWrite(roads[currentRoad].green, HIGH);
lastChangeTime = millis();
currentSystemState = VEHICLE_GREEN;
Serial.println("System Initialized. Road " + String(currentRoad) + " GREEN.");
}
// Function to turn all vehicle lights RED and pedestrian lights to DON'T WALK (RED)
void setAllVehiclesRedAllPedRed() {
for (int i = 0; i < 4; i++) {
digitalWrite(roads[i].red, HIGH);
digitalWrite(roads[i].yellow, LOW);
digitalWrite(roads[i].green, LOW);
digitalWrite(roads[i].pedRed, HIGH);
digitalWrite(roads[i].pedGreen, LOW);
}
}
// Function to set lights for a road going green
void setVehicleGreen(int roadIndex) {
setAllVehiclesRedAllPedRed(); // First, ensure all others are red
digitalWrite(roads[roadIndex].red, LOW);
digitalWrite(roads[roadIndex].green, HIGH);
digitalWrite(roads[roadIndex].pedRed, HIGH); // Pedestrians on this road see DON'T WALK
digitalWrite(roads[roadIndex].pedGreen, LOW);
Serial.println("Road " + String(roadIndex) + " VEHICLE GREEN.");
}
// Function to set lights for a road going yellow
void setVehicleYellow(int roadIndex) {
digitalWrite(roads[roadIndex].green, LOW);
digitalWrite(roads[roadIndex].yellow, HIGH);
Serial.println("Road " + String(roadIndex) + " VEHICLE YELLOW.");
}
// Function to start pedestrian crossing on a specific road
void startPedestrianWalk(int pedRoadIndex) {
setAllVehiclesRedAllPedRed(); // All vehicle lights red
// Pedestrian lights: WALK (Green ON) for the specific road, DON'T WALK for others
for (int i = 0; i < 4; i++) {
if (i == pedRoadIndex) {
digitalWrite(roads[i].pedRed, LOW);
digitalWrite(roads[i].pedGreen, HIGH);
} else {
digitalWrite(roads[i].pedRed, HIGH);
digitalWrite(roads[i].pedGreen, LOW);
}
}
Serial.println("Road " + String(pedRoadIndex) + " PEDESTRIAN WALK.");
}
// Read IR sensor: returns true if vehicle present (sensor output is LOW)
bool isVehicleDetected(int roadIndex) {
return digitalRead(roads[roadIndex].irSensor) == LOW;
}
// Check pedestrian buttons and update waiting flags
void checkPedestrianButtons() {
for (int i = 0; i < 4; i++) {
if (digitalRead(roads[i].pedButton) == LOW && !pedestrianWaiting[i]) {
pedestrianWaiting[i] = true; // Pedestrian requested crossing
Serial.println("Pedestrian button pressed for road " + String(i));
}
}
}
// Select the next road for green light or a road for pedestrian crossing
// Priority: 1. Pedestrians waiting, 2. Vehicles detected on other roads, 3. Normal cycle.
int determineNextAction(int currentVehicleRoad, int& outPedestrianRoadIndex) {
outPedestrianRoadIndex = -1; // Reset
// Check for pedestrian requests first (any road)
for (int i = 0; i < 4; i++) {
if (pedestrianWaiting[i]) {
outPedestrianRoadIndex = i; // Found a pedestrian waiting
pedestrianWaiting[i] = false; // Clear the request flag
return PEDESTRIAN_CROSSING_START; // Signal to start pedestrian sequence
}
}
// If no pedestrians, check for vehicles on other roads (simple cycle for now)
// A more advanced logic could check IR sensors of (currentVehicleRoad + 1), (currentVehicleRoad + 2) etc.
int nextRoad = (currentVehicleRoad + 1) % 4;
// For simplicity, we'll just cycle. IR sensor use is primarily for green time extension here.
return VEHICLE_GREEN; // Signal to go to next vehicle green
}
void loop() {
unsigned long currentTime = millis();
checkPedestrianButtons(); // Continuously check pedestrian buttons
switch (currentSystemState) {
case VEHICLE_GREEN:
// Green light is active for 'currentRoad'
// IR sensor could extend green time here (not implemented in this simplified state timing)
if (currentTime - lastChangeTime >= minGreenTime) {
setVehicleYellow(currentRoad);
currentSystemState = VEHICLE_YELLOW;
lastChangeTime = currentTime;
}
break;
case VEHICLE_YELLOW:
if (currentTime - lastChangeTime >= yellowTime) {
setAllVehiclesRedAllPedRed(); // All vehicles red
currentSystemState = ALL_RED_TRANSITION;
lastChangeTime = currentTime;
Serial.println("All Red Transition after Yellow for Road " + String(currentRoad));
}
break;
case ALL_RED_TRANSITION:
if (currentTime - lastChangeTime >= allRedClearanceTime) {
// Decide next action: pedestrian or next vehicle
int nextActionState = determineNextAction(currentRoad, roadForPedestrian);
if (nextActionState == PEDESTRIAN_CROSSING_START && roadForPedestrian != -1) {
pedestrianCrossingActive = true;
currentSystemState = PEDESTRIAN_WALK;
startPedestrianWalk(roadForPedestrian);
} else {
// No pedestrian, or error, proceed to next vehicle
pedestrianCrossingActive = false;
currentRoad = (currentRoad + 1) % 4; // Cycle to next road
setVehicleGreen(currentRoad);
currentSystemState = VEHICLE_GREEN;
}
lastChangeTime = currentTime;
}
break;
case PEDESTRIAN_WALK:
if (currentTime - lastChangeTime >= pedGreenTime) {
// Transition to "Don't Walk" (solid red for pedestrians)
digitalWrite(roads[roadForPedestrian].pedGreen, LOW);
digitalWrite(roads[roadForPedestrian].pedRed, HIGH);
currentSystemState = PEDESTRIAN_DONT_WALK_CLEARANCE;
lastChangeTime = currentTime;
Serial.println("Road " + String(roadForPedestrian) + " PEDESTRIAN DON'T WALK (Clearance).");
}
break;
case PEDESTRIAN_DONT_WALK_CLEARANCE:
// This state is for pedestrian clearance time, typically a flashing "Don't Walk" or just a fixed duration.
// Here, it's a fixed duration with solid "Don't Walk" (Red).
if (currentTime - lastChangeTime >= allRedClearanceTime) { // Using allRedClearanceTime as ped clearance
pedestrianCrossingActive = false;
roadForPedestrian = -1; // Reset
// After pedestrian crossing, decide next vehicle road.
// Could be the road that just had pedestrians, or the next in sequence.
// For simplicity, we cycle from the last 'currentRoad'.
currentRoad = (currentRoad + 1) % 4;
setVehicleGreen(currentRoad);
currentSystemState = VEHICLE_GREEN;
lastChangeTime = currentTime;
Serial.println("Pedestrian cycle complete. Resuming vehicle traffic.");
}
break;
}
}
Code Explanation:
- Structures and Arrays:
TrafficLightPins roads[4]organizes all pin numbers for each road, making the code cleaner and easier to manage. - Timing Constants: Durations for green, yellow, and pedestrian walk times are defined as constants for easy adjustment.
- State Variables:
currentSystemState,currentRoad,pedestrianWaiting[],pedestrianCrossingActive, androadForPedestriantrack the system's status. setup(): Initializes serial communication for debugging and configures all pins. It sets initial light states (typically all red for vehicles, then one road green).- Helper Functions:
setAllVehiclesRedAllPedRed(): Turns all vehicle lights red and pedestrian lights to "Don't Walk".setVehicleGreen(roadIndex)/setVehicleYellow(roadIndex): Control lights for a specific road.startPedestrianWalk(pedRoadIndex): Manages lights for pedestrian crossing.isVehicleDetected(roadIndex): Reads the IR sensor for a given road. (Note: The current main loop's timing for green is fixed; IR detection is primarily for future extension to dynamic green times).checkPedestrianButtons(): Scans all pedestrian buttons.determineNextAction(): Decides whether the next phase is for a pedestrian or the next vehicle road.
loop(): This is the heart of the program. It's a state machine driven bycurrentSystemStateand usesmillis()for non-blocking time management. It transitions through states likeVEHICLE_GREEN,VEHICLE_YELLOW,ALL_RED_TRANSITION(to safely switch or allow pedestrians), and various pedestrian crossing states.- IR Sensor Usage: The
isVehicleDetected()function is provided. In this version of the code, vehicle detection is mainly for future enhancements like extending green times. The primary logic focuses on timed cycles and pedestrian interrupts. To fully utilize IR sensors for dynamic timing, theVEHICLE_GREENstate would need to checkisVehicleDetected(currentRoad)and potentially extendminGreenTimeor adjustlastChangeTime.
Visualizing System Attributes
A smart traffic system can be evaluated on several attributes. The radar chart below offers a conceptual comparison between a basic timed system, one with IR sensors, and a comprehensive system including advanced pedestrian priority and adaptive controls.
This chart visualizes how adding features like IR sensors and sophisticated pedestrian handling can improve overall system performance, though potentially increasing complexity and cost.
Further Insights: Video Demonstration
For a practical demonstration of a 4-way traffic light system built with Arduino, the following video provides valuable insights into its construction and operation. While it may not cover all specific features of the code provided here (like individual pedestrian lights per road), it serves as an excellent visual aid for the general concept.
Demonstration of a 4-Way Traffic Light Control System using Arduino.
Frequently Asked Questions (FAQ)
Why use an Arduino Mega instead of an Uno for this project?
A 4-way traffic light system with individual vehicle lights (Red, Yellow, Green per road = 12 LEDs), pedestrian lights (Red, Green per road = 8 LEDs), IR sensors (4 sensors), and pedestrian buttons (4 buttons) requires a significant number of I/O pins. An Arduino Uno has 14 digital I/O pins (6 of which can be PWM) and 6 analog input pins. The Arduino Mega has 54 digital I/O pins and 16 analog input pins, providing ample capacity for all components without complex pin-sharing techniques.
How do IR (Infrared) sensors detect vehicles?
Most common IR proximity sensors used in such projects consist of an IR emitter (LED) and an IR detector (photodiode or phototransistor). The emitter sends out a beam of infrared light. If an object (like a vehicle) is within the sensor's range, the IR light reflects off the object and is picked up by the detector. This changes the state of the sensor's output pin (e.g., from HIGH to LOW, or vice versa, depending on the sensor module). The Arduino reads this change to determine vehicle presence.
What is the advantage of using `millis()` over `delay()` for timing?
delay() is a blocking function; it halts all other operations on the Arduino for the specified duration. In a traffic light system, this means the Arduino cannot read sensor inputs, check buttons, or react to any other event while a delay() is active. millis(), on the other hand, returns the number of milliseconds since the Arduino board began running the current program. By comparing the current millis() value to a previously recorded time, you can manage timing for multiple events concurrently without stopping the main loop. This non-blocking approach is crucial for responsive systems like traffic controllers that need to monitor various inputs simultaneously.
How can the green light time be made dynamic based on IR sensor input?
To make green light times dynamic, within the VEHICLE_GREEN state, you would continuously check the IR sensor for the active road. If a vehicle is detected, you could extend the planned green light duration (e.g., by adding a few seconds to `minGreenTime` or by resetting the `lastChangeTime` to effectively prolong the current green phase). This requires careful logic to ensure there's a maximum green time to prevent one road from monopolizing the intersection, and to smoothly transition to yellow when no more vehicles are detected or the maximum time is reached.
Recommended Further Exploration
- How can I add an emergency vehicle preemption feature to this traffic system?
- What algorithms can be used for more advanced traffic flow optimization across multiple intersections?
- How can I use a Real-Time Clock (RTC) module to change traffic patterns based on the time of day?
- What are the methods to synchronize multiple Arduino-controlled traffic intersections wirelessly?
References
Last updated May 18, 2025