Guías · 14 min de lectura

Semáforo con Arduino: circuito, código, botón peatonal y cruce

Cómo hacer un semáforo con Arduino: circuito con 3 LED y 220 Ω, código con delay y millis, pulsador peatonal, cruce de dos vías, Tinkercad, ESP32 y Pico.

Un semáforo con Arduino es el proyecto clásico con el que empieza casi todo el mundo en electrónica: tres LED, rojo, amarillo y verde, se encienden y apagan en orden igual que un semáforo real. Solo necesitas un Arduino Uno, tres LED, tres resistencias de 220 Ω y unos cuantos cables jumper, y tendrás la primera versión funcionando en media hora. Pero este proyecto tan sencillo enseña mucho: salidas digitales, el cálculo de la resistencia del LED, la temporización, la lectura de un pulsador y la máquina de estados, la forma más limpia de organizar el código.

En esta guía verás primero en qué orden y durante cuánto tiempo cambia un semáforo real; después montarás el circuito y escribirás el código más sencillo con delay(). Luego añadirás un pulsador para peatones, reescribirás la misma lógica con millis() y una máquina de estados para que nunca se bloquee y controlarás un cruce de dos direcciones. En las últimas secciones encontrarás cómo probar el proyecto en Tinkercad sin instalar nada, las versiones para ESP32 y Raspberry Pi Pico (MicroPython) y consejos para redactar la memoria del proyecto de clase.

LED rojos, amarillos y verdes de 5 mm para un semáforo con Arduino
LED de 5 mm: para el semáforo basta con uno rojo, uno amarillo y uno verde; reserva algunos más para el semáforo peatonal y el cruce.

¿Cómo funciona un semáforo real?

Un semáforo es un temporizador que da el paso por turnos a cada flujo de tráfico. En Alemania, Reino Unido y Turquía el ciclo es rojo → rojo + amarillo → verde → amarillo → rojo. El rojo y el amarillo (ámbar) juntos significan "va a ponerse verde, prepárate, pero no arranques todavía", y el amarillo solo significa "va a ponerse rojo, prepárate para detenerte". En España, como en EE. UU., Francia o Italia, no existe la fase rojo + amarillo: se pasa directamente del rojo al verde. La duración del amarillo tampoco es casual: en Alemania se fijan 3 segundos de amarillo en vías limitadas a 50 km/h, 4 segundos a 60 km/h y 5 segundos a 70 km/h, porque el conductor que está demasiado cerca para frenar cuando aparece el amarillo debe poder salir del cruce con seguridad.

FaseLuz encendidaSignificadoTiempo en esta guía
1RojoDetente5 s
2Rojo + amarilloVa a ponerse verde, prepárate1 s
3VerdePasa5 s
4AmarilloVa a ponerse rojo, detente3 s
En el cruceTodas las direcciones en rojoMargen de seguridad para despejar el cruce1 s

Materiales y cálculo de la resistencia de 220 Ω

MaterialCantidadNota
Arduino Uno (o Nano)1También sirve un ESP32 o una Raspberry Pi Pico
LED de 5 mm: rojo, amarillo, verde1 de cada+1 rojo y +1 verde para el semáforo peatonal, +1 juego para el cruce
Resistencia de 220 Ω3Una por LED; 8 para todas las versiones
Pulsador (6×6 mm)1Solo en la versión peatonal
Protoboard1Basta con media protoboard (400 puntos)
Cables jumper10–20Macho-macho
Cable USB1Para cargar el código y alimentar la placa

Los LED no limitan su propia corriente: un LED conectado directamente a 5 V consume más de lo que él y el pin pueden soportar. Por eso cada LED lleva una resistencia en serie. Su valor sale de la ley de Ohm: R = (tensión de alimentación − tensión directa del LED) / corriente. Para un LED rojo, (5 V − 2,0 V) / 0,015 A ≈ 200 Ω; con el valor estándar más cercano, 220 Ω, circulan unos 13,6 mA. Así el LED brilla lo suficiente y no se superan los 20 mA recomendados para un pin de Arduino. Una resistencia de 220 Ω lleva los colores rojo-rojo-marrón-dorado si tiene cuatro bandas y rojo-rojo-negro-negro-marrón si tiene cinco; para comprobarlo usa la herramienta de código de colores de resistencias, y para otros colores y tensiones la calculadora de resistencia para LED.

