Traffic Light Controller with Interrupts Help

Hello,
I'm using an Arduino Uno to control some traffic lights that I have hanging in my garage. I have two "light pattern modes" that I can run the lights in based on a toggle switch that I have attached to PIN 2. In general, it's working fine. But I'm trying to make it so it instantly jumps to that mode regardless of where the main program is at. The way it runs now, it will only make the change at the start of the while loop. Could someone take a look at my code and let me what I'm doing wrong? I have been playing with the interrupts but I'm not having much luck. Attached is the sketch. ** Disclaimer ** I'm very new to this so please forgive any novice errors you might see. :slight_smile:

Stoplights_w_while.ino (5.24 KB)

The way it runs now, it will only make the change at the start of the while loop. Could someone take a look at my code and let me what I'm doing wrong?

The problem is that you are using a while loop and the problem is compounded by using delay()s within the while loop. To make the code as horrible as possible you do not use for loops either, not that using them would solve the problem.

So, how to solve the problem of making the program responsive to inputs ? Well, you have helpfully divided your program into what you call phases. I would call them states and use switch/case to control which portion of code is executed each time through loop(). Use millis() for timing and the code will run freely so that you can read inputs each time through loop(). This form of program is often referred to as a State Machine. It sounds scary but isn't

Have a look at Using millis() for timing. A beginners guide, Several things at the same time and look at the BlinkWithoutDelay example in the IDE to see how to use millis() for non blocking timing.

As to State Machines, have a look at this example. It does not do exactly what you want but it does introduce the idea of using states to control which code is executed and of doing something else in loop(). You can run it without any LEDs and monitor what it is doing using the Serial monitor.

/*
 * State machine example
 * Requirement :
 * Blink one LED at a steady rate whilst turning on a series of LEDs for periods that are
 * different for each LED
 *
 * The single blinking LED is implemented using the BlinkWithoutDelay principle so that its
 * operation does not slow down or stop the operation of the remainder of the program
 *
 * Control of the other 3 LEDs is controlled using a "State Machine".
 * The program is in one of 3 states during which one of the LEDs is illuminated for
 * a period of time.  Again timing is implemented using millis().  Once the period for the
 * current state is over the program moves onto the next state in the series.
 *
 * NOTE : this is not necessarily the best way to write a program to meet the requirements
 * but it is written this way to illustrate the use of a "State Machine".  The Serial monitor
 * is used for feedback as to what is happening in the program so it can be tested without LEDs
 * Instead of turning an LED on for a period in each state any appropriate non blocking code for the current
 * state could be run such as testing for user input and changing state if it is detected.
 */

enum ledStates    //give the states names (actually numbers 0 to 2) to make reading the code easier
{
  REDLED,
  GREENLED,
  BLUELED
};

byte currentState;
const byte blinkLedPin = 3;
const byte redLedPin = 5;
const byte greenLedPin = 6;
const byte blueLedPin = 9;
const byte sequenceLedPins[] = {redLedPin, greenLedPin, blueLedPin};
unsigned long sequencePeriods[] = {1500, 3500, 5500};
const byte NUMBER_OF_LEDS = sizeof(sequenceLedPins) / sizeof(sequenceLedPins[0]);
unsigned long currentTime;
unsigned long blinkLedStartTime;
unsigned long sequenceLedStartTime;
unsigned long blinkLedPeriod = 300;

void setup()
{
  Serial.begin(115200);
  for (int led = 0; led < NUMBER_OF_LEDS; led++)    //set pinMode()s for LEDs
  {
    pinMode(sequenceLedPins[led], OUTPUT);
    digitalWrite(sequenceLedPins[led], HIGH); //turn all sequence LEDs off initially
  }
  pinMode(blinkLedPin, OUTPUT);
  digitalWrite(blinkLedPin, HIGH);  //turn off the blinkLed initially
  digitalWrite(sequenceLedPins[currentState], LOW);    //turn on the sequence LED for initial state
  currentState = REDLED;    //start in this state
  reportState();
}

