Guides · 14 min read

Arduino Traffic Light: Circuit, Code, Pedestrian Button, Junction

Build an Arduino traffic light: 3 LEDs and 220 Ω wiring, delay and millis code, pedestrian button, two-way junction, Tinkercad, ESP32 and Pico versions.

An Arduino traffic light is the classic first project in electronics: three LEDs, red, yellow and green, switch on and off in sequence just like a real traffic signal. All you need is an Arduino Uno, three LEDs, three 220 Ω resistors and a few jumper wires, and your first working version is done in half an hour. Yet this simple project teaches a lot: digital outputs, LED and resistor maths, timing, reading a button and the state machine, the cleanest way to organise your code, all fit into these few LEDs.

In this guide you will first see in what order and for how long real traffic lights switch, then build the circuit and write the simplest code with delay(). Next you will add a pedestrian push button, rewrite the same logic with millis() and a state machine so it never blocks, and control a two-way junction. The final sections show how to try the project in Tinkercad without installing anything, the ESP32 and Raspberry Pi Pico (MicroPython) versions, and tips for writing a school report on the project.

Red, yellow and green 5 mm LEDs for an Arduino traffic light
5 mm LEDs: one red, one yellow and one green are enough for the traffic light; keep a few more for the pedestrian signal and the junction.

How does a real traffic light work?

A traffic light is a timer that gives each stream of traffic its turn. In the UK, Germany and Turkey the cycle is red → red + yellow (amber) → green → yellow → red. Red and yellow together mean "green is coming, get ready, but don't go yet", while yellow on its own means "the light is turning red, prepare to stop". In the USA, France, Italy and Spain there is no red + yellow phase: the light goes straight from red to green. The yellow time is not random either: German rules set 3 seconds of yellow on roads with a 50 km/h limit, 4 seconds at 60 km/h and 5 seconds at 70 km/h, because a driver who is too close to stop when yellow appears must still be able to clear the junction safely.

PhaseLamp(s) onMeaningTime in this guide
1RedStop5 s
2Red + yellowGreen is coming, get ready1 s
3GreenGo5 s
4YellowRed is coming, stop3 s
At a junctionAll directions redSafety gap to clear the junction1 s

Parts list and the 220 Ω resistor calculation

PartQtyNote
Arduino Uno (or Nano)1An ESP32 or Raspberry Pi Pico works too
5 mm LEDs: red, yellow, green1 of each+1 red and +1 green for the pedestrian light, +1 set for the junction
220 Ω resistor3One per LED; 8 for all versions
Push button (6×6 mm)1Pedestrian version only
Breadboard1Half size (400 holes) is enough
Jumper wires10–20Male to male
USB cable1To upload the code and power the board

LEDs do not limit their own current; an LED connected straight to 5 V draws more current than it and the pin can handle. That is why every LED gets a series resistor. You find its value with Ohm's law: R = (supply voltage − LED forward voltage) / current. For a red LED, (5 V − 2.0 V) / 0.015 A ≈ 200 Ω; with the nearest standard value, 220 Ω, the current is about 13.6 mA. That lights the LED brightly enough and stays below the 20 mA recommended for an Arduino pin. A 220 Ω resistor is marked red-red-brown-gold on four-band parts and red-red-black-black-brown on five-band parts; to be sure, use the resistor colour code tool, and for other colours and voltages the LED resistor calculator.

LED colourForward voltage (approx.)Current at 5 V (220 Ω)Current at 3.3 V (220 Ω)
Red1.8–2.2 V≈ 13.6 mA≈ 5.9 mA
Yellow1.9–2.2 V≈ 13.2 mA≈ 5.5 mA
Green (yellowish green)2.0–2.4 V≈ 13.2 mA≈ 5.5 mA
Green (bright, pure green)2.8–3.4 V≈ 9.1 mA≈ 1.4 mA (very dim)

Wiring the circuit and the pin table

Each LED's long leg (anode, +) goes through a resistor to an Arduino digital pin, and its short leg (cathode, −) goes to GND. It does not matter whether the resistor sits on the anode or the cathode side, as long as it is in series. On a breadboard the tidiest way is to connect the Arduino GND pin to the blue (−) rail and bring the short legs of all three LEDs to that rail. The table below lists the pins used by every sketch in this guide; the first sketch only uses the first three rows. You can see your board's pin layout in the Pinout tool.

FunctionArduino UnoESP32Raspberry Pi Pico
Vehicle red (direction A)D10GPIO 5GP13
Vehicle yellow (direction A)D9GPIO 18GP12
Vehicle green (direction A)D8GPIO 19GP11
Pedestrian redD7GPIO 21GP15
Pedestrian greenD6GPIO 22GP14
Pedestrian button (other leg to GND)D2GPIO 23GP4
Direction B red / yellow / greenD5 / D4 / D3GPIO 25 / 26 / 27GP8 / GP7 / GP6
LED cathodes and buttonGNDGNDGND
A reversed LED simply stays dark and is not damaged, but if you forget the resistor the LED and the pin are overstressed and the LED can fade quickly. On the Arduino Uno aim for at most 20 mA per pin (the absolute maximum is 40 mA) and keep the total of all pins under 200 mA. If no LED lights up at all, check GND first: if the blue breadboard rail is not connected to an Arduino GND pin, the circuit is never closed.

