Leer un botón parece lo más fácil del mundo: digitalRead() y ya. Y luego pasa
lo de siempre: una pulsación cuenta como cinco, o el pin lee ruido aleatorio
cuando nadie toca nada. Ambas cosas tienen explicación y solución.
Primero: nunca dejes un pin «al aire»
Si conectas un botón entre el pin y 3V3 sin nada más, cuando no está
pulsado el pin queda flotando y lee basura. Necesitas una resistencia que lo
lleve a un nivel conocido:
- Pull-up: el pin a
HIGHen reposo; al pulsar, aGND. Es lo que uso casi siempre. - Pull-down: el pin a
LOWen reposo; al pulsar, a3V3.
El ESP32 tiene pull-up interno, así que normalmente no hace falta resistencia:
constexpr int BTN = 4;
void setup() {
Serial.begin(115200);
pinMode(BTN, INPUT_PULLUP); // reposo = HIGH, pulsado = LOW
}
void loop() {
if (digitalRead(BTN) == LOW) {
Serial.println("pulsado");
}
}
Ojo: los GPIO34–39 no tienen pull-up interno (son solo entrada). Si usas uno de esos, necesitas resistencia externa.
Por qué «rebota» (y por qué ves 5 pulsaciones)
Un pulsador no pasa de HIGH a LOW limpiamente. Durante unos milisegundos
hace contacto y lo pierde varias veces. El ESP32, que va a 240 MHz, lo detecta
todo. Eso es el bounce.
Antirrebote por software (mi favorito)
constexpr int BTN = 4;
constexpr unsigned long DEBOUNCE_MS = 30;
bool estable = HIGH;
bool ultimo = HIGH;
unsigned long cambio = 0;
void loop() {
bool lectura = digitalRead(BTN);
if (lectura != ultimo) {
cambio = millis();
ultimo = lectura;
}
if (millis() - cambio > DEBOUNCE_MS && lectura != estable) {
estable = lectura;
if (estable == LOW) Serial.println("pulsación válida");
}
}
La idea: solo aceptas un cambio si se mantiene estable 30 ms. Es suficiente para el 99 % de los botones.
Antirrebote por hardware
Si el botón es crítico o hay mucho ruido, un RC (10 kΩ + 100 nF) y, si hace falta, un Schmitt trigger (74HC14) lo arreglan. Más piezas, pero cero código.
Interrupciones: potentes y peligrosas
Con attachInterrupt() el ESP32 ejecuta tu función al instante. Es ideal para
no perder pulsaciones, pero tiene reglas:
volatile bool pulsado = false;
void IRAM_ATTR alPulsar() { // IRAM_ATTR: debe vivir en IRAM
pulsado = true;
}
void setup() {
pinMode(BTN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BTN), alPulsar, FALLING);
}
void loop() {
if (pulsado) {
pulsado = false;
Serial.println("interrupción");
}
}
Reglas de oro:
- La variable compartida debe ser
volatile. - La función debe llevar
IRAM_ATTR(si no, puede fallar con el Wi-Fi). - No hagas trabajo pesado dentro de la ISR: marca una bandera y procésala en
el
loop(). Nada deSerial.printnidelayahí dentro. - Aun con interrupción, el rebote existe: añade antirrebote igualmente.
Errores típicos
- Botón sin pull-up y pines leyendo ruido → crees que el ESP32 está roto.
- Usar GPIO34–39 para un botón y descubrir que no hay pull-up interno.
- Poner
Serial.printlndentro de la ISR → cuelgues aleatorios. - Olvidar
volatile→ el compilador «optimiza» y la bandera nunca cambia. - No hacer antirrebote y contar 5 pulsaciones por cada una.
Resumen
- Nunca dejes un pin de entrada flotando: usa
INPUT_PULLUP. - Los botones rebotan; con 30 ms de antirrebote se soluciona.
- Interrupciones:
volatile,IRAM_ATTR, trabajo mínimo y antirrebote también. - GPI34–39 no tienen pull-up: resistencia externa obligatoria.
- Con hardware (RC + Schmitt) te ahorras código si el ruido es serio.
Siguiente paso: monitor serie y depuración básica, para dejar de adivinar por qué no funciona. O vuelve al hub de la serie.