/* ==============================================================
Smart Traffic-Signal Optimizer – 4-way intersection
• IR sensors detect waiting vehicles
• Dynamic green time = (Pr / ΣPr) * C (C = totalCycle)
• Non-blocking, real-time, debuggable
============================================================== */
///////////////////// CONFIGURATION ////////////////////////////
const uint8_t NUM_LANES = 4;
// ---- Pin assignments ------------------------------------------------
const uint8_t irPins[NUM_LANES] = {2, 3, 4, 5}; // IR sensors
const uint8_t redPins[NUM_LANES] = {6, 9, 12, A0}; // adjust if needed
const uint8_t yellowPins[NUM_LANES] = {7, 10, 13, A1};
const uint8_t greenPins[NUM_LANES] = {8, 11, 14, A2};
// ---- Timing parameters -----------------------------------------------
const unsigned long TOTAL_CYCLE_MS = 60UL * 1000UL; // 60 s
const unsigned long YELLOW_MS = 3UL * 1000UL; // 3 s
const unsigned long MIN_GREEN_MS = 5UL * 1000UL;
const unsigned long MAX_GREEN_MS = 30UL * 1000UL;
const unsigned long SAMPLE_WINDOW_MS = 10UL * 1000UL; // for arrival rate λ
const unsigned long ALL_RED_GUARD_MS = 1UL * 1000UL; // safety pause
// ---- Priority formula weights -----------------------------------------
const float ALPHA = 2.0; // weight for waiting time
///////////////////// GLOBAL STATE /////////////////////////////
struct Lane {
// Sensors & counts
bool carPresent = false; // current IR reading (debounced)
uint16_t qr = 0; // cars waiting (queue length)
uint16_t lambdaR = 0; // arrivals in the current window
unsigned long tr = 0; // cumulative waiting time (ms)
// Timing
unsigned long lastArrivalSample = 0; // start of current λ window
unsigned long waitStartMs = 0; // when a car first appeared
// Light control (state machine)
enum class Phase { RED, GREEN, YELLOW, ALLRED } phase = Phase::RED;
unsigned long phaseStartMs = 0;
unsigned long greenMs = MIN_GREEN_MS; // will be recomputed each cycle
};
Lane lanes[NUM_LANES];
// ---------------------------------------------------------------------
// Helper: constrain a value
template<typename T> T clamp(T val, T lo, T hi) {
return (val < lo) ? lo : (val > hi) ? hi : val;
}
///////////////////// SETUP ///////////////////////////////////
void setup() {
[Link](115200);
while (!Serial) { /* wait for serial */ }
for (uint8_t i = 0; i < NUM_LANES; ++i) {
pinMode(irPins[i], INPUT_PULLUP); // assume active-LOW sensors
pinMode(redPins[i], OUTPUT);
pinMode(yellowPins[i], OUTPUT);
pinMode(greenPins[i], OUTPUT);
// start all lanes in RED
digitalWrite(redPins[i], HIGH);
digitalWrite(yellowPins[i], LOW);
digitalWrite(greenPins[i], LOW);
}
[Link](F("\n=== Smart Traffic Optimizer Ready ==="));
}
///////////////////// MAIN LOOP ///////////////////////////////
void loop() {
unsigned long now = millis();
// 1. Read sensors & update counts
updateSensors(now);
// 2. Update waiting times for lanes that still have cars
updateWaitingTimes(now);
// 3. Compute priorities & green allocations (once per full cycle)
static unsigned long lastAllocation = 0;
if (now - lastAllocation >= TOTAL_CYCLE_MS) {
calculatePrioritiesAndGreenTimes();
lastAllocation = now;
printDebugInfo();
}
// 4. Run the light state-machine for every lane
runLightStateMachine(now);
}
///////////////////// SENSOR HANDLING /////////////////////////
void updateSensors(unsigned long now) {
for (uint8_t i = 0; i < NUM_LANES; ++i) {
// ---- Debounce IR (simple software debounce) ----
bool raw = (digitalRead(irPins[i]) == LOW); // LOW = car detected
static bool lastState[NUM_LANES] = {false};
static unsigned long lastChange[NUM_LANES] = {0};
const unsigned long DEBOUNCE_MS = 50;
if (raw != lastState[i] && (now - lastChange[i]) > DEBOUNCE_MS) {
lastState[i] = raw;
lastChange[i] = now;
if (raw) { // car just arrived
lanes[i].carPresent = true;
if (lanes[i].qr == 0) lanes[i].waitStartMs = now; // first car
lanes[i].qr++;
// count for arrival rate λ
if (now - lanes[i].lastArrivalSample >= SAMPLE_WINDOW_MS) {
lanes[i].lambdaR = 0; // reset window
lanes[i].lastArrivalSample = now;
}
lanes[i].lambdaR++;
} else { // car left (or never there)
lanes[i].carPresent = false;
// Do NOT clear qr/tr here – we clear them only after a successful green.
}
}
}
}
///////////////////// WAITING TIME ////////////////////////////
void updateWaitingTimes(unsigned long now) {
for (uint8_t i = 0; i < NUM_LANES; ++i) {
if (lanes[i].carPresent) {
// add elapsed time since last check
lanes[i].tr += (now - lanes[i].waitStartMs);
lanes[i].waitStartMs = now; // reset for next tick
}
}
}
///////////////////// PRIORITY & GREEN CALC ///////////////////
void calculatePrioritiesAndGreenTimes() {
float Pr[NUM_LANES] = {0};
float sumPr = 0.0f;
for (uint8_t i = 0; i < NUM_LANES; ++i) {
// Pr = qr + λ·C + α·(tr/C)
float waitingSec = lanes[i].tr / 1000.0f;
Pr[i] = lanes[i].qr +
(lanes[i].lambdaR * (TOTAL_CYCLE_MS / 1000.0f)) +
(ALPHA * waitingSec / (TOTAL_CYCLE_MS / 1000.0f));
sumPr += Pr[i];
}
// ---- allocate green time proportionally ----
if (sumPr < 1e-6) { // no traffic → equal split
for (uint8_t i = 0; i < NUM_LANES; ++i) {
lanes[i].greenMs = TOTAL_CYCLE_MS / NUM_LANES;
}
} else {
for (uint8_t i = 0; i < NUM_LANES; ++i) {
float raw = (Pr[i] / sumPr) * TOTAL_CYCLE_MS;
// enforce min/max and leave room for yellow + guard
unsigned long maxAllowed = TOTAL_CYCLE_MS - (YELLOW_MS +
ALL_RED_GUARD_MS);
lanes[i].greenMs = clamp((unsigned long)raw, MIN_GREEN_MS,
min(MAX_GREEN_MS, maxAllowed));
}
}
}
///////////////////// LIGHT STATE MACHINE /////////////////////
void runLightStateMachine(unsigned long now) {
for (uint8_t i = 0; i < NUM_LANES; ++i) {
Lane &ln = lanes[i];
// ---- Helper to set LED states ----
auto setLeds = [&](bool r, bool y, bool g) {
digitalWrite(redPins[i], r ? HIGH : LOW);
digitalWrite(yellowPins[i], y ? HIGH : LOW);
digitalWrite(greenPins[i], g ? HIGH : LOW);
};
switch ([Link]) {
case Lane::Phase::RED: {
setLeds(true, false, false);
// Start green for this lane when it is its turn.
// Simple round-robin: each lane gets its slot sequentially.
// (Could be replaced by a more sophisticated scheduler.)
static uint8_t currentLane = 0;
if (i == currentLane) {
[Link] = Lane::Phase::GREEN;
[Link] = now;
setLeds(false, false, true);
}
break;
}
case Lane::Phase::GREEN: {
if (now - [Link] >= [Link]) {
[Link] = Lane::Phase::YELLOW;
[Link] = now;
setLeds(false, true, false);
}
break;
}
case Lane::Phase::YELLOW: {
if (now - [Link] >= YELLOW_MS) {
[Link] = Lane::Phase::ALLRED;
[Link] = now;
setLeds(false, false, false); // all off (safety)
}
break;
}
case Lane::Phase::ALLRED: {
if (now - [Link] >= ALL_RED_GUARD_MS) {
// Clear queue & waiting time because the lane just had a green phase
[Link] = 0;
[Link] = 0;
[Link] = 0;
// Move to next lane for next cycle
[Link] = Lane::Phase::RED;
setLeds(true, false, false);
// advance round-robin pointer (wrap around)
static uint8_t currentLane = 0;
currentLane = (currentLane + 1) % NUM_LANES;
}
break;
}
} // switch
} // for
}
///////////////////// DEBUG OUTPUT ////////////////////////////
void printDebugInfo() {
[Link](F("\n--- Cycle Update ----------------"));
[Link](F("Lane | qr | λ | tr(s) | Pr | green(s)"));
for (uint8_t i = 0; i < NUM_LANES; ++i) {
float prVal = lanes[i].qr +
(lanes[i].lambdaR * (TOTAL_CYCLE_MS / 1000.0f)) +
(ALPHA * (lanes[i].tr / 1000.0f) / (TOTAL_CYCLE_MS / 1000.0f));
[Link](i);
[Link](F(" | "));
[Link](lanes[i].qr);
[Link](F(" | "));
[Link](lanes[i].lambdaR);
[Link](F(" | "));
[Link](lanes[i].tr / 1000.0f, 1);
[Link](F(" | "));
[Link](prVal, 2);
[Link](F(" | "));
[Link](lanes[i].greenMs / 1000.0f, 1);
[Link]();
}
}