Step 1: the simplest traffic light code with delay()

Arduino (C++)
1// Arduino trafik lambası: delay() ile en basit sürüm / simplest version with delay()
2const int KIRMIZI = 10;  // kırmızı LED / red LED
3const int SARI = 9;      // sarı LED / yellow LED
4const int YESIL = 8;     // yeşil LED / green LED
5
6// Üç LED'i tek satırda ayarla / set all three LEDs in one line
7void lambalar(int k, int s, int y) {
8  digitalWrite(KIRMIZI, k);
9  digitalWrite(SARI, s);
10  digitalWrite(YESIL, y);
11}
12
13void setup() {
14  pinMode(KIRMIZI, OUTPUT);
15  pinMode(SARI, OUTPUT);
16  pinMode(YESIL, OUTPUT);
17}
18
19void loop() {
20  lambalar(HIGH, LOW, LOW);   // DUR: kırmızı / STOP: red
21  delay(5000);
22  lambalar(HIGH, HIGH, LOW);  // HAZIRLAN: kırmızı + sarı / GET READY: red + yellow
23  delay(1000);
24  lambalar(LOW, LOW, HIGH);   // GEÇ: yeşil / GO: green
25  delay(5000);
26  lambalar(LOW, HIGH, LOW);   // YAVAŞLA: sarı / SLOW DOWN: yellow
27  delay(3000);
28}

The sketch sets the three pins as outputs in setup() and runs the four phases in order inside loop(). Instead of writing three digitalWrite lines over and over, we use a small function called lambalar() ("lamps"), so each phase reads as a single line and a mistake is easy to spot. To upload, choose Arduino Uno as the board and the right port in the Tools menu, then click Upload. If the upload fails, go through the checklist in Arduino code won't upload. To change the timing, just edit the millisecond values inside delay(): 5000 means 5 seconds.

Step 2: traffic light with a pedestrian push button

The push-button crossings you see in town keep the cars on green and only give pedestrians their turn when someone presses the button. In this version we add a two-LED pedestrian light (red and green) and a button on D2 to the vehicle light. One leg of the button goes to D2 and the diagonally opposite leg to GND; because the code uses INPUT_PULLUP, no external resistor is needed and the pin reads LOW while the button is pressed. You can read more about the internal connections of a four-leg button and about debouncing in How to connect a push button to Arduino.

6×6 mm four-leg push button
6×6 mm push button: the four legs are connected internally in pairs; if you use two diagonally opposite legs, the wiring is always right.
Arduino (C++)
1// Yaya butonlu trafik lambası (delay ile) / pedestrian push-button light (with delay)
2const int KIRMIZI = 10, SARI = 9, YESIL = 8;  // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6;   // yaya lambası / pedestrian light
4const int BUTON = 2;                          // diğer ucu GND / other leg to GND
5
6void setup() {
7  pinMode(KIRMIZI, OUTPUT);
8  pinMode(SARI, OUTPUT);
9  pinMode(YESIL, OUTPUT);
10  pinMode(YAYA_KIRMIZI, OUTPUT);
11  pinMode(YAYA_YESIL, OUTPUT);
12  pinMode(BUTON, INPUT_PULLUP);               // basılınca LOW okunur / reads LOW when pressed
13  digitalWrite(YESIL, HIGH);                  // araçlar geçer / cars go
14  digitalWrite(YAYA_KIRMIZI, HIGH);           // yayalar bekler / pedestrians wait
15}
16
17void loop() {
18  if (digitalRead(BUTON) == LOW) {            // yaya butona bastı / pedestrian pressed
19    delay(2000);                              // araçlara kısa süre tanı / short grace time for cars
20    digitalWrite(YESIL, LOW);
21    digitalWrite(SARI, HIGH);                 // sarı / yellow
22    delay(3000);
23    digitalWrite(SARI, LOW);
24    digitalWrite(KIRMIZI, HIGH);              // araçlar durur / cars stop
25    delay(1000);                              // tüm kırmızı güvenlik payı / all-red safety gap
26    digitalWrite(YAYA_KIRMIZI, LOW);
27    digitalWrite(YAYA_YESIL, HIGH);           // yayalar geçer / pedestrians cross
28    delay(6000);
29    for (int i = 0; i < 6; i++) {             // yaya yeşili 3 kez yanıp söner / green man flashes 3 times
30      digitalWrite(YAYA_YESIL, i % 2 == 0 ? LOW : HIGH);
31      delay(500);
32    }
33    digitalWrite(YAYA_YESIL, LOW);
34    digitalWrite(YAYA_KIRMIZI, HIGH);
35    delay(1000);                              // tüm kırmızı / all red
36    digitalWrite(SARI, HIGH);                 // kırmızı + sarı / red + yellow
37    delay(1000);
38    digitalWrite(KIRMIZI, LOW);
39    digitalWrite(SARI, LOW);
40    digitalWrite(YESIL, HIGH);                // araçlar yeniden geçer / cars go again
41    delay(8000);                              // en kısa yeşil süresi / minimum green time
42  }
43}

