4 Way Traffic Light with buttons

Hi there,

I am currently working on a project seeking to recreate a 4-way traffic intersection with pedestrian crossings. So far I have the lights set up to change following the UK light pattern without using delays. I am designing this with the assumption that cars will only move straight across the intersection. Pedestrians will have a button to either cross vertically or horizontally on each corner (a total of 8 pushbuttons) and only one crossing direction can be clear at a time. I'm unsure how to write a statement to allow the buttons to be used. Any help or nod in the right direction would be greatly appreciated!

Thanks!

int horizontalCrossing = 7;
int verticalCrossing = 8;
//vertical 
int redv= 1;
int yellowv= 2;
int greenv= 3;
// horizontal 
int redh= 4;
int yellowh= 5;
int greenh= 6;


enum phases {
 red, redYellow, green, yellow, red2, redYellow2, green2, yellow2
};

phases phase, nextPhase;
unsigned long lightsTimer = 0;

void setup() {
  pinMode(horizontalCrossing, INPUT);
  pinMode(verticalCrossing, INPUT);
  pinMode(redv,OUTPUT);
  pinMode(yellowv,OUTPUT);
  pinMode(greenv,OUTPUT);
  pinMode(redh,OUTPUT);
  pinMode(yellowh,OUTPUT);
  pinMode(greenh,OUTPUT);
  
  phase = red;
  
}

void loop() {
  phaseChange();
}


  
 

void phaseChange() {
  if(millis() >= lightsTimer) {
    phase = nextPhase;
    switch(phase) {
      case red:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
      
        nextPhase = redYellow;
        lightsTimer = millis() + 7000UL;
        break;
      case redYellow:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, HIGH);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = green;
        lightsTimer = millis() + 1000UL;
        break;
      case green:
        digitalWrite(redv, LOW);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, HIGH);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = yellow;
        lightsTimer = millis() + 5000UL;
        break;
      case yellow:
        digitalWrite(redv, LOW);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, HIGH);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = red2;
        lightsTimer = millis() + 1000UL;
        break;
      case red2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = redYellow2;
        lightsTimer = millis() + 7000UL;
        break;
      case redYellow2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, HIGH);
        digitalWrite(greenh, LOW);
        nextPhase = green2;
        lightsTimer = millis() + 1000UL;
        break;
      case green2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, LOW);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, HIGH);
        nextPhase = yellow2;
        lightsTimer = millis() + 5000UL;
        break;
      case yellow2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, LOW);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, HIGH);
        digitalWrite(greenh, LOW);
        nextPhase = red;
        lightsTimer = millis() + 1000UL;
        break;
      default:
        digitalWrite(8, HIGH);
        digitalWrite(9, HIGH);
        digitalWrite(10, HIGH);
      break;
    }
  }
}

The button code has to respect the current state. On some states it may be allowed to truncate the delay.

You may run into problems with that form of delay. See the BlinkWithouDelay example for the preferred use of time intervals.

Every time through loop(), you should read the state of the buttons. Then, you can either choose to start a light sequence or whatever you want. You will want to look at the State Change Detection example (File->examples->02.digital->State Change Detection) so you will know when a crosswalk button was pressed, not if the button is currently pressed.

Also, how are your buttons wired up? The typical method is one side to ground, the other to the arduino pin and then declare the pin as INPUT_PULLUP and then it is HIGH when not pressed and LOW when pressed.

1 Like

When a button gets pressed, you could force your state to yellowv (and vice versa the other button to yellowh).
This will bring your state machine in the right state.
and after the yellow time is over, the traffic light should become red.

Additionally you could block the button read out to be active only in specific states (e.g. only if the cars have green the pedestrains can request a forced switch to yellow).

1 Like

Thanks for your replies!

I will take a look at that now.

Thanks for this as well I didn't know that!

That's a good idea!

two add ons:

don't do this:

        digitalWrite(8, HIGH);
        digitalWrite(9, HIGH);
        digitalWrite(10, HIGH);

use your pin variables - not magic numbers in the code.

and by the way, if I draw a diagram, which light should be which color, I end up with only 4 states

carsVertical    carsHorizontal    pedestrainVertical   pedestrainHorizontal
0 red             green             green                red
1 red yellow      yellow            red                  red
2 green           red               red                  green
3 yellow          red yellow        red                  red

4 states. One state defines all pictures for all 4 traffic lights

