Semáforo Inteligente con ESP32

En este proyecto desarrollamos un sistema de semáforo inteligente utilizando ESP32 y una Máquina de Estados Finitos (FSM), aplicando lógica de control similar a la utilizada en intersecciones reales. El proyecto incorpora control vehicular, solicitudes peatonales y validación de condiciones seguras antes de habilitar el cruce.

Datasheet ESP 32-S3 DevkitC1 v1.1

Código de Arduino IDE:

// ------------------------------
// PINES BOTONES
// ------------------------------
// Botones que simulan la solicitud peatonal
// Se usan con INPUT_PULLUP, por eso:
// - SIN presionar = HIGH
// - Presionado = LOW
const int btn_NS = 4;   // Botón peatón para cruzar NS
const int btn_EO = 5;   // Botón peatón para cruzar EO

// ------------------------------
// PINES VEHICULARES
// ------------------------------
// LEDs del semáforo vehicular para ambos sentidos
const int NS_ROJO = 6;
const int NS_AMARILLO = 7;
const int NS_VERDE = 8;

const int EO_ROJO = 9;
const int EO_AMARILLO = 10;
const int EO_VERDE = 11;

// ------------------------------
// PINES PEATONALES
// ------------------------------
// LEDs de peatones:
// SIGA = puede cruzar
// NO_SIGA = no debe cruzar
const int SIGA_NS = 12;
const int NO_SIGA_NS = 13;

const int SIGA_EO = 14;
const int NO_SIGA_EO = 16;

// ------------------------------
// BUZZER
// ------------------------------
const int BUZZER = 17;

// ------------------------------
// FSM (Máquina de estados)
// ------------------------------
// Define todos los estados posibles del sistema
enum Estado {
    ST_NS_VERDE,
    ST_NS_AMARILLO,
    ST_ALL_ROJO_NS_EO,
    ST_EO_ROJO_AMARILLO,
    ST_EO_VERDE,
    ST_EO_AMARILLO,
    ST_ALL_ROJO_EO_NS,
    ST_NS_ROJO_AMARILLO
};

// Estado inicial del sistema
Estado estadoActual = ST_NS_VERDE;

// ------------------------------
// TIEMPOS (en milisegundos)
// ------------------------------
unsigned long t_verde_NS = 20000;      // 20 segundos
unsigned long t_verde_EO = 20000;      // 20 segundos
unsigned long t_amarillo = 2000;       // 2 segundos
unsigned long t_all_rojo = 1000;       // ambos en rojo
unsigned long t_rojo_amarillo = 1000;  // transición previa a verde

// Guarda el tiempo en que inició el estado actual
unsigned long tInicio = 0;

// ------------------------------
// SOLICITUDES PEATONALES
// ------------------------------
// Se activan cuando se presiona un botón
bool solicitud_NS = false;
bool solicitud_EO = false;

// Control del buzzer (tiempo del último beep)
unsigned long ultimoBeep = 0;

// ------------------------------
void setup() {

    // Configuración de botones
    pinMode(btn_NS, INPUT_PULLUP);
    pinMode(btn_EO, INPUT_PULLUP);

    // Configuración de LEDs vehiculares
    pinMode(NS_ROJO, OUTPUT);
    pinMode(NS_AMARILLO, OUTPUT);
    pinMode(NS_VERDE, OUTPUT);

    pinMode(EO_ROJO, OUTPUT);
    pinMode(EO_AMARILLO, OUTPUT);
    pinMode(EO_VERDE, OUTPUT);

    // Configuración de LEDs peatonales
    pinMode(SIGA_NS, OUTPUT);
    pinMode(NO_SIGA_NS, OUTPUT);
    pinMode(SIGA_EO, OUTPUT);
    pinMode(NO_SIGA_EO, OUTPUT);

    pinMode(BUZZER, OUTPUT);

    // Inicializa el tiempo del primer estado
    tInicio = millis();
}