This sketch works, but it has a serious weakness: once the button is pressed, the Arduino spends about 25 seconds stuck in delay() lines. A second press during that time goes unnoticed, nothing can be read from the serial port and no display can be updated. Presses during the 8-second wait at the end are lost completely: the pedestrian presses and releases, nothing happens and they have to press again. Adding sound or a countdown makes this structure even harder to work with. The fix is to stop waiting and start measuring time.

Step 3: non-blocking code with millis() and a state machine

A state machine is a design in which the system is always in exactly one state and moves to the next state when certain conditions are met. A traffic light is a perfect example: "green for cars", "yellow for cars" and "pedestrians crossing" are each a state. The code no longer waits with delay(); on every pass loop() asks just two questions: which state am I in, and how long have I been in it? The elapsed time is measured with millis() - durumBaslangic (the moment the state started), and when the time is up the code moves on. If millis() is new to you, read Arduino millis() vs delay() first. Writing the states into a table before coding makes the job much easier:

StateVehicle lightPedestrian lightDurationNext state
ARAC_YESILGreenRedAt least 8 s, until a request arrivesARAC_SARI
ARAC_SARIYellowRed3 sYAYA_BEKLE
YAYA_BEKLERedRed1 sYAYA_GECER
YAYA_GECERRedGreen6 sYAYA_FLAS
YAYA_FLASRedGreen flashing3 sYAYA_BITTI
YAYA_BITTIRedRed1 sKIRMIZI_SARI
KIRMIZI_SARIRed + yellowRed1 sARAC_YESIL
Arduino (C++)
1// Yaya butonlu trafik lambası: millis() + durum makinesi / pedestrian light: millis() + state machine
2const int KIRMIZI = 10, SARI = 9, YESIL = 8;  // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6;   // yaya lambası / pedestrian light
4const int BUTON = 2;                          // diğer ucu GND / other leg to GND
5
6enum Durum { ARAC_YESIL, ARAC_SARI, YAYA_BEKLE, YAYA_GECER, YAYA_FLAS, YAYA_BITTI, KIRMIZI_SARI };
7const char* const ISIM[] = { "ARAC_YESIL", "ARAC_SARI", "YAYA_BEKLE", "YAYA_GECER",
8                             "YAYA_FLAS", "YAYA_BITTI", "KIRMIZI_SARI" };
9
10// Süreler (ms) / durations (ms)
11const unsigned long MIN_YESIL = 8000;   // araçlara en az yeşil / minimum green for cars
12const unsigned long SARI_SURE = 3000;   // sarı / yellow
13const unsigned long GUVENLIK = 1000;    // tüm kırmızı / all red
14const unsigned long YAYA_SURE = 6000;   // yaya yeşili / pedestrian green
15const unsigned long FLAS_SURE = 3000;   // yanıp sönen yaya yeşili / flashing green man
16const unsigned long KS_SURE = 1000;     // kırmızı + sarı / red + yellow
17
18Durum durum = ARAC_YESIL;
19unsigned long durumBaslangic = 0;       // durumun başladığı an / when the state started
20bool yayaTalebi = false;                // butona basıldı mı? / was the button pressed?
21
22void aracLamba(bool k, bool s, bool y) {
23  digitalWrite(KIRMIZI, k);
24  digitalWrite(SARI, s);
25  digitalWrite(YESIL, y);
26}
27
28void yayaLamba(bool k, bool y) {
29  digitalWrite(YAYA_KIRMIZI, k);
30  digitalWrite(YAYA_YESIL, y);
31}
32
33// Yeni duruma geç: lambaları ayarla, saati başlat / enter a state: set the lamps, restart the clock
34void gec(Durum yeni) {
35  durum = yeni;
36  durumBaslangic = millis();
37  switch (yeni) {
38    case ARAC_YESIL:   aracLamba(0, 0, 1); yayaLamba(1, 0); break;
39    case ARAC_SARI:    aracLamba(0, 1, 0); yayaLamba(1, 0); break;
40    case YAYA_BEKLE:   aracLamba(1, 0, 0); yayaLamba(1, 0); break;
41    case YAYA_GECER:   aracLamba(1, 0, 0); yayaLamba(0, 1); yayaTalebi = false; break;
42    case YAYA_FLAS:    aracLamba(1, 0, 0); yayaLamba(0, 1); break;
43    case YAYA_BITTI:   aracLamba(1, 0, 0); yayaLamba(1, 0); break;
44    case KIRMIZI_SARI: aracLamba(1, 1, 0); yayaLamba(1, 0); break;
45  }
46  Serial.println(ISIM[yeni]);           // durum günlüğü / state log
47}
48
49void setup() {
50  Serial.begin(9600);
51  pinMode(KIRMIZI, OUTPUT);
52  pinMode(SARI, OUTPUT);
53  pinMode(YESIL, OUTPUT);
54  pinMode(YAYA_KIRMIZI, OUTPUT);
55  pinMode(YAYA_YESIL, OUTPUT);
56  pinMode(BUTON, INPUT_PULLUP);
57  gec(ARAC_YESIL);
58}
59
60void loop() {
61  unsigned long gecen = millis() - durumBaslangic;  // bu durumda geçen süre / time spent in this state
62
63  // yaya yeşili yanarken basış yok sayılır / presses during the green man are ignored
64  if (digitalRead(BUTON) == LOW && durum != YAYA_GECER) {
65    yayaTalebi = true;                  // talebi hatırla / remember the request
66  }
67
68  switch (durum) {
69    case ARAC_YESIL:
70      if (yayaTalebi && gecen >= MIN_YESIL) gec(ARAC_SARI);
71      break;
72    case ARAC_SARI:
73      if (gecen >= SARI_SURE) gec(YAYA_BEKLE);
74      break;
75    case YAYA_BEKLE:
76      if (gecen >= GUVENLIK) gec(YAYA_GECER);
77      break;
78    case YAYA_GECER:
79      if (gecen >= YAYA_SURE) gec(YAYA_FLAS);
80      break;
81    case YAYA_FLAS:
82      digitalWrite(YAYA_YESIL, (gecen / 250) % 2 == 0);  // 250 ms'de bir yanıp söner / toggles every 250 ms
83      if (gecen >= FLAS_SURE) gec(YAYA_BITTI);
84      break;
85    case YAYA_BITTI:
86      if (gecen >= GUVENLIK) gec(KIRMIZI_SARI);
87      break;
88    case KIRMIZI_SARI:
89      if (gecen >= KS_SURE) gec(ARAC_YESIL);
90      break;
91  }
92}

