Los pulsadores, switches y relés están en todos lados.
Cuando los conectamos a un microcontrolador (PIC, ESP32, Arduino, etc.) esperamos un cambio de estado limpio por cada pulsación. En la práctica no ocurre así: los contactos mecánicos rebotan durante unos milisegundos y generan varias transiciones antes de estabilizarse. A esto se le llama rebote de contacto (contact bounce).

PLACEHOLDER IMG:  "button-ideal-vs-bounce.png" — Señal ideal vs con rebote

Ideal vs. real: sin rebote (arriba) vs. con rebote (abajo)

En aplicaciones lentas (encender una lámpara) el rebote pasa desapercibido.
En sistemas digitales (contadores, menús, teclados) puede convertirse en pulsaciones falsas.


¿Por qué rebotan los contactos?

Por masa, elasticidad y vibración de las piezas metálicas. Al presionar o soltar, los contactos no se quedan quietos al primer intento: golpean y “tiemblan” durante 1–20 ms (depende de tipo/calidad, desgaste, humedad, temperatura).


Pull-up vs Pull-down (y dónde aparece el rebote)

Hay dos formas comunes de cablear un botón:

  • Pull-up: el pin está en 1 por defecto (resistencia a Vcc). Al presionar, cae a 0.

    PLACEHOLDER IMG:  "button-pullup-bounce.png" — Esquema + forma de onda (alto→bajo con rebote)

    Pull-up: rebote al ir de alto a bajo

  • Pull-down: el pin está en 0 por defecto (resistencia a GND). Al presionar, sube a 1.

    PLACEHOLDER IMG:  "button-pulldown-bounce.png" — Esquema + forma de onda (bajo→alto con rebote)

    Pull-down: rebote al ir de bajo a alto


Cómo eliminar el rebote (antirrebote)

Nos quedamos con dos estrategias sencillas y efectivas:
(1) Hardware con RC y (2) Software con máquina de estados usando millis().

1) Antirrebote por hardware con filtro RC

Vamos a suponer un rebote de 20 ms para simplificar. La idea es que el filtro sea un poco más lento que ese rebote para que lo “suavice”.

  • Esquema (pull-up típico): usa un pull-up a Vcc, el botón a GND, y coloca un condensador del pin a GND.
  • Cálculo directo: un RC se define por su constante de tiempo ( \tau = R \cdot C ). Queremos que (5\tau > 20,\text{ms}), así el pin no cruza varias veces el umbral durante el rebote.

Ejemplo práctico (con 20 ms):
[ R = 100,\text{k}\Omega, \quad C = 47,\text{nF} \quad \Rightarrow \quad \tau \approx 4.7,\text{ms} \quad \Rightarrow \quad 5\tau \approx 23.5,\text{ms} ]

PLACEHOLDER IMG:  "waveform-rc-debounce.png" — Rebote crudo vs salida filtrada por RC

Arriba: rebote crudo. Abajo: el RC hace que el pin cruce el umbral una sola vez

Circuito recomendado (3.3 V o 5 V, pull-up)

  • Rpull-up = 100 kΩ a Vcc
  • SW1 a GND
  • C = 47 nF del pin a GND
  • (Opcional) Rserie = 100 Ω con el botón para limitar picos de corriente hacia el condensador
PLACEHOLDER IMG:  "circuito-antirrebote-rc.png" — Esquema con Rpull-up, C al pin y Rserie

Pull-up externo de 100 kΩ + C de 47 nF → 5τ ≈ 23.5 ms

Nota: El RC añade un retardo a la lectura. Si quieres más rapidez, reduce un poco (R) o (C) y valida que siga filtrando bien.


2) Antirrebote por software con millis() (no bloqueante)

Usaremos una máquina de estados muy simple: detecta un cambio crudo, abre una ventana de estabilidad y confirma el nuevo estado si pasan ~35 ms sin más cambios. No bloquea la CPU.

const int buttonPin = 2;             // INPUT_PULLUP si no usas RC
const int led = 13;
const unsigned long debounceMs = 35; // ventana de estabilidad

int stableState = HIGH;              // reposo en HIGH (pull-up)
int lastRead = HIGH;
unsigned long lastEdgeMs = 0;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);  // o INPUT si usas RC externo
  pinMode(led, OUTPUT);
}

void loop() {
  int reading = digitalRead(buttonPin);

  // 1) Cambio crudo → reinicia ventana
  if (reading != lastRead) {
    lastEdgeMs = millis();
    lastRead = reading;
  }

  // 2) Pasado debounceMs sin más cambios → confirma estado
  if ((millis() - lastEdgeMs) > debounceMs && reading != stableState) {
    stableState = reading;

    // Ejemplo: actuar al PRESIONAR (pull-up: HIGH->LOW)
    if (stableState == LOW) {
      digitalWrite(led, !digitalRead(led)); // toggle
    }

    // Si prefieres actuar al SOLTAR: (stableState == HIGH)
  }
}

Tip: si usas interrupciones, es mejor llevar al pin de IRQ una señal ya filtrada por RC y, en la ISR, solo detectar el flanco.

Conclusión y recomendaciones (en 8 puntos)

  1. Usa millis() si no quieres tocar la PCB.
  2. Combina ambos (RC moderado + millis()) para producto robusto.
  3. Valores de arranque: RC 100 kΩ + 47 nF (≈5τ = 23.5 ms), debounceMs 35 ms.
  4. Mide el rebote con osciloscopio; si no, diseña para ~20 ms y ajusta.
  5. Evita delay(); usa ventana no bloqueante con millis() o FSM.
  6. En PCB, deja pads DNF para R/C y pon el C cerca del pin (retorno GND corto).
  7. Si usas IRQ, filtra antes con RC y en la ISR solo detecta el flanco.