Tu botón se activa dos veces. Aquí está el porqué, y aquí está la solución.
- 465componentes en el banco de trabajo
- 43placas funcionando ahora
- 1.00xtiempo real, en todas las placas
Un botón táctil es un pequeño domo de metal que se empuja hacia un contacto, y no llega y se queda quieto. Llega, rebota una fracción de milímetro, vuelve a llegar y repite eso algunas veces antes de asentarse, igual que una moneda que se cae no se detiene en cuanto toca la mesa. Eléctricamente, el contacto se abre y se cierra varias veces en los primeros milisegundos de una pulsación, y otra vez al soltarlo, antes de quedarse estable. Un sketch que solo cuenta flancos en ese pin cuenta mal, porque una sola pulsación puede registrarse como dos, tres o más.
Mokxi modela esto en lugar de esconderlo. Cada botón en el banco de trabajo rebota de forma predeterminada, con un perfil llamado typical: de tres a ocho transiciones, que se asientan entre 1 y 5 milisegundos después de que empieza la pulsación. Eso se parece mucho a un botón táctil real, y significa que el circuito de arriba no es una demostración montada de un error; es el error en sí, pasando igual que en tu mesa de trabajo.
Lo que muestra el simulador
El programa que corre arriba es el ejemplo integrado interrupt del Uno. Conecta un manejador de interrupción al pin 2 que se activa en cada flanco de bajada (cada momento en que el pin baja de 5 V a 0 V) y le suma uno a un contador, sin ninguna lógica de antirrebote, a propósito. Abre el circuito en el editor, abre el monitor serie y presiona el botón una vez, limpiamente, como presionarías cualquier botón. La cuenta que se imprime después de una sola pulsación muchas veces es dos o tres, no uno, y el LED de la placa, que cambia según si el contador es par o impar, a veces queda en el estado que no esperabas. Nada está roto. El botón rebotó, el manejador vio cada rebote como una pulsación separada y los contó todos fielmente, porque eso es exactamente lo que el código le pide.
Esta es toda la lección en una frase: el botón, no el código, produjo las pulsaciones de más, y la solución va en el código porque el botón no se puede hacer perfectamente limpio.
Tres formas de solucionarlo
La primera solución, y la más barata, es una ventana de antirrebote por software: después de cualquier cambio en el pin, ignora los cambios siguientes durante un tiempo corto, lo bastante largo para cubrir el tiempo de asentamiento y lo bastante corto para que una segunda pulsación real nunca se confunda con un rebote. Algo entre 5 y 20 milisegundos cubre de sobra un botón típico.
La segunda es una solución por hardware: un capacitor pequeño en paralelo con el botón. Hace que el voltaje suba y baje con la lentitud suficiente para que los rebotes nunca lleguen a una transición lógica completa, así que el pin realmente solo ve un flanco limpio. Esto funciona en la vida real porque un capacitor real tarda un tiempo real en cargarse y descargarse, y funciona en Mokxi por la misma razón: aquí el capacitor integra en el tiempo con una respuesta RC exponencial real en lugar de cambiar al instante, así que una versión a escala de esta solución sí funciona en el simulador, no solo en teoría.
La tercera es hacer el antirrebote dentro del propio manejador de interrupción, lo que necesita una regla extra: el manejador debe hacer lo menos posible, porque corre con las demás interrupciones detenidas. Guarda el tiempo y sal; toma las decisiones de verdad, incluida la ventana de antirrebote, de regreso en loop().
La versión con millis()
El programa integrado toggle del Uno es la solución por software completa, en el mismo pin 2 que usa la demostración del rebote. Guarda la última lectura y el momento en que cambió por última vez, y solo actúa ante un cambio cuando ya pasaron DEBOUNCE_MS, veinticinco milisegundos, desde el anterior. Presiona el botón una vez con este programa corriendo y la lámpara del pin 13 se enciende; presiónalo otra vez y se apaga, exactamente una vez por pulsación, que es justo de lo que se trata.
Fíjate en qué está comparando realmente el código. No cuenta cuánto tiempo llevas presionando el botón; mide cuánto tiempo ha pasado desde el último cambio aceptado, en cualquiera de los dos flancos, al presionar o al soltar. Esa diferencia es lo que hace simétrica la ventana: se ignora un rebote justo después de presionar, y también uno justo después de soltar, con la misma regla de veinticinco milisegundos para los dos, sin casos especiales según el flanco.
const int BUTTON_PIN = 2;
const int LAMP_PIN = 13;
const unsigned long DEBOUNCE_MS = 25;
int lastReading = HIGH;
unsigned long lastChange = 0;
bool lampOn = false;
void loop() {
int reading = digitalRead(BUTTON_PIN);
unsigned long now = millis();
if (reading != lastReading && now - lastChange >= DEBOUNCE_MS) {
lastChange = now;
lastReading = reading;
if (reading == LOW) {
lampOn = !lampOn;
digitalWrite(LAMP_PIN, lampOn ? HIGH : LOW);
}
}
}La cuestión de la resistencia pull-up
Tanto la demostración del rebote como su solución conectan el botón directo al pin 2 sin resistencia externa, porque el sketch activa la pull-up interna del pin con INPUT_PULLUP. Esa pull-up mantiene el pin en 5 V hasta que el botón lo une a tierra, y por eso el pin lee LOW mientras mantienes presionado el botón en lugar de HIGH, un detalle que confunde a mucha gente la primera vez, porque es lo contrario de lo que parecería que debe hacer un botón.
La alternativa es una pull-up externa de verdad, una resistencia de 10 kilohm del pin a 5 V, con el botón llevando esa misma red a tierra al presionarlo. Eléctricamente las dos son la misma idea; la interna simplemente es más débil (unos 35 kilohm) y no necesita un componente extra. La página de la resistencia pull-up arma la versión externa desde cero, y vale la pena leerla si alguna vez sientes que la interna salió de la nada.
Por qué el perfil predeterminado es así
typical, de tres a ocho transiciones que se asientan en 1 a 5 milisegundos, es a propósito un botón táctil común, del tipo que viene en casi cualquier kit de protoboard, no el peor caso elegido para que el problema se vea peor de lo que es. También existe un perfil worst, que llega hasta veinte transiciones en 10 a 20 milisegundos, más parecido a un botón gastado o a un interruptor de límite de palanca larga que ya se usó mucho, y vale la pena probar tu ventana de antirrebote con él una vez que resuelvas el caso típico: una solución ajustada a un rebote de 5 milisegundos sin margen todavía puede fallar con un botón que vibra durante 15.
Preguntas
¿Cuánto debe durar el retardo de antirrebote?
Algo entre 5 y 20 milisegundos sirve para un botón táctil típico, que también es el rango en el que se asienta el perfil de rebote predeterminado de Mokxi. Si es muy corto, se cuela algo de rebote; si es muy largo, una doble pulsación rápida empieza a sentirse lenta.
¿Necesito el capacitor si ya hago el antirrebote por software?
No. Las dos son alternativas que resuelven el mismo problema en lugares distintos, y usar ambas no está mal, solo es redundante. Elige la que mejor le quede al circuito: el software no cuesta nada extra, y el hardware funciona aunque el código que lee el pin nunca cambie.
¿Por qué mi interrupción sigue activándose dos veces después de agregar una revisión de antirrebote?
Casi siempre es porque la revisión de la ventana de antirrebote está dentro del manejador de interrupción junto con trabajo lento, o porque el tiempo se compara contra el valor guardado equivocado. Deja que el manejador solo lea el reloj y active una bandera, y haz la comparación en loop().
¿INPUT_PULLUP también necesita una resistencia externa?
No. Activa una resistencia pull-up integrada en el propio ATmega328P, así que el botón solo necesita dos cables: uno al pin y otro a GND.
¿Qué valor de capacitor sirve para hacer el antirrebote de un botón típico por hardware?
Algo alrededor de 100 nanofaradios en paralelo con el botón, muchas veces junto con una pequeña resistencia en serie para limitar la corriente de descarga en la primera pulsación, es un punto de partida común; el valor correcto depende del botón, y vale la pena comprobarlo en la vida real con el componente que vas a usar en lugar de confiar en un solo número para todos los botones.
¿El rebote solo es un problema de los botones?
No. Cualquier contacto mecánico rebota, incluidos los relevadores, los interruptores de palanca y los interruptores de límite de un motor o una puerta. La solución es la misma: una ventana de tiempo después del primer cambio, o un capacitor en paralelo con el contacto, lo que mejor le quede al circuito que lo lee.
Sigue así
Ármalo en la vida real
Abre el editor, cambia un valor y mira cómo el número cambia con él. Nada que instalar y sin necesidad de cuenta.