
Chismes del Lince 32: El timbre de la cueva
Chismes del Lince 32: El timbre de la cueva
6 oct 2026 · @José Luis Usero Vílchez
La cueva del Lince de la Noche está en lo más escondido de Sierra Bermeja. Es el sitio que no debería encontrar nadie. Y, sin embargo, tiene una puerta con timbre.
—Es que soy muy educado —se justificaba el Lince cada vez que alguien se lo mencionaba.
El problema es que la cueva es enorme. Tiene el laboratorio, el taller, la biblioteca, tres mesas llenas de placas, cables y cámaras, y un rincón con una hamaca. Desde el fondo no se oye nada de lo que pasa en la entrada.
Aquella tarde, la Gata Montesa llegó justo a tiempo de ver una escena de lo más rara. Delante de la puerta estaba ButanHero, con su traje naranja y su bombona al hombro, pegando voces.
—¡Lince! ¡Soy yo! ¡Que he subido hasta aquí con la bombona!
Dentro, a cien metros, el Lince soldaba, o más bien lo intentaba, con la radio puesta. No oía nada.
—Esto es una locura —dijo la Gata, que acababa de entrar a buscarlo—. Tienes un timbre, pero no sirve de nada si no lo oyes desde el fondo.
—Voy a poner un altavoz gigante —respondió el Lince, quitándose las gafas de soldar—. Con cámaras. Y con sensores de movimiento por todo el monte.
—Lince. Lo que necesitas es un botón.
—¿Un botón?
—Un botón en la puerta que le avise a tu móvil. Cuando alguien lo pulse, tu móvil suena y se ilumina. Y esta vez nada de copiar y pegar: vas a explicarme cómo funciona, línea a línea.
ButanHero dejó la bombona en el suelo y se secó la frente.
—Mientras lo montas, ¿puedo pasar? Hace mucho calor aquí fuera.
Introducción
En este chisme vamos a aprender a usar un botón con ESP32 para construir un timbre que avisa al móvil. Cuando alguien pulse el botón conectado a la placa, el móvil sonará con un ding-dong, se iluminará la pantalla y aparecerá el Lince en la puerta.
Es el segundo chisme de la serie pensado para leerse entero. En el anterior controlamos un LED desde una página web. Ahora hacemos lo contrario: es la placa la que avisa a la página cuando alguien toca el botón.
Si entiendes cómo funciona un botón con ESP32, entiendes la base de casi cualquier proyecto con entradas: timbres, pulsadores, interruptores, alarmas y mandos.
Por qué este chisme
Un botón parece lo más fácil del mundo. Pero esconde tres ideas que aparecen en todos los proyectos electrónicos.
La primera: un botón no “manda” nada. Solo cierra o abre un circuito. Es la placa la que tiene que mirar si está cerrado.
La segunda: hay una gran diferencia entre “el botón está pulsado” y “alguien acaba de pulsar el botón”. La primera es un estado y la segunda es un evento. Un timbre necesita eventos.
La tercera: los botones reales no son perfectos. Al pulsarlos “rebotan” y la placa puede contar tres pulsaciones donde tú solo hiciste una.
El programa tiene cuatro partes y solo una es el chisme: la del botón, de unas quince líneas. La pantalla de la placa es opcional, el Bluetooth es casi igual que en el chisme anterior y el resto es el esqueleto del programa.
Así podrás señalar con el dedo cada línea y decir qué hace. Ese es el objetivo.