Color del LEDTensión directa (aprox.)Corriente a 5 V (220 Ω)Corriente a 3,3 V (220 Ω)
Rojo1,8–2,2 V≈ 13,6 mA≈ 5,9 mA
Amarillo1,9–2,2 V≈ 13,2 mA≈ 5,5 mA
Verde (verde amarillento)2,0–2,4 V≈ 13,2 mA≈ 5,5 mA
Verde (brillante, verde puro)2,8–3,4 V≈ 9,1 mA≈ 1,4 mA (muy tenue)

Conexión del circuito y tabla de pines

La pata larga de cada LED (ánodo, +) va a un pin digital del Arduino a través de una resistencia, y la pata corta (cátodo, −) va a GND. Da igual que la resistencia esté del lado del ánodo o del cátodo, siempre que esté en serie. En la protoboard lo más ordenado es unir el pin GND del Arduino a la línea azul (−) y llevar a esa línea las patas cortas de los tres LED. La tabla siguiente muestra los pines que usan todos los códigos de esta guía; el primer código solo usa las tres primeras filas. Puedes ver la distribución de pines de tu placa en la herramienta Pinout.

FunciónArduino UnoESP32Raspberry Pi Pico
Rojo vehículos (dirección A)D10GPIO 5GP13
Amarillo vehículos (dirección A)D9GPIO 18GP12
Verde vehículos (dirección A)D8GPIO 19GP11
Rojo peatonesD7GPIO 21GP15
Verde peatonesD6GPIO 22GP14
Pulsador peatonal (otra pata a GND)D2GPIO 23GP4
Dirección B rojo / amarillo / verdeD5 / D4 / D3GPIO 25 / 26 / 27GP8 / GP7 / GP6
Cátodos de los LED y pulsadorGNDGNDGND
Un LED colocado al revés simplemente no se enciende y no se estropea, pero si olvidas la resistencia el LED y el pin trabajan forzados y el LED puede perder brillo enseguida. En el Arduino Uno no pases de 20 mA por pin (el máximo absoluto es 40 mA) y mantén la suma de todos los pines por debajo de 200 mA. Si no se enciende ningún LED, revisa primero GND: si la línea azul de la protoboard no está unida a un pin GND del Arduino, el circuito no se cierra.

Paso 1: el código de semáforo más sencillo con 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}

El código configura los tres pines como salidas en setup() y recorre las cuatro fases en orden dentro de loop(). En vez de escribir tres líneas digitalWrite una y otra vez, usamos una pequeña función llamada lambalar() ("luces"), así cada fase se lee en una sola línea y los errores se ven enseguida. Para cargarlo, elige Arduino Uno como placa y el puerto correcto en el menú Herramientas y pulsa Subir. Si la carga falla, revisa la lista de comprobación de Arduino no sube el código. Para cambiar los tiempos basta con editar los milisegundos dentro de delay(): 5000 son 5 segundos.

Paso 2: semáforo con pulsador para peatones

Los semáforos con pulsador que ves en la ciudad mantienen el verde para los coches y solo dan paso a los peatones cuando alguien pulsa el botón. En esta versión añadimos al semáforo de vehículos uno peatonal de dos LED (rojo y verde) y un pulsador en D2. Una pata del pulsador va a D2 y la pata opuesta en diagonal a GND; como el código usa INPUT_PULLUP, no hace falta ninguna resistencia externa y el pin lee LOW mientras está pulsado. Las conexiones internas de un pulsador de cuatro patas y el antirrebote (debounce) se explican en Cómo conectar un pulsador a Arduino.

Pulsador de 6×6 mm con cuatro patas
Pulsador de 6×6 mm: sus cuatro patas están unidas por dentro de dos en dos; si usas dos patas opuestas en diagonal, la conexión siempre es correcta.
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}

Este código funciona, pero tiene un punto débil importante: tras pulsar el botón, el Arduino pasa unos 25 segundos parado en las líneas delay(). Si alguien vuelve a pulsar en ese tiempo no se detecta, no se puede leer el puerto serie ni actualizar una pantalla. Las pulsaciones durante la espera de 8 segundos del final se pierden por completo: el peatón pulsa, suelta, no pasa nada y tiene que volver a pulsar. Si quieres añadir sonido o una cuenta atrás, esta estructura lo complica todavía más. La solución es dejar de esperar y empezar a medir el tiempo.

Paso 3: sin bloqueos con millis() y una máquina de estados