The heart of the sketch is the gec() ("go to") function: it stores the new state, restarts the timer, sets the lamps for that state and prints the state name to the Serial Monitor. The switch inside loop() only checks whether the time is up. Pressing the button does not trigger anything immediately; it raises the yayaTalebi (pedestrian request) flag, and the cycle starts once the cars have had at least 8 seconds of green. Presses are never lost now, and because the button only changes a flag, contact bounce causes no trouble either. Presses while the green man is already lit are ignored; otherwise a new, unnecessary cycle would start right after the pedestrians have crossed. Open the Serial Monitor at 9600 baud to see the name of every transition; this output helps both with debugging and in your school report.

When you want to add a feature, first add a row to the table, then write a case in the code. For example, you can add a night state in which the yellow light flashes and switch to it when an LDR light sensor detects darkness. To learn state machines in more depth, see Arduino state machine.

Step 4: a two-way junction

At a real junction the lights of the two roads are linked: while road A is green, road B must be red. This version uses six LEDs: D10, D9 and D8 for direction A, and D5, D4 and D3 for direction B. An important detail is the short all-red clearance interval: when one direction turns red, the other does not turn green immediately; the controller waits 1 second so that vehicles still inside the junction can leave. This time the states go into a table (an array) instead of a switch; for each step, the lamps that are on are stored as bits in a single byte and the duration in milliseconds.

