3.11. Eliminación de rebotes (debouncing)

Un interruptor se dibuja como un contacto perfecto abierto o cerrado, pero los contactos de un interruptor real no conmutan limpiamente entre ambos estados. Vibran – establecen y rompen el contacto eléctrico muchas veces en unos pocos milisegundos antes de estabilizarse. Una entrada GPIO que lee el pin lo percibe como una ráfaga de bordes; un bucle de sondeo descuidado cuenta varias «pulsaciones» para una sola pulsación real, y un manejador de interrupciones se ejecuta varias veces por cada pulsación real.

Una traza de osciloscopio idealizada que muestra la señal de entrada de un interruptor. La señal comienza en alto (interruptor abierto), cae a bajo, rebota de un lado a otro varias veces en unos pocos milisegundos y luego se estabiliza en bajo (interruptor cerrado).

Un interruptor con rebotes produce una ráfaga de transiciones rápidas antes de estabilizarse.

La eliminación de rebotes es la práctica de filtrar la vibración para que cada pulsación física se registre como un único evento. Dos enfoques resuelven esto – software (una regla de temporización en el firmware) o hardware (un pequeño filtro en el cable). No son mutuamente excluyentes.

3.11.1. Eliminación de rebotes por software

La idea es recordar cuándo cambió por última vez la entrada y rechazar cambios posteriores dentro de una ventana corta a partir de esa marca de tiempo. El rebote de los contactos suele durar menos de 10 ms; una pulsación real tarda de 50 a 100 ms; una ventana de 30 a 50 ms captura todos los rebotes sin bloquear las pulsaciones reales.

En un bucle de sondeo, lee el pin, compáralo con el último valor estable y acepta un cambio solo después de que haya transcurrido la ventana de eliminación de rebotes:

import time
from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_state  = 1
last_change = 0
DEBOUNCE_MS = 50

while True:
    now = time.ticks_ms()
    state = button.value()
    if state != last_state and time.ticks_diff(now, last_change) > DEBOUNCE_MS:
        last_change = now
        last_state = state
        if state == 0:
            do_action()
    time.sleep_ms(10)

Para las lecturas controladas por interrupciones, aplica la misma regla de temporización dentro del manejador y luego pasa la pulsación real al contexto principal mediante micropython.schedule() (consulta Entrada GPIO):

import time
import micropython
from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_irq = 0
DEBOUNCE_MS = 50

def handle_press(pin):
    do_action()

def on_press(pin):
    global last_irq
    now = time.ticks_ms()
    if time.ticks_diff(now, last_irq) < DEBOUNCE_MS:
        return
    last_irq = now
    micropython.schedule(handle_press, pin)

button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)

La ISR filtra los rebotes por marca de tiempo y encola la función de retorno (callback); handle_press se ejecuta de nuevo en el contexto principal, donde la asignación de memoria y la E/S lenta son seguras.

3.11.2. Eliminación de rebotes por hardware

La eliminación de rebotes por hardware filtra la vibración eléctricamente, antes de que llegue al pin. La herramienta estándar es un condensador.

Un condensador es un componente de dos terminales que almacena carga eléctrica. Físicamente, son dos placas conductoras separadas una corta distancia por un aislante (el dieléctrico).

Un condensador dibujado como dos placas horizontales paralelas con un dieléctrico (aislante) entre ellas. Un terminal conecta cada placa a un borne externo -- A arriba, B abajo. Cargas iguales y opuestas +Q y -Q se acumulan en las dos placas cuando se aplica una tensión V entre los bornes.

Un condensador de placas paralelas: dos conductores separados por una capa aislante.

Aplicar una tensión entre sus terminales impulsa cargas iguales y opuestas hacia las dos placas; la relación es

Q = C × V

donde Q es la carga almacenada (culombios), V es la tensión entre los terminales del condensador y C es su capacitancia (faradios). La capacitancia viene fijada por la construcción del dispositivo; más capacitancia significa más carga almacenada a la misma tensión.