Una máquina de estados es un diseño en el que el sistema está siempre en un único estado y pasa al siguiente cuando se cumplen ciertas condiciones. Un semáforo es el ejemplo perfecto: "verde para coches", "amarillo para coches" y "peatones cruzando" son estados distintos. El código ya no espera con delay(); en cada vuelta, loop() solo se hace dos preguntas: ¿en qué estado estoy? y ¿cuánto tiempo llevo en él? El tiempo transcurrido se mide con millis() - durumBaslangic (el instante en que empezó el estado) y, cuando se cumple, se pasa al siguiente. Si millis() es nuevo para ti, lee antes Arduino millis() en lugar de delay(). Escribir los estados en una tabla antes de programar facilita mucho el trabajo:

EstadoSemáforo de vehículosSemáforo peatonalDuraciónEstado siguiente
ARAC_YESILVerdeRojoAl menos 8 s, hasta que llegue una peticiónARAC_SARI
ARAC_SARIAmarilloRojo3 sYAYA_BEKLE
YAYA_BEKLERojoRojo1 sYAYA_GECER
YAYA_GECERRojoVerde6 sYAYA_FLAS
YAYA_FLASRojoVerde intermitente3 sYAYA_BITTI
YAYA_BITTIRojoRojo1 sKIRMIZI_SARI
KIRMIZI_SARIRojo + amarilloRojo1 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}

El corazón del código es la función gec() ("pasar a"): guarda el nuevo estado, reinicia el tiempo, ajusta las luces de ese estado y escribe su nombre en el monitor serie. El switch de loop() solo comprueba si se ha cumplido el tiempo. Pulsar el botón no provoca nada inmediato: activa el indicador yayaTalebi (petición peatonal) y el ciclo arranca cuando los coches han tenido al menos 8 segundos de verde. Ahora ninguna pulsación se pierde y, como el botón solo cambia un indicador, los rebotes mecánicos tampoco causan problemas. Las pulsaciones mientras el verde peatonal ya está encendido se ignoran; si no, empezaría un ciclo innecesario justo después de que crucen los peatones. Abre el monitor serie a 9600 baudios para ver el nombre de cada transición; esta salida te ayuda a depurar y también en la memoria del proyecto.

Cuando quieras añadir una función, primero añade una fila a la tabla y después escribe un case en el código. Por ejemplo, puedes crear un estado nocturno con el amarillo intermitente y pasar a él cuando un sensor de luz LDR detecte oscuridad. Para profundizar en las máquinas de estados, consulta Máquina de estados con Arduino.

Paso 4: un cruce de dos direcciones

En un cruce real, los semáforos de las dos calles están relacionados: mientras la calle A está en verde, la B tiene que estar en rojo. Esta versión usa seis LED: D10, D9 y D8 para la dirección A y D5, D4 y D3 para la dirección B. Un detalle importante es el breve intervalo de todo rojo: cuando una dirección se pone en rojo, la otra no pasa a verde de inmediato, sino que el controlador espera 1 segundo para que los vehículos que siguen dentro del cruce puedan salir. Esta vez los estados van en una tabla (un array) en lugar de un switch; en cada paso, las luces encendidas se guardan como bits de un único byte y la duración en milisegundos.

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}

Cada fila del array PLAN es un paso. En números binarios como 0b001100, los bits de izquierda a derecha representan A rojo, A amarillo, A verde, B rojo, B amarillo y B verde; 1 es encendido y 0 apagado. La función uygula() ("aplicar") escribe esos seis bits en los pines por orden. La comprobación del principio de la función evita que, por una errata, las dos direcciones se pongan en verde a la vez y en ese caso pone todo en rojo. Los controladores de cruces reales también tienen un circuito de seguridad independiente que detecta verdes incompatibles. Para cambiar tiempos o añadir pasos solo editas la tabla, sin tocar el resto del código. Si te quedas sin pines al añadir más LED, un registro de desplazamiento 74HC595 te da ocho salidas con tres pines.

Pruébalo en Tinkercad: simulación sin componentes

  1. Crea una cuenta gratuita en tinkercad.com y empieza un circuito nuevo en la sección Circuits (Circuitos).
  2. Arrastra desde la lista de componentes de la derecha un Arduino Uno R3, una protoboard, tres LED y tres resistencias. Haz clic en un LED para elegir su color y en una resistencia para poner 220 Ω.
  3. Dibuja los cables: ánodos de los LED a D10, D9 y D8 a través de las resistencias y cátodos a GND. Cambiar el color de los cables (negro para GND, por ejemplo) hace que el esquema se lea mejor.
  4. Pulsa Code (Código), elige Text (Texto) en el menú desplegable, borra el código de ejemplo y pega el de esta guía.
  5. Pulsa Start Simulation (Iniciar simulación); los LED empiezan a cambiar en orden. La salida serie aparece en el Serial Monitor, en la parte inferior del panel de código.

