16/06/2023
En el apasionante mundo de la electrónica y la programación de microcontroladores como Arduino, la interacción con el usuario es fundamental. Una de las formas más comunes de interacción es a través de botones pulsadores. Sin embargo, lo que a simple vista parece una acción sencilla —presionar un botón— puede convertirse en un verdadero desafío para un microcontrolador. Aquí es donde entra en juego la crucial función antirebote, una técnica indispensable para garantizar que cada pulsación sea detectada de forma única y precisa, evitando lecturas erróneas que pueden desestabilizar nuestros proyectos.

Cuando presionamos un botón físico, especialmente los de tipo mecánico, sus contactos internos no se cierran de forma instantánea y limpia. En su lugar, debido a las imperfecciones mecánicas y la elasticidad de los materiales, los contactos pueden "rebotar" o "bailar" durante milisegundos antes de establecer una conexión estable. Durante este breve período, el microcontrolador puede interpretar estos rebotes como múltiples pulsaciones rápidas, lo que resulta en un comportamiento inesperado. Por ejemplo, si un botón debe encender una luz una vez, el rebote podría hacer que la luz parpadee o cambie de estado varias veces, arruinando la lógica de nuestro programa.
- El Problema: ¿Por Qué los Botones "Rebotan"?
- Estrategias para la Detección Fiable: El Antirebote
- Tabla Comparativa: Antirebote con delay() vs. millis()
- Implementación Práctica: Construyendo tu Circuito Antirebote con millis()
- Consideraciones Adicionales y Mejoras
- Preguntas Frecuentes (FAQ)
- ¿Por qué mi botón registra múltiples pulsaciones si no uso antirebote?
- ¿Cuál es el valor ideal para debounceDelay?
- ¿Puedo usar la lógica antirebote para otros sensores?
- ¿El antirebote por hardware es mejor que el de software?
- ¿Qué sucede si mi función loop() es muy larga y no llamo a debounceDid() con suficiente frecuencia?
- Conclusión
El Problema: ¿Por Qué los Botones "Rebotan"?
El fenómeno del rebote, también conocido como "bouncing" en inglés, es una característica inherente a los interruptores mecánicos. Cuando los contactos metálicos de un botón se cierran o abren, no lo hacen de forma perfecta y limpia. En lugar de una transición instantánea de "abierto" a "cerrado" (o viceversa), se producen una serie de micro-conexiones y desconexiones rápidas, generando lo que se conoce como ruido eléctrico. Este ruido se manifiesta como una señal digital que oscila rápidamente entre los estados alto y bajo, en lugar de una transición suave.
Para un microcontrolador que lee el estado de un pin digital miles o incluso millones de veces por segundo, estas oscilaciones son detectadas como múltiples cambios de estado, aunque el usuario solo haya realizado una única pulsación. Si nuestro código simplemente reacciona a cada cambio detectado, el resultado será una operación errática. La solución a este problema reside en la implementación de una estrategia que "filtre" estos cambios espurios, permitiendo que solo los cambios de estado estables y significativos sean registrados.
Estrategias para la Detección Fiable: El Antirebote
Existen dos enfoques principales para implementar la lógica antirebote: por hardware y por software. Ambos buscan dar tiempo suficiente para que el estado del botón se estabilice antes de considerarlo como un cambio válido.
Antirebote por Hardware
El antirebote por hardware implica el uso de componentes electrónicos adicionales, como condensadores y resistencias (circuitos RC) o circuitos integrados específicos (como los disparadores Schmitt), para suavizar la señal eléctrica proveniente del botón antes de que llegue al pin del microcontrolador. Esta es una solución robusta y efectiva, pero añade complejidad al diseño del circuito y un costo adicional en componentes. Para muchos proyectos de hobby con Arduino, especialmente aquellos con limitaciones de espacio o presupuesto, el antirebote por software es una alternativa más flexible y económica.
Antirebote por Software: La Solución Programática
El antirebote por software se implementa directamente en el código del microcontrolador. Esta técnica se basa en ignorar las lecturas del botón durante un breve período de tiempo (generalmente entre 20 y 100 milisegundos) después de que se detecta un cambio inicial, asumiendo que cualquier cambio posterior dentro de ese lapso es parte del rebote. Existen varias formas de implementar esto, siendo las más comunes el uso de la función delay() o el temporizador no bloqueante millis().
Método 1: El Enfoque con delay() (Simple y Bloqueante)
Este método es intuitivo para principiantes, ya que utiliza la función delay() para pausar brevemente la ejecución del programa mientras el botón se estabiliza. El principio es simple: cuando se detecta un cambio en el estado del botón, se espera un corto periodo y luego se vuelve a leer para confirmar si el estado sigue siendo el mismo. Si es así, se registra como una pulsación válida.
/* Practica 6 - Función antirebote Funcion antirebote para leer correctamente el estado del boton */ const int boton = 4; // boton conectado al pin 4 const int tiempoAntirebote = 10; // Tiempo en ms para el antirebote int cuenta = 0; // Guarda el numero de veces que el boton ha sido presionado int estadoBoton; int estadoBotonAnterior; /*Función antirebote*/ boolean antirebote (int pin) { int contador = 0; boolean estado; // guarda el estado del boton boolean estadoAnterior; // guarda el ultimo estado del boton do { estado = digitalRead(pin); if (estado != estadoAnterior) { // comparamos el estado actual contador = 0; // reiniciamos el contador estadoAnterior = estado; } else { contador = contador + 1; // aumentamos el contador en 1 } delay(1); // Pequeño retardo para dar tiempo a que el estado se estabilice } while (contador < tiempoAntirebote); // Continuar hasta que el estado sea estable por 'tiempoAntirebote' milisegundos return estado; } void setup() { Serial.begin(9600); // Iniciamos la comunicacion serial pinMode(boton, INPUT); // declaramos el boton como entrada } void loop() { estadoBoton = digitalRead(boton); // leemos el estado del boton if (estadoBoton != estadoBotonAnterior) { // si hay cambio con respeto al estado anterior if (antirebote(boton)) { // checamos si esta presionado y si lo esta cuenta++; // aumentamos la cuenta Serial.println(cuenta); } } estadoBotonAnterior = estadoBoton; // guardamos el estado del boton para la proxima iteracion } En el código anterior, la función antirebote() utiliza un bucle do-while. Este bucle lee repetidamente el estado del pin y, si el estado se mantiene constante durante el tiempoAntirebote especificado (en este caso, 10 milisegundos, con un delay(1) por iteración), entonces se considera estable. La variable contador se reinicia cada vez que se detecta un cambio, asegurando que solo se confirme el estado una vez que se ha mantenido sin cambios por el tiempo deseado.
La ventaja de este método es su simplicidad conceptual. Sin embargo, su principal desventaja es que la función delay()bloquea la ejecución del resto del programa. Mientras el microcontrolador está en el delay(), no puede hacer ninguna otra tarea, como leer otros sensores, actualizar pantallas o comunicarse. Esto lo hace inadecuado para proyectos más complejos que requieren multitarea o una alta responsividad.
Método 2: El Enfoque con millis() (Eficiente y No Bloqueante)
El método basado en millis() es la forma preferida de implementar antirebote en proyectos Arduino, ya que es no bloqueante. Esto significa que el microcontrolador puede seguir ejecutando otras partes del código mientras espera que el botón se estabilice. Utiliza el tiempo transcurrido desde el inicio del programa (millis() devuelve el número de milisegundos desde que la placa Arduino comenzó a ejecutarse) para determinar si ha pasado suficiente tiempo desde el último cambio detectado.
//Parameters const int didPin = 2; // Pin digital al que está conectado el botón //Variables bool didStatus = false; // Estado confirmado actual del botón bool oldDidStatus = false; // Estado anterior del botón (para detectar cambios) unsigned long lastDebounceTime = 0; // Último momento en que se detectó un posible cambio de estado unsigned long debounceDelay = 50; // Tiempo en ms para el antirebote (típicamente 20-100ms) void setup() { //Init Serial USB Serial.begin(9600); Serial.println(F("Initialize System")); //Init digital input pinMode(didPin, INPUT_PULLUP); // Configura el pin como entrada con resistencia pull-up interna } void loop() { debounceDid(); // Llama a la función de antirebote en cada ciclo del loop } void debounceDid() { /* function debounceDid */ int reading = digitalRead(didPin); // Lee el estado actual del botón // Si el estado actual es diferente al estado anterior, significa que hubo un cambio (posible rebote) if (reading != oldDidStatus) { lastDebounceTime = millis(); // Registra el momento de este posible cambio } // Si ha pasado más tiempo que 'debounceDelay' desde el último posible cambio... if ((millis() - lastDebounceTime) > debounceDelay) { // ...y el estado actual es diferente al estado CONFIRMADO... if (reading != didStatus) { didStatus = reading; // ...entonces el nuevo estado se considera válido y se confirma Serial.print(F("Sensor state: ")); Serial.println(didStatus); // Imprime el estado confirmado } } oldDidStatus = reading; // Actualiza el estado anterior para la próxima iteración } Este código mantiene un registro del lastDebounceTime, que es el momento en que se detectó por última vez un cambio en el pin. Solo después de que ha transcurrido un tiempo suficiente (debounceDelay) desde ese último cambio, el sistema verifica si el estado actual del pin es el mismo que el estado confirmado. Si es así, y si el estado actual es diferente al estado confirmado (didStatus), entonces se actualiza didStatus, lo que significa que se ha producido una pulsación o liberación válida.

