Electrónica Digital · Tema 11

Botones y rebotes

Hasta ahora las entradas eran perfectas: 0 o 1, limpias y a tiempo. El mundo real entra por un pulsador, y un pulsador miente: antes de decir la verdad, tartamudea. Este tema trata de cómo definir el valor de una entrada cuando nadie la maneja, por qué un contacto genera decenas de flancos y cómo limpiarlos con hardware o con software.

≈ 45 minutos Sin materiales: solo el navegador Incluye simulador y Python

01 · Punto de partida

Presionás una vez, cuenta veinte

Armaste un contador que suma 1 cada vez que se pulsa un botón. Lo probás: pulsás una vez y el display muestra 7. Pulsás otra vez y salta a 19. El circuito está bien, el botón no está roto: el problema es que un contacto metálico, al cerrarse, rebota como una pelota contra el piso y genera una ráfaga de aperturas y cierres antes de quedar firme.

Una escena típica. Una interrupción por flanco en un microcontrolador debería ejecutarse una vez por pulsación. En la práctica se ejecuta cinco, diez o veinte veces, y el menú de tu aparato salta de la opción 1 a la 6. Con un LED que solo muestra el estado actual no notás nada, porque el ojo promedia; pero apenas el circuito cuenta eventos, el rebote lo rompe.

Para el programador es el mismo problema que un usuario que hace varios clics seguidos en un botón lento: hay que decidir cuándo dos eventos son «el mismo». La diferencia es que acá sucede en milisegundos y no se ve.

  • ¿Cómo se define el valor de una entrada cuando el botón está suelto?
  • ¿Cuánto dura un rebote y qué circuitos rompe?
  • ¿Cómo se filtra con un capacitor y un Schmitt trigger?
  • ¿Cómo se filtra con dos puertas NAND?
  • ¿Cómo se filtra en software sin bloquear el programa?

02 · Pull-up y pull-down

Una entrada que nadie maneja

Un pulsador solo conecta dos puntos cuando se aprieta; suelto, no conecta nada. Si una entrada digital está conectada a un botón suelto, queda flotando: nadie la tira a 0 ni a 1, capta ruido y puede leer cualquier cosa (Temas 3 y 7). La solución es una resistencia que le da un valor por defecto: una resistencia de pull.

Las cuatro formas de conectar un botón a una entrada.
ConexiónSueltoPulsadoComentario
Pull-up a VCC + botón a GND10La convención. Activo en bajo (Tema 7). Consume solo mientras se pulsa
Pull-down a GND + botón a VCC01Válida. Intuitiva («1 = pulsado»), menos común en microcontroladores
Botón a GND sin resistenciaflota0Mal: suelto, la entrada capta ruido
Botón a VCC sin resistenciaflota1Mal por lo mismo. Y si el pin está en modo salida, pulsar lo cortocircuita
suelto → el pull-up sube la entrada → 1→ pulso →el botón conecta a masa → 0 (con rebotes)

Qué valor de resistencia

Con pull-up y botón a masa, la resistencia queda entre VCC y GND cuando se pulsa, y por ella circula I = VCC / R. Un valor chico da una entrada firme pero malgasta corriente; uno grande ahorra, pero deja la entrada débil frente al ruido y a la pequeña corriente de fuga de la propia entrada.

Pull-up de distintos valores con VCC = 5 V.
RCorriente pulsadoInmunidad al ruidoCuándo
1 kΩ5 mAExcelenteCables largos o muy ruidosos; consume bastante
4,7 kΩ≈ 1 mAMuy buenaCables de decenas de centímetros, entornos con ruido
10 kΩ0,5 mABuenaEl valor por defecto: cumple casi siempre
100 kΩ0,05 mAAceptableSistemas a batería con cables cortos
1 MΩ5 µAPobreAntena: una fuga de 1 µA ya cae 1 V
Pull-up interno
La mayoría de los microcontroladores traen pull-ups programables (típicamente de 20 a 50 kΩ). Ahorran la resistencia externa pero no el rebote, y con cables largos o ruidosos pueden quedar cortos de fuerza.
Un pull consume poco
Con pull-up y botón a masa, la corriente solo circula mientras se pulsa. Suelto, el botón es un circuito abierto y no pasa nada: 0 mA.
Protección de la entrada
Con cables largos o botones accesibles al usuario se suele agregar una resistencia en serie (100 a 470 Ω) antes del pin para limitar corrientes por descargas estáticas o picos.

03 · Rebote

Veinte flancos por un dedo