Tinkercad es la forma más sencilla de probar el cableado y el código antes de montarlo con componentes reales; si olvidas una resistencia, el simulador muestra en el LED un aviso de que la corriente es demasiado alta. Para probar la versión peatonal, haz clic con el ratón en el pulsador mientras corre la simulación. En un ordenador lento la simulación puede ir más despacio que el tiempo real, así que los tiempos pueden parecer algo más largos. Para entregar un trabajo de clase puedes adjuntar el enlace para compartir de Tinkercad o una captura del circuito. Encontrarás instrucciones detalladas en Simulación de Arduino en Tinkercad y una alternativa que también admite ESP32 y Pico en Wokwi.

Semáforo con ESP32 y modo nocturno

Los códigos de Arduino de esta guía también funcionan en el ESP32: elige ESP32 Dev Module como placa en el IDE de Arduino y cambia las constantes de pines según la tabla. La versión siguiente añade una función: el modo nocturno. En las horas de poco tráfico muchos cruces pasan a ámbar intermitente. Las placas ESP32 DevKit tienen un botón BOOT conectado a GPIO 0, así que ni siquiera necesitas un pulsador extra: una vez arrancada la placa, se puede leer como una entrada normal. Cada pulsación alterna entre el modo normal y el nocturno.

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}

La lectura del botón incluye un antirrebote sencillo: una pulsación solo cuenta si el valor leído se mantiene igual durante 50 ms. La tabla PLAN con los estados de las luces es una versión más legible de la idea del código del cruce. Como los pines del ESP32 dan 3,3 V, los LED consumen menos corriente que en Arduino y brillan algo menos. Los LED rojo y amarillo se ven bien con 220 Ω, pero un LED verde brillante con una tensión directa de unos 3 V puede quedar muy tenue; en ese caso baja la resistencia del verde a 100 Ω. Si empiezas con el ESP32, ¿Qué es el ESP32? es un buen punto de partida para la instalación y los pines seguros.

Semáforo peatonal con Raspberry Pi Pico (MicroPython)

En la Raspberry Pi Pico escribimos la misma máquina de estados en MicroPython. Esta vez los estados están en una lista: cada fila contiene el nombre del estado, los valores de las cinco luces y la duración. El pulsador usa una interrupción: cuando GP4 baja a LOW se ejecuta la función buton_kesmesi() ("interrupción del botón"), que solo activa el indicador de petición. Si guardas el código en la Pico como main.py con Thonny, arrancará solo cada vez que se encienda la placa.

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)

En MicroPython se usa time.ticks_ms() en lugar de millis() y time.ticks_diff() en lugar de una resta normal; el contador de ticks vuelve a empezar al llegar a cierto valor, así que restar dos lecturas directamente puede dar resultados erróneos. Mantén corta la función de interrupción: nada de print, sleep ni cálculos largos, solo cambia un indicador. Los pines de la Pico también trabajan a 3,3 V, así que la nota sobre el LED verde de la sección del ESP32 vale también aquí. Cómo instalar MicroPython y la numeración de los pines se explica en ¿Qué es la Raspberry Pi Pico?, y lo básico del lenguaje en Fundamentos de MicroPython.

Errores frecuentes y cómo solucionarlos

SíntomaCausa probableSolución
No se enciende ningún LEDGND sin conectar o la línea de alimentación de la protoboard cortada por la mitadUne el GND del Arduino a la línea azul; en protoboards largas, puentea las dos mitades
Un LED no se enciendeLED al revés o conectado al pin equivocadoPata larga (ánodo) al pin a través de la resistencia y pata corta a GND; compara el número de pin con el código
Se enciende el color equivocadoLos pines del rojo y el verde están intercambiados en el códigoCorrige los cables o las constantes según la tabla de pines
LED muy brillante que se calientaFalta la resistenciaPon una resistencia de 220 Ω en serie con cada LED
El pulsador no responde o parece siempre pulsadoPulsador conectado en dos patas que ya están unidas por dentro o falta INPUT_PULLUPUsa las patas en diagonal y escribe pinMode(BUTON, INPUT_PULLUP)
El botón no reacciona al instanteAún no se ha cumplido el verde mínimo de los cochesNo es un error; reduce MIN_YESIL
El programa hace cosas raras al cabo de un ratoUna variable de tiempo declarada como intDeclara como unsigned long todas las variables que usan millis()

Consejos para el trabajo de clase y la memoria del proyecto

El semáforo es uno de los trabajos de Arduino más habituales en las clases de tecnología, informática y robótica. Normalmente el profesor no solo quiere ver un circuito que funcione, sino que lo entiendes y sabes explicarlo. Si organizas la memoria con estos apartados, quedará ordenada y no te dejarás nada importante:

  • Objetivo: Explica en una o dos frases qué demuestra el proyecto, por ejemplo "temporizar un semáforo con salidas digitales".
  • Lista de materiales: Indica el nombre, la cantidad y el valor (220 Ω) de cada componente, como en la tabla anterior.
  • Esquema del circuito: Añade una captura de Tinkercad o Fritzing; si lo dibujas a mano, usa los símbolos correctos. Cómo leer un esquema eléctrico te ayudará.
  • Cálculo de la resistencia: Muestra R = (5 − 2) / 0,015 ≈ 200 Ω y explica por qué elegiste 220 Ω.
  • Tabla de estados o diagrama de flujo: Incluye la tabla con estados, tiempos y transiciones; es la mejor prueba de que entiendes el código.
  • Código y explicación: Añade el código completo y explica con tus palabras las líneas importantes.
  • Pruebas y observaciones: Cronometra las fases, mide con un multímetro la corriente de un LED y compárala con tu cálculo.
  • Conclusión y mejoras: Describe los problemas que encontraste, cómo los resolviste y qué funciones podrías añadir (pulsador peatonal, sonido, modo nocturno).
Si quieres destacar, compara tu primera versión con delay() con la de millis(): explica por qué en la primera se pierden pulsaciones y en la segunda nunca. Un vídeo de 20 a 30 segundos con el circuito funcionando y una captura del monitor serie refuerzan todavía más el trabajo.

Ideas para ampliar tu semáforo

  • Cuenta atrás: Muestra en un display de 7 segmentos los segundos que faltan para que acabe el verde.
  • Aviso sonoro para personas ciegas: Mientras el semáforo peatonal esté en verde, haz pitar rápido un zumbador, y despacio cuando esté en rojo.
  • Cruce inteligente: Detecta con un sensor de distancia HC-SR04 si hay un coche esperando en la calle lateral y da verde a la dirección B solo cuando lo haya.
  • Modo nocturno automático: Usa un LDR para pasar al ámbar intermitente cuando oscurezca.
  • Control remoto: Crea una página web en el ESP32 y cambia el modo y los tiempos desde el móvil.
  • Paso a nivel: Con la misma lógica, haz las luces de un paso a nivel con dos LED rojos que parpadean alternados; los proyectos de coche fantástico y luces secuenciales usan ideas de temporización parecidas.

Un semáforo parece un proyecto pequeño, pero sus ideas, la máquina de estados, la temporización sin bloqueos y las transiciones seguras, son también la base de los robots, alarmas y dispositivos domóticos que construirás más adelante. Para elegir tu próximo montaje, echa un vistazo a las ideas de proyectos con Arduino. En la app BlueGrays encontrarás proyectos de semáforo para Arduino, ESP32 y Raspberry Pi Pico, y continuaciones como el semáforo de cuatro modos para Pico y las luces de aviso de paso a nivel con LED, cada uno con esquema paso a paso y código listo para probar en tu propia placa.

Preguntas frecuentes

¿Cómo se hace un semáforo con Arduino?

Conecta las patas largas de un LED rojo, uno amarillo y uno verde a pines digitales del Arduino (por ejemplo D10, D9 y D8) a través de resistencias de 220 Ω, y las patas cortas a GND. En el código configura esos pines como OUTPUT y enciende y apaga los LED por orden dentro de loop(). Para una primera versión basta con delay(); para pulsadores y funciones extra necesitas millis().

¿Qué resistencia necesito para los LED de un semáforo?

En un Arduino Uno a 5 V, 220 Ω por LED es una elección habitual y segura; un LED rojo consume entonces unos 13–14 mA. Con 330 Ω brilla menos, pero se sigue viendo bien. En el ESP32 y la Pico, que trabajan a 3,3 V, también sirven 220 Ω; solo si un LED verde brillante queda muy tenue conviene bajar a 100 Ω.

¿Por qué se encienden a la vez el rojo y el amarillo en algunos semáforos?