La gran ventaja de este método es que el programa no se detiene en ningún momento. El loop() principal sigue ejecutándose, y la función debounceDid() se encarga de gestionar el botón de forma asíncrona. Esto es esencial para proyectos donde el tiempo es crítico o donde múltiples tareas deben ejecutarse simultáneamente.
Tabla Comparativa: Antirebote con delay() vs. millis()
Comprender las diferencias entre estos dos métodos es fundamental para elegir el más adecuado para tu proyecto:
| Característica | Antirebote con delay() | Antirebote con millis() |
|---|---|---|
| Facilidad de Implementación | Alta (más intuitivo para principiantes) | Media (requiere entender el concepto de tiempo no bloqueante) |
| Impacto en la CPU | Bloqueante (detiene la ejecución del programa durante el retardo) | No bloqueante (permite que el programa siga ejecutándose sin interrupciones) |
| Precisión | Depende de la duración del delay, puede ser menos precisa si el delay es muy corto o muy largo. | Muy precisa, configurable con debounceDelay. Es la preferida para la mayoría de los casos. |
| Aplicaciones | Proyectos muy simples que no requieren ninguna otra tarea mientras se espera la pulsación. | Cualquier proyecto que requiera multitarea, alta responsividad o comunicación con otros dispositivos. |
| Consumo de Energía | Puede ser más alto si el delay es largo, ya que el microcontrolador está ocioso. | Más eficiente, ya que el CPU no está bloqueado y puede realizar otras tareas o entrar en modo de bajo consumo si es necesario. |
Implementación Práctica: Construyendo tu Circuito Antirebote con millis()
Ahora, vamos a poner en práctica el método no bloqueante con millis(), que es el más recomendado para la mayoría de los proyectos.
Materiales Necesarios:
- Una tarjeta ARDUINO UNO
- Un cable USB para Arduino (Tipo A macho a B macho)
- Una tarjeta Protoboard
- Software IDE de Arduino (instalado en tu computadora)
- Un botón pulsador (normalmente abierto)
- Cables de conexión (jumpers)
Esquema de Conexión:
Para simplificar el circuito y aprovechar una característica de Arduino, usaremos la resistencia pull-up interna del microcontrolador. Esto significa que no necesitarás una resistencia externa de 10K, como se mencionó en una de las descripciones iniciales, lo que reduce el número de componentes y el cableado.
- Conecta el pin
GNDde tu Arduino a la línea de tierra (azul o negra) de tu protoboard. - Conecta un lado del botón pulsador a la línea de tierra de la protoboard.
- Conecta el otro lado del botón pulsador al pin digital 2 de tu Arduino (este es el
didPinen nuestro código). - Asegúrate de que el cable USB esté conectado a tu Arduino y a tu computadora.
Con esta configuración, cuando el botón no está presionado, el pin 2 será "alto" (HIGH) debido a la resistencia pull-up interna. Cuando el botón se presiona, el pin 2 se conectará directamente a tierra (GND), haciendo que su estado sea "bajo" (LOW). Esta es una configuración común y robusta para botones.
Análisis Detallado del Código (Versión millis()):
Vamos a desglosar las partes clave del código para entender cómo funciona la lógica antirebote no bloqueante:
const int didPin = 2;: Define el pin digital al que está conectado el botón. Usamosconstporque este valor no cambiará.bool didStatus = false;: Esta variable booleana guardará el estado confirmado y estable del botón (trueofalse, oHIGH/LOW). Es el estado que realmente nos interesa.bool oldDidStatus = false;: Almacena el estado del botón en la lectura anterior. Se utiliza para detectar si hay un posible cambio de estado que podría ser un rebote.unsigned long lastDebounceTime = 0;: Una variable de tipounsigned longpara almacenar el valor demillis()en el momento en que se detectó por última vez un cambio en la lectura del botón. Es crucial que seaunsigned longporquemillis()devuelve un valor que puede crecer hasta aproximadamente 50 días.unsigned long debounceDelay = 50;: Este es el tiempo en milisegundos que el sistema esperará para confirmar que un cambio de estado es estable. Un valor de 50 ms es un buen punto de partida para la mayoría de los botones.void setup():Serial.begin(9600);: Inicializa la comunicación serial para poder ver los mensajes en el Monitor Serial de Arduino IDE.pinMode(didPin, INPUT_PULLUP);: Configura el pindidPin(pin 2) como una entrada digital. La adición deINPUT_PULLUPactiva la resistencia pull-up interna del microcontrolador, eliminando la necesidad de una resistencia externa. Esto significa que el pin estará HIGH por defecto y LOW cuando el botón se presione.
void loop():debounceDid();: Esta es la única línea en elloop(), lo que significa que la funcióndebounceDid()se ejecuta repetidamente, miles de veces por segundo, sin bloquear el programa principal.
void debounceDid():int reading = digitalRead(didPin);: Lee el estado actual del pin del botón.if (reading != oldDidStatus) { lastDebounceTime = millis(); }: Esta es la primera parte de la lógica. Si la lectura actual es diferente de la lectura anterior, significa que ha habido un cambio de estado (o un rebote). En ese momento, se registra el tiempo actual enlastDebounceTime. Esto reinicia el "temporizador" de antirebote.if ((millis() - lastDebounceTime) > debounceDelay) { ... }: Esta es la parte central de la lógica no bloqueante. Comprueba si el tiempo transcurrido desde que se detectó el último cambio (millis() - lastDebounceTime) es mayor que nuestrodebounceDelay. Si es así, significa que el estado del botón se ha mantenido constante durante ese período, y ahora podemos confiar en la lectura.if (reading != didStatus) { didStatus = reading; Serial.print(F("Sensor state: ")); Serial.println(didStatus); }: Dentro del bloque anterior, si el estado actual (reading) es diferente del estado confirmado previamente (didStatus), entonces se considera que ha ocurrido un cambio de estado verdadero y estable. Se actualizadidStatuscon el nuevo estado y se imprime en el Monitor Serial.oldDidStatus = reading;: Finalmente, la lectura actual se guarda enoldDidStatuspara la próxima iteración delloop(), para que podamos detectar futuros cambios.
Resultados Esperados:
Al cargar este código en tu Arduino y abrir el Monitor Serial (asegúrate de que la velocidad de baudios sea 9600), observarás que cada vez que presionas el botón, el Monitor Serial mostrará un único cambio de estado (por ejemplo, de 1 a 0, o de 0 a 1, dependiendo de si el botón está pull-up o pull-down). No importa lo rápido que lo pulses o si tus dedos "rebotan" en el botón, el contador solo avanzará en uno por cada pulsación clara y estable. Esto confirma que la función antirebote está trabajando eficazmente, proporcionando una lectura fiable y limpia de tu botón.
Consideraciones Adicionales y Mejoras
La implementación de antirebote es un paso fundamental, pero hay aspectos adicionales a considerar para proyectos más avanzados:
- Ajuste de
debounceDelay: El valor dedebounceDelay(50 ms en nuestro ejemplo) es un punto de partida. Algunos botones pueden requerir un valor ligeramente mayor (hasta 100 ms) o menor (20 ms). La mejor manera de determinar el valor óptimo es mediante la experimentación. Si sigues viendo múltiples pulsaciones, aumenta el valor; si el botón se siente poco responsivo, redúcelo ligeramente. - Librerías Específicas: Para simplificar aún más el código y añadir funcionalidades avanzadas, puedes utilizar librerías de terceros. Una de las más populares es
OneButton.h. Esta librería no solo maneja el antirebote de forma eficiente, sino que también permite detectar fácilmente eventos como una pulsación simple, una doble pulsación o una pulsación larga, lo que es invaluable para interfaces de usuario complejas. - Máquinas de Estados para Interacciones Complejas: Para interacciones de usuario más sofisticadas (por ejemplo, un solo botón que enciende/apaga una luz con una pulsación corta, y cambia el modo de la luz con una pulsación larga), la lógica de antirebote se integra a menudo en una máquina de estados. Esto permite gestionar de forma ordenada los diferentes estados del botón y las acciones asociadas, haciendo el código más robusto y fácil de mantener.
Preguntas Frecuentes (FAQ)
Aquí respondemos algunas de las preguntas más comunes sobre la función antirebote:
¿Por qué mi botón registra múltiples pulsaciones si no uso antirebote?
Esto se debe al fenómeno de "rebote" mecánico de los contactos del botón. Cuando los contactos se cierran o abren, oscilan rápidamente durante milisegundos, generando una serie de señales eléctricas que el microcontrolador interpreta como múltiples pulsaciones en lugar de una sola.
¿Cuál es el valor ideal para debounceDelay?
No hay un valor único "ideal", ya que depende del botón específico y del entorno. Sin embargo, un rango común y efectivo es entre 20 y 100 milisegundos. Para la mayoría de los botones pulsadores, 50 ms suele funcionar muy bien. Si observas rebotes, puedes aumentarlo; si el botón se siente lento, puedes reducirlo un poco.
¿Puedo usar la lógica antirebote para otros sensores?
Sí, la lógica antirebote es aplicable a cualquier sensor o interruptor que devuelva estados discretos (encendido/apagado, alto/bajo) y que pueda presentar un comportamiento "rebotante" debido a factores mecánicos o eléctricos. Esto incluye interruptores de límite, sensores de puerta simples, etc.
¿El antirebote por hardware es mejor que el de software?
Depende de la aplicación. El antirebote por hardware es generalmente más robusto y fiable en entornos industriales o de alta interferencia, ya que filtra la señal antes de que llegue al microcontrolador. Sin embargo, para la mayoría de los proyectos de hobby con Arduino, el antirebote por software es más flexible, económico y fácil de implementar, especialmente el método basado en millis().
¿Qué sucede si mi función loop() es muy larga y no llamo a debounceDid() con suficiente frecuencia?
Si la función debounceDid() (o su equivalente) no se llama con regularidad y rapidez dentro del loop(), podrías perder pulsaciones cortas o experimentar un retraso en la detección de las pulsaciones. Esto se debe a que el microcontrolador podría estar ocupado con otras tareas y no leer el botón en el momento crítico del cambio de estado o durante el período de estabilización. Por eso es vital que el loop() se ejecute rápidamente y que las funciones de lectura de sensores sean no bloqueantes.
Conclusión
La función antirebote es un concepto fundamental en la programación de microcontroladores que interactúan con el mundo físico. Aunque los botones parecen simples, la implementación de una lógica antirebote robusta es crucial para crear interfaces de usuario fiables y evitar comportamientos erráticos en tus proyectos. Al dominar el método no bloqueante con millis(), no solo resolverás el problema del rebote, sino que también sentarás las bases para desarrollar sistemas más complejos y eficientes, donde el microcontrolador puede gestionar múltiples tareas sin interrupciones. ¡Ahora estás un paso más cerca de crear tus propios proyectos interactivos con la confianza de que tus botones responderán exactamente como esperas!
Si quieres conocer otros artículos parecidos a Función Antirebote en Arduino: Botones Perfectos puedes visitar la categoría Librerías.