Un pulsador mecánico es una lámina elástica que se aprieta contra un contacto. Cuando choca, la lámina rebota y se separa y vuelve a tocar varias veces hasta que se asienta. Mientras tanto, la entrada ve una serie de flancos rápidos. Lo mismo ocurre, en menor grado, al soltar.

Duración típica del rebote según el tipo de contacto (valores orientativos: medí el tuyo).
ContactoDuración habitualComentario
Pulsador táctil pequeño0,1 a 5 msLos baratos son más ruidosos que los buenos
Interruptor de palanca1 a 10 msRebota al cerrar y al abrir
Pulsador de panel, botón grande5 a 50 msMayor masa y recorrido, más rebote
Relé mecánico1 a 10 msSu contacto rebota igual que un botón
Sensor magnético (reed)0,5 a 3 msRebote corto pero presente

Como orden de magnitud: unos pocos milisegundos, hasta unas decenas en los peores. Un microcontrolador a 16 MHz ejecuta miles de instrucciones en ese tiempo; para él, el rebote es una eternidad llena de eventos.

Qué circuitos se rompen con el rebote y cuáles no.
CircuitoSíntomaRemedio
Contador por flancosSuma varias unidades por pulsaciónAntirrebote obligatorio
Interrupción por flancoDispara muchas veces el servicio por pulsaciónAntirrebote por tiempo o por hardware
Reloj manual de un circuito secuencialAvanza varios pasos de una vezRC + Schmitt, o latch SR
Alternar un estado (toggle)Cambia de estado un número aleatorio de vecesAntirrebote
LED que sigue el botónNada visible: el ojo promediaNinguno
Lectura de nivel cada 100 msCasi nunca lo veNinguno, o una segunda lectura

04 · Antirrebote por hardware

RC + Schmitt, o dos NAND

Un filtro RC con un Schmitt trigger

La idea: si la señal pasa por una resistencia R hacia un capacitor C, el voltaje del capacitor no puede cambiar de golpe. Cambia con una constante de tiempo τ = R · C. Los rebotes, que son más rápidos que τ, apenas lo mueven; el cambio sostenido sí lo hace subir o bajar hasta el nuevo valor.

botón con rebotes→ R y C: suavizan →rampa lenta→ Schmitt (74HC14) →un solo flanco limpio

Pero una rampa lenta en una entrada lógica común es un problema: mientras el voltaje cruza la zona entre 0 y 1 (Tema 3), la puerta puede oscilar o dar flancos dobles. El Schmitt trigger lo resuelve con histéresis: tiene dos umbrales. Para pasar de 0 a 1, la entrada tiene que subir hasta el umbral alto; para volver a 0, tiene que bajar hasta el umbral bajo, que es menor. Una vez que cruzó, pequeñas oscilaciones no lo hacen volver.

74HC14
Seis inversores Schmitt en un chip (Tema 7). A 4,5 V sus umbrales típicos rondan 2,4 V (subida) y 1,4 V (bajada); varían de un chip a otro y con la tensión, así que no los uses como valor exacto. Es el componente clásico de este filtro.
74HC132
Cuatro NAND de dos entradas con Schmitt. Sirve para el mismo filtro con una entrada extra de habilitación.
Qué valor de τ
El mismo orden que el rebote medido: si el botón rebota 10 ms, τ de unos 5 a 15 ms. Con 10 kΩ, 1 µF da 10 ms; 100 nF da 1 ms. Más grande filtra más, pero agrega retardo: el cambio demora unos 0,8 a 1,2 τ en cruzar los umbrales.
Variantes del circuito
La simétrica (R en serie después del botón y C a masa) demora por igual al pulsar y soltar. La simple (pull-up y C en paralelo con el botón) descarga el capacitor por el contacto casi sin retardo al pulsar, y tarda R·C al soltar. En ambas, el Schmitt va al final.
Combinaciones comunes de R y C en el filtro simétrico (modelo con umbrales al 55 % y 30 % de VCC).
RCτSube (≈ 0,8 τ)Baja (≈ 1,2 τ)Para rebotes de…
10 kΩ100 nF1 ms0,8 ms1,2 msHasta ~1 ms (pulsadores táctiles buenos)
10 kΩ470 nF4,7 ms3,8 ms5,7 msHasta ~5 ms
10 kΩ1 µF10 ms8 ms12 msHasta ~10 ms (el caso general)
10 kΩ4,7 µF47 ms38 ms57 msBotones grandes; se siente lento