void loop()
{
  currentTime = millis();   //used throughout loop() for consistent timing
  //let's start with the single blinking LED
  if (currentTime - blinkLedStartTime >= blinkLedPeriod)  //time to change the single LED state
  {
    digitalWrite(blinkLedPin, !digitalRead(blinkLedPin));
    blinkLedStartTime = currentTime;
    Serial.println(F("\tBLINK"));
  }
  //now we need to check which state we are in and run the appropriate code for it
  //note that by using millis() for timing we keep loop() running freely
  //so that we can blink the single LED
  //when the priod for the current state ends we set the entry conditions for the next state,
  //change the current state so that next time through the code for the next state is executed
  switch (currentState)
  {
    case REDLED:
      //when the priod for the current state ends we set the entry conditions for the next state,
      //change the current state so that next time through the code for the next state is executed
      if (currentTime - sequenceLedStartTime >= sequencePeriods[currentState])  //time to change states
      {
        digitalWrite(sequenceLedPins[currentState], HIGH);    //turn off current LED
        sequenceLedStartTime = currentTime;   //start time for next state
        currentState = GREENLED;    //next state to move to
        reportState();
        digitalWrite(sequenceLedPins[currentState], LOW);    //turn on LED for target state
      }
      break;  //end of code for this state
    //
    case GREENLED:
      if (currentTime - sequenceLedStartTime >= sequencePeriods[currentState])  //time to change states
      {
        digitalWrite(sequenceLedPins[currentState], HIGH);    //turn off current LED
        sequenceLedStartTime = currentTime;   //start time for next state
        currentState = BLUELED;    //next state to move to
        reportState();
        digitalWrite(sequenceLedPins[currentState], LOW);    //turn on LED for target state
      }
      break;  //end of code for this state
    //
    case BLUELED:
      if (currentTime - sequenceLedStartTime >= sequencePeriods[currentState])  //time to change states
      {
        digitalWrite(sequenceLedPins[currentState], HIGH);    //turn off current LED
        sequenceLedStartTime = currentTime;   //start time for next state
        currentState = REDLED;    //next state to move to
        reportState();
        digitalWrite(sequenceLedPins[currentState], LOW);    //turn on LED for target state
      }
      break;  //end of code for this state
  }
}

void reportState()
{
  Serial.print(F("Now in state "));
  Serial.print(currentState);
  Serial.print(F(" for "));
  Serial.print(sequencePeriods[currentState]);
  Serial.println(F(" milliseconds"));
}

Thank you very much for the feedback! I'll work on this.
Chad

Hi UKHeliBob,
Thanks again for your assistance. I have completely rewritten my sketch from the ground up using the Switch/case method as you suggested. I attached my latest attempt. It's currently running my lights perfectly. I have not written anything in the sketch yet about the toggle switch mode. I just wanted to get the basics workin first.

I do have a question however. On a normal traffic light, the don't walk sign will blink X amount of times before it goest solid red. The only way I could make it work (with my limited knowledge) with this method was to create a "case" for every on and off event. Not a big deal because I only wanted it to blink 10 times. But I can imagine this getting very messy if someone wanted it to happen 100 times. Any suggestions on how I could improve the "don't walk" blink part of my code?

Thanks in advance.
Chad

Stoplight_2_0.ino (9.96 KB)

It's currently running my lights perfectly

Well done. That is good news

Any suggestions on how I could improve the "don't walk" blink part of my code?

Sure. In the don't walk case have a counter that is incremented each time the DW LED changes state and exit the case when the counter reaches the required blink count. That way you only need one case

Hi UKHeliBob,
Worked on the "Don't walk" blinking case today. This is what I came up with if you were wondering. I had to do some t-shooting with the serial monitor to catch a few bugs but this seems to be working now...
Thanks again!
Chad

case Side1DW: //Side 1 Don't Walk


if ((dwcounter < 30) && (currentTime - sequenceLedStartTime >= 500)) 
{ 
    if (DWState == 'L')
          {
          digitalWrite(leddw, HIGH);
          DWState = 'H';
          goto skip; //Skips the next step.
          }
          
    if (DWState == 'H')
          {
          digitalWrite(leddw, LOW);
          DWState = 'L';
          }

      skip:
      sequenceLedStartTime = currentTime;   //start time for next state
      dwcounter++;
}

if (dwcounter >= 30) // Check to see if it's time to move on.
{
 currentState = Side1Yellow;
 dwcounter = 0; //Reset DW Counter
 sequenceLedStartTime = currentTime;   //start time for next state
}
break;
goto skip;

YUK !

Why not

  if (DWState == 'L')
  {
    digitalWrite(leddw, HIGH);
    DWState = 'H';
  }
  else if (DWState == 'H')
  {
    digitalWrite(leddw, LOW);
    DWState = 'L';
  }

Even better, if you make DWState a byte with a value of HIGH or LOW instead of a char you could replace the lot with just 2 statements

DWState = !DWState;  //invert the state
digitalWrite(leddw, DWState);

Can this traffic light kit be used indoors?
------------------------trẻ hóa vùng kín

alexle228:
Can this traffic light kit be used indoors?
------------------------trẻ hóa vùng kín

It is not clear whether the OP is using real traffic lights but even if he is then why not use them indoors if you have enough space

Hi alexle228,
They are real, old retired traffic lights. I have an Arduino UNO controlling them. It is connected to a 8 Channel, 5v Relay Module. I keep them in my garage. My wife prohibits them from entering the house.. I attached a few old photos. I don't have a recent the shows the walk/don't walk light.
Chad