DEV Community

Cover image for Building a Traffic Light Simulator with a Countdown using Arduino and a 7-Segment Display
Michael Ikoko
Michael Ikoko

Posted on • Edited on

Building a Traffic Light Simulator with a Countdown using Arduino and a 7-Segment Display

Introduction

After blinking your first LED on Arduino, what's next? Before moving on to more complex concepts, you can design a real-world application for controlling LEDs using a microcontroller by simulating traffic lights.

This is a beginner-friendly tutorial on simulating traffic lights using Arduino. It also adds complexity by including a countdown timer using a 7-segment display.

At the end of the tutorial, you will be able to control three LEDs to mimic traffic lights. Additionally, you will have an understanding of what a 7-segment display is and how to program it with a microcontroller.

Prerequisites

This article makes use of the Wokwi simulator for designing. The same design concepts can be applied to any other microcontroller simulator of your choice, like Tinkercad or Fritzing. If you have the components, do well to connect them physically.

The following components are used in this tutorial:

  • Arduino UNO
  • Breadboard
  • Three LEDs (red, yellow, and green)
  • Four 200-ohm resistors
  • A 7-segment display
  • Jumper cables

The tutorial also assumes a basic understanding of how to use a breadboard.

Circuit Design

The circuit you will be designing in this tutorial will consist of two major parts:

  • The traffic light control
  • Countdown timer display

Connecting the LEDs

The traffic light control is made up of three LEDs (red, yellow, and green). You connect each LED in series with a 200-ohm resistor. The resistor limits the current flowing to the LED, thereby preventing the LED from burning out. You should note that it doesn't matter if you place the resistor before or after the LED.

Breadboard circuit with just LEDs

To construct the circuit shown above on a breadboard, proceed with the following steps:

  1. Connect each LED across the central gap of the breadboard so that the terminals of the LED are on opposite sides of the breadboard.
  2. Connect the anode (the longer leg) of each LED to a digital output (PWM) pin on the Arduino. Specifically, the red, yellow, and green LEDs are connected to digital pins 4, 3, and 2, respectively.
  3. Connect one end of a resistor to the cathode (the shorter leg) of each LED. Connect the other end of the resistor to the negative (ground) rail of the breadboard.
  4. Finally, connect the negative rail of the breadboard to one of the Arduino’s ground (GND) pins.

Understanding the 7-Segment Display

A 7-segment display is an electronic component that is primarily used to display digits (0-9). As the name implies, a 7-segment display consists of seven different LEDs. Each LED on a 7-segment display represents a segment and is labelled from a through g. The segments of a 7-segment display are arranged to form digits by illuminating various combinations of segments, each of which can be illuminated independently.

7-segment display pin diagram

There are 10 pins in a standard 7-segment display. These include:

  • Pins a to g: These pins control the LEDs in individual segments.
  • The Decimal Point (DP) pin: This pin controls the optional decimal point segment.
  • Two common pins: The common pins are the shared connection for all the segments in the display. These are either connected to ground (in common cathode displays) or to a voltage source (in common anode displays).

7-segment displays are classified according to the common terminal type. There are two main types of 7-segment displays:

  • The Common Cathode (CC).
  • The Common Anode (CA).

The Common Cathode (CC)

In the common cathode 7-segment display, all the cathodes of the LEDs in each segment are connected to a LOW logic level (logic "0"). This requires you to connect either or both of the common pins to ground. Individual segments are illuminated by applying a HIGH to the signal pins. A Common Cathode 7-segment display is active HIGH.

Common cathode 7-segment display

Truth Table for Common Cathode 7-Segment Display

This tutorial makes use of the Common Cathode 7-segment display. A truth table showing the logic levels of individual segments corresponding to each decimal digit is as follows:

Digit a b c d e f g
0 1 1 1 1 1 1 0
1 0 1 1 0 0 0 0
2 1 1 0 1 1 0 1
3 1 1 1 1 0 0 1
4 0 1 1 0 0 1 1
5 1 0 1 1 0 1 1
6 1 0 1 1 1 1 1
7 1 1 1 0 0 0 0
8 1 1 1 1 1 1 1
9 1 1 1 1 0 1 1