Dos NAND cruzadas: un latch SR

El Tema 9 mostró que dos NAND cruzadas guardan un bit. Con un interruptor de dos posiciones (SPDT, un común y dos contactos), cada contacto va a una entrada del latch con su pull-up. Mientras el común toca un contacto, esa entrada está en 0 y fija la salida. Cuando la lámina rebota y se separa de ese contacto, la entrada vuelve a 1 y el latch está en (1, 1): mantiene su estado. Solo cambia cuando el común toca el contacto opuesto, y eso no sucede hasta completar el recorrido.

Latch SR con NAND y un conmutador SPDT.
Posición del conmutador~S~RQ
Toca el contacto «set»011
Rebota (en el aire)11sigue en 1
Vuelve a tocar «set»011
Toca el contacto «reset»100

Es un antirrebote perfecto, sin retardo y sin dependencia de un tiempo, pero necesita un conmutador de tres terminales, no un pulsador común. Se usa en relojes manuales de bancos de práctica y en interruptores de fin de carrera.

05 · Antirrebote por software

Ignorar el cambio por un rato

Con un microcontrolador, el antirrebote por software cuesta cero componentes: un par de líneas de código. Hay cuatro técnicas, de la más simple a la más robusta.

Técnicas de antirrebote por software.
TécnicaCómo funcionaVentajasProblemas
Espera bloqueanteAl ver un cambio, delay(20) y releerTrivialCongela todo el programa durante la espera; no escala a varios botones
Ventana de ignorar (no bloqueante)Se acepta el primer cambio y se ignoran los siguientes durante W ms, comparando con millis()Responde de inmediato; no bloqueaSi W es menor que el rebote, se cuelan rebotes
Muestras consecutivas (integrador)Se acepta el cambio cuando N muestras seguidas coinciden (por ejemplo, un registro de 8 bits que debe valer 0x00 o 0xFF)Robusto ante ruido; filtra también glitches ajenos al reboteAgrega retardo de N muestras
Máquina de estadosEstados «suelto», «pulsado confirmado», con temporizadoresDetecta pulsación larga, doble clic, repeticiónMás código
Con qué frecuencia leer
Lo más sencillo es muestrear el botón desde un temporizador cada 1 a 10 ms (sondeo, o polling) y procesar los cambios en el programa principal. Es más fácil de depurar que filtrar dentro de una interrupción.
Ventana mayor o igual al rebote
La regla de oro: W ≥ el tiempo de rebote medido, con margen. Típicamente 20 a 50 ms. Más largo agrega demora al responder a pulsaciones rápidas.
Detectar el cambio con XOR
El detector de cambios del Tema 10 sirve acá: cambio = nuevo ^ anterior marca qué botones cambiaron en un puerto entero, y se combina con la ventana para aceptarlos.
Interrupciones
Una interrupción por flanco dispara con el primer rebote. Una opción sensata: en la interrupción, solo anotar la hora y desactivarla durante W ms; la acción real se hace después, ya filtrada.

06 · Casos reales

Más allá del botón simple

Soltar también rebota
Al liberar el botón hay otra ráfaga. Un contador que cuenta flancos de bajada y no filtra puede contar falsos «cierres» durante esa ráfaga, porque el contacto cae un instante antes de quedar suelto. Filtrá los dos sentidos.
Pulsación larga y repetición
Con el estado ya filtrado, medir cuánto dura la pulsación permite distinguir un toque corto de uno largo (por ejemplo 300 ms o más) y repetir una acción mientras se mantiene. Es una pequeña máquina de estados.
Teclado matricial
Un teclado de 16 teclas se lee con 8 pines (4 filas y 4 columnas) en lugar de 16. Se activa una fila por vez y se leen las columnas; cada tecla rebota como un botón individual, así que el filtrado se aplica por tecla.
Encoder rotativo
Dos contactos que se abren y cierran en cuadratura al girar. También rebotan y se leen con la lógica del código Gray (Tema 5): entre un estado y el siguiente cambia un solo bit, y un salto de dos bits indica un error de lectura.
Cables largos y ruido
Un botón al final de un cable de metros recibe interferencias que parecen pulsaciones. Se combina pull-up fuerte (1 a 4,7 kΩ), resistencia en serie, capacitor y filtrado por software con ventana larga.
Tensiones distintas
Si el botón está en un sistema de 12 o 24 V, nunca lo conectes directo a un pin de 3,3 o 5 V: hace falta un divisor, un optoacoplador o una entrada industrial.

07 · Simulador

