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:
![]()



