
Chismes del Lince 31: La luciérnaga del Lince
Aquella noche en Sierra Bermeja no había luna, y el Lince de la Noche tenía por fin algo que no había tenido nunca: una alumna. La Gata Montesa estaba sentada sobre una piedra, con la cola enroscada y cara de escéptica, mirando cómo él sacaba de la mochila una placa, un LED, una resistencia y un puñado de cables.
—Hoy te voy a enseñar a hacer los bichos —anunció el Lince, muy solemne—. El primero va a ser una luciérnaga. Se enciende y se apaga cuando se lo ordenes desde el móvil, y lo hará a la velocidad de la luz.
—¿A la velocidad de la luz? —preguntó la Gata, levantando una ceja—. ¿Y eso cuánto es?
—Pues… muy rápido. Casi trescientos mil kilómetros por segundo. Creo.
—Entonces me imagino que tu luciérnaga se encenderá antes de que pulse el botón.
El Lince abrió la boca, la cerró y se puso a revisar los cables con mucho interés. La Gata sonrió. Le gustaba que el Lince tuviera ideas grandes y, sobre todo, le gustaba comprobar si eran verdad. Sacó el móvil y dijo:
—Vale, enséñame cómo se hace. Pero esta vez quiero entender cada línea. Nada de copiar y pegar sin leer. Y después medimos si tu luciérnaga es tan rápida como dices.
Introducción
En este chisme vamos a aprender a encender un LED con ESP32 por Bluetooth: una página web en el móvil o en el ordenador con un solo botón, una placa Heltec/TTGO LoRa32 y un LED rojo con su resistencia. Cuando pulsas el botón, el LED se enciende. Cuando vuelves a pulsarlo, se apaga.
Es el chisme más sencillo de toda la serie, y lo es a propósito. En los anteriores hemos construido cosas más vistosas: radares, mensajes firmados, localizadores. Pero muchos alumnos copian el código, lo pegan, ven que funciona y no saben decir qué hace ni una sola línea. Aquí el objetivo es el contrario: que seas capaz de señalar con el dedo el trozo de programa que enciende el LED y el trozo de la página que lo pide. Si entiendes este chisme, entiendes la mitad de todos los demás.
Por qué este chisme
La Gata Montesa lo dijo enseguida: “Si no entiendo lo que copio, no he construido nada, solo he hecho de fotocopiadora”. Tiene razón. Este chisme está diseñado para que se pueda leer entero en pocos minutos.
El programa de la placa tiene cuatro partes y solo una es el chisme: la del LED, de unas diez líneas. Otra parte es la pantalla, y está marcada con un cartel que dice “no es necesaria”: el LED funciona igual sin ella. La tercera es el Bluetooth, que es siempre igual en todos los chismes, y la cuarta es el esqueleto del programa, setup() y loop(). La página web sigue el mismo plan: lo importante arriba y el Bluetooth abajo.
Además, el LED es un resultado que se ve a simple vista. Si algo falla, no hay que interpretar números ni gráficas: o se enciende o no se enciende, y eso obliga a razonar dónde está el fallo. A nosotros nos pasó: el programa funcionaba y la pantalla respondía, pero el LED no se encendía. El problema eran unos pines mal conectados. Aprender a buscar un fallo así, paso a paso, vale tanto como aprender a programar.
Cómo funciona encender un LED con ESP32 por Bluetooth
Piensa en dos personas que se hablan por una cuerda: la página web y la placa. La página da órdenes y la placa las obedece y luego cuenta cómo ha quedado todo. Nada más.
El Lince quería complicarlo con mensajes largos. La Gata le preguntó: “¿Cuántas cosas puede hacer un LED?”. “Dos”, respondió él. “Pues con un carácter para cada cosa nos sobra”. Así que el idioma entre página y placa tiene solo tres órdenes y dos respuestas:
| Quién habla | Mensaje | Significado |
|---|---|---|
| La página | 1 | Enciende el LED |
| La página | 0 | Apaga el LED |
| La página | ? | Cuéntame cómo está el LED |
| La placa | LED:1 | Te aviso: el LED está encendido |
| La placa | LED:0 | Te aviso: el LED está apagado |
Para que los mensajes viajen sin cables usamos Bluetooth de bajo consumo (BLE). La placa se anuncia con el nombre Heltec-Luciernaga y tiene dos buzones: en uno recibe las órdenes y por el otro avisa. El navegador Chrome (o Edge) puede hablar con ella gracias a una tecnología llamada Web Bluetooth, que solo funciona en páginas seguras (https) o abiertas desde un archivo local.
Hay un detalle que te conviene fijarte porque es una idea importante de programación: la página no cambia el dibujo cuando pulsas el botón. Espera a que la placa conteste “LED:1” o “LED:0” y entonces lo cambia. Así, lo que ves en pantalla es lo que de verdad ha ocurrido en el LED, no lo que la página cree que ha ocurrido. Es la diferencia entre pedir algo y comprobar que se ha hecho.
Qué necesitamos
- Una placa Heltec WiFi LoRa 32 o TTGO LoRa32 (ESP32) con su cable USB. Para este chisme no necesitas la antena LoRa, porque no usamos la radio LoRa.
- Un LED rojo.
- Una resistencia de 220 Ω (rojo, rojo, marrón).
- Dos cables dupont y, si quieres, una protoboard.
- El Arduino IDE con la placa “ESP32 Dev Module” y las librerías Adafruit SSD1306 y Adafruit GFX (solo para la pantalla opcional).
- Un móvil Android u ordenador con Chrome o Edge. En iPhone, Chrome y Safari no permiten Web Bluetooth.
Si tu LED es azul o blanco, verás que luce menos: esos LED necesitan más voltaje para encenderse y la placa solo da 3,3 V. Con uno rojo, amarillo o naranja no hay problema.
Construcción
Haz el montaje con la placa desenchufada. Solo hay dos conexiones:
GPIO 13 ──[ resistencia 220 Ω ]──> pata LARGA del LED (+)
pata CORTA del LED (−) ───────────────> GND
La pata larga del LED es la positiva (ánodo) y la corta es la negativa (cátodo). El LED es un diodo: la corriente solo pasa en un sentido, así que si lo pones al revés no se enciende (no se estropea, solo hay que darle la vuelta).
La resistencia es necesaria. Un LED sin resistencia deja pasar demasiada corriente y se puede quemar, además de forzar el pin de la placa. Con 220 Ω y 3,3 V, un LED rojo recibe una corriente de unos 6 mA, que es suficiente para verlo bien y segura para la placa. (La cuenta es la ley de Ohm: se resta al voltaje de la placa lo que “se queda” el LED, unos 2 V en uno rojo, y se divide entre la resistencia: (3,3 − 2) / 220 ≈ 0,006 A.)
Un consejo que aprendimos probando: si el LED no se enciende y todo lo demás funciona (la pantalla se actualiza y la página dice “LED: ENCENDIDO”), sospecha de los contactos. En nuestro caso el fallo eran los pines mal conectados en la placa: los pines de estas placas son finos y en una protoboard a veces no hacen contacto. Antes de culpar al programa, comprueba el cableado y prueba el LED con otra fuente, por ejemplo un Arduino o una pila de botón de 3 V.
El código de la placa
Copia el programa completo en el Arduino IDE (un archivo nuevo, por ejemplo chisme-31-luciernaga.ino), elige la placa “ESP32 Dev Module” y súbelo. Pero antes de pulsar “Subir”, lee las instrucciones de este apartado. Está dividido en cuatro partes con cartelitos grandes:
- Parte 1, el LED: es el chisme. Léela línea a línea.
- Parte 2, la pantalla: no es necesaria. Solo enseña el estado en la OLED. No hace falta entenderla para que el LED funcione: puedes saltarte esta parte al leer. Las líneas que llevan la nota “solo para la pantalla” son las que la usan.
- Parte 3, el Bluetooth: es igual en todos los chismes. No hace falta tocarla.
- Parte 4,
setup()yloop(): el esqueleto.setup()se ejecuta una vez al encender la placa yloop()se repite sin parar.
/*
Chisme 31 · La luciérnaga del Lince
La página web manda una ORDEN por Bluetooth ("1" = encender, "0" = apagar)
y la placa enciende o apaga un LED. Después le CUENTA a la página cómo ha
quedado el LED, y la página lo dibuja.
Este programa tiene 4 partes. Solo la PARTE 1 es el chisme:
PARTE 1 · EL LED <- LEE ESTA. Es lo único importante.
PARTE 2 · LA PANTALLA <- NO ES NECESARIA. Solo enseña el estado en la OLED.
PARTE 3 · EL BLUETOOTH <- Es siempre igual en todos los chismes. No hace falta tocarla.
PARTE 4 · setup() y loop()
CABLEADO (con la placa DESENCHUFADA):
GPIO 13 ──[ resistencia 220 Ω ]──> pata LARGA del LED (+)
pata CORTA del LED (−) ──────────> GND
Si el LED no se enciende, dale la vuelta: tiene sentido (polaridad).
La resistencia protege el LED: sin ella, pasaría demasiada corriente.
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 LED (lo importante)
============================================================ */
const int PIN_LED = 13; // el pin donde hemos conectado el LED
bool ledEncendido = false; // aquí recordamos cómo está el LED
void ponerLed(bool encender) { // enciende o apaga el LED
ledEncendido = encender;
digitalWrite(PIN_LED, encender ? HIGH : LOW); // HIGH = 3,3 V (encendido), LOW = 0 V (apagado)
}
void alRecibirOrden(String orden) { // la página ha mandado una orden
if (orden == "1") ponerLed(true); // "1" -> encender
if (orden == "0") ponerLed(false); // "0" -> apagar
avisarAlMovil(); // contamos cómo ha quedado el LED
pintarPantalla(); // (solo para la pantalla)
}
/* ============================================================
PARTE 2 · LA PANTALLA (NO ES NECESARIA: el LED funciona sin ella)
Solo sirve para ver el estado 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("Luciernaga del Lince");
display.setCursor(0, 20);
display.print("LED:");
display.setTextSize(2);
display.setCursor(0, 32);
display.print(ledEncendido ? "ENCENDIDO" : "APAGADO");
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 (igual en todos los chismes)
La placa se anuncia como "Heltec-Luciernaga". Tiene dos "buzones":
RX: por aquí LLEGAN las órdenes de la página.
TX: por aquí la placa AVISA a la página.
============================================================ */
#define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
#define UUID_RX "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
#define UUID_TX "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
BLECharacteristic *tx = nullptr;
String ordenPendiente = "";
volatile bool hayOrden = false;
class AlConectar : public BLEServerCallbacks {
void onConnect(BLEServer *s) { conectado = true; }
void onDisconnect(BLEServer *s) { conectado = false; BLEDevice::startAdvertising(); }
};
class AlEscribir : public BLECharacteristicCallbacks { // llega algo desde la página
void onWrite(BLECharacteristic *c) {
auto v = c->getValue();
String orden = "";
for (size_t i = 0; i < v.length(); i++)
if (v[i] >= 32) orden += (char)v[i]; // nos quedamos con las letras y números
ordenPendiente = orden;
hayOrden = true; // loop() la atenderá enseguida
}
};
void avisarAlMovil() { // manda "LED:1" o "LED:0" a la página
if (!conectado || !tx) return;
String aviso = ledEncendido ? "LED:1" : "LED:0";
tx->setValue((uint8_t *)aviso.c_str(), aviso.length());
tx->notify();
}
void arrancarBluetooth() {
BLEDevice::init("Heltec-Luciernaga");
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());
BLECharacteristic *rx = svc->createCharacteristic(UUID_RX,
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
rx->setCallbacks(new AlEscribir());
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_LED, OUTPUT); // el pin del LED será una SALIDA
ponerLed(false); // empezamos con el LED apagado
arrancarPantalla(); // (solo para la pantalla)
arrancarBluetooth();
pintarPantalla(); // (solo para la pantalla)
}
bool conectadoAntes = false;
void loop() {
if (hayOrden) { // ¿ha llegado una orden?
hayOrden = false;
alRecibirOrden(ordenPendiente);
}
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é hace
pinMode(PIN_LED, OUTPUT)y por qué hace falta? - En
ponerLed, ¿qué voltaje sale por el pin conHIGH? ¿Y conLOW? - Si la página manda la letra “x”, ¿se enciende el LED? ¿Qué hace la placa entonces? (Pista: mira los dos
ifdealRecibirOrden.) - ¿Por qué la placa guarda la orden en
ordenPendientey la ejecuta enloop()en lugar de hacerlo dentro del Bluetooth? (Respuesta corta: el Bluetooth es una tarea aparte de la placa y es mejor no hacer allí cosas largas como encender pantallas.) - Cambia
13por otro pin libre y vuelve a subirlo: ¿qué tienes que cambiar también en el cableado?

La página web
La página es un único archivo. Créalo con el Bloc de notas (o cualquier editor de texto), guárdalo como luciernaga.html y ábrelo con Chrome o Edge. Tiene tres cosas: una zona de HTML (lo que se ve), una de CSS (cómo se ve) y el JavaScript (lo que hace). Para este chisme solo importa la Parte 1 del JavaScript, que son unas quince líneas. La Parte 2 es el Bluetooth y, como en la placa, es igual en todos los chismes.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chisme 31 · La luciérnaga del Lince</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; }
button:disabled { opacity:.45; cursor:not-allowed; }
/* El bicho: un círculo que se ilumina cuando el LED está encendido */
#bicho { width:9rem; height:9rem; margin:1.5rem auto; border-radius:50%;
background:var(--apagada); transition:background .3s, box-shadow .3s; }
#bicho.encendido { background:var(--luz); box-shadow:0 0 3rem 1rem var(--luz); }
</style>
</head>
<body>
<h1>La luciérnaga del Lince</h1>
<p id="estado">Sin conectar.</p>
<button id="botonConectar">Conectar la placa</button>
<div id="bicho"></div>
<p id="textoLed">LED: ?</p>
<button id="botonLed" disabled>Encender</button>
<p id="registro"></p>
<script>
/* ============================================================
PARTE 1 · LO IMPORTANTE (léela)
============================================================ */
let ledEncendido = false; // cómo está el LED, según nos ha contado la placa
// Cuando pulsas el botón mandamos la orden contraria a cómo está el LED:
// si está encendido mandamos "0" (apagar); si está apagado, "1" (encender).
document.getElementById("botonLed").onclick = function () {
const orden = ledEncendido ? "0" : "1";
enviarOrden(orden);
document.getElementById("registro").textContent = "Orden enviada: " + orden;
};
// Cuando la placa nos AVISA ("LED:1" o "LED:0"), cambiamos lo que se ve.
// Fíjate: la página no se fía de su propio botón, sino de lo que cuenta la placa.
function alRecibirAviso(texto) {
ledEncendido = (texto === "LED:1");
document.getElementById("bicho").classList.toggle("encendido", ledEncendido);
document.getElementById("textoLed").textContent = "LED: " + (ledEncendido ? "ENCENDIDO" : "APAGADO");
document.getElementById("botonLed").textContent = ledEncendido ? "Apagar" : "Encender";
}
/* ============================================================
PARTE 2 · EL BLUETOOTH (igual en todos los chismes; no hace falta tocarlo)
============================================================ */
const SERVICIO = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const UUID_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; // aquí ESCRIBIMOS las órdenes
const UUID_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // por aquí RECIBIMOS los avisos
let dispositivo, canalOrdenes;
const estado = document.getElementById("estado");
const espera = ms => new Promise(r => setTimeout(r, ms));
async function enviarOrden(orden) {
try { await canalOrdenes.writeValue(new TextEncoder().encode(orden)); }
catch (e) { estado.textContent = "No se pudo enviar: " + e.message; }
}
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);
canalOrdenes = await servicio.getCharacteristic(UUID_RX);
const canalAvisos = await servicio.getCharacteristic(UUID_TX);
canalAvisos.addEventListener("characteristicvaluechanged", e => alRecibirAviso(new TextDecoder().decode(e.target.value)));
await canalAvisos.startNotifications();
await enviarOrden("?"); // "?" = cuéntame cómo está el LED
estado.textContent = "Conectada a " + dispositivo.name + ".";
document.getElementById("botonLed").disabled = false;
return;
} catch (e) {
if (intento === 3) throw e;
await espera(800);
}
}
}
document.getElementById("botonConectar").onclick = async function () {
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».";
document.getElementById("botonLed").disabled = true;
});
await abrirCanales();
} catch (e) {
estado.textContent = "Error: " + e.message + " (elige «Heltec-Luciernaga» en la lista).";
}
};
</script>
</body>
</html>
Lee y predice, ahora con la página:
- En el botón, ¿por qué se manda “0” si el LED está encendido y “1” si está apagado?
- ¿Qué pasaría si la página cambiara el círculo al pulsar el botón, sin esperar el aviso de la placa? ¿Cuándo se vería una mentira?
- Cambia el color
--luz:#ffd23fpor#ff3030y recarga. ¿Qué cambia? ¿Y si cambias “Encender” por “Dale luz” en el HTML? - En
alRecibirAviso, ¿qué valeledEncendidocuando llega “LED:0”?
Cómo probarlo
- Sube el programa a la placa. En la pantalla OLED de la placa deberías leer “Luciernaga del Lince”, “LED: APAGADO” y “Bluetooth: esperando”.
- Abre
luciernaga.htmlen Chrome o Edge y pulsa “Conectar la placa”. Elige “Heltec-Luciernaga” en la lista. - Cuando la página diga “Conectada”, el botón se activa. Púlsalo: el LED se enciende, el círculo de la página se ilumina y la OLED pone “ENCENDIDO”. Púlsalo otra vez y todo se apaga.
- Desconecta y vuelve a conectar sin tocar la placa: la página pregunta con un “?” y se pone sola como esté el LED.
Si la pantalla cambia pero el LED no se enciende, el programa y el Bluetooth están bien y el fallo está en el montaje. Revisa en este orden: que el LED no esté al revés; que la resistencia esté en serie; que el cable llegue al pin 13 de verdad (en algunas placas la serigrafía es diminuta); que los pines hagan buen contacto con la protoboard; y que el LED funcione con otra fuente. Y si el pin 13 está ocupado en tu modelo, cambia PIN_LED por otro pin libre.
Una prueba que sirve para separar el programa del cableado: muchas placas TTGO y Heltec LoRa32 tienen un LED integrado en el pin 25 (compruébalo en la documentación de tu modelo). Cambia PIN_LED a 25 y comprueba que se enciende sin cables.
Qué estamos midiendo realmente
En este chisme no hay ningún sensor, pero hay ciencia de la buena. Volvamos a la apuesta del Lince: “mi luciérnaga va a la velocidad de la luz”.
La velocidad de la luz en el vacío es de 299 792 458 metros por segundo, casi trescientos mil kilómetros cada segundo. Y es verdad que en nuestro montaje hay luz y también algo que va a esa velocidad: el Bluetooth son ondas de radio de 2,4 GHz, que son ondas electromagnéticas igual que la luz visible, solo que con otra frecuencia. Una orden que recorre un metro entre el móvil y la placa tarda unos 3 nanosegundos (3 mil millonésimas de segundo), y la luz del LED tarda otro tanto en llegar a tus ojos. Si todo fuera solo el viaje, el LED se encendería de forma instantánea para cualquier persona.
Pero entonces, ¿por qué se nota un pequeño retardo al pulsar el botón? Porque casi todo el tiempo no se va en viajar, sino en procesar. El móvil agrupa los mensajes Bluetooth en turnos (el llamado intervalo de conexión, de varios milisegundos), la placa los atiende en loop() y hay un delay(5) de cinco milisegundos, el navegador tiene que ejecutar el JavaScript, y por último nuestro cerebro tarda más de cien milisegundos en percibir un cambio. Es decir: la luz va muy rápido, pero el sistema entero es millones de veces más lento que la luz. Medir cuánto tarda algo no es lo mismo que medir lo rápido que va la señal que lo transporta.
Hay otra idea importante, que es la diferencia entre dato y medida. Cuando la página dice “LED: ENCENDIDO”, ese dato no lo ha medido ningún sensor de luz. Es la placa contando lo que ella misma ha ordenado: ha puesto el pin a 3,3 V y lo da por hecho. Nos pasó en las pruebas: el pin estaba en HIGH, la página decía “ENCENDIDO” y, sin embargo, el LED estaba apagado porque el cable no hacía contacto. Lo que la placa nos cuenta es “he dado la orden”, no “se ve luz”. Para saber si hay luz de verdad habría que añadir un sensor de luz (un LDR, por ejemplo) y leerlo, y eso es un buen reto para más adelante.
Lo que sí podemos medir con lo que tenemos es el retardo total. Una forma es grabar con el móvil en cámara lenta (120 o 240 fotogramas por segundo) la pantalla del ordenador y el LED a la vez y contar fotogramas entre que se pulsa el botón y que el LED luce. Si el vídeo va a 240 fps, cada fotograma son unos 4 milisegundos.
Para el aula
Este chisme está pensado para trabajar con alumnado de 1.º de Bachillerato que no ha visto antes ni la placa ni la programación web. La clave es que nadie suba el código sin haberlo leído. Una secuencia que funciona en una sesión de 50 minutos:
- Leer sin ordenador (10 minutos). Se reparte el programa de la placa impreso. Por parejas, subrayan solo la Parte 1 y contestan las cinco preguntas de “Lee y predice”. Se ponen en común antes de tocar nada.
- Montar el circuito (10 minutos). Cada pareja dibuja primero el esquema (pin 13, resistencia, LED, GND) y el profesor lo revisa antes de enchufar. Así se evitan LED quemados y se habla de polaridad.
- Subir y probar (10 minutos). Primero se prueba y después se vuelve a hablar de por qué funciona.
- Cambiar una cosa (15 minutos). Cada pareja elige un cambio de la lista y primero escribe qué va a pasar. Después lo prueba. Si la predicción falla, se averigua por qué.
- Cierre (5 minutos). Cada pareja explica en voz alta qué hace una línea del programa elegida al azar por el profesor.
Preguntas para pensar y discutir:
- ¿Qué variable estamos controlando? ¿Cuántos valores puede tomar? (Una variable que solo puede ser verdadera o falsa se llama booleana.)
- ¿Qué voltaje sale del pin con
HIGHy conLOW? Mide con un polimétro, si lo hay. - ¿Por qué la página espera el aviso de la placa en lugar de cambiar el círculo directamente? ¿En qué casos del día a día pasa lo mismo? (Un semáforo, un cajero que dice “operación realizada”…)
- La placa dice “ENCENDIDO” y el LED está apagado. ¿Quién miente? Diseña un experimento para averiguar si el fallo está en el programa, en el Bluetooth o en el cableado.
- ¿Qué error tendría nuestra medida de “cuánto tarda en encenderse”? ¿Qué diferencia habría entre dos móviles distintos? ¿Cómo la repetirías para tener confianza en el resultado?
Una actividad de investigación más ambiciosa: medir el retardo con vídeo en cámara lenta, repetirlo veinte veces, calcular la media y la dispersión y comparar el resultado entre un móvil y un ordenador. Es el primer paso de la forma de pensar de AIDARAC: sensor, dato, conjunto de datos, análisis y conclusión.
Retos
- Reto 1: haz que funcione. Monta el circuito, sube el programa, abre la página y enciende la luciérnaga desde el móvil.
- Reto 2: modifica algo. Cambia el color del círculo y el texto de los botones. Después cambia el nombre Bluetooth “Heltec-Luciernaga” por el tuyo (en la placa y en el mensaje de ayuda de la página).
- Reto 3: añade un LED. Conecta otro LED con otra resistencia a otro pin. Haz que la orden “2” encienda el segundo LED y la “3” lo apague. ¿Cuántas líneas tienes que cambiar en la placa y cuántas en la página?
- Reto 4: haz que parpadee. Añade una orden “P” que haga parpadear el LED cinco veces. Cuidado: si usas
delay()largos, la placa no atiende al Bluetooth mientras espera. ¿Cómo lo resolverías? - Reto 5: guarda los datos. Haz que la página apunte en una lista la hora de cada orden y de cada aviso, y calcula cuánto pasa entre una y otra. Es el retardo de tu sistema, medido por la propia página.
- Reto 6: representálo. Dibuja una gráfica con los retardos de veinte pulsaciones. ¿Son todos iguales? ¿Por qué no?
- Reto 7: comprueba que hay luz de verdad. Añade un sensor de luz (un LDR) frente al LED y haz que la placa avise de lo que ve, no de lo que ha ordenado. Entonces el aviso será una medida y no un dato asumido.
- Reto 8: conviértelo en una pequeña investigación. ¿Depende el retardo de la distancia entre el móvil y la placa? ¿Y de que haya paredes por medio? Formula una hipótesis, diseña el experimento, repite cada medida y concluye solo lo que los datos permitan.
Qué hemos aprendido
Encender un LED con ESP32 por Bluetooth parece poca cosa, y sin embargo ya contiene la estructura de casi cualquier proyecto de electrónica conectada: una orden que sale de una página, una placa que la ejecuta sobre el mundo físico (un pin que pasa de 0 V a 3,3 V) y un aviso que vuelve para confirmar lo ocurrido.
Hemos aprendido a leer el programa en partes y a distinguir lo importante de lo que es solo decoración; que la resistencia protege el LED; que la página debe fiarse de lo que cuenta la placa y no de su propio botón; que la luz y las ondas de radio van a la misma velocidad, 299 792 458 m/s, pero que el retardo que notamos viene casi todo del software; y que un dato dicho por la placa (“he ordenado encender”) no es lo mismo que una medida (“se ve luz”).
La Gata Montesa lo resumió mirando al Lince: “Tu luciérnaga no va a la velocidad de la luz, pero tu idea de enseñarme sí fue rápida”. El Lince se lo agradeció y se prometió no volver a hacer una apuesta sin medirla antes. A los dos minutos ya estaba pensando en el siguiente bicho.
Para seguir investigando
- Los Chismes del Lince: colección completa
- Chismes del Lince 25: El letrero del Lince, Heltec LoRa 32, Bluetooth y móvil: el primer chisme con la Heltec y el mismo Bluetooth que usamos aquí.
- Chismes del Lince 27: Semáforo Bluetooth con Heltec: el paso siguiente natural, con tres LED en lugar de uno.
- Valor de la velocidad de la luz en el vacío (NIST): la constante fundamental c, con su valor exacto.
Etiqueta:Arduino IDE, Bluetooth, esp32, GPIO, Heltec LoRa 32, led, velocidad de la luz, Web Bluetooth


