arduino-semaforo-basico

How to Make a Traffic Light with Arduino

  • 5 min

If “Blink” (flashing an LED) is the “Hello World” of Arduino, the traffic light is, without a doubt, the logical second step in our learning.

It seems simple: turn three lights on and off. However, this project is fundamental because it forces us to stop thinking about a single element and think about a sequence of states.

In this tutorial, we will build a standard car traffic light, managing the timing of each light (Green, Yellow, and Red) and ensuring the transition between them is smooth and correct.

This project is the ideal foundation for understanding the delay() function and the sequential structure of a program, before moving on to more complex concepts like millis() or multitasking.

Sequences and Timing

A traffic light is nothing more than a very simple state machine that repeats itself in an infinite loop. It doesn’t turn lights on “randomly,” but follows a strict order to ensure safety.

The standard sequence we will program is the European one:

Green: Passage allowed. (Long duration).

Yellow: Caution, about to change to red. (Short duration).

Red: Stop. (Long duration).

(Return to Green).

The challenge here is not electronic, but about order. If we turn on the red light without turning off the green one, we have a conceptual problem. We must ensure the microcontroller manages the transitions by turning off the previous state before activating the next one.

For this setup, we will need to manage 3 digital outputs.

Components

  • [1x|blue] Arduino Uno | The brain of the project
  • [1x|red] Red LED (5mm) | Indicates stop
  • [1x|yellow] Yellow or Amber LED | Indicates caution
  • [1x|green] Green LED | Indicates passage allowed
  • [3x|gray] 220Ω or 330Ω Resistor | Limits LED current
  • [Varios|gray] Breadboard and wires | Connections

Why those resistors?

A standard LED usually has a forward voltage drop of approximately 2 V (red or yellow) to 3 V (green or blue). Since the Arduino Uno pins operate at 5 V, we need to limit the current to avoid damaging the LED or overloading the output.

For safety and to avoid overloading the Arduino pin, we use common higher values like 220Ω or 330Ω.

Connection Diagram

The assembly is very clean. We will connect each LED to an independent digital pin.

Traffic Light LEDs

We connect the anode of each LED to its pin via a resistor, and connect all cathodes to the GND rail.

[ { “from”: “Green LED + 220Ω”, “to”: “Pin 11”, “color”: “green”, “note”: “Anode to pin, cathode to GND” }, { “from”: “Yellow LED + 220Ω”, “to”: “Pin 12”, “color”: “yellow” }, { “from”: “Red LED + 220Ω”, “to”: “Pin 13”, “color”: “red” }, { “from”: “LED Cathodes”, “to”: “Arduino GND”, “color”: “black” } ]

Using consecutive pins makes assembly and the visual order of wires easier, something very important when projects grow.

The Code

We will write the code in a clean and readable way. A beginner’s mistake would be to hardcode pin numbers inside the loop. We will use constants to make it easy to modify.

Definition and setup

We define the pins and set them as outputs.

// Pin definitions
const int pinGreen = 11;
const int pinYellow = 12;
const int pinRed = 13;

// Time definitions (in milliseconds)
const int timeGreen = 5000;    // 5 seconds
const int timeYellow = 2000; // 2 seconds
const int timeRed = 5000;     // 5 seconds

void setup() {
  // Set the pins as outputs
  pinMode(pinGreen, OUTPUT);
  pinMode(pinYellow, OUTPUT);
  pinMode(pinRed, OUTPUT);
  
  // Ensure we start with everything off
  digitalWrite(pinGreen, LOW);
  digitalWrite(pinYellow, LOW);
  digitalWrite(pinRed, LOW);
}
Copied!

The Main Loop (loop)

Here we implement the sequence. The logic is always the same:

  1. Turn on the current color.
  2. Wait for the defined time.
  3. Turn off the current color (cleanup).
void loop() {
  // --- PHASE 1: GREEN ---
  digitalWrite(pinGreen, HIGH);   // Turn on Green
  delay(timeGreen);               // Wait
  digitalWrite(pinGreen, LOW);    // Turn off Green

  // --- PHASE 2: YELLOW ---
  digitalWrite(pinYellow, HIGH); // Turn on Yellow
  delay(timeYellow);              // Wait
  digitalWrite(pinYellow, LOW);  // Turn off Yellow

  // --- PHASE 3: RED ---
  digitalWrite(pinRed, HIGH);     // Turn on Red
  delay(timeRed);                 // Wait
  digitalWrite(pinRed, LOW);      // Turn off Red
}
Copied!

Refactoring the Code

The previous code works, but if we wanted to add a pedestrian traffic light, the loop would become very long. A good programming practice is to modularize.

We are going to create a function to change the lights. This makes the loop read like natural language.

void loop() {
  changeLight(pinGreen, timeGreen);
  changeLight(pinYellow, timeYellow);
  changeLight(pinRed, timeRed);
}

// Helper function that groups the logic
void changeLight(int pin, int duration) {
  digitalWrite(pin, HIGH); // Turn on
  delay(duration);         // Wait
  digitalWrite(pin, LOW);  // Turn off and ready for the next one
}
Copied!

Notice the difference! Now the loop tells us what is happening, and the changeLight function takes care of the how.

Next Steps

This traffic light is “dumb,” meaning it is blind to what is happening in the environment. It operates on fixed timings. How could we improve it?

Here are some ideas to evolve this project:

  1. Add a pedestrian button: The traffic light is always green for cars and only changes to red when a pedestrian presses the button. With delay() we won’t be able to attend to it while waiting; we will need to check the button without blocking, for example, using millis().
  2. Dual traffic light: Simulate an intersection of two streets. When one is green, the other must necessarily be red.
  3. Sound: Add a buzzer that emits beeps when the pedestrian traffic light is green (for the visually impaired).

It is a simple setup, but mastering the logical sequence is the first step into robotics.