Traffic Light Control with millis timing issue

I've been working on a project involving a traffic light sequence using timing rather than delay so that I may mesh together several programs.

This code currently has the traffic light loops and a bridge strobe light loop.

I'm having a hard time with the sequence of the traffic lights, the bridge light operates as desired.

I would like for the LED outputs to work in relation to each other rather than the time since the program began running.

If I only program one traffic light, meaning one of each red, yellow and green, I am fine. It is when I try to add a traffic light for the other direction, which would need to operate opposite of the first traffic light, that I have trouble.

The timing I have figured gives me a good traffic light pattern, however the way I have it programmed is wrong; i need the timing to occur in sequence not all at once.

I also would like the Red leds to overlap as they do in a prototypical traffic light so I can't get the opposite traffic light to function with just the wiring (I wish it were that simple)

I've been learning how to use the millis function but am not quite to the point of solving this timing issue.

My questions are;

Am I correct in using millis for this project or should I be using an array or some other functions in conjunction?

If I am using the right function, is there a way to use the previousMillis function to compare the time elapsed to that of another action rather than the currentMillis? Maybe by using an if statement?

To help myself I have played with the blink without delay example and the multiple things at once example as well as reading another similar post on this forum and trial and error with millis and programming the Arduino in general. I've also looked at and played with programming created by others to help me learn how millis works.

// This program controls all the traffic lights at one intersection as well as
// a single red strobe light on top of a bridge
// program assumes common Anode LED configuration

// --------CONSTANTS (won't change)---------------
#define ledON LOW
#define ledOFF HIGH

const int NSG = 2;      // the pin numbers for the LEDs
const int NSY = 3;
const int NSR = 4;
const int EWG = 5;
const int EWY = 6;
const int EWR = 9;
const int bridgelight = 8;

const int bridgelight_off_time = 1000; // How long the bridge light is off
const int bridgelight_on_time = 25; // how long the bridge light is on

const int NSG_on_time = 10000; // How long the northsouth green LED is on
const int NSG_off_time = 18000; // How long the northsouth Green LED is off

const int NSY_on_time = 2000; // northsouth yellow 
const int NSY_off_time = 26000; //northsouth yellow 

const int NSR_on_time = 16000; //northsouth red on
const int NSR_off_time = 12000; //northsouth red off

const int EWG_on_time = 10000; // same as above, but ewastwest leds
const int EWG_off_time = 180000;

const int EWY_on_time = 2000;
const int EWY_off_time = 26000; 

const int EWR_on_time = 16000;
const int EWR_off_time = 12000;

//------------ VARIABLES (will change)---------------------

byte NSG_State = ledON;             // used to record whether the LEDs are on or off
byte NSY_State = ledOFF;          
byte NSR_State = ledOFF;
byte EWG_State = ledOFF;             // used to record whether the LEDs are on or off
byte EWY_State = ledOFF;          
byte EWR_State = ledON;
byte bridgelight_State = ledOFF;



unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long NSG_previousMillis = 0;   // will store last time the LED was updated for each iteration of loop
unsigned long NSR_previousMillis = 0;
unsigned long EWG_previousMillis = 0;   // will store last time the LED was updated for each iteration of loo
unsigned long EWR_previousMillis = 0;
unsigned long NSY_previousMillis = 0;
unsigned long EWY_previousMillis = 0;
unsigned long bridge_previousMillis = 0;

void setup() {

  Serial.begin(9600);
    
      // set the Led pins as output:
  pinMode(NSG, OUTPUT);
  pinMode(NSY, OUTPUT);
  pinMode(NSR, OUTPUT);
  pinMode(EWG, OUTPUT);
  pinMode(EWY, OUTPUT);
  pinMode(EWR, OUTPUT);  
  pinMode(bridgelight,OUTPUT);    
}

void loop() {

  currentMillis = millis();
   
  NSG_update();
  NSY_update();
  NSR_update();
  EWG_update();
  EWY_update();
  EWR_update();
  switchLeds();
  bridgeflash();
}

void NSG_update() {

  if (NSG_State == ledOFF) {
          // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - NSG_previousMillis >= NSG_off_time) {
       NSG_State = ledON;
       NSG_previousMillis += NSG_off_time;
    }
  }
  else {
    if (currentMillis - NSG_previousMillis >= NSG_on_time) {
       NSG_State = ledOFF;
       NSG_previousMillis += NSG_on_time;
    } 
  }
}

void NSY_update() {

  if (NSY_State == ledOFF) {
          // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - NSY_previousMillis >= NSY_off_time) {
       NSY_State = ledON;
       NSY_previousMillis += NSY_off_time;
    }
  }
  else {
    if (currentMillis - NSY_previousMillis >= NSY_on_time) {
       NSY_State = ledOFF;
       NSY_previousMillis += NSG_on_time;
    } 
  }
}    

void NSR_update() {

  if (NSR_State == ledOFF) {
    if (currentMillis - NSR_previousMillis >= NSR_off_time) {
       NSR_State = ledON;
       NSR_previousMillis += NSR_off_time;
    }
  }
  else {
    if (currentMillis - NSR_previousMillis >= NSR_on_time) {
       NSR_State = ledOFF;
       NSR_previousMillis += NSR_on_time;
    }
  }    
}

