Proyecto maker · Gratis

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.

Nivel intermedio 2-3 horas Material: 12-25 €

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.

Semáforo de cruce con Arduino terminado y funcionando
Antes de montarlo

Míralo funcionar

Cargando…
Coche A
Peatón A
Peatón B
Coche B
Cómo funciona

Tres pasos: código, modelo, montaje

1 Código — el sketch de Arduino está en GitHub, listo para descargar y subir a tu placa. Ver el código en GitHub →
2 Modelo 3D — el archivo para imprimir el cuerpo del semáforo está en Printables, listo para tu slicer. Ver el modelo en Printables →
3 Montaje — conecta los LEDs a la placa siguiendo el diagrama, sube el código, y encaja las piezas impresas. Guía completa más abajo.
1. Qué necesitas

Materiales

🎛️
Arduino Uno (o compatible)
Usamos un Elegoo Uno R3 — ver kits en Elegoo.com →
1
LEDs rojos
Estándar 5mm, tensión directa ~2V
4
LEDs amarillos
Estándar 5mm, tensión directa ~2V
2
LEDs verdes
Estándar 5mm, tensión directa ~2V
4
〰️
Resistencias 220Ω
Una por cada LED — limita la corriente a ~14mA
10
🔲
Pulsadores
Momentáneos táctiles — en pines 2 (INT0) y 3 (INT1)
2
🧩
Protoboard
830 puntos recomendada
1
🔗
Cables dupont
Macho-macho surtidos — usa colores distintos por señal
~20
¿Por qué 220Ω? R = (5V − 2V) / 0,02A = 150Ω. Usamos 220Ω (el valor estándar más cercano por encima) como margen de seguridad. Los LEDs se ven perfectamente. Corriente total: 10 × 14mA = 140mA — muy por debajo del límite de 500mA del Uno.
2. Cómo funciona

Ciclo del semáforo

DuraciónCoche ACoche BPeatón APeatón BNota
8 sVERDErojorojorojoCiclo normal
2 sAMARILLOrojorojorojo
1 srojorojorojorojoTodo rojo — se comprueba el botón aquí
8 srojoVERDErojorojoCiclo normal
2 srojoAMARILLOrojorojo
1 srojorojorojorojoTodo rojo — se comprueba el botón aquí

Cuando se pulsa un botón (cualquiera)

DuraciónCoche ACoche BPeatón APeatón BNota
1 srojorojorojorojoTodo rojo — petición detectada
4 srojorojoVERDEVERDEAmbos peatones cruzan a la vez
2 srojorojoPARPADEOPARPADEOAviso — el cruce va a cerrarse
1 srojorojorojorojoTodo rojo — vuelta al ciclo de coches
3. Conexiones

Diagrama de conexiones

BUS GND ARDUINO UNO USB GND 13★ 12 11 10 9 8 7 6 5 4 3 2 BTN A · INT0 BTN B · INT1 COCHE A R A V 220Ω 220Ω 220Ω PEATÓN A R V 220Ω 220Ω COCHE B R A V 220Ω 220Ω 220Ω PEATÓN B R V 220Ω 220Ω ★ el LED integrado de la placa también parpadea en el pin 13

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.

4. Referencia

Tabla de pines

PinConstanteComponenteModoColor de cable
2BTN_APulsador A — INT0INPUT_PULLUP + interrupciónAzul
3BTN_BPulsador B — INT1INPUT_PULLUP + interrupciónMorado
4COCHE_A_ROJOCoche A — LED rojoOUTPUTRojo
5COCHE_A_AMARILLOCoche A — LED amarilloOUTPUTAmarillo
6COCHE_A_VERDECoche A — LED verdeOUTPUTVerde
7PEAT_A_ROJOPeatón A — LED rojoOUTPUTRojo
8PEAT_A_VERDEPeatón A — LED verdeOUTPUTVerde
9COCHE_B_ROJOCoche B — LED rojoOUTPUTRojo
10COCHE_B_AMARILLOCoche B — LED amarilloOUTPUTAmarillo
11COCHE_B_VERDECoche B — LED verdeOUTPUTVerde
12PEAT_B_ROJOPeatón B — LED rojoOUTPUTRojo
13 ★PEAT_B_VERDEPeatón B — LED verdeOUTPUTVerde
GNDBus GND común (todos los LEDs y pulsadores)Negro
★ Pin 13: El Arduino Uno tiene un LED integrado en el pin 13. Parpadeará junto con el LED verde del Peatón B — es solo estético y no afecta al funcionamiento.
5. Programación