Arduino (C++)
1// İki yönlü kavşak: tablo tabanlı durum makinesi / two-way junction: table-driven state machine
2const byte A_KIRMIZI = 10, A_SARI = 9, A_YESIL = 8;  // A yönü (ana yol) / direction A (main road)
3const byte B_KIRMIZI = 5,  B_SARI = 4, B_YESIL = 3;  // B yönü (ara yol) / direction B (side road)
4const byte PINLER[6] = { A_KIRMIZI, A_SARI, A_YESIL, B_KIRMIZI, B_SARI, B_YESIL };
5
6struct Adim {
7  byte maske;            // bit sırası: A_K A_S A_Y B_K B_S B_Y / bit order: A_R A_Y A_G B_R B_Y B_G
8  unsigned long sure;    // ms
9};
10
11const Adim PLAN[] = {
12  { 0b001100, 8000 },   // A yeşil, B kırmızı / A green, B red
13  { 0b010100, 3000 },   // A sarı, B kırmızı / A yellow, B red
14  { 0b100100, 1000 },   // tüm yönler kırmızı / all red
15  { 0b100110, 1000 },   // A kırmızı, B kırmızı + sarı / A red, B red + yellow
16  { 0b100001, 5000 },   // A kırmızı, B yeşil / A red, B green
17  { 0b100010, 3000 },   // A kırmızı, B sarı / A red, B yellow
18  { 0b100100, 1000 },   // tüm yönler kırmızı / all red
19  { 0b110100, 1000 },   // A kırmızı + sarı, B kırmızı / A red + yellow, B red
20};
21const byte ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
22
23byte adim = 0;
24unsigned long adimBaslangic = 0;
25
26void uygula(byte maske) {
27  if ((maske & 0b001000) && (maske & 0b000001)) {  // iki yeşil aynı anda yanamaz / never two greens
28    maske = 0b100100;                              // güvenli durum: tüm kırmızı / fail-safe: all red
29  }
30  for (byte i = 0; i < 6; i++) {
31    digitalWrite(PINLER[i], (maske >> (5 - i)) & 1);  // soldaki bit A_KIRMIZI / leftmost bit is A_RED
32  }
33}
34
35void setup() {
36  for (byte i = 0; i < 6; i++) pinMode(PINLER[i], OUTPUT);
37  uygula(PLAN[0].maske);
38  adimBaslangic = millis();
39}
40
41void loop() {
42  if (millis() - adimBaslangic >= PLAN[adim].sure) {
43    adimBaslangic += PLAN[adim].sure;   // kaymasız zamanlama / drift-free timing
44    adim = (adim + 1) % ADIM_SAYISI;
45    uygula(PLAN[adim].maske);
46  }
47  // Burada buton, sensör ya da ekran işleri çalışabilir / buttons, sensors or a display can run here
48}

Each row of the PLAN array is one step. In binary numbers such as 0b001100, the bits from left to right stand for A red, A yellow, A green, B red, B yellow and B green; 1 means on and 0 means off. The uygula() ("apply") function writes these six bits to the pins in order. The check at the start of the function stops a typo from showing green in both directions at once and puts the junction into all red instead. Real junction controllers also have a separate safety circuit that catches conflicting greens. To change timings or add steps you only edit the table and never touch the rest of the code. If you run out of pins as the number of LEDs grows, a 74HC595 shift register gives you eight outputs from three pins.

Try it in Tinkercad: simulation without any parts

  1. Create a free account at tinkercad.com and start a new circuit in the Circuits section.
  2. Drag an Arduino Uno R3, a breadboard, three LEDs and three resistors from the component list on the right. Click an LED to choose its colour and a resistor to set its value to 220 Ω.
  3. Draw the wires: LED anodes through the resistors to D10, D9 and D8, cathodes to GND. Setting wire colours (black for GND, for example) makes the schematic easy to read.
  4. Click Code, choose Text from the drop-down menu, delete the sample code and paste the sketch from this guide.
  5. Click Start Simulation; the LEDs start switching in sequence. Serial output appears in the Serial Monitor at the bottom of the code panel.

Tinkercad is the easiest way to test wiring and code before building with real parts; if you forget a resistor, the simulator shows a warning on the LED that the current is too high. To test the pedestrian version, just click the button with the mouse while the simulation runs. On a slow computer the simulation can run slower than real time, so the timings may look a little long. For a school assignment you can hand in Tinkercad's share link or a screenshot of the circuit. For detailed instructions see Tinkercad Arduino simulation, and for an alternative that also supports the ESP32 and Pico, see Wokwi.

ESP32 traffic light with night mode

The Arduino sketches in this guide also run on the ESP32: select ESP32 Dev Module as the board in the Arduino IDE and change the pin constants as shown in the table. The version below adds one feature: night mode. At quiet hours many junctions switch to flashing yellow. ESP32 DevKit boards have a BOOT button wired to GPIO 0, so you do not even need an extra button: once the board has started, it can be read like a normal input. Each press toggles between normal mode and night mode.