Laboratorio: presionar sin mentir

Cada vez que apretás «Presionar», el simulador genera un ciclo completo: el dedo aprieta el botón, lo mantiene y lo suelta. Cada transición produce una ráfaga de rebotes al azar. Esa señal cruda pasa por tres detectores en paralelo, y cada uno cuenta cuántas pulsaciones cree haber visto. Con las perillas modificás el rebote, el filtro RC y la ventana de software, y los trazos se actualizan al instante.

  • Fila 1: el pin real (1 = suelto con pull-up, 0 = pulsado). Los puntos naranjas son los flancos de bajada que vería una interrupción simple.
  • Fila 2: la tensión del capacitor del filtro RC, con sus dos umbrales de Schmitt.
  • Filas 3 y 4: la salida del Schmitt y la salida del antirrebote por software con ventana. Los puntos naranjas son las pulsaciones que cada uno detecta.
SIMULADOR 11

Presionómetro

Ciclo de ejemplo (aún sin contar)

Rebote de un botón y sus tres detectores Cuatro trazas en el tiempo: el pin crudo con rebotes, la tensión del capacitor del filtro RC con los dos umbrales del Schmitt, la salida del Schmitt y la salida del antirrebote por software. Los puntos naranjas marcan las pulsaciones detectadas. UN CICLO: PRESIONAR, MANTENER, SOLTAR (140 ms)
Pulsaciones reales0
Contador crudo (flancos de bajada)0
Con RC + Schmitt0
Con software (ventana)0

Presioná para generar rebotes y contar.

Recorrido guiado (7 minutos)

  1. 1
    Presioná varias veces con los valores iniciales.

    El contador crudo suma varias unidades por ciclo, mientras que el RC y el software suman 1. Un solo gesto del dedo, una sola pulsación real.

  2. 2
    Mirá la fila del pin al soltar.

    La suelta también rebota. Por eso el crudo cuenta más que solo los rebotes de la pulsación: algunos de sus puntos naranjas aparecen cuando el botón se está soltando.

  3. 3
    Bajá τ a 0,5 ms.

    El capacitor sigue a los rebotes y el Schmitt los deja pasar: el contador RC empieza a contar de más. Un filtro más rápido que el rebote no filtra.

  4. 4
    Subí τ a 20 ms.

    La salida del Schmitt tarda más en reaccionar (mirá el retardo entre el dedo y el flanco limpio). Filtra bien, pero cuesta latencia; con una pulsación muy corta podría directamente no llegar al umbral.

  5. 5
    Bajá la ventana de software a menos que el rebote.

    Con rebote de 10 ms y ventana de 3 ms, el software acepta un cambio cuando todavía rebota y cuenta pulsaciones falsas. La ventana tiene que ser mayor o igual que el rebote.

  6. 6
    Subí el rebote a 40 ms con ventana de 20 ms.

    Ahora el rebote es más largo que la ventana: el software también falla. Un antirrebote es tan bueno como la medición que lo calibró.

  7. 7
    Ventana en 0.

    Sin ventana el software cuenta igual que el pin crudo, muestreado cada milisegundo. Un detector sin filtro es solo un contador de flancos.

Preguntas para pensar

  1. Con los valores iniciales, presioná 5 veces. ¿Cuánto vale cada contador? ¿Cuántas debería valer?
  2. ¿Por qué el contador crudo suma más que los rebotes de la pulsación?
  3. ¿Qué ventana mínima necesitás para un rebote de 25 ms? ¿Y qué τ de RC?
  4. ¿Qué pasa si presionás con un rebote de 1 ms y una ventana de 50 ms? ¿Hay algún costo?
Ver respuestas sugeridas
  1. El real vale 5; el RC y el software deberían valer 5; el crudo, bastante más (cada ciclo aporta varios flancos de bajada entre pulsación y suelta).
  2. Porque cuenta todos los flancos de bajada, incluidos los que ocurren al soltar el botón, cuando el contacto cae un instante antes de abrirse del todo.
  3. Ventana de al menos 25 ms; en la práctica 40 a 50 ms para tener margen. Un τ de unos 15 a 25 ms, con la latencia correspondiente (≈ 1 τ).
  4. Funciona, pero se pierde velocidad de respuesta: dos pulsaciones más cercanas que 50 ms se confunden. Es el costo de una ventana demasiado grande.

08 · Rebote en código

Generar, filtrar y contar

