#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// --- 1. MAIN ROAD PINS ---
const int MAIN_GREEN = 8;
const int MAIN_YELLOW = 9;
const int MAIN_RED = 10;
// --- 2. SIDE ROAD 1 PINS (Left Lane) ---
const int SIDE1_GREEN = 11;
const int SIDE1_YELLOW = 12;
const int SIDE1_RED = 13;
// --- 3. SIDE ROAD 2 PINS (Right Lane) ---
const int SIDE2_GREEN = A1;
const int SIDE2_YELLOW = A2;
const int SIDE2_RED = A3;
// --- 4. PEDESTRIAN & SENSOR PINS ---
const int PED_GREEN = 5;
const int PED_RED = 6;
const int BUTTON_PIN = 2;
const int TRIG_PIN = 4;
const int ECHO_PIN = 3;
const int LDR_PIN = A0;
const int PIEZO_PIN = 7;
// --- SETUP OPTIMIZATION ARRAY ---
// This list allows us to set all these pins to OUTPUT in just 3 lines of code in setup()
const int OUTPUT_PINS[] = {
MAIN_GREEN, MAIN_YELLOW, MAIN_RED,
SIDE1_GREEN, SIDE1_YELLOW, SIDE1_RED,
SIDE2_GREEN, SIDE2_YELLOW, SIDE2_RED,
PED_GREEN, PED_RED, TRIG_PIN
};
/* --- Streamlined State Machine Definition --- */
typedef enum {
STATE_MAIN_GREEN,
STATE_MAIN_YELLOW,
STATE_ALL_RED_BUFFER, // The SINGLE universal safety buffer
STATE_PED_WALK,
STATE_SIDE1_GREEN,
STATE_SIDE1_YELLOW,
STATE_SIDE2_GREEN,
STATE_SIDE2_YELLOW
} TrafficState;
TrafficState currentState = STATE_MAIN_GREEN;
TrafficState nextTargetState = STATE_SIDE1_GREEN;
unsigned long stateStartTime = 0;
unsigned long stateDuration = 15000;
unsigned long lastBlinkTime = 0;
volatile int buttonPressed = 0;
int activeCrossing = 0;
int sensorReadExecuted = 0;
int isBusyTraffic = 0;
int lastRemainingSeconds = -1;
int blinkState = LOW;
LiquidCrystal_I2C lcd(0x27, 16, 2);
/* --- Function Prototypes --- */
void setLights(int mG, int mY, int mR, int s1G, int s1Y, int s1R, int s2G, int s2Y, int s2R, int pR, int pG);
void changeState(TrafficState next, unsigned long duration);
void printLCD(const char* line1, const char* line2, int timeVal);
void handleButtonPress();
long readDistance();
void setup() {
// Loop through the array to set all 12 output pins automatically
for (int i = 0; i < 12; i++) {
pinMode(OUTPUT_PINS[i], OUTPUT);
}
pinMode(ECHO_PIN, INPUT);
pinMode(LDR_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonPress, RISING);
noTone(PIEZO_PIN);
lcd.init();
lcd.backlight();
lcd.clear();
stateStartTime = millis();
}
void loop() {
unsigned long currentTime = millis();
// LDR Digital Read
int isNight = 0;
if (digitalRead(LDR_PIN) == HIGH) {
isNight = 1;
}
// 200ms Blink Engine
if (currentTime - lastBlinkTime >= 200) {
lastBlinkTime = currentTime;
if (blinkState == LOW) {
blinkState = HIGH;
} else {
blinkState = LOW;
}
}
// Button Memory
if (buttonPressed == 1) {
buttonPressed = 0;
if (currentState != STATE_PED_WALK && currentState != STATE_ALL_RED_BUFFER) {
activeCrossing = 1;
}
}
// Display Engine Math
int secondsLeft = max(0L, (long)(stateDuration - (currentTime - stateStartTime))) / 1000 + 1;
secondsLeft = min(secondsLeft, (int)(stateDuration / 1000));
if (secondsLeft != lastRemainingSeconds) {
lastRemainingSeconds = secondsLeft;
switch (currentState) {
case STATE_MAIN_GREEN:
if (isBusyTraffic == 1) {
printLCD("busy traffic", "Green: ", secondsLeft);
} else {
printLCD("main flow", "Green: ", secondsLeft);
}
break;
case STATE_MAIN_YELLOW:
printLCD("clear main", "Yellow: ", secondsLeft);
break;
case STATE_ALL_RED_BUFFER:
printLCD("PREPARING", "HOLD POS: ", secondsLeft);
break;
case STATE_PED_WALK:
printLCD("WALK SIGNAL ON", "CROSS: ", secondsLeft);
break;
case STATE_SIDE1_GREEN:
printLCD("side 1 active", "Green: ", secondsLeft);
break;
case STATE_SIDE1_YELLOW:
printLCD("clear side 1", "Yellow: ", secondsLeft);
break;
case STATE_SIDE2_GREEN:
printLCD("side 2 active", "Green: ", secondsLeft);
break;
case STATE_SIDE2_YELLOW:
printLCD("clear side 2", "Yellow: ", secondsLeft);
break;
}
}
// --- TRAFFIC LIGHT STATE MACHINE ---
switch (currentState) {
case STATE_MAIN_GREEN:
noTone(PIEZO_PIN);
if (isNight == 1) {
setLights(HIGH, blinkState, LOW, LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, LOW);
} else {
setLights(HIGH, LOW, LOW, LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, LOW);
}
// Ultrasonic Sensor Check
if ((currentTime - stateStartTime >= 13000) && (sensorReadExecuted == 0)) {
sensorReadExecuted = 1;
long dist = readDistance();
if (dist >= 5 && dist <= 250 && activeCrossing == 0) {
stateDuration = stateDuration + 5000;
isBusyTraffic = 1;
}
}
if (currentTime - stateStartTime >= stateDuration) {
changeState(STATE_MAIN_YELLOW, 3000);
}
break;
case STATE_MAIN_YELLOW:
setLights(LOW, HIGH, LOW, LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, LOW);
if (currentTime - stateStartTime >= stateDuration) {
nextTargetState = STATE_SIDE1_GREEN;
changeState(STATE_ALL_RED_BUFFER, 2000);
}
break;
case STATE_ALL_RED_BUFFER:
if (isNight == 1) {
setLights(LOW, blinkState, HIGH, LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, LOW);
} else {
setLights(LOW, LOW, HIGH, LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, LOW);
}
if (currentTime - stateStartTime >= stateDuration) {
if (activeCrossing == 1 && nextTargetState == STATE_SIDE1_GREEN) {
changeState(STATE_PED_WALK, 10000);
} else {
if (nextTargetState == STATE_MAIN_GREEN) {
activeCrossing = 0;
sensorReadExecuted = 0;
isBusyTraffic = 0;
}
changeState(nextTargetState, 15000);
}
}
break;
case STATE_PED_WALK:
tone(PIEZO_PIN, 1000);
setLights(LOW, LOW, HIGH, LOW, LOW, HIGH, LOW, LOW, HIGH, LOW, HIGH);
if (currentTime - stateStartTime >= stateDuration) {
noTone(PIEZO_PIN);
changeState(STATE_SIDE1_GREEN, 15000);
}
break;
case STATE_SIDE1_GREEN:
if (isNight == 1) {
setLights(LOW, LOW, HIGH, HIGH, blinkState, LOW, LOW, LOW, HIGH, HIGH, LOW);
} else {
setLights(LOW, LOW, HIGH, HIGH, LOW, LOW, LOW, LOW, HIGH, HIGH, LOW);
}
if (currentTime - stateStartTime >= stateDuration) {
changeState(STATE_SIDE1_YELLOW, 3000);
}
break;
case STATE_SIDE1_YELLOW:
setLights(LOW, LOW, HIGH, LOW, HIGH, LOW, LOW, LOW, HIGH, HIGH, LOW);
if (currentTime - stateStartTime >= stateDuration) {
nextTargetState = STATE_SIDE2_GREEN;
changeState(STATE_ALL_RED_BUFFER, 2000);
}
break;
case STATE_SIDE2_GREEN:
if (isNight == 1) {
setLights(LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, blinkState, LOW, HIGH, LOW);
} else {
setLights(LOW, LOW, HIGH, LOW, LOW, HIGH, HIGH, LOW, LOW, HIGH, LOW);
}
if (currentTime - stateStartTime >= stateDuration) {
changeState(STATE_SIDE2_YELLOW, 3000);
}
break;
case STATE_SIDE2_YELLOW:
setLights(LOW, LOW, HIGH, LOW, LOW, HIGH, LOW, HIGH, LOW, HIGH, LOW);
if (currentTime - stateStartTime >= stateDuration) {
nextTargetState = STATE_MAIN_GREEN;
changeState(STATE_ALL_RED_BUFFER, 2000);
}
break;
}
}
/* --- Helper Functions --- */
void changeState(TrafficState next, unsigned long duration) {
currentState = next;
stateStartTime = millis();
stateDuration = duration;
lastRemainingSeconds = -1;
}
void printLCD(const char* line1, const char* line2, int timeVal) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(line1);
lcd.setCursor(0, 1);
lcd.print(line2);
lcd.print(timeVal);
lcd.print("s");
}
void setLights(int mG, int mY, int mR, int s1G, int s1Y, int s1R, int s2G, int s2Y, int s2R, int pR, int pG) {
digitalWrite(MAIN_GREEN, mG);
digitalWrite(MAIN_YELLOW, mY);
digitalWrite(MAIN_RED, mR);
digitalWrite(SIDE1_GREEN, s1G);
digitalWrite(SIDE1_YELLOW, s1Y);
digitalWrite(SIDE1_RED, s1R);
digitalWrite(SIDE2_GREEN, s2G);
digitalWrite(SIDE2_YELLOW, s2Y);
digitalWrite(SIDE2_RED, s2R);
digitalWrite(PED_RED, pR);
digitalWrite(PED_GREEN, pG);
}
void handleButtonPress() {
buttonPressed = 1;
}
long readDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) {
return -1;
} else {
return (duration * 0.0343) / 2;
}
}