Arduino (C++)
1// ESP32 trafik lambası + gece modu (BOOT düğmesi) / ESP32 traffic light + night mode (BOOT button)
2const int KIRMIZI = 5;      // GPIO 5
3const int SARI = 18;        // GPIO 18
4const int YESIL = 19;       // GPIO 19
5const int BOOT_BUTON = 0;   // kart üzerindeki BOOT düğmesi / on-board BOOT button
6
7struct Adim { bool k, s, y; unsigned long sure; };
8const Adim PLAN[] = {
9  { true,  false, false, 5000 },   // kırmızı / red
10  { true,  true,  false, 1000 },   // kırmızı + sarı / red + yellow
11  { false, false, true,  5000 },   // yeşil / green
12  { false, true,  false, 3000 },   // sarı / yellow
13};
14const int ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
15
16int adim = 0;
17unsigned long adimBaslangic = 0;
18bool geceModu = false;
19int sonOkuma = HIGH, kararli = HIGH;     // debounce için / for debouncing
20unsigned long degisimZamani = 0;
21
22void lambalar(bool k, bool s, bool y) {
23  digitalWrite(KIRMIZI, k);
24  digitalWrite(SARI, s);
25  digitalWrite(YESIL, y);
26}
27
28void setup() {
29  Serial.begin(115200);
30  pinMode(KIRMIZI, OUTPUT);
31  pinMode(SARI, OUTPUT);
32  pinMode(YESIL, OUTPUT);
33  pinMode(BOOT_BUTON, INPUT_PULLUP);
34  lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
35  adimBaslangic = millis();
36}
37
38void loop() {
39  unsigned long simdi = millis();
40
41  // 50 ms kararlı kalan basışı kabul et / accept a press that stays stable for 50 ms
42  int okuma = digitalRead(BOOT_BUTON);
43  if (okuma != sonOkuma) {
44    sonOkuma = okuma;
45    degisimZamani = simdi;
46  }
47  if (simdi - degisimZamani >= 50 && okuma != kararli) {
48    kararli = okuma;
49    if (kararli == LOW) {                  // yeni basış / new press
50      geceModu = !geceModu;
51      Serial.println(geceModu ? "Gece modu / Night mode" : "Normal mod / Normal mode");
52      adim = 0;                            // normal mod kırmızıdan başlar / normal mode restarts on red
53      adimBaslangic = simdi;
54      lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
55    }
56  }
57
58  if (geceModu) {
59    lambalar(false, (simdi / 500) % 2 == 0, false);  // yanıp sönen sarı / flashing yellow
60    return;
61  }
62
63  if (simdi - adimBaslangic >= PLAN[adim].sure) {
64    adimBaslangic = simdi;
65    adim = (adim + 1) % ADIM_SAYISI;
66    lambalar(PLAN[adim].k, PLAN[adim].s, PLAN[adim].y);
67  }
68}

The button reading includes a simple debounce: a press only counts if the reading stays unchanged for 50 ms. The PLAN table holding the lamp states is a more readable form of the idea used in the junction sketch. Because the ESP32 pins output 3.3 V, the LEDs draw less current than on the Arduino and look a little dimmer. Red and yellow LEDs are bright enough with 220 Ω, but a bright green LED with a forward voltage around 3 V can stay very dim; in that case drop the green resistor to 100 Ω. If you are new to the ESP32, What is the ESP32? is a good place to start for installation and safe pins.

Pedestrian traffic light with Raspberry Pi Pico (MicroPython)

On the Raspberry Pi Pico we write the same state machine in MicroPython. This time the states live in a list: each row holds the state name, the values of the five lamps and the duration. The button uses an interrupt: when GP4 falls to LOW, the buton_kesmesi() ("button interrupt") function runs and only raises the request flag. If you save the code to the Pico as main.py with Thonny, it starts automatically every time the board powers up.