Estos bloques modelan un botón con rebotes reproducibles, calculan el efecto del pull-up, implementan tres filtros (ventana de tiempo, registro de muestras y máquina de estados con pulsación corta y larga) y simulan el filtro RC con Schmitt para comprobar qué τ funciona.

Python en tu navegador. Los bloques son editables: cambiá el rebote, las ventanas o la semilla y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

Paso 1: el pull-up y su corriente

VCC = 5.0
FUGA = 1e-6          # corriente de fuga de la entrada, orden de magnitud

print("   R (ohm)   I pulsado    caída por fuga")
for R in (1_000, 4_700, 10_000, 100_000, 1_000_000):
    i = VCC / R
    caida = FUGA * R
    print(f"{R:>10,}   {i * 1000:7.3f} mA   {caida:6.3f} V")

10 kΩ: 0,5 mA y apenas 10 mV de caída. 1 MΩ: 5 µA de consumo, pero una fuga de 1 µA ya resta 1 V a un «1» de 5 V: la entrada queda débil.

Paso 2: un ciclo de pulsación con rebotes

import random


def ciclo(rng, rebote=10, t_p=5, t_r=40, largo=70):
    # una muestra por ms: 1 = suelto (pull-up), 0 = pulsado
    def cambios():
        k = rng.choice([1, 2, 3, 4])
        return [0] + sorted(rng.sample(range(1, rebote), 2 * k))

    cp, cr = cambios(), cambios()
    muestras = []
    for t in range(largo):
        n = sum(1 for c in cp if t - t_p >= c) + sum(1 for c in cr if t - t_r >= c)
        muestras.append(1 if n % 2 == 0 else 0)
    return muestras


def bajadas(m):
    return sum(1 for a, b in zip(m, m[1:]) if a == 1 and b == 0)


rng = random.Random(7)
m = ciclo(rng)
print("".join("▔" if x else "▁" for x in m))
print("flancos de bajada vistos por una interrupción simple:", bajadas(m), "(el dedo hizo 1)")

El dedo hizo una pulsación, pero el pin tiene varios flancos de bajada: los de la pulsación y los de la suelta. Cambiá la semilla y verás otro número, casi siempre mayor que 1.

Paso 3: ventana de ignorar (no bloqueante)

import random


def ciclo(rng, rebote=10, t_p=5, t_r=40, largo=70):
    def cambios():
        k = rng.choice([1, 2, 3, 4])
        return [0] + sorted(rng.sample(range(1, rebote), 2 * k))
    cp, cr = cambios(), cambios()
    return [1 if (sum(1 for c in cp if t - t_p >= c) + sum(1 for c in cr if t - t_r >= c)) % 2 == 0 else 0
            for t in range(largo)]


def debounce(muestras, ventana):
    estado, ultimo, eventos = 1, -10**9, []
    for t, m in enumerate(muestras):
        if m != estado and t - ultimo >= ventana:
            estado, ultimo = m, t
            eventos.append((t, "presión" if m == 0 else "suelta"))
    return eventos


m = ciclo(random.Random(7), rebote=10)
for ventana in (0, 2, 5, 10, 20):
    ev = debounce(m, ventana)
    pulsaciones = sum(1 for _, e in ev if e == "presión")
    print(f"ventana {ventana:>2} ms: {pulsaciones} presión(es), {len(ev)} eventos")

Con ventana 0 el filtro acepta todo y cuenta de más. A medida que crece y alcanza el rebote (10 ms), los eventos bajan a 2 (una presión y una suelta). La ventana tiene que ser ≥ al rebote.

Paso 4: registro de muestras consecutivas

import random


def ciclo(rng, rebote=10, t_p=5, t_r=40, largo=70):
    def cambios():
        k = rng.choice([1, 2, 3, 4])
        return [0] + sorted(rng.sample(range(1, rebote), 2 * k))
    cp, cr = cambios(), cambios()
    return [1 if (sum(1 for c in cp if t - t_p >= c) + sum(1 for c in cr if t - t_r >= c)) % 2 == 0 else 0
            for t in range(largo)]


def debounce_registro(muestras):
    h, estado, eventos = 0xFF, 1, []
    for t, m in enumerate(muestras):
        h = ((h << 1) | m) & 0xFF           # historial de las últimas 8 muestras
        if h == 0x00 and estado == 1:
            estado = 0
            eventos.append((t, "presionado"))
        elif h == 0xFF and estado == 0:
            estado = 1
            eventos.append((t, "soltado"))
    return eventos


for semilla in (7, 8, 9):
    m = ciclo(random.Random(semilla))
    print(f"semilla {semilla}:", debounce_registro(m))