// ------------------------------
// DETECCIÓN DE BOTONES
// ------------------------------
// Detecta el flanco de bajada (cuando se presiona)
void leerBotones() {

    // Guarda el estado anterior del botón
    static bool last_NS = HIGH;
    static bool last_EO = HIGH;

    bool actual_NS = digitalRead(btn_NS);
    bool actual_EO = digitalRead(btn_EO);

    // Detecta transición de HIGH → LOW (presión)
    if (last_NS == HIGH && actual_NS == LOW) {
        solicitud_NS = true;
    }

    if (last_EO == HIGH && actual_EO == LOW) {
        solicitud_EO = true;
    }

    // Actualiza estados anteriores
    last_NS = actual_NS;
    last_EO = actual_EO;
}

// ------------------------------
// BUZZER DINÁMICO
// ------------------------------
// Genera pitidos más rápidos a medida que se acaba el tiempo
void manejarBuzzer(unsigned long ahora,
                   unsigned long tiempoRestante,
                   unsigned long tiempoTotal) {

    unsigned long intervalo;

    // Define velocidad del beep según tiempo restante
    if (tiempoRestante > tiempoTotal * 0.6) {
        intervalo = 800;  // lento
    } else if (tiempoRestante > tiempoTotal * 0.3) {
        intervalo = 400;  // medio
    } else {
        intervalo = 150;  // rápido (fin de cruce)
    }

    // Genera beep sin bloquear el programa
    if (ahora - ultimoBeep >= intervalo) {
        tone(BUZZER, 2000, 80);
        ultimoBeep = ahora;
    }
}