MicroPython
1# Raspberry Pi Pico (MicroPython): yaya butonlu trafik lambası / pedestrian traffic light
2from machine import Pin
3import time
4
5yesil = Pin(11, Pin.OUT)              # araç yeşil / vehicle green
6sari = Pin(12, Pin.OUT)               # araç sarı / vehicle yellow
7kirmizi = Pin(13, Pin.OUT)            # araç kırmızı / vehicle red
8yaya_yesil = Pin(14, Pin.OUT)         # yaya yeşil / pedestrian green
9yaya_kirmizi = Pin(15, Pin.OUT)       # yaya kırmızı / pedestrian red
10buton = Pin(4, Pin.IN, Pin.PULL_UP)   # GP4, diğer ucu GND / other leg to GND
11
12# (durum, kırmızı, sarı, yeşil, yaya kırmızı, yaya yeşil, süre ms)
13# (state, red, yellow, green, ped red, ped green, duration ms)
14DURUMLAR = [
15    ("ARAC_YESIL",   0, 0, 1, 1, 0, 8000),   # en az yeşil süresi / minimum green
16    ("ARAC_SARI",    0, 1, 0, 1, 0, 3000),
17    ("YAYA_BEKLE",   1, 0, 0, 1, 0, 1000),   # tüm kırmızı / all red
18    ("YAYA_GECER",   1, 0, 0, 0, 1, 6000),
19    ("YAYA_FLAS",    1, 0, 0, 0, 1, 3000),
20    ("YAYA_BITTI",   1, 0, 0, 1, 0, 1000),   # tüm kırmızı / all red
21    ("KIRMIZI_SARI", 1, 1, 0, 1, 0, 1000),
22]
23
24talep = False
25adim = 0                              # şu anki durumun sırası / index of the current state
26
27def buton_kesmesi(pin):               # kesmede yalnız bayrak kaldır / only set a flag in the IRQ
28    global talep
29    if DURUMLAR[adim][0] != "YAYA_GECER":   # yaya yeşilinde basış yok sayılır / ignored during the green man
30        talep = True
31
32buton.irq(trigger=Pin.IRQ_FALLING, handler=buton_kesmesi)
33
34def uygula(i):
35    global talep
36    ad, k, s, y, yk, yy, _ = DURUMLAR[i]
37    kirmizi.value(k)
38    sari.value(s)
39    yesil.value(y)
40    yaya_kirmizi.value(yk)
41    yaya_yesil.value(yy)
42    if ad == "YAYA_GECER":
43        talep = False                 # talep karşılandı / request served
44    print("Durum / state:", ad)
45
46uygula(adim)
47baslangic = time.ticks_ms()
48
49while True:
50    gecen = time.ticks_diff(time.ticks_ms(), baslangic)   # taşmaya karşı güvenli / wrap-safe
51    ad = DURUMLAR[adim][0]
52
53    if ad == "YAYA_FLAS":
54        yaya_yesil.value((gecen // 250) % 2 == 0)          # yanıp sönen yaya yeşili / flashing green man
55
56    if gecen >= DURUMLAR[adim][6] and (ad != "ARAC_YESIL" or talep):
57        adim = (adim + 1) % len(DURUMLAR)                   # yeşil, talep gelene kadar sürer / green lasts until a request
58        uygula(adim)
59        baslangic = time.ticks_ms()
60
61    time.sleep_ms(10)

In MicroPython you use time.ticks_ms() instead of millis() and time.ticks_diff() instead of plain subtraction; the ticks counter wraps around after a certain value, so subtracting two readings directly can give wrong results. Keep the interrupt handler short: no print, no sleep and no long calculations, just change a flag. The Pico's pins also run at 3.3 V, so the green LED note from the ESP32 section applies here too. You will find how to install MicroPython and the pin numbering in What is the Raspberry Pi Pico?, and the language basics in MicroPython basics.

Common mistakes and how to fix them

SymptomLikely causeFix
No LED lights upGND not connected, or the breadboard power rail is split in the middleConnect Arduino GND to the blue rail; bridge the two halves on a long breadboard
One LED stays darkLED reversed or wired to the wrong pinLong leg (anode) through the resistor to the pin, short leg to GND; compare the pin number with the code
The wrong colour lights upThe red and green pins are swapped in the codeFix the wires or the constants according to the pin table
LED very bright and getting warmNo resistor fittedAdd a 220 Ω series resistor to every LED
Button does nothing or always seems pressedButton fitted across two legs that are already connected inside, or INPUT_PULLUP forgottenUse diagonal legs and write pinMode(BUTON, INPUT_PULLUP)
No immediate reaction to the buttonThe cars' minimum green time has not run out yetNot a bug; reduce MIN_YESIL
The program goes haywire after a whileA time variable is declared as intDeclare every variable used with millis() as unsigned long

Tips for your school assignment and project report

The traffic light is one of the most common Arduino assignments in science, technology and robotics classes. Your teacher usually wants to see not just a working circuit but that you understand it and can explain it. If you structure your report with the following sections, it will look organised and you will not miss anything important:

  • Aim: Describe in one or two sentences what the project demonstrates, for example "timing a traffic light with digital outputs".
  • Parts list: Give the name, quantity and value (220 Ω) of every part, as in the table above.
  • Circuit diagram: Add a Tinkercad or Fritzing screenshot; if you draw it by hand, use the correct symbols. How to read a circuit diagram helps with this.
  • Resistor calculation: Show R = (5 − 2) / 0.015 ≈ 200 Ω and explain why you chose 220 Ω.
  • State table or flowchart: Include the table of states, durations and transitions; it is the strongest proof that you understand the code.
  • Code and explanation: Include the full code and explain the important lines in your own words.
  • Tests and observations: Time the phases with a stopwatch, measure one LED's current with a multimeter and compare it with your calculation.
  • Conclusion and improvements: Describe the problems you met, how you solved them and features you could add (pedestrian button, sound, night mode).
If you want to stand out, compare your first delay() version with the millis() version: explain why button presses are missed in the first and never missed in the second. A 20–30 second video of the working circuit and a screenshot of the Serial Monitor output make the report even stronger.

Ideas to take your traffic light further

  • Countdown display: Show the seconds left until green ends on a 7-segment display.
  • Audible signal for visually impaired pedestrians: While the pedestrian light is green, beep fast with a buzzer, and slowly while it is red.
  • Smart junction: Detect a car waiting on the side road with an HC-SR04 distance sensor and give direction B green only when a car is there.
  • Automatic night mode: Use an LDR to switch to flashing yellow when it gets dark.
  • Remote control: Build a web page on the ESP32 and change the mode and timings from your phone.
  • Level crossing: Use the same logic for railway crossing lights with two red LEDs flashing alternately; the Knight Rider and running LED projects use similar timing ideas.

A traffic light looks like a small project, but the ideas in it, state machines, non-blocking timing and safe transitions, are also the foundation of the robots, alarm systems and smart home devices you will build later. To choose what to build next, browse the Arduino project ideas list. In the BlueGrays app you will find traffic light projects for Arduino, ESP32 and Raspberry Pi Pico, plus follow-ups such as the Pico four-mode traffic light and LED level crossing warning lights, each with a step-by-step circuit diagram and ready-to-use code to try on your own board.

Frequently asked questions

How do you make a traffic light with Arduino?

Connect the long legs of a red, a yellow and a green LED to Arduino digital pins (for example D10, D9 and D8) through 220 Ω resistors and the short legs to GND. In the code, set those pins as OUTPUT and switch the LEDs on and off in order inside loop(). delay() is enough for a first version; for buttons and extra features you need millis().

What resistor do I need for a traffic light LED?

On a 5 V Arduino Uno, 220 Ω per LED is a common and safe choice; a red LED then draws about 13–14 mA. 330 Ω gives a dimmer but still clearly visible light. 220 Ω also works on the 3.3 V ESP32 and Pico; only if a bright green LED stays dim should you go down to 100 Ω.

Why do red and yellow light up together on a traffic light?

In countries such as the UK, Germany and Turkey, red and yellow together announce that green is about to come on: drivers get ready but do not move until green appears. The USA, France, Italy and Spain do not use this phase and go straight from red to green. In your Arduino project you can include or leave out this phase as you like.

Should I use delay or millis for an Arduino traffic light?

If you only switch the LEDs in sequence, delay() is the simplest option and works fine. As soon as you add a button, sensor, display or sound, delay() pauses the program and those inputs are missed. Code written with millis() and a state machine never waits and can keep track of everything at once.

Can I build the traffic light project in Tinkercad?

Yes. Tinkercad Circuits includes the Arduino Uno, LEDs, resistors and push buttons as ready-made components; draw the circuit, paste the code into the Text editor and start the simulation. It is ideal for testing the circuit and code without real parts and for getting a screenshot or share link for your assignment.

How does a pedestrian push-button traffic light work?

Cars normally have green, and pressing the button registers a request. Once the cars' minimum green time is over, the light turns yellow, then red, and after a short safety gap the pedestrian light turns green. In this guide's code the green man flashes before it ends, and then the cycle returns to the cars.

See all projects
Traffic Light
#3Beginner

Traffic Light

Project that simulates a real traffic light with red, yellow and green LEDs. Teaches timing and sequential control logic.

4.8(126)
337
ESP32 Traffic Light
#107Beginner

ESP32 Traffic Light

Real traffic light simulation with 3 LEDs red-yellow-green. Timings close to real traffic logic: red long, yellow short, green long. Classic example of sequential GPIO control.

4.4(21)
44
Raspberry Pi Pico Traffic Light Simulator (3 LEDs Sequential, GP11/GP12/GP13)
#150Beginner

Raspberry Pi Pico Traffic Light Simulator (3 LEDs Sequential, GP11/GP12/GP13)

We simulate a real traffic light with red, yellow and green LEDs — the first 3-LED project of the Pico series! Green 4 sec "go", yellow 1.5 sec "ready", red 3 sec "stop" and red+yellow 1 sec "starting" (European traffic light pattern). Learn **function definition** (`def all_off()`), **state machine logic** (sequence) and **multi-GPIO synchronization**. Very simple circuit: 3 LEDs on GP11 (Green), GP12 (Yellow), GP13 (Red), each with a 220Ω current-limiting resistor. GP11-GP12-GP13 are ad

4.0(1)
12
Two LEDs Blinking Alternately
#2Beginner

Two LEDs Blinking Alternately

Project that creates a flip-flop effect by sequentially turning on and off two LEDs. Teaches how to control multiple output pins.

4.6(103)
317
LED Control with a Push Button (Push)
#9Beginner

LED Control with a Push Button (Push)

Push control project that turns on the LED when the button is pressed and turns it off when it is released. Teaches button reading and conditional expression usage with digitalRead.

4.9(112)
190
LED Police Lights
#4Beginner

LED Police Lights

Project that creates a police strobe light effect by flashing two LEDs at fast intervals. Teaches for loop and timing control.

4.7(97)
227
Raspberry Pi Pico Traffic Light with 4-Mode Button Selector (State Machine, GP11-13 + GP4)
#158Intermediate
Premium

Raspberry Pi Pico Traffic Light with 4-Mode Button Selector (State Machine, GP11-13 + GP4)

4 different traffic light modes with just 1 button! The mode changes with each press of the button: **Normal cycle** (red → yellow → green in sequence), **Red Only** (stop signal), **Yellow Flash** (caution/night mode), **Off** (maintenance). Concepts: **state machine** (the `mode` variable determines which behavior runs), **modulo mode cycling** (`(mode + 1) % 4` → 0→1→2→3→0 infinite loop), **non-blocking timing** (LED phases are timed with `time.ticks_ms()` + `time.ticks_diff()`; however, the

5.0(1)
14
LED Railroad Crossing Warning Lights
#146Advanced
Premium

LED Railroad Crossing Warning Lights

LED Railroad Crossing Warning Lights.

5.0(2)
14