La consecuencia: un condensador no puede cambiar su tensión instantáneamente. La carga que entra o sale tiene que atravesar cualquier resistencia que haya en el camino, y esa resistencia determina la rapidez con la que puede cambiar la tensión.

3.11.2.1. Constante de tiempo RC

Cargar un condensador a través de una resistencia produce un suave aumento exponencial hacia la tensión de alimentación, no un escalón. El tiempo característico de ese aumento es la constante de tiempo RC:

τ = R × C

Después de una τ, el condensador ha alcanzado alrededor del 63 % de la tensión de alimentación. Después de 5 τ, supera el 99 % – «completamente cargado» a efectos prácticos.

Un gráfico que muestra cómo la tensión de un condensador aumenta siguiendo una curva exponencial desde 0 V hacia la línea de alimentación. El tiempo τ = RC está marcado en el eje x donde la curva alcanza el 63 % de la tensión de alimentación.

Un condensador se carga siguiendo una curva exponencial. τ = RC es el tiempo para alcanzar el 63 % de la tensión final.

La descarga a través de una resistencia sigue la imagen especular: la tensión cae exponencialmente desde su valor inicial hacia cero, descendiendo al 37 % de la tensión inicial después de una τ, y por debajo del 1 % después de 5 τ.

Un gráfico que muestra cómo la tensión de un condensador cae siguiendo una curva exponencial desde Vmax hacia 0 V. El tiempo τ = RC está marcado en el eje x donde la curva desciende al 37 % de la tensión inicial.

Un condensador se descarga siguiendo un decaimiento exponencial. τ = RC es el tiempo para caer al 37 % de la tensión inicial.

3.11.2.2. El circuito de eliminación de rebotes

Un condensador entre un pin de entrada y tierra, alimentado a través de una resistencia en serie, forma un filtro paso bajo. Los picos rápidos no tienen tiempo de cargar o descargar el condensador a través de esa resistencia; el pin permanece cerca de la tensión que tenía antes del pico. Los cambios lentos – una pulsación deliberada – cargan o descargan el condensador y la lectura los sigue.

R1 lleva el lado alto del interruptor hasta Vcc, produciendo una señal de interruptor en bruto que rebota. R2 y C luego filtran en paso bajo esa señal hacia el pin:

Una entrada de interruptor con eliminación de rebotes por hardware. Vcc se conecta a través de una resistencia pull-up de 10 kΩ hasta un nodo de unión. Ese nodo se conecta a tierra a través del interruptor en una rama, y a través de una resistencia en serie de 10 kΩ hacia el pin en la otra rama. Un condensador de 100 nF entre el pin y tierra completa el filtro paso bajo.

Eliminación de rebotes por hardware: R2 y C filtran en paso bajo la señal en bruto del interruptor antes de que llegue al pin.

Valores típicos: R1 = 10 (pull-up), R2 = 10 (en serie), C = 100 nF.

Cuando el interruptor está abierto, la corriente fluye Vcc → R1R2 → condensador (en serie), cargando el condensador a Vcc con τ_charge = (R1 + R2) × C = 2 ms.

Cuando el interruptor se cierra, el nodo del interruptor se fija a tierra y el condensador se descarga únicamente a través de R2 hacia esa tierra con τ_discharge = R2 × C = 1 ms.

Ambos bordes se filtran mediante RC. Como el condensador se encuentra en su propio nodo, aguas abajo de R2 respecto al interruptor, oscila limpiamente entre Vcc (abierto) y 0 V (cerrado) – en estado estacionario no tiene que fluir corriente a través de R1 en ninguno de los dos casos.

3.11.3. Elegir entre ambos

  • El software es la opción por defecto. No cuesta nada en componentes, el umbral es fácil de ajustar y funciona en cualquier pin que la CPU pueda leer.

  • El hardware merece la pena los componentes cuando el rebote alcanza algo distinto al código de sondeo de la CPU – una interrupción que no debe dispararse dos veces, un contador por hardware, un periférico sin filtro propio.

La eliminación de rebotes por software y por hardware también conviven sin problemas: un pequeño filtro RC suprime los peores picos, y una ventana de eliminación de rebotes por software cubre lo que queda.