// ------------------------------
void loop() {

    // Tiempo actual del sistema
    unsigned long ahora = millis();

    // Tiempo transcurrido en el estado actual
    unsigned long tEstado = ahora - tInicio;

    // Lectura de botones
    leerBotones();

    // Margen de seguridad:
    // tiempo necesario antes de que el semáforo cambie
    unsigned long margen =
        t_amarillo + t_all_rojo + t_rojo_amarillo;

    // Máquina de estados
    switch (estadoActual) {

    // ==============================
    case ST_NS_VERDE: {

        unsigned long restante = t_verde_NS - tEstado;

        if (tEstado >= t_verde_NS) {
            solicitud_EO = false;
            estadoActual = ST_NS_AMARILLO;
            tInicio = ahora;
            break; // ← CRÍTICO
        }

        // VEHICULAR
        digitalWrite(NS_VERDE, HIGH);
        digitalWrite(NS_AMARILLO, LOW);
        digitalWrite(NS_ROJO, LOW);

        digitalWrite(EO_ROJO, HIGH);
        digitalWrite(EO_VERDE, LOW);
        digitalWrite(EO_AMARILLO, LOW);

        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);

        // PEATONAL
        if (solicitud_EO && restante > margen) {

            digitalWrite(SIGA_EO, HIGH);
            digitalWrite(NO_SIGA_EO, LOW);

            manejarBuzzer(ahora, restante, t_verde_NS);

        } else {

            digitalWrite(SIGA_EO, LOW);
            digitalWrite(NO_SIGA_EO, HIGH);
        }

        break;
    }

    // ==============================
    case ST_NS_AMARILLO: {

        digitalWrite(NS_VERDE, LOW);
        digitalWrite(NS_AMARILLO, HIGH);
        digitalWrite(NS_ROJO, LOW);

        digitalWrite(EO_ROJO, HIGH);
        digitalWrite(EO_AMARILLO, LOW);
        digitalWrite(EO_VERDE, LOW);

        // Todos los peatones en NO SIGA
        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);
        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        if (tEstado >= t_amarillo) {
            estadoActual = ST_ALL_ROJO_NS_EO;
            tInicio = ahora;
        }

        break;
    }

    // ==============================
    case ST_ALL_ROJO_NS_EO: {

        // Ambos en rojo
        digitalWrite(NS_ROJO, HIGH);
        digitalWrite(EO_ROJO, HIGH);

        digitalWrite(NS_VERDE, LOW);
        digitalWrite(NS_AMARILLO, LOW);
        digitalWrite(EO_VERDE, LOW);
        digitalWrite(EO_AMARILLO, LOW);

        // Peatones bloqueados
        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);
        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        if (tEstado >= t_all_rojo) {
            estadoActual = ST_EO_ROJO_AMARILLO;
            tInicio = ahora;
        }

        break;
    }

    // ==============================
    case ST_EO_ROJO_AMARILLO: {

        digitalWrite(NS_ROJO, HIGH);
        digitalWrite(NS_VERDE, LOW);
        digitalWrite(NS_AMARILLO, LOW);

        digitalWrite(EO_ROJO, HIGH);
        digitalWrite(EO_AMARILLO, HIGH);
        digitalWrite(EO_VERDE, LOW);

        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);
        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        if (tEstado >= t_rojo_amarillo) {
            estadoActual = ST_EO_VERDE;
            tInicio = ahora;
        }

        break;
    }

    // ==============================
    case ST_EO_VERDE: {

        unsigned long restante_EO = t_verde_EO - tEstado;

        if (tEstado >= t_verde_EO) {
            solicitud_NS = false;
            estadoActual = ST_EO_AMARILLO;
            tInicio = ahora;
            break;
        }

        digitalWrite(EO_VERDE, HIGH);
        digitalWrite(EO_AMARILLO, LOW);
        digitalWrite(EO_ROJO, LOW);

        digitalWrite(NS_ROJO, HIGH);
        digitalWrite(NS_VERDE, LOW);
        digitalWrite(NS_AMARILLO, LOW);

        if (solicitud_NS && restante_EO > margen) {

            digitalWrite(SIGA_NS, HIGH);
            digitalWrite(NO_SIGA_NS, LOW);

            manejarBuzzer(ahora, restante_EO, t_verde_EO);

        } else {

            digitalWrite(SIGA_NS, LOW);
            digitalWrite(NO_SIGA_NS, HIGH);
        }

        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        break;
    }

    // ==============================
    case ST_EO_AMARILLO: {

        digitalWrite(EO_VERDE, LOW);
        digitalWrite(EO_AMARILLO, HIGH);
        digitalWrite(EO_ROJO, LOW);

        digitalWrite(NS_ROJO, HIGH);
        digitalWrite(NS_VERDE, LOW);
        digitalWrite(NS_AMARILLO, LOW);

        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);
        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        if (tEstado >= t_amarillo) {
            estadoActual = ST_ALL_ROJO_EO_NS;
            tInicio = ahora;
        }

        break;
    }

    // ==============================
    case ST_ALL_ROJO_EO_NS: {

        digitalWrite(NS_ROJO, HIGH);
        digitalWrite(EO_ROJO, HIGH);

        digitalWrite(NS_AMARILLO, LOW);
        digitalWrite(NS_VERDE, LOW);
        digitalWrite(EO_AMARILLO, LOW);
        digitalWrite(EO_VERDE, LOW);

        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);
        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        if (tEstado >= t_all_rojo) {
            estadoActual = ST_NS_ROJO_AMARILLO;
            tInicio = ahora;
        }

        break;
    }

    // ==============================
    case ST_NS_ROJO_AMARILLO: {

        digitalWrite(NS_ROJO, HIGH);
        digitalWrite(NS_AMARILLO, HIGH);
        digitalWrite(NS_VERDE, LOW);

        digitalWrite(EO_ROJO, HIGH);
        digitalWrite(EO_AMARILLO, LOW);
        digitalWrite(EO_VERDE, LOW);

        digitalWrite(SIGA_NS, LOW);
        digitalWrite(NO_SIGA_NS, HIGH);
        digitalWrite(SIGA_EO, LOW);
        digitalWrite(NO_SIGA_EO, HIGH);

        if (tEstado >= t_rojo_amarillo) {
            estadoActual = ST_NS_VERDE;
            tInicio = ahora;
        }

        break;
    }
    }

    // Apaga buzzer si no hay cruce activo
    if (!(digitalRead(SIGA_NS) || digitalRead(SIGA_EO))) {
        noTone(BUZZER);
    }
}

Video explicativo:

Loading

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *