Semáforo de cruce con Arduino.
Un semáforo completo, con dos conjuntos de luces: coche (rojo, ámbar, verde) y peatón (rojo, verde) — 10 luces en total. Los peatones cruzan cuando pulsas el botón, ambos a la vez, con todos los coches parados. Código, modelo 3D e instrucciones de montaje, completos y gratis.
Qué vas a construir
Un semáforo de cruce funcional: cuerpo impreso en 3D con las luces de coche (rojo/ámbar/verde) y de peatón (rojo/verde) controladas por un Arduino. En su ciclo normal, los coches se alternan; al pulsar cualquiera de los dos botones, el cruce se para por completo y ambos peatones cruzan a la vez. Es el mismo tipo de proyecto que hacemos en talleres — aquí tienes todo lo necesario para montarlo por tu cuenta.

Míralo funcionar
Tres pasos: código, modelo, montaje
Materiales
Ciclo del semáforo
| Duración | Coche A | Coche B | Peatón A | Peatón B | Nota |
|---|---|---|---|---|---|
| 8 s | VERDE | rojo | rojo | rojo | Ciclo normal |
| 2 s | AMARILLO | rojo | rojo | rojo | |
| 1 s | rojo | rojo | rojo | rojo | Todo rojo — se comprueba el botón aquí |
| 8 s | rojo | VERDE | rojo | rojo | Ciclo normal |
| 2 s | rojo | AMARILLO | rojo | rojo | |
| 1 s | rojo | rojo | rojo | rojo | Todo rojo — se comprueba el botón aquí |
Cuando se pulsa un botón (cualquiera)
| Duración | Coche A | Coche B | Peatón A | Peatón B | Nota |
|---|---|---|---|---|---|
| 1 s | rojo | rojo | rojo | rojo | Todo rojo — petición detectada |
| 4 s | rojo | rojo | VERDE | VERDE | Ambos peatones cruzan a la vez |
| 2 s | rojo | rojo | PARPADEO | PARPADEO | Aviso — el cruce va a cerrarse |
| 1 s | rojo | rojo | rojo | rojo | Todo rojo — vuelta al ciclo de coches |
Diagrama de conexiones
Los pulsadores usan INPUT_PULLUP — no necesitan resistencia externa. Un terminal al pin 2 ó 3, el otro a GND. Todos los cátodos y el terminal de tierra de los pulsadores se unen en el bus GND compartido (línea discontinua), que a su vez va al pin GND del Arduino.
Tabla de pines
| Pin | Constante | Componente | Modo | Color de cable |
|---|---|---|---|---|
| 2 | BTN_A | Pulsador A — INT0 | INPUT_PULLUP + interrupción | Azul |
| 3 | BTN_B | Pulsador B — INT1 | INPUT_PULLUP + interrupción | Morado |
| 4 | COCHE_A_ROJO | Coche A — LED rojo | OUTPUT | Rojo |
| 5 | COCHE_A_AMARILLO | Coche A — LED amarillo | OUTPUT | Amarillo |
| 6 | COCHE_A_VERDE | Coche A — LED verde | OUTPUT | Verde |
| 7 | PEAT_A_ROJO | Peatón A — LED rojo | OUTPUT | Rojo |
| 8 | PEAT_A_VERDE | Peatón A — LED verde | OUTPUT | Verde |
| 9 | COCHE_B_ROJO | Coche B — LED rojo | OUTPUT | Rojo |
| 10 | COCHE_B_AMARILLO | Coche B — LED amarillo | OUTPUT | Amarillo |
| 11 | COCHE_B_VERDE | Coche B — LED verde | OUTPUT | Verde |
| 12 | PEAT_B_ROJO | Peatón B — LED rojo | OUTPUT | Rojo |
| 13 ★ | PEAT_B_VERDE | Peatón B — LED verde | OUTPUT | Verde |
| GND | — | Bus GND común (todos los LEDs y pulsadores) | — | Negro |
Código Arduino
También disponible en GitHub: github.com/MitsuMira/maker-projects →
/*
* MitsuMira — Semáforo de cruce v3
*
* Lógica:
* - Ciclo normal: Coche A verde → amarillo → todo rojo → Coche B verde → amarillo → todo rojo → repetir
* - Los peatones están siempre en ROJO durante el ciclo de coches
* - Cualquier pulsador activa una interrupción hardware → activa un flag
* - El flag se comprueba en la siguiente transición todo-rojo
* - Si el flag está activo: se extiende el todo-rojo con cruce peatonal
* - Durante el cruce: ambos peatones en VERDE simultáneamente
* - Parpadeo de aviso antes de cerrar el cruce
* - Segunda pulsación durante el cruce: ignorada hasta el siguiente ciclo
*
* Pines:
* BTN_A : 2 (INT0 — interrupción hardware)
* BTN_B : 3 (INT1 — interrupción hardware)
* Coche A : Rojo=4, Amarillo=5, Verde=6
* Peatón A : Rojo=7, Verde=8
* Coche B : Rojo=9, Amarillo=10, Verde=11
* Peatón B : Rojo=12, Verde=13
*/
// --- Pines ---
const int BTN_A = 2; // INT0
const int BTN_B = 3; // INT1
const int COCHE_A_ROJO = 4;
const int COCHE_A_AMARILLO = 5;
const int COCHE_A_VERDE = 6;
const int PEAT_A_ROJO = 7;
const int PEAT_A_VERDE = 8;
const int COCHE_B_ROJO = 9;
const int COCHE_B_AMARILLO = 10;
const int COCHE_B_VERDE = 11;
const int PEAT_B_ROJO = 12;
const int PEAT_B_VERDE = 13; // el LED integrado de la placa también parpadea aquí
// --- Tiempos en milisegundos ---
const unsigned long T_COCHE_VERDE = 8000; // tiempo en verde para los coches
const unsigned long T_COCHE_AMARILLO = 2000; // tiempo en amarillo
const unsigned long T_TODO_ROJO = 1000; // transición todo rojo entre fases
const unsigned long T_PEAT_VERDE = 6000; // tiempo total del verde peatonal (incluye parpadeo)
const unsigned long T_PEAT_PARPADEO = 2000; // últimos 2s del verde — parpadeo de aviso
const unsigned long T_INTERVALO_BLINK = 400; // intervalo del parpadeo
// --- Estados posibles del semáforo ---
enum Estado {
COCHE_A_VERDE_E, // Coche A en verde, Coche B en rojo
COCHE_A_AMARILLO_E, // Coche A en amarillo
TODO_ROJO_1, // Todo rojo entre A→B: aquí se comprueba el pulsador
COCHE_B_VERDE_E, // Coche B en verde, Coche A en rojo
COCHE_B_AMARILLO_E, // Coche B en amarillo
TODO_ROJO_2, // Todo rojo entre B→A: aquí se comprueba el pulsador
PEAT_VERDE_E, // Cruce peatonal: ambos peatones en verde
PEAT_PARPADEO_E, // Cruce peatonal: parpadeo de aviso
PEAT_TODO_ROJO // Todo rojo tras el cruce — vuelta al ciclo de coches
};
// Declaración adelantada — necesaria para avr-gcc / Tinkercad
void cambiarEstado(Estado e);
Estado estadoActual;
Estado estadoRetorno; // estado al que se vuelve tras el cruce peatonal
unsigned long inicioEstado;
unsigned long ultimoParpadeo;
bool luzParpadeo = true;
// volatile: variable escrita por la ISR, leída en el loop principal
volatile bool peatonPulsado = false;
// --- ISR compartida para los dos pulsadores ---
// Se llama automáticamente cuando cualquier pulsador se pulsa
void alPulsar() {
peatonPulsado = true;
}
// -----------------------------------------------
// Apaga todos los LEDs
// -----------------------------------------------
void apagarTodo() {
int pines[] = {
COCHE_A_ROJO, COCHE_A_AMARILLO, COCHE_A_VERDE,
COCHE_B_ROJO, COCHE_B_AMARILLO, COCHE_B_VERDE,
PEAT_A_ROJO, PEAT_A_VERDE,
PEAT_B_ROJO, PEAT_B_VERDE
};
for (int p : pines) digitalWrite(p, LOW);
}
// -----------------------------------------------
// Cambia al nuevo estado y activa los LEDs correspondientes
// -----------------------------------------------
void cambiarEstado(Estado e) {
estadoActual = e;
inicioEstado = millis();
ultimoParpadeo = millis();
luzParpadeo = true;
apagarTodo();
switch (e) {
case COCHE_A_VERDE_E:
// Coche A circula — peatones en rojo
digitalWrite(COCHE_A_VERDE, HIGH);
digitalWrite(COCHE_B_ROJO, HIGH);
digitalWrite(PEAT_A_ROJO, HIGH);
digitalWrite(PEAT_B_ROJO, HIGH);
break;
case COCHE_A_AMARILLO_E:
digitalWrite(COCHE_A_AMARILLO, HIGH);
digitalWrite(COCHE_B_ROJO, HIGH);
digitalWrite(PEAT_A_ROJO, HIGH);
digitalWrite(PEAT_B_ROJO, HIGH);
break;
case TODO_ROJO_1:
case TODO_ROJO_2:
case PEAT_TODO_ROJO:
// Todo rojo: coches parados, peatones en rojo
digitalWrite(COCHE_A_ROJO, HIGH);
digitalWrite(COCHE_B_ROJO, HIGH);
digitalWrite(PEAT_A_ROJO, HIGH);
digitalWrite(PEAT_B_ROJO, HIGH);
break;
case COCHE_B_VERDE_E:
// Coche B circula — peatones en rojo
digitalWrite(COCHE_A_ROJO, HIGH);
digitalWrite(COCHE_B_VERDE, HIGH);
digitalWrite(PEAT_A_ROJO, HIGH);
digitalWrite(PEAT_B_ROJO, HIGH);
break;
case COCHE_B_AMARILLO_E:
digitalWrite(COCHE_A_ROJO, HIGH);
digitalWrite(COCHE_B_AMARILLO, HIGH);
digitalWrite(PEAT_A_ROJO, HIGH);
digitalWrite(PEAT_B_ROJO, HIGH);
break;
case PEAT_VERDE_E:
// Cruce peatonal: coches parados, ambos peatones en verde
digitalWrite(COCHE_A_ROJO, HIGH);
digitalWrite(COCHE_B_ROJO, HIGH);
digitalWrite(PEAT_A_VERDE, HIGH);
digitalWrite(PEAT_B_VERDE, HIGH);
break;
case PEAT_PARPADEO_E:
// El parpadeo lo gestiona gestionarParpadeo()
// Ponemos verde como estado inicial antes de que empiece a parpadear
digitalWrite(COCHE_A_ROJO, HIGH);
digitalWrite(COCHE_B_ROJO, HIGH);
digitalWrite(PEAT_A_VERDE, HIGH);
digitalWrite(PEAT_B_VERDE, HIGH);
break;
}
}
// -----------------------------------------------
// Hace parpadear los dos LEDs verdes de peatón
// -----------------------------------------------
void gestionarParpadeo() {
if (millis() - ultimoParpadeo >= T_INTERVALO_BLINK) {
luzParpadeo = !luzParpadeo;
digitalWrite(PEAT_A_VERDE, luzParpadeo ? HIGH : LOW);
digitalWrite(PEAT_B_VERDE, luzParpadeo ? HIGH : LOW);
ultimoParpadeo = millis();
}
}
// -----------------------------------------------
// Máquina de estados — se ejecuta en cada iteración del loop
// -----------------------------------------------
void actualizar() {
unsigned long tiempoTranscurrido = millis() - inicioEstado;
switch (estadoActual) {
case COCHE_A_VERDE_E:
if (tiempoTranscurrido >= T_COCHE_VERDE) cambiarEstado(COCHE_A_AMARILLO_E);
break;
case COCHE_A_AMARILLO_E:
if (tiempoTranscurrido >= T_COCHE_AMARILLO) cambiarEstado(TODO_ROJO_1);
break;
case TODO_ROJO_1:
if (tiempoTranscurrido >= T_TODO_ROJO) {
if (peatonPulsado) {
// Hay una petición de cruce — activamos el cruce peatonal
peatonPulsado = false;
estadoRetorno = COCHE_B_VERDE_E; // después del cruce, sigue el Coche B
cambiarEstado(PEAT_VERDE_E);
} else {
cambiarEstado(COCHE_B_VERDE_E);
}
}
break;
case COCHE_B_VERDE_E:
if (tiempoTranscurrido >= T_COCHE_VERDE) cambiarEstado(COCHE_B_AMARILLO_E);
break;
case COCHE_B_AMARILLO_E:
if (tiempoTranscurrido >= T_COCHE_AMARILLO) cambiarEstado(TODO_ROJO_2);
break;
case TODO_ROJO_2:
if (tiempoTranscurrido >= T_TODO_ROJO) {
if (peatonPulsado) {
peatonPulsado = false;
estadoRetorno = COCHE_A_VERDE_E; // después del cruce, sigue el Coche A
cambiarEstado(PEAT_VERDE_E);
} else {
cambiarEstado(COCHE_A_VERDE_E);
}
}
break;
case PEAT_VERDE_E:
// Cuando faltan T_PEAT_PARPADEO ms para terminar, pasamos al parpadeo
if (tiempoTranscurrido >= T_PEAT_VERDE - T_PEAT_PARPADEO) {
cambiarEstado(PEAT_PARPADEO_E);
}
break;
case PEAT_PARPADEO_E:
gestionarParpadeo();
if (tiempoTranscurrido >= T_PEAT_PARPADEO) cambiarEstado(PEAT_TODO_ROJO);
break;
case PEAT_TODO_ROJO:
if (tiempoTranscurrido >= T_TODO_ROJO) cambiarEstado(estadoRetorno);
break;
}
}
// -----------------------------------------------
void setup() {
// Configurar pines de salida
int salidas[] = {
COCHE_A_ROJO, COCHE_A_AMARILLO, COCHE_A_VERDE,
COCHE_B_ROJO, COCHE_B_AMARILLO, COCHE_B_VERDE,
PEAT_A_ROJO, PEAT_A_VERDE,
PEAT_B_ROJO, PEAT_B_VERDE
};
for (int p : salidas) pinMode(p, OUTPUT);
// Pulsadores con resistencia pull-up interna (no necesitan resistencia externa)
pinMode(BTN_A, INPUT_PULLUP);
pinMode(BTN_B, INPUT_PULLUP);
// Interrupciones hardware
// FALLING = flanco de bajada (el pulsador lleva el pin de HIGH a LOW al pulsar)
attachInterrupt(digitalPinToInterrupt(BTN_A), alPulsar, FALLING);
attachInterrupt(digitalPinToInterrupt(BTN_B), alPulsar, FALLING);
// Estado inicial
cambiarEstado(COCHE_A_VERDE_E);
}
// -----------------------------------------------
void loop() {
actualizar();
}Código y modelo bajo licencia Creative Commons BY-NC-SA 4.0 — puedes usarlo y adaptarlo libremente citando la fuente, siempre que no sea con fines comerciales y compartas tus cambios bajo la misma licencia.
Cómo funciona el código
Máquina de estados
El semáforo siempre está en uno de los 9 estados definidos en el enum Estado. La función actualizar() comprueba si ha pasado suficiente tiempo y cambia al siguiente estado cuando corresponde.
Interrupciones hardware
Los pulsadores están en los pines 2 (INT0) y 3 (INT1) — los únicos dos pines de interrupción hardware del Uno. Ambos llaman a la misma función alPulsar(), que simplemente activa un flag.
La palabra volatile
La variable peatonPulsado es volatile porque la escribe la ISR y la lee el loop principal. Sin esta palabra, el compilador podría optimizarla y el programa nunca vería la pulsación.
El flag se comprueba en todo-rojo
El pulsador puede pulsarse en cualquier momento, pero el cruce solo se activa en la siguiente transición todo-rojo. La fase de coche actual siempre termina de forma natural — nunca se interrumpe a mitad.
Ambos peatones cruzan juntos
Cuando se activa el cruce, los dos peatones se ponen en verde simultáneamente y todos los coches están en rojo. Es lo más sencillo y lo más seguro para este tipo de cruce.
Nunca usamos delay()
Todo el tiempo se gestiona con millis(). Si usáramos delay(), el programa se bloquearía durante ese tiempo y las interrupciones no podrían actuar — el pulsador parecería no responder.
Consejos de montaje
T_COCHE_VERDE, T_PEAT_VERDE, etc. Cámbialos sin tocar la lógica.Cómo se ensambla la pieza impresa
El modelo es un único poste con tres secciones: el semáforo de coche arriba, el de peatón a media altura, y el soporte del pulsador en la base. Cada LED se ancla por sus patas en unos agujeros, y la parte trasera de la pieza tiene canales tallados que hacen de guía para los cables.
¿Quieres el modelo 3D para imprimirlo tú? Está en Printables, listo para tu slicer.
Ver modelo en Printables →¿Prefieres que te lo imprimamos nosotros? Pídenoslo y te lo mandamos listo.
Pedir la pieza →Si prefieres aprenderlo guiado, o que lo montemos nosotros.
Este proyecto es gratis y para hacerlo tú mismo. Si prefieres un taller guiado en persona, también podemos.