En países como Alemania, Reino Unido o Turquía, el rojo y el amarillo juntos avisan de que el verde está a punto de encenderse: el conductor se prepara, pero no arranca hasta que aparece el verde. En España, EE. UU., Francia o Italia no existe esta fase y se pasa directamente del rojo al verde. En tu proyecto con Arduino puedes incluirla o quitarla.

¿Uso delay o millis para un semáforo con Arduino?

Si solo vas a encender los LED en orden, delay() es lo más sencillo y funciona bien. En cuanto añades un pulsador, un sensor, una pantalla o sonido, delay() detiene el programa y esas entradas se pierden. El código con millis() y una máquina de estados nunca espera y puede atender todo a la vez.

¿Se puede hacer el semáforo con Arduino en Tinkercad?

Sí. Tinkercad Circuits incluye Arduino Uno, LED, resistencias y pulsadores como componentes listos para usar; dibujas el circuito, pegas el código en el editor de texto e inicias la simulación. Es ideal para probar circuito y código sin componentes reales y para obtener una captura o un enlace para entregar el trabajo.

¿Cómo funciona un semáforo con pulsador para peatones?

Los coches tienen normalmente el verde y, al pulsar el botón, se registra una petición. Cuando termina el verde mínimo de los coches, el semáforo pasa a amarillo y luego a rojo, y tras un breve margen de seguridad se enciende el verde peatonal. En el código de esta guía el verde peatonal parpadea antes de terminar y después el ciclo vuelve a los coches.

Ver todos los proyectos
Semáforo
#3Principiante

Semáforo

Proyecto que simula un semáforo real con LED rojos, amarillos y verdes. Enseña temporización y lógica de control secuencial.

4.8(126)
337
ESP32 Semáforo
#107Principiante

ESP32 Semáforo

Simulación de un semáforo real con 3 LED rojo-amarillo-verde. Los tiempos se aproximan a la lógica del tráfico real: rojo largo, amarillo corto, verde largo. Ejemplo clásico de control GPIO secuencial.

4.4(21)
44
Simulador de Semáforo Raspberry Pi Pico (3 LEDs Secuenciales, GP11/GP12/GP13)
#150Principiante

Simulador de Semáforo Raspberry Pi Pico (3 LEDs Secuenciales, GP11/GP12/GP13)

Simulamos un semáforo real con LEDs rojo, amarillo y verde. Verde 4 seg "pase", amarillo 1,5 seg "prepárese", rojo 3 seg "detenga" y rojo+amarillo 1 seg "arranque" (patrón europeo). Aprende definición de función, lógica de máquina de estados y sincronización multi-GPIO. Circuito simple: 3 LEDs en GP11/GP12/GP13.

4.0(1)
12
Dos LED parpadeando alternadamente
#2Principiante

Dos LED parpadeando alternadamente

Un proyecto que crea un efecto flip-flop encendiendo y apagando dos LEDs secuencialmente. Enseña a controlar más de un pin de salida.

4.6(103)
317
Encender un LED con pulsador (Push)
#9Principiante

Encender un LED con pulsador (Push)

Proyecto de control instantáneo (push) que enciende el LED al pulsar el botón y lo apaga al soltarlo. Enseña el uso de la lectura de botones y la expresión condicional con digitalRead.

4.9(112)
190
Luces de policía LED
#4Principiante

Luces de policía LED

Proyecto que crea un efecto de luz estroboscópica policial haciendo parpadear dos LEDs a intervalos rápidos. Enseña para el control de bucle y temporización.

4.7(97)
227
Semáforo Raspberry Pi Pico con selector de 4 modos (máquina de estados, GP11-13 + GP4)
#158Intermedio
Premium

Semáforo Raspberry Pi Pico con selector de 4 modos (máquina de estados, GP11-13 + GP4)

¡4 modos diferentes para el semáforo con un solo botón! Cada vez que se pulsa el botón, cambia el modo: **Ciclo normal** (rojo → ámbar → verde en ese orden), **Solo rojo** (señal de parada), **Ámbar intermitente** (atención/modo nocturno), **Apagado** (mantenimiento). Conceptos: **máquina de estados** (la variable `mode` determina qué comportamiento se ejecutará), **ciclo de modos por módulo** (`(mode + 1) % 4` → 0→1→2→3→0, ciclo infinito), **sincronización sin bloqueo** (las fases de los LED se

5.0(1)
14
Luces intermitentes LED para pasos a nivel
#146Avanzado
Premium

Luces intermitentes LED para pasos a nivel

Luces intermitentes LED para pasos a nivel.

5.0(2)
14