Código Arduino

También disponible en GitHub: github.com/MitsuMira/maker-projects →

semaforo_cruce_v3.ino
/*
 * 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.

6. A fondo

Cómo funciona el código

1
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.

2
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.

3
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.

4
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.

5
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.

6
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.

7. Consejos

Consejos de montaje

💡Polaridad del LED: la pata más larga (ánodo) va al pin a través de la resistencia. La pata más corta (cátodo) va a GND. Si un LED no enciende, dale la vuelta.
🔲Orientación del pulsador: un pulsador de 4 patas tiene dos pares conectados internamente. Colócalo cruzando la ranura central de la protoboard — un lado al pin 2 ó 3, el otro a GND. No necesita resistencia externa.
🎨Usa cables de colores: rojo para LEDs rojos, amarillo para amarillos, verde para verdes, negro para GND, azul y morado para los pulsadores. Ahorra mucho tiempo al depurar.
🔬Prueba los LEDs de uno en uno primero: conecta cada LED con su resistencia a 5V y GND antes del montaje completo. Es mucho más fácil detectar un LED roto antes de que esté enterrado en la protoboard.
🛠️Ajusta los tiempos fácilmente: todos los tiempos están en constantes al principio del código — T_COCHE_VERDE, T_PEAT_VERDE, etc. Cámbialos sin tocar la lógica.
La corriente es segura: 10 LEDs × ~14mA = 140mA en total. El Uno puede suministrar hasta 500mA — hay margen de sobra incluso con todos los LEDs encendidos a la vez.
8. Montaje de la carcasa

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.

1Semáforo de coche (arriba): inserta las patas de los 3 LEDs (rojo, ámbar, verde) en sus agujeros, dejando las dos patas de cada uno asomando por la parte trasera. Fíjate en la disposición: los 3 agujeros de un lado son los cátodos, y están unidos entre sí por un único canal — ese es el bus de GND, un solo cable para los tres. Los 3 agujeros del otro lado son los ánodos: cada uno tiene su propio canal independiente, con su propio cable hacia el pin correspondiente (rojo, ámbar o verde).
2Semáforo de peatón (a media altura): mismo sistema con los 2 LEDs (rojo, verde) — los 2 cátodos comparten el bus de GND común, y cada ánodo tiene su propio canal y su propio cable de señal.
3Pulsador (en la base): dos cables — uno a GND, otro al pin de señal.
4Bajada por el poste: todos los cables se introducen por el poste interior y bajan hasta la base. Los tres GND (coche + peatón + pulsador) bajan cada uno por su lado y se unen ahí en un único cable de tierra.
5Conectores (opcional): recomendamos usar dos conectores JST XH para poder separar el semáforo del cableado — uno de 4 pines para el coche (GND + rojo/ámbar/verde) y otro de 3 pines para peatón + pulsador (rojo, verde, señal del pulsador — sin pin de tierra propio, porque el cable de GND único del paso anterior ya sale por el conector de 4 pines y llega a la misma tierra compartida de la protoboard). Si prefieres algo más sencillo, puedes saltarte los conectores y llevar los cables directamente a la protoboard.
Las resistencias no van dentro de la pieza impresa — se colocan en la protoboard, en el lado positivo de cada LED, como en el diagrama de la sección 3.

¿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 →
¿Prefieres que te ayudemos?

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.

← Ver todos los proyectos

Scroll al inicio