Hace falta ver 8 muestras iguales seguidas (0x00 o 0xFF) para aceptar el cambio. Cuesta un retardo de unos 8 ms, pero filtra también picos de ruido que no son rebotes.

Paso 5: pulsación corta o larga

import random


def ciclo(rng, rebote=10, t_p=5, t_r=40, largo=70):
    def cambios():
        k = rng.choice([1, 2, 3, 4])
        return [0] + sorted(rng.sample(range(1, rebote), 2 * k))
    cp, cr = cambios(), cambios()
    return [1 if (sum(1 for c in cp if t - t_p >= c) + sum(1 for c in cr if t - t_r >= c)) % 2 == 0 else 0
            for t in range(largo)]


class Boton:
    def __init__(self, ventana=20, largo_ms=300):
        self.estado, self.ult, self.t_press = 1, -10**9, None
        self.ventana, self.largo = ventana, largo_ms

    def muestrear(self, t, m):
        if (m ^ self.estado) and t - self.ult >= self.ventana:   # XOR: ¿cambió?
            self.estado, self.ult = m, t
            if m == 0:
                self.t_press = t
                return "presión"
            return "suelta larga" if t - self.t_press >= self.largo else "suelta corta"
        return None


rng = random.Random(3)
for nombre, t_r, largo in (("toque", 55, 100), ("sostenido", 405, 500)):
    b = Boton()
    ev = [(t, e) for t, m in enumerate(ciclo(rng, 10, 5, t_r, largo)) if (e := b.muestrear(t, m))]
    print(nombre, ev)

Con el estado ya filtrado, medir el tiempo entre la presión y la suelta distingue un toque (≈ 50 ms) de una pulsación sostenida (≈ 400 ms). El detector de cambios es el XOR del Tema 10.

Paso 6: filtro RC con Schmitt

from math import log
import random


def ciclo(rng, rebote=10, t_p=5, t_r=60, largo=110):
    def cambios():
        k = rng.choice([1, 2, 3, 4])
        return [0] + sorted(rng.sample(range(1, rebote), 2 * k))
    cp, cr = cambios(), cambios()
    return [1 if (sum(1 for c in cp if t - t_p >= c) + sum(1 for c in cr if t - t_r >= c)) % 2 == 0 else 0
            for t in range(largo)]


def rc_schmitt(muestras_ms, tau_ms, vt_alto=0.55, vt_bajo=0.30, dt=0.1):
    vc, salida, caidas = 1.0, 1, 0
    for m in muestras_ms:
        for _ in range(round(1 / dt)):
            vc += (m - vc) * dt / tau_ms
            if salida == 1 and vc <= vt_bajo:
                salida, caidas = 0, caidas + 1
            elif salida == 0 and vc >= vt_alto:
                salida = 1
    return caidas


m = ciclo(random.Random(3))
for tau in (0.3, 1, 3, 5, 10):
    print(f"τ = {tau:>4} ms -> pulsaciones contadas: {rc_schmitt(m, tau)}")

print()
for R, C in ((10_000, 100e-9), (10_000, 1e-6), (10_000, 4.7e-6)):
    tau = R * C * 1000
    print(f"R={R:,} C={C * 1e6:g} µF  τ={tau:5.1f} ms  sube≈{tau * log(1 / (1 - 0.55)):5.1f} ms  baja≈{tau * log(1 / 0.30):5.1f} ms")

Con τ mucho menor que el rebote el Schmitt deja pasar flancos de más; con τ del orden del rebote cuenta 1. La segunda tabla da los retardos: el cambio recién se ve a 0,8 τ (subida) y 1,2 τ (bajada).

09 · Analogía de programador

Debounce, throttle y el usuario impaciente

Si trabajaste con interfaces, ya conocés el problema con otros nombres. En JavaScript hay dos técnicas con nombre propio, y las dos aparecen en el hardware.