Cómo funciona un botón con ESP32
Piensa en un puente levadizo. Mientras está levantado, nadie pasa. Al pulsar, el botón baja el puente y une dos cables, y la corriente puede pasar. Eso es todo.
La placa no siente la pulsación. Lo que hace es mirar un pin y comprobar si hay tensión o no. Un pin que lee tensión da HIGH (alto) y uno que lee cero da LOW (bajo).
Aquí aparece un problema. Si el botón está suelto, el pin no está conectado a nada y su lectura es aleatoria. A eso se le llama un pin “flotante”. Hay que dar al pin un valor por defecto.
La solución está dentro de la placa: una resistencia interna que tira del pin hacia 3,3 V cuando no hay nada más. Se activa con INPUT_PULLUP (entrada con resistencia hacia arriba) y por eso no necesitamos poner ninguna resistencia fuera.
El montaje queda así: un lado del botón va al pin y el otro a GND (0 V). Con esto, el botón suelto da HIGH y el botón pulsado da LOW. Parece al revés, pero es así, y conviene recordarlo.
| Situación | Lo que lee el pin |
|---|---|
| Botón suelto | HIGH (3,3 V, por la resistencia interna) |
| Botón pulsado | LOW (0 V, conectado a GND) |
Luego viene la parte del Bluetooth. Cuando la placa detecta una pulsación, envía la palabra TIMBRE. La página la recibe, hace sonar el ding-dong, ilumina el círculo y muestra al Lince. El viaje es de una sola dirección: de la placa a la página.
Qué necesitamos
- Una placa Heltec WiFi LoRa 32 o TTGO LoRa32 (ESP32) con su cable USB. No necesitas la antena LoRa porque no usamos la radio.
- Un botón pequeño de 4 patas (pulsador de placa). También sirve el grande de 2 patas, pero empezamos con el pequeño.
- Dos cables dupont y, si quieres, una protoboard. No hace falta ninguna resistencia.
- El Arduino IDE con la placa “ESP32 Dev Module” y las librerías Adafruit SSD1306 y Adafruit GFX (solo para la pantalla opcional).
- Chrome o Edge en un móvil Android o en un ordenador. En iPhone no funciona Web Bluetooth.
- La página
chisme-32-timbre.htmly la imagenlince-timbre.pngtienen que estar en la misma carpeta.
Construcción
Haz el montaje con la placa desenchufada. Solo hay dos conexiones:
GPIO 13 ──> una pata del botón
GND ──> la pata de la esquina opuesta (en diagonal)
El botón pequeño tiene cuatro patas, pero por dentro están unidas de dos en dos. Al pulsar, se unen las dos parejas.
Por eso conviene usar dos patas en diagonal: así siempre estás conectando un lado con el otro. Si usas dos patas del mismo lado, el botón puede quedar siempre conectado y la placa no verá ninguna pulsación.
No pongas resistencia. La placa ya la tiene dentro, y la activamos con INPUT_PULLUP en el programa.
Si usas el botón grande de 2 patas, es todavía más fácil: una pata a GPIO 13 y la otra a GND.
Un consejo que aprendimos con el chisme anterior: si algo no responde, sospecha de los contactos. Los pines de estas placas son finos y en una protoboard a veces no hacen contacto. Prueba a pulsar apretando el cable contra el pin.
El código de la placa
Copia el programa completo en un archivo nuevo del Arduino IDE (por ejemplo chisme-32-timbre.ino), elige la placa “ESP32 Dev Module” y súbelo. Pero antes, léelo. Está dividido en cuatro partes con cartelitos grandes:
- Parte 1, el botón: es el chisme. Léela línea a línea.
- Parte 2, la pantalla: no es necesaria. Solo enseña las visitas en la OLED. Puedes saltarte esta parte al leer.
- Parte 3, el Bluetooth: casi igual que en el chisme anterior. Esta vez solo hay un buzón, el de avisos, porque la página no manda órdenes.
- Parte 4,
setup()yloop(): el esqueleto.setup()se ejecuta una vez al encender yloop()se repite sin parar.
/*
Chisme 32 · El timbre de la cueva
Un botón conectado a la placa hace de TIMBRE. Cuando lo pulsas, la placa
AVISA por Bluetooth al móvil (o al ordenador) y la página suena y se ilumina.
Este programa tiene 4 partes. Solo la PARTE 1 es el chisme:
PARTE 1 · EL BOTÓN <- LEE ESTA. Es lo único importante.
PARTE 2 · LA PANTALLA <- NO ES NECESARIA. Solo enseña las visitas en la OLED.
PARTE 3 · EL BLUETOOTH <- Casi igual que en el chisme 31. No hace falta tocarla.
PARTE 4 · setup() y loop()
CABLEADO (con la placa DESENCHUFADA), botón pequeño de 4 patas:
GPIO 13 ──> una pata del botón
GND ──> la pata de la ESQUINA OPUESTA (en diagonal)
No hace falta resistencia: la placa tiene una dentro (INPUT_PULLUP).
Las 4 patas están unidas de dos en dos. Al pulsar, se unen las dos parejas.
Si usas dos patas en diagonal, siempre funciona.
Librerías: Adafruit SSD1306 y Adafruit GFX (solo para la pantalla).
Placa en el Arduino IDE: "ESP32 Dev Module".
Si al compilar dice "Sketch too big":
Herramientas -> Partition Scheme -> "Huge APP (3MB No OTA/1MB SPIFFS)".
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
/* ============================================================
PARTE 1 · EL BOTÓN (lo importante)
============================================================ */
const int PIN_BOTON = 13; // el pin donde hemos conectado el botón
bool botonAntes = HIGH; // cómo estaba el botón la última vez que miramos
unsigned long ultimaPulsacion = 0; // cuándo fue la última pulsación (en milisegundos)
int pulsaciones = 0; // cuántas veces han llamado
void mirarBoton() {
// Con INPUT_PULLUP: botón SUELTO = HIGH, botón PULSADO = LOW (¡al revés de lo que parece!)
bool botonAhora = digitalRead(PIN_BOTON);
// ¿Acaba de pasar de "suelto" a "pulsado"? Eso es UNA pulsación.
if (botonAntes == HIGH && botonAhora == LOW) {
// Los botones "rebotan": al pulsar dan varios toques muy rápidos.
// Ignoramos todo lo que ocurra en los 200 ms siguientes a una pulsación.
if (millis() - ultimaPulsacion > 200) {
ultimaPulsacion = millis();
pulsaciones = pulsaciones + 1; // una visita más
avisarAlMovil(); // ¡a la página!
pintarPantalla(); // (solo para la pantalla)
}
}
botonAntes = botonAhora; // nos acordamos para la próxima vez
}
/* ============================================================
PARTE 2 · LA PANTALLA (NO ES NECESARIA: el timbre funciona sin ella)
Solo sirve para ver el número de visitas en la OLED de la placa.
============================================================ */
#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RST 16
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST);
volatile bool conectado = false; // ¿hay un móvil conectado?
void pintarPantalla() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Timbre de la cueva");
display.setCursor(0, 20);
display.print("Visitas:");
display.setTextSize(3);
display.setCursor(0, 32);
display.print(pulsaciones);
display.setTextSize(1);
display.setCursor(0, 56);
display.print(conectado ? "Bluetooth: conectado" : "Bluetooth: esperando");
display.display();
}
void arrancarPantalla() {
Wire.begin(OLED_SDA, OLED_SCL);
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW); delay(20); digitalWrite(OLED_RST, HIGH);
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
display.setTextColor(SSD1306_WHITE);
}
/* ============================================================
PARTE 3 · EL BLUETOOTH
La placa se anuncia como "Heltec-Timbre". Esta vez solo tiene un "buzón":
TX: por aquí la placa AVISA a la página ("TIMBRE").
(La página no manda órdenes, así que no hace falta el buzón de entrada.)
============================================================ */
#define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
#define UUID_TX "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
BLECharacteristic *tx = nullptr;
class AlConectar : public BLEServerCallbacks {
void onConnect(BLEServer *s) { conectado = true; }
void onDisconnect(BLEServer *s) { conectado = false; BLEDevice::startAdvertising(); }
};
void avisarAlMovil() { // manda la palabra "TIMBRE" a la página
if (!conectado || !tx) return; // si no hay nadie conectado, no hay a quién avisar
String aviso = "TIMBRE";
tx->setValue((uint8_t *)aviso.c_str(), aviso.length());
tx->notify();
}
void arrancarBluetooth() {
BLEDevice::init("Heltec-Timbre");
BLEServer *servidor = BLEDevice::createServer();
servidor->setCallbacks(new AlConectar());
BLEService *svc = servidor->createService(SERVICE_UUID);
tx = svc->createCharacteristic(UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
tx->addDescriptor(new BLE2902());
svc->start();
BLEAdvertising *pub = BLEDevice::getAdvertising();
pub->addServiceUUID(SERVICE_UUID);
pub->setScanResponse(true);
BLEDevice::startAdvertising();
}
/* ============================================================
PARTE 4 · setup() y loop()
setup() se ejecuta UNA vez al encender. loop() se repite sin parar.
============================================================ */
void setup() {
pinMode(PIN_BOTON, INPUT_PULLUP); // el pin del botón será una ENTRADA con resistencia interna
arrancarPantalla(); // (solo para la pantalla)
arrancarBluetooth();
pintarPantalla(); // (solo para la pantalla)
}
bool conectadoAntes = false;
void loop() {
mirarBoton(); // ¿han pulsado el botón? (se mira unas 200 veces por segundo)
if (conectado != conectadoAntes) { // (solo para la pantalla) ¿cambió la conexión?
conectadoAntes = conectado;
pintarPantalla();
}
delay(5);
}
Lee y predice. Antes de subirlo, responde en tu cuaderno:
- ¿Qué lee
digitalReadcon el botón suelto? ¿Y con el botón pulsado? ¿Por qué? - ¿Por qué no hace falta poner una resistencia en el montaje?
- En la parte 1, ¿por qué se comprueba
botonAntes == HIGH && botonAhora == LOWy no solobotonAhora == LOW? ¿Qué pasaría si mantienes el botón pulsado dos segundos? - ¿Qué hace la línea
millis() - ultimaPulsacion > 200? ¿Para qué sirve? - Cambia
200por0, sube el programa y pulsa diez veces. ¿Cuántas visitas cuenta la pantalla? ¿Por qué?
La página web del timbre
La página es un solo archivo HTML. Créalo con el Bloc de notas, guárdalo como chisme-32-timbre.html y déjalo en una carpeta junto con la imagen lince-timbre.png. Ábrelo con Chrome o Edge.
Tiene tres zonas: el HTML (lo que se ve), el CSS (cómo se ve) y el JavaScript (lo que hace). Para este chisme solo importa la Parte 1 del JavaScript. La Parte 2 es el sonido y la Parte 3, el Bluetooth.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chisme 32 · El timbre de la cueva</title>
<style>
/* Los colores, en un solo sitio: cámbialos y cambia toda la página */
:root { --fondo:#eef2ef; --texto:#10201c; --suave:#4d625b; --verde:#1f5b4a; --luz:#ffd23f; --apagada:#8b9a94; }
@media (prefers-color-scheme: dark) {
:root { --fondo:#0f1a17; --texto:#e8f1ed; --suave:#9db3aa; --verde:#5fc4a2; --apagada:#3a4b45; }
}
body { margin:0; padding:1.5rem 1rem; background:var(--fondo); color:var(--texto);
font:18px/1.45 system-ui, sans-serif; text-align:center; }
h1 { font-size:1.5rem; margin:0 0 .3rem; }
p { color:var(--suave); margin:.3rem 0; }
button { font:inherit; font-weight:700; padding:.8rem 1.4rem; margin:.6rem .3rem;
border:2px solid var(--verde); background:var(--verde); color:var(--fondo);
border-radius:.9rem; cursor:pointer; }
/* La puerta: un círculo gris. Cuando llaman, se ilumina y parpadea. */
#puerta { width:11rem; height:11rem; margin:1.5rem auto 0.5rem; border-radius:50%;
background:var(--apagada); display:flex; align-items:center; justify-content:center;
overflow:hidden; transition:background .3s; }
#puerta.abierta { background:var(--luz); animation:parpadeo .5s ease-in-out 4; }
@keyframes parpadeo {
0%, 100% { box-shadow:0 0 0 0 var(--luz); }
50% { box-shadow:0 0 3rem 1.5rem var(--luz); }
}
/* El Lince solo se ve cuando llaman a la puerta */
#lince { height:10rem; opacity:0; transition:opacity .3s; }
#puerta.abierta #lince { opacity:1; }
#mensaje { font-size:1.3rem; font-weight:700; color:var(--texto); }
</style>
</head>
<body>
<h1>El timbre de la cueva</h1>
<p id="estado">Sin conectar.</p>
<button id="botonConectar">Conectar la placa</button>
<div id="puerta"><img id="lince" src="lince-timbre.png" alt="El Lince de la Noche"></div>
<p id="mensaje">Esperando visitas...</p>
<p id="cuenta">Visitas: 0</p>
<script>
/* ============================================================
PARTE 1 · LO IMPORTANTE (léela)
============================================================ */
let visitas = 0; // cuántas veces han llamado
let reloj; // un temporizador para apagar la luz después de unos segundos
// Esta función se ejecuta CADA VEZ que la placa nos avisa.
function alRecibirAviso(texto) {
if (texto === "TIMBRE") { // la placa dice: "han pulsado el botón"
visitas = visitas + 1; // una visita más
sonarTimbre(); // suena (está en la parte 2)
mostrarVisita(); // se ilumina y sale el Lince
}
}
function mostrarVisita() {
document.getElementById("puerta").classList.add("abierta"); // enciende la luz
document.getElementById("mensaje").textContent = "¡Llaman a la puerta!";
document.getElementById("cuenta").textContent = "Visitas: " + visitas;
clearTimeout(reloj); // si ya había uno, lo cancelamos
reloj = setTimeout(function () { // a los 3 segundos, todo vuelve a su sitio
document.getElementById("puerta").classList.remove("abierta");
document.getElementById("mensaje").textContent = "Esperando visitas...";
}, 3000);
}
/* ============================================================
PARTE 2 · EL SONIDO (un "ding-dong" hecho con dos notas)
El navegador solo deja sonar cuando has tocado la página antes:
por eso el altavoz se prepara al pulsar «Conectar la placa».
============================================================ */
let altavoz; // el sistema de audio del navegador
function sonarTimbre() {
if (!altavoz) return;
nota(659, 0.0, 0.6); // "ding": nota Mi (659 Hz), empieza ya y dura 0,6 s
nota(523, 0.5, 1.0); // "dong": nota Do (523 Hz), empieza a los 0,5 s y dura 1 s
}
function nota(frecuencia, inicio, duracion) {
const oscilador = altavoz.createOscillator(); // genera una onda de sonido
const volumen = altavoz.createGain(); // controla lo fuerte que suena
oscilador.frequency.value = frecuencia;
oscilador.connect(volumen);
volumen.connect(altavoz.destination);
const t = altavoz.currentTime + inicio;
volumen.gain.setValueAtTime(0.4, t);
volumen.gain.exponentialRampToValueAtTime(0.001, t + duracion); // el sonido se va apagando
oscilador.start(t);
oscilador.stop(t + duracion);
}
/* ============================================================
PARTE 3 · EL BLUETOOTH (casi igual que en el chisme 31; no hace falta tocarlo)
============================================================ */
const SERVICIO = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const UUID_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // por aquí RECIBIMOS los avisos
let dispositivo;
const estado = document.getElementById("estado");
const espera = ms => new Promise(r => setTimeout(r, ms));
async function abrirCanales() {
for (let intento = 1; intento <= 3; intento++) { // a veces falla la primera vez: reintentamos
try {
const servidor = await dispositivo.gatt.connect();
await espera(600);
const servicio = await servidor.getPrimaryService(SERVICIO);
const canalAvisos = await servicio.getCharacteristic(UUID_TX);
canalAvisos.addEventListener("characteristicvaluechanged", e => alRecibirAviso(new TextDecoder().decode(e.target.value)));
await canalAvisos.startNotifications();
estado.textContent = "Conectada a " + dispositivo.name + ". ¡Pulsa el botón!";
return;
} catch (e) {
if (intento === 3) throw e;
await espera(800);
}
}
}
document.getElementById("botonConectar").onclick = async function () {
if (!altavoz) altavoz = new (window.AudioContext || window.webkitAudioContext)(); // preparamos el sonido
if (!navigator.bluetooth) { estado.textContent = "Este navegador no permite Bluetooth. Usa Chrome."; return; }
try {
estado.textContent = "Buscando la placa...";
dispositivo = await navigator.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: [SERVICIO] });
dispositivo.addEventListener("gattserverdisconnected", () => {
estado.textContent = "Conexión perdida. Pulsa «Conectar la placa».";
});
await abrirCanales();
} catch (e) {
estado.textContent = "Error: " + e.message + " (elige «Heltec-Timbre» en la lista).";
}
};
</script>
</body>
</html>
Lee y predice, ahora con la página:
- ¿Qué palabra tiene que mandar la placa para que la página haga algo? ¿Qué pasa si manda otra?
- ¿Por qué el altavoz se prepara al pulsar “Conectar la placa” y no al cargar la página?
- Cambia
3000por6000. ¿Qué cambia? ¿En qué unidad está ese número? - Cambia
659por880y523por660. ¿Suena igual el timbre? ¿Por qué? - ¿Qué hace
clearTimeout(reloj)? Quítalo y pulsa el botón dos veces seguidas: ¿qué notas?
Cómo probar el botón con ESP32
- Sube el programa a la placa. En la pantalla OLED deberías ver “Timbre de la cueva”, “Visitas: 0” y “Bluetooth: esperando”.
- Abre
chisme-32-timbre.htmlen Chrome o Edge y pulsa “Conectar la placa”. Elige “Heltec-Timbre” en la lista. - Cuando la página diga “Conectada”, pulsa el botón de la placa.
- Debe pasar todo a la vez: suena el ding-dong, el círculo se ilumina y parpadea, aparece el Lince y la página dice “Visitas: 1”. La OLED también marca 1.
- A los tres segundos todo vuelve a quedar tranquilo. Pulsa otra vez y el contador sube a 2.
Si no suena, sube el volumen del móvil y comprueba que no está en silencio. El navegador solo permite el sonido después de que hayas pulsado “Conectar la placa”.
Si la pantalla de la placa no cuenta ninguna visita al pulsar, el problema está en el botón. Revisa que las patas estén en diagonal y que los cables hagan buen contacto.
Si la placa cuenta pero la página no reacciona, el problema está en el Bluetooth. Vuelve a pulsar “Conectar la placa” y recuerda que la página tiene que estar abierta con Chrome o Edge.
Qué estamos midiendo realmente
Un botón con ESP32 no mide una magnitud, como la temperatura o la luz. Detecta un solo dato: si un circuito está cerrado o abierto. Es una señal digital, con solo dos valores posibles.
Aun así, hay ciencia de la buena. Empecemos por la diferencia entre estado y evento.
El estado es “el botón está pulsado ahora mismo”. El evento es “el botón ha cambiado de suelto a pulsado”. Un timbre necesita el evento. Si el programa contara el estado, una pulsación larga contaría cientos de visitas. Por eso guardamos botonAntes y comparamos con botonAhora.
Lo segundo es el rebote. Dentro del botón hay una lámina de metal que, al tocar, vibra durante unos milisegundos. El contacto se abre y se cierra varias veces antes de quedarse firme.
Para una persona es una sola pulsación. Para una placa que mira el pin unas doscientas veces por segundo, puede parecer una pulsación repetida. Nuestra solución es sencilla: tras una pulsación, ignoramos todo durante 200 ms.
Es una solución tosca, y esa es la gracia. Si pulsaras más rápido que cinco veces por segundo, perderíamos pulsaciones. Medir no significa medir bien: siempre hay que elegir un compromiso y saber qué se sacrifica.
Lo tercero es el pin flotante. Un pin sin nada conectado no lee cero ni 3,3 V: lee ruido eléctrico. La resistencia interna de INPUT_PULLUP le da un valor de reposo. Es como un micrófono abierto en una sala vacía: capta cualquier ruido y lo interpreta como una señal.
Ahora el sonido. Las dos notas del timbre son de 659 Hz (un Mi) y 523 Hz (un Do). La frecuencia es el número de vibraciones por segundo: cuanto más alta, más aguda. El navegador crea las ondas con un oscilador, sin ningún archivo de audio.
Por último, hay dos contadores: el de la placa y el de la página. Normalmente coinciden. Pero si el Bluetooth se corta y alguien pulsa, la placa cuenta y la página no se entera. Son dos copias del mismo dato y pueden dejar de coincidir.
Eso nos enseña algo importante: un aviso que no llega es un aviso perdido. Nuestro timbre no guarda lo que se pierde mientras no hay conexión.
Para el aula
Una sesión de 50 minutos, pensada para 1.º de Bachillerato sin experiencia previa:
- Leer sin ordenador (10 minutos). Por parejas, subrayan la parte 1 del programa y contestan las preguntas de “Lee y predice”.
- Montar el circuito (10 minutos). Dibujan primero el esquema y el profesor lo revisa antes de enchufar.
- Subir y probar (10 minutos). Primero prueban y después explican por qué funciona.
- Experimento (15 minutos). Cada pareja elige uno de los experimentos de abajo.
- Cierre (5 minutos). El profesor elige una línea al azar y la pareja explica qué hace.
Tres experimentos para comprobar la teoría:
- Sin rebote. Cambian
200por0, suben el programa y pulsan el botón diez veces con cuidado. Anotan cuántas visitas cuenta la pantalla. Después prueban con 20, 50, 100 y 200 ms. ¿Cuál es el tiempo mínimo que elimina el rebote en su botón? - Pin flotante. Cambian
INPUT_PULLUPporINPUTy observan qué pasa sin tocar nada. ¿Por qué cuenta visitas solas? ¿Qué ocurre si tocan el cable con el dedo? - Estado frente a evento. Cambian la condición a
botonAhora == LOW(sin comprobarbotonAntes), con200ms. Mantienen pulsado el botón tres segundos. ¿Cuántas visitas cuenta? ¿Por qué ese número y no otro?
Preguntas para discutir:
- ¿Qué diferencia hay entre una señal digital y una analógica? ¿Por qué un botón es digital?
- ¿Qué variable estamos observando y cuántos valores puede tomar?
- Si el Bluetooth se corta y alguien pulsa, ¿qué hace la placa y qué hace la página? ¿Cómo lo solucionarías?
- ¿Dónde hay otros botones en la vida real que tengan que filtrar rebotes? (Teclados, mandos, ascensores.)
Una idea más ambiciosa: medir el rebote de verdad. Con el botón conectado, escriben en el monitor serie cada vez que el pin cambia, con la hora en microsegundos, y comparan los datos de varios botones. Es el primer paso de la forma de pensar de AIDARAC: sensor, dato, conjunto de datos y conclusión.
Retos
- Reto 1: haz que funcione. Monta el botón con ESP32, sube el programa y haz que el móvil suene al pulsar.
- Reto 2: modifica algo. Cambia las notas del timbre, el tiempo que dura la luz y el texto “¡Llaman a la puerta!”. Cambia el nombre de Bluetooth “Heltec-Timbre” por el tuyo.
- Reto 3: añade un segundo botón. Con otro pin y otra palabra (por ejemplo “URGENTE”), haz que suene un timbre distinto. ¿Cuántas líneas cambias en la placa y cuántas en la página?
- Reto 4: guarda los datos. Haz que la página apunte en una lista la hora de cada visita.
- Reto 5: representa los datos. Dibuja cuántas visitas hay por minuto. ¿Hay horas con más visitas?
- Reto 6: automatiza el proceso. Haz que, al pulsar el botón, se encienda un LED en la placa durante dos segundos (usa lo que aprendiste en el chisme anterior).
- Reto 7: haz que otro dispositivo participe. Abre la página en dos móviles a la vez. ¿Suenan los dos? ¿Por qué sí o por qué no?
- Reto 8: conviértelo en una pequeña investigación. Pulsa el botón cincuenta veces a distintas distancias del móvil. Compara el contador de la placa con el de la página. ¿Hay pulsaciones perdidas? Escribe una hipótesis, repite cada medida y concluye solo lo que los datos permitan.
Qué hemos aprendido
Un botón con ESP32 parece poca cosa, pero ya hemos visto lo esencial de cualquier entrada digital: el pin lee HIGH o LOW, la resistencia interna evita el pin flotante y hay que distinguir entre estado y evento.
Hemos aprendido que los botones rebotan, que un filtro sencillo basta pero tiene un precio, y que dos contadores pueden dejar de coincidir si un aviso se pierde por el camino.
También que un buen programa se puede leer por partes: lo importante, lo opcional y lo que siempre es igual.
El Lince terminó montando el timbre en la puerta de la cueva. ButanHero pasó, dejó la bombona en el suelo y pidió un vaso de agua.
—¿Y si te llega alguien cuando estás dormido? —preguntó la Gata.
—Para eso, el siguiente bicho —contestó el Lince, ya con el soldador en la mano.
Para seguir investigando
Web Audio API, en MDN: la tecnología que usa el navegador para fabricar el ding-dong.6 oct 2026 · @José Luis Usero Vílchez
Los Chismes del Lince: colección completa
Chismes del Lince 27: Semáforo Bluetooth con Heltec: otra salida controlada por Bluetooth, con tres LED.
Etiqueta:Arduino IDE, bachillerato, Bluetooth, botón, esp32, Heltec LoRa 32, INPUT_PULLUP, pulsador, rebote, Web Bluetooth


