A Traffic Light Simulation with pedestrian crosswalks
These days, when I’m not managing teams and projects, I am primary a Python developer. But, recently I took it upon myself to learn Arduino, and it’s been fun going back to old hobbies – building circuits and C!
The Arduino programming language include some C++ elements as well, especially classes, so this has allowed me to introduce object-oriented design concepts as well. Here’s a simple circuit with two interconnected traffic lights, programmed in such a way that neither can be green at the same time. The connected pedestrian crosswalk switches initiate a countdown, then switching one light to red before allowing the other light to turn green. I’ve added an additional safety consideration for broken red lights.
By using a TrafficLight class, I’m able to reuse code as well, instead of repeating code for each traffic light circuit. The method names clearly indicate their functional purpose, so the code is easy to read.
There’s several ways to approach this circuit control, the code here is just for demo purposes, something I hacked out rather quickly. Overall, it was a fun project to develop, and my first Arduino circuit beyond the starter package projects.
The TrafficLight class header
The interest element of this class is interconnectedLight. Notice that it is defined as a TrafficLight class as well. One traffic light refers to another traffic light. This being C++, the syntax is actually a pointer to a class, thus TrafficLight*.
class TrafficLight {
byte red, yellow, green, switchPin ;
TrafficLight* interconnectedLight ;
boolean switchWasPressed = false ;
public:
void attach(int switchPin, int red, int yellow, int green) ;
void interconnect(TrafficLight anotherLight) ;
void countdownToGreen() ;
boolean canSwitch() ;
boolean isSwitchPressed() ;
void turnLightGreenInitial() ;
private:
boolean isGreen() ;
boolean switchAlreadyPressed() ;
void turnLightRedInitial() ;
void flashLights() ;
boolean isRedFunctioning() ;
void turnLightRed() ;
void turnLightGreen() ;
void greenOn() ;
} ;
The TrafficLight method implementations
Modeled after the Servo class in Arduino, the class is initialized with an attach() method, passing in all the pins wired on your circuit. It also sets up all the pinModes and initial settings for the LED lights.
void TrafficLight::attach(int aPin, int aRedPin, int aYellowPin, int aGreenPin) {
switchPin = aPin ;
red = aRedPin ;
yellow = aYellowPin ;
green = aGreenPin ;
pinMode(yellow,OUTPUT) ;
pinMode(green,OUTPUT) ;
pinMode(red,OUTPUT) ;
pinMode(switchPin,INPUT) ;
digitalWrite(red,LOW) ;
digitalWrite(yellow,LOW) ;
digitalWrite(green,LOW) ;
}
void TrafficLight::interconnect(TrafficLight anotherLight) {
interconnectedLight = &anotherLight ;
}
Also make note of the interconnect() method. Passed in is another TrafficLight, and since interconnectedLight is a pointer definition, we use &anotherLight, the “reference” to this object, to get the pointer assignment. For those new to C++, this pointer and address references are perhaps the most tricky syntaxes you’ll have to learn.
turnLightGreen() is perhaps the the most significant method in this class. The main microcontroller calls countdownToGreen() when the appropriate button is pushed; that method then calls turnLightGreen(). Notice what it does: it’s very first step is to turn the other light red. In then double checks that light, is it really red – isRedFunctioning() ? If it does, it then proceeds to turn this light green, after a suitable delay.
Oh, here’s another syntax quirk of C++: see the -> ? That’s the arrow reference, is used when calling a method of another class pointer. It is also used with this. This is not necessary, but makes most code easier to read: this->greenOn() meets means to call the greenOn() method on the class object itself. That is, if light1.turnLightGreen() is called, this is light1.
void TrafficLight::turnLightGreen() {
interconnectedLight->turnLightRed() ;
if (interconnectedLight->isRedFunctioning()) {
delay(1000) ;
this->greenOn() ;
crosswalkSounding() ;
}
else {
this->flashLights() ;
}
switchWasPressed = false ;
}
Here are the remaining methods. They should all be fairly easy to understand, but if you have any questions, feel free to email me.
void TrafficLight::countdownToGreen() {
switchWasPressed = true ;
countdown() ;
this->turnLightGreen() ;
}
void TrafficLight::turnLightGreenInitial() {
interconnectedLight->turnLightRedInitial() ;
if (interconnectedLight->isRedFunctioning()) {
this->greenOn() ;
}
else {
this->flashLights() ;
}
}
void TrafficLight::turnLightRedInitial() {
digitalWrite(green,LOW) ;
digitalWrite(yellow,LOW) ;
digitalWrite(red,HIGH) ;
}
void TrafficLight::turnLightRed() {//this should probably be a private method
digitalWrite(green,LOW) ;
//if (!isRed()) { //when recovering from malfunction, the light may already be red
digitalWrite(yellow,HIGH) ;
delay(5000) ;
digitalWrite(yellow,LOW) ;
//}
digitalWrite(red,HIGH) ;
}
boolean TrafficLight::isGreen() {
return digitalRead(green) == HIGH ;
}
boolean TrafficLight::isRedFunctioning() {
pinMode(red,INPUT_PULLUP) ;
boolean pullup_high ;
pullup_high = digitalRead(red) ;//if input_pullup is high, this means the LED is off or missing
pinMode(red,OUTPUT) ;
return !pullup_high ;
}
boolean TrafficLight::isSwitchPressed() {
//int switchState = 0 ;
//switchState = digitalRead(switchPin) ;
return digitalRead(switchPin) == HIGH ;
}
boolean TrafficLight::canSwitch() {
return !this->switchAlreadyPressed() && this->isGreen() ;
}
boolean TrafficLight::switchAlreadyPressed() {
return switchWasPressed ;
}
void TrafficLight::flashLights() {
while (!interconnectedLight->isRedFunctioning()) {
digitalWrite(green,HIGH) ;
digitalWrite(yellow,HIGH) ;
digitalWrite(red,HIGH) ;
delay(500) ;
digitalWrite(green,LOW) ;
digitalWrite(yellow,LOW) ;
digitalWrite(red,LOW) ;
delay(500) ;
}
this->turnLightGreen() ;
}
void TrafficLight::greenOn() {
digitalWrite(green,HIGH) ;
digitalWrite(yellow,LOW) ;
digitalWrite(red,LOW) ;
}
The microcontroller setup and main loop:
The setup() is straightforward: define light1 and light2 as TrafficLights, then “attach” them to their appropriate pins. interconnect light1 to light2 and, vice versa. Then turn on one of the lights. Calling turnLightGreenInitial() will automatically turn light2 to red.
TrafficLight light1 ;
TrafficLight light2 ;
const int piezoPin = 7 ;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(LED_BUILTIN, LOW) ;
light1.attach(2,3,4,5) ; //switch, red, yellow, & green pins
light2.attach(9,10,11,12) ;
light1.interconnect(light2) ;
light2.interconnect(light1) ;
light1.turnLightGreenInitial() ;
}
The microcontroller loop() itself if deceptively simple: it’s just checking whether any of the switches are pressed, and if the the light can be switched (i.e., isn’t already green or in the middle of being switched), then go ahead and start the countdown.
void loop() {
if (light1.isSwitchPressed() && light1.canSwitch()) {
light2.countdownToGreen() ;
}
if (light2.isSwitchPressed() && light2.canSwitch()) {
light1.countdownToGreen() ;
}
}
Here’s an additional few functions, to control the piezo sound. I could have made a Piezo object as well, but there’s not much gain to such for this simple example.
void countdown() {
for (int i = 0 ; i < 10; i++) {
tone(piezoPin,1000,50) ;
delay(1000) ;
}
}
void crosswalkSounding() {
for (int i = 0 ; i < 50; i++) {
tone(piezoPin,250,10) ;
delay(100) ;
}
}