El objetivo de este proyecto es controlar un array de 9 LEDs mediante 4 pulsadores, implementando una arquitectura de software totalmente no bloqueante, es decir, sin utilizar la función delay().
Al no usar delay(), el procesador puede responder instantáneamente a la pulsación de cualquier botón en cualquier milisegundo, sin congelar las animaciones ni perder lecturas.
13, 12, 11, 10, 9, 8, 7, 6, 5.28, 26, 24, 22 (ordenados de izquierda a derecha: Pulsador 1, 2, 3 y 4).INPUT_PULLUP (el pulsador se conecta directamente entre el pin y GND; al pulsarse lee nivel LOW).| Pulsador | Pin Arduino | Modo Asociado | Comportamiento de los LEDs |
|---|---|---|---|
| Pulsador 1 | Pin 28 | MODO_TODOS_ENCENDIDOS | Se encienden todos los LEDs de forma fija. |
| Pulsador 2 | Pin 26 | MODO_TODOS_APAGADOS | Se apagan todos los LEDs de forma fija. |
| Pulsador 3 | Pin 24 | MODO_CICLICO_SIMPLE | Se enciende un LED y se apaga el anterior de manera cíclica (efecto desplazamiento o carrusel secuencial). |
| Pulsador 4 | Pin 22 | MODO_CICLICO_ACUMULADO | Se enciende el primer LED, luego el siguiente sumándose al anterior hasta llegar al último (barra progresiva); en ese instante se apagan todos y el ciclo vuelve a comenzar. |
La función delay() detiene la ejecución del microcontrolador durante el tiempo indicado. Durante esa pausa, la CPU no puede leer los botones ni procesar eventos.
Para solucionarlo, se utiliza la función nativa millis(), que devuelve los milisegundos transcurridos desde que arrancó la placa. Calculamos el tiempo transcurrido:
Si la condición se cumple, se avanza un paso en la animación y se actualiza ultimoCambioPaso = tiempoActual. Si no se cumple, la función retorna de inmediato permitiendo que el bucle loop() continúe evaluando los botones miles de veces por segundo.
Los botones mecánicos generan rebotes parásitos (oscilaciones rápidas entre 0 y 1 durante los primeros 5-20 ms). Para evitar pulsaciones fantasma sin usar delay(), se define una estructura individual para cada pulsador:
Cuando la lectura física cambia, se reinicia el contador de rebote (ultimoTiempoRebote = millis()). Solo si el pin permanece estable durante más de 50 ms (TIEMPO_ANTIRREBOTE), se valida el cambio de estado y, al detectar el flanco de bajada (ESTADO_PRESIONADO == LOW), se conmuta el modo del sistema.
El comportamiento global se organiza como una máquina de estados con 4 modos posibles:
MODO_TODOS_ENCENDIDOS: Ejecuta encenderTodosLosLeds() de inmediato.MODO_TODOS_APAGADOS: Ejecuta apagarTodosLosLeds() de inmediato.MODO_CICLICO_SIMPLE: Utiliza la variable pasoAnimacion modulada con % numeroLeds, encendiendo únicamente leds[pasoAnimacion] y manteniendo los demás en LOW.MODO_CICLICO_ACUMULADO: Incrementa pasoAnimacion en cada intervalo. Enciende todos los LEDs cuyo índice sea menor o igual al paso (i <= pasoAnimacion). Al rebasar el último LED, apaga todos y reinicia la cuenta a -1.// ==========================================
// DEFINICIÓN DE PINES
// ==========================================
const int leds[] = {13, 12, 11, 10, 9, 8, 7, 6, 5};
const int entradas[] = {28, 26, 24, 22};
const int numeroLeds = sizeof(leds) / sizeof(leds[0]);
const int numeroEntradas = sizeof(entradas) / sizeof(entradas[0]);
// Configuración de pulsadores (INPUT_PULLUP = LOW al presionar)
const int ESTADO_PRESIONADO = LOW;
// ==========================================
// TIEMPOS (en ms usando millis)
// ==========================================
const unsigned long INTERVALO_ANIMACION = 300; // Velocidad del ciclo de LEDs
const unsigned long TIEMPO_ANTIRREBOTE = 50; // Filtro contra falsas pulsaciones
// ==========================================
// MODOS DE OPERACIÓN
// ==========================================
enum Modo {
MODO_TODOS_ENCENDIDOS,
MODO_TODOS_APAGADOS,
MODO_CICLICO_SIMPLE,
MODO_CICLICO_ACUMULADO
};
Modo modoActual = MODO_TODOS_APAGADOS;
unsigned long ultimoCambioPaso = 0;
int pasoAnimacion = 0;
// Estructura para el antirrebote individual de cada pulsador sin delay()
struct Pulsador {
int ultimoEstadoLectura;
int estadoEstable;
unsigned long ultimoTiempoRebote;
};
Pulsador pulsadores[numeroEntradas];
// ==========================================
// FUNCIONES DE CONTROL DE LEDS
// ==========================================
void apagarTodosLosLeds() {
for (int i = 0; i < numeroLeds; i++) {
digitalWrite(leds[i], LOW);
}
}
void encenderTodosLosLeds() {
for (int i = 0; i < numeroLeds; i++) {
digitalWrite(leds[i], HIGH);
}
}
// Configura y arranca el modo inmediatamente
void seleccionarModo(Modo nuevoModo) {
modoActual = nuevoModo;
pasoAnimacion = 0;
ultimoCambioPaso = millis();
switch (modoActual) {
case MODO_TODOS_ENCENDIDOS:
encenderTodosLosLeds();
break;
case MODO_TODOS_APAGADOS:
apagarTodosLosLeds();
break;
case MODO_CICLICO_SIMPLE:
apagarTodosLosLeds();
if (numeroLeds > 0) {
digitalWrite(leds[0], HIGH);
}
break;
case MODO_CICLICO_ACUMULADO:
apagarTodosLosLeds();
if (numeroLeds > 0) {
digitalWrite(leds[0], HIGH);
}
break;
}
}
// Actualiza las secuencias cíclicas según millis()
void actualizarAnimaciones() {
unsigned long tiempoActual = millis();
if (tiempoActual - ultimoCambioPaso < INTERVALO_ANIMACION) {
return; // Aún no ha pasado el intervalo
}
ultimoCambioPaso = tiempoActual;
if (modoActual == MODO_CICLICO_SIMPLE) {
// 3 => Se enciende un led y se apaga el anterior (cíclico)
pasoAnimacion = (pasoAnimacion + 1) % numeroLeds;
for (int i = 0; i < numeroLeds; i++) {
digitalWrite(leds[i], (i == pasoAnimacion) ? HIGH : LOW);
}
}
else if (modoActual == MODO_CICLICO_ACUMULADO) {
// 4 => Se enciende un led y luego el siguiente, así hasta el último,
// entonces se apagan todos (cíclico)
pasoAnimacion++;
if (pasoAnimacion < numeroLeds) {
for (int i = 0; i < numeroLeds; i++) {
digitalWrite(leds[i], (i <= pasoAnimacion) ? HIGH : LOW);
}
} else {
// Llegó al final: se apagan todos los LEDs y reinicia
apagarTodosLosLeds();
pasoAnimacion = -1; // En el próximo tick pasará a 0
}
}
}
// Lectura con antirrebote independiente para cada pulsador
void leerPulsadores() {
unsigned long tiempoActual = millis();
for (int i = 0; i < numeroEntradas; i++) {
int lectura = digitalRead(entradas[i]);
if (lectura != pulsadores[i].ultimoEstadoLectura) {
pulsadores[i].ultimoTiempoRebote = tiempoActual;
pulsadores[i].ultimoEstadoLectura = lectura;
}
if ((tiempoActual - pulsadores[i].ultimoTiempoRebote) > TIEMPO_ANTIRREBOTE) {
if (lectura != pulsadores[i].estadoEstable) {
pulsadores[i].estadoEstable = lectura;
// Detecta el momento en que se pulsa el botón
if (pulsadores[i].estadoEstable == ESTADO_PRESIONADO) {
switch (i) {
case 0: // Pulsador 1 (Pin 28)
seleccionarModo(MODO_TODOS_ENCENDIDOS);
break;
case 1: // Pulsador 2 (Pin 26)
seleccionarModo(MODO_TODOS_APAGADOS);
break;
case 2: // Pulsador 3 (Pin 24)
seleccionarModo(MODO_CICLICO_SIMPLE);
break;
case 3: // Pulsador 4 (Pin 22)
seleccionarModo(MODO_CICLICO_ACUMULADO);
break;
}
}
}
}
}
}
// ==========================================
// SETUP Y LOOP
// ==========================================
void setup() {
for (int i = 0; i < numeroLeds; i++) {
pinMode(leds[i], OUTPUT);
digitalWrite(leds[i], LOW);
}
for (int i = 0; i < numeroEntradas; i++) {
pinMode(entradas[i], INPUT_PULLUP);
pulsadores[i].ultimoEstadoLectura = HIGH;
pulsadores[i].estadoEstable = HIGH;
pulsadores[i].ultimoTiempoRebote = 0;
}
seleccionarModo(MODO_TODOS_APAGADOS);
}
void loop() {
leerPulsadores(); // Lee los 4 pulsadores al instante
actualizarAnimaciones(); // Gestiona los tiempos con millis() sin bloquear
}