FYI, Use S3 as an example:

I'd add

  • all red (between direction switch, emergency passthrough)
  • all yellow blink (inactive)
  • all off (maintenance)

Fancy
:nerd_face:

What would this look like in terms of code?

switch

I currently have my switches wired as such, with the black to the shared ground and the pink going to pin 7. Is this correct? As I can't seem to pick up any input from my switches.

Rotate the switch by 90°. Pairs of contacts are connected internally with the button in between both pairs.

you define an array with two dimensions, put in your data and than you just iterate over this array when time is over.

I'm not so happy with the code for the buttons, but the maintenance of the sequence should be quite obvious.

// Traffic light simulation
// 
// https://forum.arduino.cc/t/4-way-traffic-light-with-buttons/931477/12
// simulation: https://wokwi.com/arduino/projects/316968080688284225
// states of one trafic light
// by noiasca
// 2021-12-03

enum class Lightstate {RED, REDYELLOW, YELLOW, GREEN};  // for future use: YELLOWBLINK, GREENBLINK, BLACK ...
// the patterns to be shown on the crossing
Lightstate pattern[4][4]         
{ //  carsVertical          carsHorizontal        pedestrainVertical pedestrainHorizontal
  {Lightstate::RED,       Lightstate::GREEN,      Lightstate::GREEN, Lightstate::RED},      // 0
  {Lightstate::REDYELLOW, Lightstate::YELLOW,     Lightstate::RED,   Lightstate::RED},      // 1
  {Lightstate::GREEN,     Lightstate::RED,        Lightstate::RED,   Lightstate::GREEN},    // 2
  {Lightstate::YELLOW,    Lightstate::REDYELLOW,  Lightstate::RED,   Lightstate::RED}       // 3
};
// one traffic light needs up to three pins:
struct Light {
  const byte redPin;
  const byte yellowPin;
  const byte greenPin;
};
// lets create 4 traffic lights (in reality there might be 8, but left/right, up/down are the same currently
Light light[4] {    // assign pins to the 4 lights:
  {2, 3, 4},        // carsVertical
  {4, 5, 6},        // carsHorizontal
  {7, 255, 8},      // pedestrainVertical (255 - no LED connected)
  {9, 255, 10}      // pedestrainHorizontal
};
constexpr byte requestVerticalPin = A0;                           // pedestrain request
constexpr byte requestHorizontalPin = A1;                         // pedestrain request
const size_t noOfLights = sizeof(light) / sizeof(light[0]);       // total number of (individual) traffic lights
const size_t noOfPatterns = sizeof(pattern) / sizeof(pattern[0]); // total number of traffic light pattern
uint32_t previousMillis = -5000;                                  // time management
size_t currentPattern = 0;                                        // actual state of crossing

void show(byte index, Lightstate lightstate) // helper function to output one traffic light
{
  byte red = LOW , green = LOW, yellow = LOW;
  switch (lightstate)
  {
    case Lightstate::RED :
      red = HIGH;
      break;
    case Lightstate::REDYELLOW :
      red = HIGH;
      yellow = HIGH;
      break;
    case Lightstate::GREEN :
      green = HIGH;
      break;
    case Lightstate::YELLOW :
      yellow = HIGH;
      break;
  }
  Serial.print(index); Serial.print(" "); Serial.print(" r"); Serial.print(red); Serial.print(" y"); Serial.print(yellow); Serial.print(" g"); Serial.println(green);
  if (light[index].redPin < 255)    digitalWrite(light[index].redPin, red);
  if (light[index].greenPin < 255)  digitalWrite(light[index].greenPin, green);
  if (light[index].yellowPin < 255) digitalWrite(light[index].yellowPin, yellow);
}

void timerTraffic()            // time management
{
  if (millis() - previousMillis > 3000)  {
    previousMillis = millis();
    if (++currentPattern >= noOfPatterns) currentPattern = 0;
    setPattern(currentPattern);
  }
}

void setPattern(byte newPattern)          // set a new state / pattern for the crossing
{
  currentPattern = newPattern;
  previousMillis = millis();
  Serial.print(F("currentPattern=")); Serial.println(currentPattern);
  for (size_t i = 0; i < noOfLights; i++) show(i, pattern[currentPattern][i]);  // activate the fitting pattern for each light
}

void readButton() 
{
  if (digitalRead(requestHorizontalPin) == LOW && currentPattern == 0) setPattern(1); // magic numbers ;-( 
  if (digitalRead(requestVerticalPin) == LOW && currentPattern == 2) setPattern(3);   // magic numbers ;-( 
}

void setup(void) 
{
  Serial.begin(115200);
  pinMode(requestHorizontalPin, INPUT_PULLUP);
  pinMode(requestVerticalPin, INPUT_PULLUP);
  for (size_t i = 0; i < noOfLights; i++) {
    if (light[i].redPin < 255) pinMode(light[i].redPin, OUTPUT);
    if (light[i].yellowPin < 255) pinMode(light[i].yellowPin, OUTPUT);
    if (light[i].greenPin < 255) pinMode(light[i].greenPin, OUTPUT);
    show(i, Lightstate::YELLOW);
  }
}

void loop(void) 
{
  timerTraffic();
  readButton();
}

thanks, I've done that!

with this bit of code added I should be getting the onboard LED lit when I press the button correct? As this isn't the case. Instead, the state of the switch is always LOW and the onboard LED is constantly lit.

int horizontalCrossing = 7;
int verticalCrossing = 8;
//vertical 
int redv= 1;
int yellowv= 2;
int greenv= 3;
// horizontal 
int redh= 4;
int yellowh= 5;
int greenh= 6;


enum phases {
 red, redYellow, green, yellow, red2, redYellow2, green2, yellow2
};

phases phase, nextPhase;
unsigned long lightsTimer = 0;

void setup() {
  Serial.begin(9600);
  pinMode(horizontalCrossing, INPUT_PULLUP);
  pinMode(verticalCrossing, INPUT_PULLUP);
  pinMode(redv,OUTPUT);
  pinMode(yellowv,OUTPUT);
  pinMode(greenv,OUTPUT);
  pinMode(redh,OUTPUT);
  pinMode(yellowh,OUTPUT);
  pinMode(greenh,OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  
  phase = red;
  
}

void loop() {
  phaseChange();
  if (digitalRead(horizontalCrossing) == LOW){
    
   digitalWrite(LED_BUILTIN, HIGH);
  }
  }
  



  
 

void phaseChange() {
  if(millis() >= lightsTimer) {
    phase = nextPhase;
    switch(phase) {
      case red:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
      
        nextPhase = redYellow;
        lightsTimer = millis() + 7000UL;
        break;
      case redYellow:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, HIGH);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = green;
        lightsTimer = millis() + 1000UL;
        break;
      case green:
        digitalWrite(redv, LOW);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, HIGH);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = yellow;
        lightsTimer = millis() + 5000UL;
        break;
      case yellow:
        digitalWrite(redv, LOW);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, HIGH);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = red2;
        lightsTimer = millis() + 1000UL;
        break;
      case red2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, LOW);
        nextPhase = redYellow2;
        lightsTimer = millis() + 7000UL;
        break;
      case redYellow2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, HIGH);
        digitalWrite(greenh, LOW);
        nextPhase = green2;
        lightsTimer = millis() + 1000UL;
        break;
      case green2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, LOW);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenh, HIGH);
        nextPhase = yellow2;
        lightsTimer = millis() + 5000UL;
        break;
      case yellow2:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, LOW);
        digitalWrite(yellowv, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(yellowh, HIGH);
        digitalWrite(greenh, LOW);
        nextPhase = red;
        lightsTimer = millis() + 1000UL;
        break;
      default:
        digitalWrite(redv, HIGH);
        digitalWrite(redh, HIGH);
        digitalWrite(yellowv, LOW);
        digitalWrite(yellowh, LOW);
        digitalWrite(greenv, LOW);
        digitalWrite(greenh, LOW);
      break;
    }
  }
}


  
  


  
  

Then you most probably use a connected pair of pins. Or you have both pins on the same contact row of the breadboard. What if you unplug the switch?

I had a switch wired the wrong way that was connected to the same pin!

Thank you very much for this! It is quite complex but I think I follow it. When I edit it to fit my pins and try to run it I get:

error: 'Lightstate' has not been declared

does the unmodified sketch from #12 compile? it should as it does on my side for an Arduino UNO.

did you accidentely delete my enumeration?

As is this doesn't compile for me, using UNO R3 on tinkercad. Get that same error.

// states of one trafic light
enum class Lightstate {RED, REDYELLOW, YELLOW, GREEN};  // for future use: YELLOWBLINK, GREENBLINK, BLACK ...
// the patterns to be shown on the crossing
Lightstate pattern[4][4]         
{ //  carsVertical          carsHorizontal        pedestrainVertical pedestrainHorizontal
  {Lightstate::RED,       Lightstate::GREEN,      Lightstate::GREEN, Lightstate::RED},      // 0
  {Lightstate::REDYELLOW, Lightstate::YELLOW,     Lightstate::RED,   Lightstate::RED},      // 1
  {Lightstate::GREEN,     Lightstate::RED,        Lightstate::RED,   Lightstate::GREEN},    // 2
  {Lightstate::YELLOW,    Lightstate::REDYELLOW,  Lightstate::RED,   Lightstate::RED}       // 3
};
// one traffic light needs up to three pins:
struct Light {
  const byte redPin;
  const byte yellowPin;
  const byte greenPin;
};
// lets create 4 traffic lights (in reality there might be 8, but left/right, up/down are the same currently
Light light[4] {    // assign pins to the 4 lights:
  {2, 3, 4},        // carsVertical
  {4, 5, 6},        // carsHorizontal
  {7, 255, 8},      // pedestrainVertical (255 - no LED connected)
  {9, 255, 10}      // pedestrainHorizontal
};
constexpr byte requestVerticalPin = A0;                           // pedestrain request
constexpr byte requestHorizontalPin = A1;                         // pedestrain request
const size_t noOfLights = sizeof(light) / sizeof(light[0]);       // total number of (individual) traffic lights
const size_t noOfPatterns = sizeof(pattern) / sizeof(pattern[0]); // total number of traffic light pattern
uint32_t previousMillis = -5000;                                  // time management
size_t currentPattern = 0;                                        // actual state of crossing

void show(byte index, Lightstate lightstate) // helper function to output one traffic light
{
  byte red = LOW , green = LOW, yellow = LOW;
  switch (lightstate)
  {
    case Lightstate::RED :
      red = HIGH;
      break;
    case Lightstate::REDYELLOW :
      red = HIGH;
      yellow = HIGH;
      break;
    case Lightstate::GREEN :
      green = HIGH;
      break;
    case Lightstate::YELLOW :
      green = HIGH;
      break;
  }
  Serial.print(index); Serial.print(" "); Serial.print(" r"); Serial.print(red); Serial.print(" y"); Serial.print(yellow); Serial.print(" g"); Serial.println(green);
  if (light[index].redPin < 255)    digitalWrite(light[index].redPin, red);
  if (light[index].greenPin < 255)  digitalWrite(light[index].greenPin, green);
  if (light[index].yellowPin < 255) digitalWrite(light[index].yellowPin, yellow);
}

void timerTraffic()            // time management
{
  if (millis() - previousMillis > 3000)  {
    previousMillis = millis();
    if (++currentPattern >= noOfPatterns) currentPattern = 0;
    setPattern(currentPattern);
  }
}

void setPattern(byte newPattern)          // set a new state / pattern for the crossing
{
  currentPattern = newPattern;
  previousMillis = millis();
  Serial.print(F("currentPattern=")); Serial.println(currentPattern);
  for (size_t i = 0; i < noOfLights; i++) show(i, pattern[currentPattern][i]);  // activate the fitting pattern for each light
}

void readButton() 
{
  if (digitalRead(requestHorizontalPin) == LOW && currentPattern == 0) setPattern(1); // magic numbers ;-( 
  if (digitalRead(requestVerticalPin) == LOW && currentPattern == 3) setPattern(2);   // magic numbers ;-( 
}

void setup(void) 
{
  Serial.begin(115200);
  pinMode(requestHorizontalPin, INPUT_PULLUP);
  pinMode(requestVerticalPin, INPUT_PULLUP);
  for (size_t i = 0; i < noOfLights; i++) {
    if (light[i].redPin < 255) pinMode(light[i].redPin, OUTPUT);
    if (light[i].yellowPin < 255) pinMode(light[i].yellowPin, OUTPUT);
    if (light[i].greenPin < 255) pinMode(light[i].greenPin, OUTPUT);
    show(i, Lightstate::YELLOW);
  }
}

void loop(void) 
{
  timerTraffic();
  readButton();
}

It compiles fine for me locally.

1 Like

That is why it is always best to connect the switch on opposing corners. That way, it does not matter if you rotate it 90 degrees - it will still work.

1 Like