Traffic light controller project with pedestrian crossing support, countdown system, and state machine implementation.
// ================= PINS =================
const int NS_RED = 23;
const int NS_YELLOW = 22;
const int NS_GREEN = 21;
const int EW_RED = 19;
const int EW_YELLOW = 18;
const int EW_GREEN = 5;
const int PED_BTN_1 = 32;
const int PED_BTN_2 = 33;
const int LED_ON = LOW;
const int LED_OFF = HIGH;
const unsigned long GREEN_TIME = 15000; // 15 sec
const unsigned long YELLOW_TIME = 3000; // 3 sec
const unsigned long PED_WAIT = 3000; // 3 sec after button press
const unsigned long PED_RED_TIME = 10000; // 10 sec both red
const unsigned long DEBOUNCE_MS = 50;
// ================= STATES =================
enum State {
NS_GREEN_STATE,
NS_YELLOW_STATE,
EW_GREEN_STATE,
EW_YELLOW_STATE,
PED_BOTH_YELLOW_STATE,
PED_BOTH_RED_STATE
};
State currentState = NS_GREEN_STATE;
State resumeState = NS_GREEN_STATE;
unsigned long stateStartTime = 0;
long lastCountdownSecond = -1;
// pedestrian request
bool pedWaiting = false;
unsigned long pedRequestTime = 0;
long lastPedCountdownSecond = -1;
// debounce vars
bool lastReading1 = HIGH;
bool stableReading1 = HIGH;
unsigned long lastDebounce1 = 0;
bool lastReading2 = HIGH;
bool stableReading2 = HIGH;
unsigned long lastDebounce2 = 0;
// ================= LIGHT FUNCTIONS =================
void allLightsOff() {
digitalWrite(NS_RED, LED_OFF);
digitalWrite(NS_YELLOW, LED_OFF);
digitalWrite(NS_GREEN, LED_OFF);
digitalWrite(EW_RED, LED_OFF);
digitalWrite(EW_YELLOW, LED_OFF);
digitalWrite(EW_GREEN, LED_OFF);
}
void setLights(int nsR, int nsY, int nsG, int ewR, int ewY, int ewG) {
digitalWrite(NS_RED, nsR);
digitalWrite(NS_YELLOW, nsY);
digitalWrite(NS_GREEN, nsG);
digitalWrite(EW_RED, ewR);
digitalWrite(EW_YELLOW, ewY);
digitalWrite(EW_GREEN, ewG);
}
const char* stateName(State state) {
switch (state) {
case NS_GREEN_STATE:
return "NS GREEN, EW RED";
case NS_YELLOW_STATE:
return "NS YELLOW, EW RED";
case EW_GREEN_STATE:
return "EW GREEN, NS RED";
case EW_YELLOW_STATE:
return "EW YELLOW, NS RED";
case PED_BOTH_YELLOW_STATE:
return "BOTH YELLOW";
case PED_BOTH_RED_STATE:
return "BOTH RED";
}
return "UNKNOWN";
}
unsigned long stateDuration(State state) {
switch (state) {
case NS_GREEN_STATE:
case EW_GREEN_STATE:
return GREEN_TIME;
case NS_YELLOW_STATE:
case EW_YELLOW_STATE:
case PED_BOTH_YELLOW_STATE:
return YELLOW_TIME;
case PED_BOTH_RED_STATE:
return PED_RED_TIME;
}
return 0;
}
void logStateCountdown(unsigned long now) {
unsigned long duration = stateDuration(currentState);
if (duration == 0) {
return;
}
unsigned long elapsed = now - stateStartTime;
unsigned long remaining = (elapsed >= duration) ? 0 : duration - elapsed;
long remainingSeconds = (remaining + 999) / 1000;
if (remainingSeconds != lastCountdownSecond) {
lastCountdownSecond = remainingSeconds;
Serial.print("TIME BEFORE CHANGE (");
Serial.print(stateName(currentState));
Serial.print("): ");
Serial.print(remainingSeconds);
Serial.println(" sec");
}
}
void logPedestrianCountdown(unsigned long now) {
if (!pedWaiting) {
return;
}
unsigned long elapsed = now - pedRequestTime;
unsigned long remaining = (elapsed >= PED_WAIT) ? 0 : PED_WAIT - elapsed;
long remainingSeconds = (remaining + 999) / 1000;
if (remainingSeconds != lastPedCountdownSecond) {
lastPedCountdownSecond = remainingSeconds;
Serial.print("PEDESTRIAN: both yellow starts in ");
Serial.print(remainingSeconds);
Serial.println(" sec");
}
}
void changeState(State newState) {
currentState = newState;
stateStartTime = millis();
lastCountdownSecond = -1;
switch (currentState) {
case NS_GREEN_STATE:
// NS green, EW red
setLights(LED_OFF, LED_OFF, LED_ON,
LED_ON, LED_OFF, LED_OFF);
Serial.print("STATE: ");
Serial.println(stateName(currentState));
break;
case NS_YELLOW_STATE:
// NS yellow, EW red
setLights(LED_OFF, LED_ON, LED_OFF,
LED_ON, LED_OFF, LED_OFF);
Serial.print("STATE: ");
Serial.println(stateName(currentState));
break;
case EW_GREEN_STATE:
// EW green, NS red
setLights(LED_ON, LED_OFF, LED_OFF,
LED_OFF, LED_OFF, LED_ON);
Serial.print("STATE: ");
Serial.println(stateName(currentState));
break;
case EW_YELLOW_STATE:
// EW yellow, NS red
setLights(LED_ON, LED_OFF, LED_OFF,
LED_OFF, LED_ON, LED_OFF);
Serial.print("STATE: ");
Serial.println(stateName(currentState));
break;
case PED_BOTH_YELLOW_STATE:
// both yellow
setLights(LED_OFF, LED_ON, LED_OFF,
LED_OFF, LED_ON, LED_OFF);
Serial.print("STATE: ");
Serial.println(stateName(currentState));
break;
case PED_BOTH_RED_STATE:
// both red
setLights(LED_ON, LED_OFF, LED_OFF,
LED_ON, LED_OFF, LED_OFF);
Serial.print("STATE: ");
Serial.println(stateName(currentState));
break;
}
}
// ================= BUTTON FUNCTIONS =================
bool checkButtonPressed(int pin, bool &lastReading, bool &stableReading, unsigned long &lastDebounce) {
bool reading = digitalRead(pin);
if (reading != lastReading) {
lastDebounce = millis();
}
if ((millis() - lastDebounce) > DEBOUNCE_MS) {
if (reading != stableReading) {
stableReading = reading;
// active LOW button
if (stableReading == LOW) {
lastReading = reading;
return true;
}
}
}
lastReading = reading;
return false;
}
void handleButtons() {
bool btn1Pressed = checkButtonPressed(PED_BTN_1, lastReading1, stableReading1, lastDebounce1);
bool btn2Pressed = checkButtonPressed(PED_BTN_2, lastReading2, stableReading2, lastDebounce2);
if ((btn1Pressed || btn2Pressed) &&
!pedWaiting &&
currentState != PED_BOTH_YELLOW_STATE &&
currentState != PED_BOTH_RED_STATE) {
pedWaiting = true;
pedRequestTime = millis();
resumeState = currentState;
lastPedCountdownSecond = -1;
Serial.println("PEDESTRIAN REQUEST REGISTERED");
Serial.print("PEDESTRIAN: will resume ");
Serial.println(stateName(resumeState));
}
}
// ================= SETUP =================
void setup() {
Serial.begin(115200);
pinMode(NS_RED, OUTPUT);
pinMode(NS_YELLOW, OUTPUT);
pinMode(NS_GREEN, OUTPUT);
pinMode(EW_RED, OUTPUT);
pinMode(EW_YELLOW, OUTPUT);
pinMode(EW_GREEN, OUTPUT);
pinMode(PED_BTN_1, INPUT_PULLUP);
pinMode(PED_BTN_2, INPUT_PULLUP);
allLightsOff();
changeState(NS_GREEN_STATE);
}
// ================= LOOP =================
void loop() {
handleButtons();
unsigned long now = millis();
unsigned long elapsed = now - stateStartTime;
logPedestrianCountdown(now);
logStateCountdown(now);
// If pedestrian request has waited 3 sec, start pedestrian sequence.
if (pedWaiting &&
(currentState == NS_GREEN_STATE || currentState == NS_YELLOW_STATE ||
currentState == EW_GREEN_STATE || currentState == EW_YELLOW_STATE) &&
(now - pedRequestTime >= PED_WAIT)) {
pedWaiting = false;
lastPedCountdownSecond = -1;
changeState(PED_BOTH_YELLOW_STATE);
return;
}
switch (currentState) {
case NS_GREEN_STATE:
if (elapsed >= GREEN_TIME) {
changeState(NS_YELLOW_STATE);
}
break;
case NS_YELLOW_STATE:
if (elapsed >= YELLOW_TIME) {
changeState(EW_GREEN_STATE);
}
break;
case EW_GREEN_STATE:
if (elapsed >= GREEN_TIME) {
changeState(EW_YELLOW_STATE);
}
break;
case EW_YELLOW_STATE:
if (elapsed >= YELLOW_TIME) {
changeState(NS_GREEN_STATE);
}
break;
case PED_BOTH_YELLOW_STATE:
if (elapsed >= YELLOW_TIME) {
changeState(PED_BOTH_RED_STATE);
}
break;
case PED_BOTH_RED_STATE:
if (elapsed >= PED_RED_TIME) {
changeState(resumeState);
}
break;
}
}
# Crossroad Traffic Light Controller
Arduino/ESP32 project for controlling a simple two-way crossroad traffic light system with pedestrian buttons.
The sketch is in `crossroad.ino`.
## Features
- Controls North/South and East/West traffic lights.
- Uses two pedestrian buttons.
- Runs with a non-blocking `millis()` state machine.
- Prints state changes and countdown logs to the Serial Monitor.
- Uses button debounce to avoid repeated false presses.
## Hardware
This project is written for an ESP32.
### Traffic Light Pins
| Direction | Color | GPIO |
| --- | --- | --- |
| North/South | Red | 23 |
| North/South | Yellow | 22 |
| North/South | Green | 21 |
| East/West | Red | 19 |
| East/West | Yellow | 18 |
| East/West | Green | 5 |
### Pedestrian Button Pins
| Button | GPIO |
| --- | --- |
| Pedestrian button 1 | 32 |
| Pedestrian button 2 | 33 |
## Wiring Notes
### LEDs
The code uses active-low LEDs:
```cpp
const int LED_ON = LOW;
const int LED_OFF = HIGH;
```
That means an LED turns on when the ESP32 pin is `LOW`.
Typical wiring:
```text
3.3V -> resistor -> LED -> GPIO pin
```
If your LEDs are wired from GPIO to resistor to GND, the lights may behave inverted.
### Buttons
The pedestrian buttons use `INPUT_PULLUP`:
```cpp
pinMode(PED_BTN_1, INPUT_PULLUP);
pinMode(PED_BTN_2, INPUT_PULLUP);
```
Each button should be wired like this:
```text
GPIO 32 -> button -> GND
GPIO 33 -> button -> GND
```
When the button is not pressed, the pin reads `HIGH`.
When the button is pressed, the pin connects to GND and reads `LOW`.
## Timing
| Event | Duration |
| --- | --- |
| Normal green light | 15 seconds |
| Normal yellow light | 3 seconds |
| Wait after pedestrian button press | 3 seconds |
| Pedestrian both-yellow phase | 3 seconds |
| Pedestrian both-red phase | 10 seconds |
| Button debounce | 50 milliseconds |
## Normal Traffic Flow
The normal traffic cycle is:
```text
NS green, EW red
NS yellow, EW red
EW green, NS red
EW yellow, NS red
repeat
```
## Pedestrian Flow
When either pedestrian button is pressed:
1. The request is registered.
2. The controller waits 3 seconds.
3. Both directions turn yellow.
4. After 3 seconds, both directions turn red.
5. After 10 seconds, the controller resumes the traffic state that was active when the button was pressed.
## Serial Monitor
Open the Serial Monitor at:
```text
115200 baud
```
Example logs:
```text
STATE: NS GREEN, EW RED
TIME BEFORE CHANGE (NS GREEN, EW RED): 15 sec
TIME BEFORE CHANGE (NS GREEN, EW RED): 14 sec
PEDESTRIAN REQUEST REGISTERED
PEDESTRIAN: will resume NS GREEN, EW RED
PEDESTRIAN: both yellow starts in 3 sec
STATE: BOTH YELLOW
TIME BEFORE CHANGE (BOTH YELLOW): 3 sec
STATE: BOTH RED
TIME BEFORE CHANGE (BOTH RED): 10 sec
STATE: NS GREEN, EW RED
```
## Uploading
1. Open `crossroad.ino` in the Arduino IDE.
2. Select your ESP32 board.
3. Select the correct USB port.
4. Upload the sketch.
5. Open Serial Monitor at `115200` baud to see logs.
~