El mismo concepto, en dos mundos.
En softwareEn electrónica digital
throttle: ejecutar el primer evento y ignorar los siguientes durante un tiempoLa ventana de ignorar por software: respuesta inmediata, cambios posteriores descartados
debounce: esperar a que se calme la ráfaga y reaccionar al finalEl filtro RC + Schmitt: el cambio se confirma cuando la señal se estabilizó
Clics múltiples en un botón lento: el usuario insisteLos rebotes: el contacto «insiste» hasta asentarse
Valor por defecto de un parámetro ausenteLa resistencia de pull: qué lee la entrada cuando nadie escribe
Operación idempotente (hacerla dos veces no cambia nada)El latch SR: los rebotes repiten «set» pero el estado ya es 1
Sondeo (polling) frente a eventosLeer el botón cada ms frente a una interrupción por flanco
Histéresis para evitar parpadeo (umbral de subida distinto de bajada)El Schmitt trigger: dos umbrales distintos
Una máquina de estados con temporizadorPulsación corta, larga y repetición
  1. 1
    Rebote = ráfaga de eventos duplicados.

    Un solo gesto genera muchos eventos casi iguales; hay que decidir cuándo son «el mismo».

  2. 2
    Ventana de software = throttle.

    Reacciona al primer flanco y descarta los siguientes. Rápido, pero se confía en que el primer flanco es real.

  3. 3
    RC + Schmitt = debounce.

    Espera a que la señal se calme. Más lento, pero filtra picos aislados que el throttle aceptaría como un primer evento.

10 · Comprensión

Confusiones frecuentes

«Un botón sin resistencia anda igual»

Anda a veces: suelto, la entrada flota y capta ruido, así que lee 0 o 1 sin que nadie toque nada. Hace falta una resistencia de pull (o activar el pull interno).

«Un pull-up de 1 MΩ gasta menos, así que es mejor»

Gasta menos, pero es una antena: el ruido y la fuga de la entrada alcanzan para falsear el nivel. El pull de 10 kΩ es el punto de equilibrio habitual, y encima solo consume mientras se pulsa.

«Mi botón no rebota»

Todos rebotan, incluso los caros. Si tu contador anda sin filtro, algo ya filtra por vos: un condensador cercano, una puerta lenta, o un muestreo espaciado. Con un osciloscopio vas a ver la ráfaga.

«Un capacitor más grande es siempre mejor»

Filtra más, pero agrega retardo y además hace más lenta la respuesta a pulsaciones rápidas. Con un filtro RC sin resistencia en serie, descargarlo a través del contacto genera un pico de corriente que desgasta el botón. Ajustá τ al rebote medido.

«El RC solo alcanza, el Schmitt sobra»

Una rampa lenta en una entrada común pasa lentamente por la zona prohibida y puede dar flancos dobles u oscilaciones. El Schmitt, con sus dos umbrales, asegura un solo flanco limpio.

«Con delay(50) en la interrupción resuelvo todo»

Bloquear dentro de una interrupción detiene todo lo demás, y en muchos micros delay() ni siquiera funciona ahí dentro. Mejor sondear con un temporizador o anotar la hora y filtrar fuera de la interrupción.

«Una ventana de 5 ms sirve para todos los botones»

Depende del rebote de cada botón. Hay que medirlo (o ser conservador con 20 a 50 ms). Una ventana menor que el rebote deja pasar pulsaciones falsas.

11 · Práctica guiada

Ejercicios de botones

Ejercicio 1: elegir el RC

Medís un rebote de 15 ms. Elegí R y C para que τ sea del mismo orden. ¿Sirven 10 kΩ y 1,5 µF? ¿Cuánto tarda en verse el cambio al pulsar y al soltar (umbrales al 30 % y 55 % de VCC)?

Ver solución razonada

τ = 10 kΩ × 1,5 µF = 15 ms, del mismo orden que el rebote (el 1,5 µF existe en la serie E6). El Schmitt ve la bajada tras ≈ 1,2 τ = 18 ms y la subida tras ≈ 0,8 τ = 12 ms. Es un retardo aceptable para un botón humano (el límite perceptible está en las decenas de ms). Verificalo con el Paso 6.

Ejercicio 2: el antirrebote con dos NAND

Dibujá un conmutador SPDT con dos NAND cruzadas y dos pull-ups. Explicá por qué los rebotes no alteran la salida. ¿Por qué no sirve un pulsador común de dos terminales?

Ver solución razonada

Cada contacto va a una entrada del latch (con su pull-up). Al tocar «set», ~S = 0 y Q = 1; si rebota y se separa, ~S vuelve a 1, el latch queda en (1, 1) y mantiene Q = 1. Solo cambia al tocar el contacto opuesto. Un pulsador de dos terminales no tiene el segundo contacto: al soltarse, la entrada «reset» quedaría flotando, y no habría forma de ordenarle al latch volver a 0.

Ejercicio 3: medir tu botón

Con un osciloscopio sobre un botón con pull-up, configurá el disparo en flanco de bajada, modo único, y presioná 5 veces. ¿Qué anotás de cada captura? ¿Cómo elegís la ventana de software?