The Common Anode (CA)

In the common anode 7-segment display, all the anodes of the LEDs in each segment are connected to a HIGH logic level (logic "1"). This implies that you connect the common pins to a voltage source. Individual segments are illuminated by applying a LOW to the signal pins. A Commode Anode display is active LOW.

Common anode 7-segment display

Connecting the 7-segment Display

In this tutorial, you use the 7-segment display to display a countdown timer during the traffic light phases. To connect the 7-segment display, follow these steps:

  1. Insert the 7-segment display across the central gap of the breadboard. The top five pins of the display are connected in one half of the breadboard, the bottom five pins are connected in the other half.
  2. Recall that this tutorial uses a Common Cathode (CC) 7-segment display. This means that the common pins are connected to ground. Connect a 200-ohm resistor from the common pin to the negative rail of the breadboard (which should already be connected to the Arduino's GND pin). This resistor limits the current flowing to the display and protects the LEDs in the display.
  3. Connect each of the segment pins (a through g) of the display to separate digital output pins on the Arduino. The table below shows the mapping between the segment pins and the corresponding Arduino pins:
7-Segment Display Pin Arduino Pin
A D12
B D13
C D5
D D6
E D7
F D11
G D10

The complete circuit should look like this:

Final breadboard circuit image

Traffic Light Phases

The traffic light simulation is a continuous cycle consisting of four different phases. Each phase runs for a specific period, and a countdown is displayed to indicate the time remaining in each phase.

Traffic Light Sequence

  1. Red Phase (STOP): This phase mimics the red traffic light, signalling drivers to stop. In the stop phase, you turn on the red LED only. The countdown for this phase begins from 9 and decreases to 4. When the countdown reaches 3, the simulation transitions into the “GET-READY” phase.
  2. Red-Yellow Phase (GET READY): This phase mimics the "get-ready" traffic signal, signalling drivers that the lights are about to turn green. In this phase, you turn on both the red and yellow LEDs. The duration of this phase is 3 seconds, covering the last 3 seconds of the red phase countdown.
  3. Green Phase (GO): This phase mimics the green light, signalling drivers to proceed. In the green phase, you turn on the green LED only. The countdown is reset, restarting from 9 and decreasing to 4. When the countdown reaches 3, the simulation begins the “CAUTION” phase.
  4. Yellow Phase (CAUTION): This phase mimics the yellow traffic light, warning drivers that the lights are about to turn red. In the caution phase, you turn on the yellow LED only. The duration of this phase is 3 seconds, covering the last 3 seconds of the green phase countdown

Arduino Code

The software is a core aspect of any microcontroller-based project. It is where you describe the behaviour of the hardware components connected to the microcontroller.

In this section, you will develop the Arduino sketch responsible for simulating the behaviour of the traffic lights and the countdown display. You will work on the logic for controlling red, yellow, and green LEDs, the traffic phase timing logic, and displaying the countdown.

Pin Configuration

To improve code readability and maintainability, it’s best practice to define descriptive variable names for the pins you're using. This allows you to reference each pin by its function, rather than by its number.

In the sketch.ino, begin by defining the three digital output pins used by the red, yellow, and green LEDs. Then, you define each of the seven digital output pins connected to the 7-segment display. Finally, create an array to group the pins used by the 7-segment display:

const int RED = 4;
const int YELLOW = 3;
const int GREEN = 2;

const int a = 12;
const int b = 13;
const int c = 5;
const int d = 6;
const int e = 7;
const int f = 11;
const int g = 10;

const int segmentPins[7] = {a, b, c, d, e, f, g};
Enter fullscreen mode Exit fullscreen mode

Timing Constants

Next, you create variables storing the duration of each traffic light phase. Given that you are only using one 7-segment display, the maximum digit that can be displayed is 9. The “STOP” and “GO” phases will last for 9 seconds, and the two transition phases (“GET-READY” and “CAUTION”) will each last for 3 seconds. At your discretion, you can change the duration.

Add the following lines to the sketch.ino file:

//...
const int DELAY_RED = 9;
const int DELAY_YELLOW = 3;
const int DELAY_GREEN = 9;
Enter fullscreen mode Exit fullscreen mode

7-Segment Display Truth Table Array

You learned earlier that to display a digit on the 7-segment display, each segment (labelled a through g) is turned ON (logic level 1) and OFF (logic level 0) in a particular combination. A truth table is used to show the logic levels (0 and 1) of each segment for a particular digit.

To implement this, you create an array to store the various combinations of logic levels used for displaying digits 0 through 9. The array digitMap is a two-dimensional array where each row defines the logic levels for a particular digit, and each column represents a specific segment. The array has 10 rows (one for digit 0 to 9), and 7 columns (one for each segment a to g). The array digitMap stores the truth table for the 7-segment display. This means that you can simply reference digitMap[digit][segment] to determine what segments to activate when displaying a number.

Add the following lines to the sketch.ino file:

//...
const bool digitMap[10][7] = {
  {1, 1, 1, 1, 1, 1, 0}, // 0
  {0, 1, 1, 0, 0, 0, 0}, // 1
  {1, 1, 0, 1, 1, 0, 1}, // 2
  {1, 1, 1, 1, 0, 0, 1}, // 3
  {0, 1, 1, 0, 0, 1, 1}, // 4
  {1, 0, 1, 1, 0, 1, 1}, // 5
  {1, 0, 1, 1, 1, 1, 1}, // 6
  {1, 1, 1, 0, 0, 0, 0}, // 7
  {1, 1, 1, 1, 1, 1, 1}, // 8
  {1, 1, 1, 0, 0, 1, 1}  // 9
};
Enter fullscreen mode Exit fullscreen mode

The setup Function

The setup function in every Arduino sketch runs once when the microcontroller is powered on or reset. In the setup function, you perform initialization tasks, such as setting pin modes, configuring peripherals, or initializing serial communication.

In the setup function for this project, you perform the following:

  1. You configure the pins used by the red, yellow, and green LEDs as OUTPUT, so that the Arduino can send voltage signals to turn them ON and OFF.
  2. You initialize the 7-segment display pins as OUTPUT, allowing you to control each segment.
  3. You set the baud rate of the microcontroller to enable serial communication, which is useful for debugging.

Create the setup function in the sketch.ino file with the following contents:

//...
void setup() {
  // Traffic lights
  pinMode(RED, OUTPUT);
  pinMode(YELLOW, OUTPUT);
  pinMode(GREEN, OUTPUT);

  // 7-segment display
  for (int i = 0; i < 7; i++) {
    pinMode(segmentPins[i], OUTPUT);
  }

  Serial.begin(9600);
}
Enter fullscreen mode Exit fullscreen mode

Helper Functions

Before jumping into the loop function, you should create a couple of functions that handle certain tasks. You do this to make the logic in the main loop readable and modular.

The helper functions in this project include:

  • Traffic light control functions: The red_light(), red_yellow_light(), yellow_light(), and green_light() functions set the appropriate LEDs ON or OFF for their respective traffic phase.
  • 7-segment display functions: The display() function shows a digit on the display, and the clearDisplay() function turns off all segments.

Add the following lines to the sketch.ino file:

//... 
void red_light() {
  digitalWrite(RED, HIGH);
  digitalWrite(YELLOW, LOW);
  digitalWrite(GREEN, LOW);
}

void red_yellow_light() {
  digitalWrite(RED, HIGH);
  digitalWrite(YELLOW, HIGH);
  digitalWrite(GREEN, LOW);
}

void yellow_light() {
  digitalWrite(RED, LOW);
  digitalWrite(YELLOW, HIGH);
  digitalWrite(GREEN, LOW);
}

void green_light() {
  digitalWrite(RED, LOW);
  digitalWrite(YELLOW, LOW);
  digitalWrite(GREEN, HIGH);
}

void display(int num) {
  if (num < 0 || num > 9) {
    clearDisplay();
    return;
  }

  for (int i = 0; i < 7; i++) {
    digitalWrite(segmentPins[i], digitMap[num][i] ? HIGH : LOW);
  }

  Serial.print("Countdown: ");
  Serial.println(num);
}

void clearDisplay() {
  for (int i = 0; i < 7; i++) {
    digitalWrite(segmentPins[i], LOW);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the red_light(), red_yellow_light(), yellow_light(), and green_light() functions, you set the state of red, yellow, and green LEDs for their respective traffic phase. The digitalWrite function is used to send either a HIGH(about 5V) or a LOW(0V) to the specified pin. A HIGH signal will turn on the LED, and a LOW signal will turn off the LED.

The display() function is responsible for displaying a particular digit on the 7-segment display. The function has a parameter num, which is an integer specifying the digit to be displayed. The display() function is written such that it prevents you from manually setting each segment anytime you want to update a digit.

The display() function operates as follows when called with an integer argument:

  1. Firstly, it verifies if the value of num is within the valid range (0 to 9). If the value of num is less than 0 or greater than 9, the display is cleared using clearDisplay() and the function exits.
  2. If the value of num is valid. The function loops through each 7-segment. Each segment is turned ON(HIGH) or OFF(LOW) based on the logic levels for the particular digit (num) specified by the digitMap array. The digitalWrite(segmentPins[i], digitMap[num][i] ? HIGH : LOW); line achieves that.

The clearDisplay() function turns OFF(LOW) all segments, and displays nothing on the 7-segment display.

Main Loop

The code in the loop() function is run repeatedly by the microcontroller. The loop() function is where you define the core logic of the traffic light simulation.

In this project, the loop() function consists of two separate loops, corresponding to two stages:

  1. The STOP Stage: In the first stage, you begin the for loop from the value specified in DELAY_RED(9 seconds) to 1, with a -1 decrement. The red LED is turned ON for the "STOP" phase, and in the last 3 seconds of the countdown (DELAY_YELLOW), the red and yellow LEDs are turned ON for the "GET-READY" phase.
  2. The GO Stage: In the second stage, you start the for loop from the value specified in DELAY_GREEN(9 seconds) to 1, with a -1 decrement. The green LED is turned ON for the "GO" phase, and in the last 3 seconds of the countdown(DELAY_YELLOW), the yellow LED is turned on for the "CAUTION" phase.

In both stages, the display() function displays the countdown number on the 7-segment, and each iteration pauses for 1 second (delay(1000)), so that the countdown updates every second.

Create the loop() function in the sketch.ino file, with the following contents:

//...
void loop() {
  // STOP PHASE
  for (int i = DELAY_RED; i > 0; i--) {
    if (i <= DELAY_YELLOW) {
      // RED/YELLOW TRANSITION
      red_yellow_light();
      display(i);
    } else {
      // RED LIGHT
      red_light();
      display(i);
    }
    delay(1000);
  }

  // GO PHASE
  for (int i = DELAY_GREEN; i > 0; i--) {
    if (i <= DELAY_YELLOW) {
      // YELLOW TRANSITION
      yellow_light();
      display(i);
    } else {
      // GREEN LIGHT
      green_light();
      display(i);
    }
    delay(1000);
  }
}
Enter fullscreen mode Exit fullscreen mode

Final Code

After combining all the logic discussed above, the final code in the sketch.ino is:

const int RED = 4;
const int YELLOW = 3;
const int GREEN = 2;

const int DELAY_RED = 9;
const int DELAY_YELLOW = 3;
const int DELAY_GREEN = 9;

const int a = 12;
const int b = 13;
const int c = 5;
const int d = 6;
const int e = 7;
const int f = 11;
const int g = 10;

const bool digitMap[10][7] = {
  {1, 1, 1, 1, 1, 1, 0}, // 0
  {0, 1, 1, 0, 0, 0, 0}, // 1
  {1, 1, 0, 1, 1, 0, 1}, // 2
  {1, 1, 1, 1, 0, 0, 1}, // 3
  {0, 1, 1, 0, 0, 1, 1}, // 4
  {1, 0, 1, 1, 0, 1, 1}, // 5
  {1, 0, 1, 1, 1, 1, 1}, // 6
  {1, 1, 1, 0, 0, 0, 0}, // 7
  {1, 1, 1, 1, 1, 1, 1}, // 8
  {1, 1, 1, 0, 0, 1, 1}  // 9
};

const int segmentPins[7] = {a, b, c, d, e, f, g};

void setup() {
  // Traffic lights
  pinMode(RED, OUTPUT);
  pinMode(YELLOW, OUTPUT);
  pinMode(GREEN, OUTPUT);

  // 7-segment display
  for (int i = 0; i < 7; i++) {
    pinMode(segmentPins[i], OUTPUT);
  }

  Serial.begin(9600);
}

void loop() {
  // STOP PHASE
  for (int i = DELAY_RED; i > 0; i--) {
    if (i <= DELAY_YELLOW) {
      // RED/YELLOW TRANSITION
      red_yellow_light();
      display(i);
    } else {
      // RED LIGHT
      red_light();
      display(i);
    }
    delay(1000);
  }

  // GO PHASE
  for (int i = DELAY_GREEN; i > 0; i--) {
    if (i <= DELAY_YELLOW) {
      // YELLOW TRANSITION
      yellow_light();
      display(i);
    } else {
      // GREEN LIGHT
      green_light();
      display(i);
    }
    delay(1000);
  }
}

void red_light() {
  digitalWrite(RED, HIGH);
  digitalWrite(YELLOW, LOW);
  digitalWrite(GREEN, LOW);
}

void red_yellow_light() {
  digitalWrite(RED, HIGH);
  digitalWrite(YELLOW, HIGH);
  digitalWrite(GREEN, LOW);
}

void yellow_light() {
  digitalWrite(RED, LOW);
  digitalWrite(YELLOW, HIGH);
  digitalWrite(GREEN, LOW);
}

void green_light() {
  digitalWrite(RED, LOW);
  digitalWrite(YELLOW, LOW);
  digitalWrite(GREEN, HIGH);
}

void display(int num) {
  if (num < 0 || num > 9) {
    clearDisplay();
    return;
  }

  for (int i = 0; i < 7; i++) {
    digitalWrite(segmentPins[i], digitMap[num][i] ? HIGH : LOW);
  }

  Serial.print("Countdown: ");
  Serial.println(num);
}

void clearDisplay() {
  for (int i = 0; i < 7; i++) {
    digitalWrite(segmentPins[i], LOW);
  }
}
Enter fullscreen mode Exit fullscreen mode

Simulation in Wokwi

Try the simulation on Wokwi
Wokwi Simulation GIF

Physical Connection

If you have access to the components, I encourage you to replicate the project on a breadboard. The connections are the same as in the simulation, so you won't need to change the code.

Here is what my final setup looked like:

Physical circuit image
Physical simulation GIF

Conclusion

By following this tutorial, you built a complete traffic light simulation with a countdown timer using an Arduino and a 7-segment display. You configured digital outputs to control LEDs representing traffic lights and used a truth table approach to drive the 7-segment display dynamically. You also explored how traffic light phases operate in practice and simulated your design using Wokwi, with the option to replicate it physically on a breadboard.

Consider extending this system by using multiple 7-segment displays, using an LCD, or implementing multi-intersection traffic control with ultrasonic sensors. The possibilities are limitless—the core concepts you've applied here can serve as a springboard into more advanced embedded systems projects.

References

  1. Electronics Tutorials7 Segment Display Tutorial

  2. Sun Founder Docs7-Segment Display (UNO/Mega Kit)

  3. Components1017-Segment Display: Pinout, Working & Datasheet

  4. VeygoTraffic Lights: What They Mean and How to Respond

Top comments (0)