void EWG_update() {

  if (EWG_State == ledOFF) {
          // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - EWG_previousMillis >= EWG_off_time) {
       EWG_State = ledON;
       EWG_previousMillis += EWG_off_time;
    }
  }
  else {
    if (currentMillis - EWG_previousMillis >= EWG_on_time) {
       EWG_State = ledOFF;
       EWG_previousMillis += EWG_on_time;
    } 
  }
}

void EWY_update() {

  if (EWY_State == ledOFF) {
          // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - EWY_previousMillis >= EWY_off_time) {
       EWY_State = ledON;
       EWY_previousMillis += EWY_off_time;
    }
  }
  else {
    if (currentMillis - EWY_previousMillis >= EWY_on_time) {
       EWY_State = ledOFF;
       EWY_previousMillis += EWY_on_time;
    } 
  }
}       

void EWR_update() {

  if (EWR_State == ledOFF) {
    if (currentMillis - EWR_previousMillis >= EWR_off_time) {
       EWR_State = ledON;
       EWR_previousMillis += EWR_off_time;
    }
  }
  else {
    if (currentMillis - EWR_previousMillis >= EWR_on_time) {
       EWR_State = ledOFF;
       EWR_previousMillis += EWR_on_time;
    }
  }    
}

void bridgeflash()
{
if (bridgelight_State == ledOFF) {
    if (currentMillis - bridge_previousMillis >= bridgelight_off_time) {
       bridgelight_State = ledON;
       bridge_previousMillis += bridgelight_off_time;
      }
}
  else {
    if (currentMillis - bridge_previousMillis >= bridgelight_on_time) {
       bridgelight_State = ledOFF;
       bridge_previousMillis += bridgelight_on_time;
    }
  }
}   
void switchLeds() {
      // this is the code that actually switches the LEDs on and off

  digitalWrite(NSG, NSG_State);
  digitalWrite(NSY, NSY_State);
  digitalWrite(NSR, NSR_State);
  digitalWrite(EWG, EWG_State);
  digitalWrite(EWY, EWY_State);
  digitalWrite(EWR, EWR_State);
  digitalWrite(bridgelight, bridgelight_State); 
}

i need the timing to occur in sequence not all at once.

it looks like you independently time each light. I think it would be much easier to do this as a sequence of states.

each state determines which lights are on and how long to remain in that state before changing states. This includes a state where lights in both directions are red for a short period of time.

without considering what the bridge requires, this is a simple sequencer, going thru the same sequence of states repeatedly.

very little code may be required. You could have a table (array of structs) specifying the state of the lamps in each state and the time to remain in the state. The table index wraps from the last table entry.

I'm no guru but the traffic light project has been brought up enough times that I have put a little thought into how I might handle it. And I think I would use the switch/case method. Then use the millis timing function to switch cases.

Something like (not complete code)

switch(currentStage){
   case 0:
     //add code to set lights red all directions
     if(startNextSequence == 1){
         startMillis = millis();
      }
     delaytime = 5000;
     timingFunction(delayTime);
   case 1:
      //add code to set lights north and south green
     if(startNextSequence == 1){
         startMillis = millis();
      }
      delay time = 30000;
      timingFunction(delayTime);

Then do something like

void timingFunction(myDelay){
    startNextSequence = 0;
    unsigned long currentMillis = millis();
    if(currentMillis - startMillis >= myDelay){
        currentStage++;
        startNextSequence = 1;
     }
}

Like I said, it's by no means complete. But I think it gets the point across.

I'm sure there may be better ways.

assuming that examples are a good way to learn, how about this code i tested on my laptop.

the Light_t enums could be for each lamp, not just color

typedef enum {
    Off,
    Red,
    Yellow,
    Green
} Light_t;

const char * lightStr [] = {
    "",
    "\033[31mRed\033[0m",
    "\033[33mYellow\033[0m",
    "\033[32mGreen\033[0m"
};

typedef struct  {
    Light_t     dirA;
    Light_t     dirB;
    int         sec;
} Pattern_t;

Pattern_t pattern [] = {
    { Green,  Red,    5 },
    { Yellow, Red,    1 },
    { Red,    Red,    1 },
    { Red,    Green,  3 },
    { Red,    Yellow, 1 },
    { Red,    Red,    1 },
};
#define PatSize  (sizeof(pattern)/ sizeof(Pattern_t))

void
application (char *filename)  {
    int  i = 0;
    while (true)  {
        Pattern_t  *p = & pattern [i];

        printf ("%20s %15s %15s\n",
            "", lightStr [p->dirA], lightStr [p->dirB]);
        sleep (p->sec);
        if (PatSize <= ++i)
            i = 0;
    }
}

Thank you for all the ideas to try. I appreciate the help. Looks like I am making things more complicated than they need to be. But that is part of the learning process I suppose. I will play with the program some more and will post my results and, hopefully, a successful code to share with others who have a similar project in mind.