Ver solución razonada

Anotá, por captura, la duración total del rebote (del primer flanco a la última transición) y la cantidad de flancos. Típicamente son entre 1 y 20 ms y de 3 a 15 flancos, con mucha variación entre pulsaciones. La ventana se elige tomando el peor caso medido y sumando margen (por ejemplo, el doble). No uses el dato de la hoja de datos como único criterio: cada botón tiene su firma y el desgaste la empeora.

Ejercicio 4: elegir el pull-up

Un botón a 2 metros de la placa, en un ambiente con motores, funciona a 3,3 V. ¿Qué valor de pull-up elegís y cuánto consume pulsado? ¿Y si el aparato corre con una batería y el botón se usa pocas veces por día?

Ver solución razonada

Un cable largo y ruidoso pide una entrada firme: 4,7 kΩ, que consume 3,3 V / 4,7 kΩ ≈ 0,7 mA solo mientras se pulsa. Para la batería, el consumo promedio del pull es despreciable (el botón rara vez está apretado), así que no hay razón de subirlo; lo que pesa es el consumo del resto. Se suma una resistencia en serie de unos 220 Ω y un capacitor de 100 nF cerca del pin.

Ejercicio 5: ventana mínima en software

Medís un rebote máximo de 12 ms. Tu programa lee el botón cada 1 ms. ¿Qué ventana elegís y qué pasa con dos pulsaciones muy rápidas? Verificá con debounce().

Ver solución razonada

Una ventana de 20 ms (≥ 12 ms con margen). Dos pulsaciones separadas por menos de 20 ms se tratan como una; para una persona es imposible (un dedo no pulsa a más de unas 10 a 15 veces por segundo, es decir, cada 70 ms o más), así que no se pierde nada. Con el Paso 3 podés probar ventanas de 5, 10 y 20 ms.

Ejercicio 6: cuatro botones en un puerto

Cuatro botones se leen juntos en los bits de un puerto, una vez por milisegundo. ¿Cómo sabés cuáles cambiaron desde la lectura anterior con una sola operación, y cuáles se acaban de pulsar (con pull-up, activos en bajo)?

Ver solución razonada

cambio = nuevo ^ anterior marca con 1 los botones que cambiaron (Tema 10). Los que se acaban de pulsar son los que cambiaron y ahora valen 0: pulsados = cambio & ~nuevo. Los que se soltaron: cambio & nuevo. Para filtrar cada botón, se mantiene un registro de historial por bit, o un contador por bit que se reinicia con cada cambio.

12 · Síntesis

Ideas para recordar

  • Una entrada que nadie maneja flota: se le da un valor por defecto con una resistencia de pull. La convención es botón a masa con pull-up de 10 kΩ (0,5 mA, solo mientras se pulsa).
  • Un contacto mecánico rebota: de décimas de milisegundo a decenas de milisegundos, tanto al cerrar como al abrir.
  • Si el circuito cuenta flancos o reacciona a ellos, hay que filtrar; si solo muestra niveles, no se nota.
  • RC + Schmitt (74HC14): τ = R·C del orden del rebote; el Schmitt, con dos umbrales, entrega un flanco limpio. El costo es un retardo de ≈ 1 τ.
  • Latch SR con dos NAND y un conmutador SPDT: el primer contacto fija el estado y los rebotes lo mantienen.
  • Software: ventana de ignorar (no bloqueante), registro de muestras o máquina de estados. La ventana debe ser ≥ al rebote medido, típicamente 20 a 50 ms.
  • Ventana de ignorar ≈ throttle; RC + Schmitt ≈ debounce. Todos los botones rebotan: medí el tuyo.

¿Listo para el Tema 12?

Podés avanzar si sos capaz de responder esto sin mirar:

  • Dibujar un botón con pull-up y decir qué lee la entrada suelto y pulsado.
  • Calcular la corriente de un pull-up de 4,7 kΩ a 3,3 V.
  • Explicar por qué un contador por flancos suma de más y un LED no lo nota.
  • Elegir R y C para un rebote de 10 ms y decir qué retardo agregan.
  • Explicar para qué sirven los dos umbrales de un Schmitt trigger.
  • Describir cómo dos NAND cruzadas ignoran los rebotes.
  • Elegir una ventana de software y justificarla con una medición.

Ya sabés dominar una entrada rebelde. Ahora hay que conectar todo en una placa de verdad: en el próximo tema, chips 74HCxx en la mesa de trabajo.