
Chismes del Lince 19: El cuenta pasos del Lince (micro:bit, Bluetooth y móvil)
Hasta ahora todos nuestros chismes usaban lo que lleva el móvil dentro. En este damos un salto: el micro:bit cuenta los pasos y se los manda al móvil por Bluetooth. El móvil los enseña en grande, calcula la cadencia (pasos por minuto) y la compara con el ritmo de un metrónomo.
Para probarlo, basta con agitar el micro:bit: cada sacudida cuenta como un paso. Es el programa más sencillo posible, y por eso funciona a la primera.
Lo que necesitas
- Un micro:bit V2 con su portapilas.
- Un móvil Android con Chrome, con el Bluetooth y la ubicación encendidos. En iPhone, Safari no sirve: hay que abrir la página en el navegador Bluefy.
- El ordenador con MakeCode para programar.
Paso 1. Programa el micro:bit
Entra en MakeCode y crea un proyecto nuevo. Usa solo bloques.
- Añade la extensión Bluetooth (Extensiones → Bluetooth). No añadas la de Radio.
- Crea la variable pasos.
- Monta este programa:
al iniciar
bluetooth servicio uartdar a pasos el valor 0mostrar icono(el que quieras, para saber que ha arrancado)
si agitar
cambiar pasos por 1mostrar número pasosuart bluetooth escribir líneaconunir "Pasos: " pasos

El bloque para siempre queda vacío.
Si prefieres JavaScript, cambia al modo JavaScript y pega esto:
javascript
let pasos = 0
bluetooth.startUartService()
basic.showIcon(IconNames.Happy)
input.onGesture(Gesture.Shake, function () {
pasos += 1
basic.showNumber(pasos)
bluetooth.uartWriteLine("Pasos: " + pasos)
})
Paso 2. Sin emparejamiento (muy importante)
Antes de descargar, hay que cambiar un ajuste para que la página pueda conectar sin pedir códigos:
- Pulsa el engranaje ⚙️ → Configuración del proyecto.
- Marca No se requiere emparejamiento (No Pairing Required: Anyone can connect via Bluetooth).
Paso 3. Pásalo al micro:bit
- Descarga el programa y cópialo a la unidad MICROBIT.
- Espera a que deje de parpadear el led amarillo de atrás.
- Reinicia el micro:bit con el botón de atrás. Debe salir tu icono.
Si cambias algo del programa, repite los pasos 2 y 3.
Paso 4. Prepara el móvil
- Bluetooth y ubicación encendidos.
- Si en Ajustes → Bluetooth aparece “BBC micro:bit”, pulsa Olvidar. No hay que emparejarla desde ahí.
- Cierra cualquier otra app que use el micro:bit.
Paso 5. Abre la página y conecta
- Abre en Chrome, escribiendo la dirección directamente: https://montesteam.org/app/chisme-19-cuenta-pasos-microbit.html
- Pulsa Conectar micro:bit.
- Elige BBC micro:bit […] en la lista.
- Cuando la página ponga Conectado, agita el micro:bit.
Verás que suben los pasos y que el círculo marca cada uno. Activa el metrónomo y mueve la barra para elegir un ritmo objetivo (de 60 a 140): la página te dirá si vas al ritmo o si conviene ir un poco más rápido o más despacio. El botón Poner a cero reinicia la cuenta.
Retos para clase
- ¿Cuál es tu ritmo natural agitando? ¿Y el de tus compañeros?
- Pon el metrónomo a 100 e intenta seguirlo durante un minuto.
- ¿Qué pasa con la cadencia si agitas cada vez más deprisa?
- Reto avanzado: que el micro:bit pite cuando la cadencia se salga del ritmo objetivo.
Versión opcional: caminar de verdad con el micro:bit en el tobillo
Al pisar, el pie da un golpe seco que el acelerómetro detecta muy bien. En el tobillo, sujeta el micro:bit con una goma ancha, una muñequera o un calcetín alto, con la matriz hacia fuera y bien apretado. En lugar de “agitar”, este programa usa un umbral de fuerza. Crea las variables pasos, ultimo y ahora:
javascript
let pasos = 0
let ultimo = 0
let ahora = 0
bluetooth.startUartService()
basic.forever(function () {
ahora = input.runningTime()
if (input.acceleration(Dimension.Strength) > 1500 && ahora - ultimo > 300) {
pasos += 2
ultimo = ahora
bluetooth.uartWriteLine("" + pasos)
}
basic.pause(10)
})
Suma 2 porque con el micro:bit en un solo tobillo solo notamos la pisada de ese pie: cada golpe es una zancada, y una zancada son dos pasos.
Para calibrarlo, camina 20 pasos contándolos tú y mira cuántos cuenta el micro:bit. Si cuenta de menos, baja el 1500 (prueba 1200); si cuenta de más, súbelo (prueba 1800).
Si algo falla
- No aparece en la lista: comprueba que está encendido, que el programa está descargado y que activaste «No se requiere emparejamiento».
- Se conecta pero no suma: comprueba que el programa tiene el bloque
bluetooth servicio uartal iniciar. - En iPhone no conecta: abre la página con Bluefy.
Qué hemos aprendido
Un sensor (el acelerómetro) mide una fuerza. Un gesto o un umbral convierte esa medida en una decisión. Bluetooth manda el resultado al móvil. Y la cadencia sale de dividir los pasos entre el tiempo que han tardado.
Una advertencia: no he podido comprobar si el gesto “agitar” detecta bien los pasos con el micro:bit en el tobillo. Para caminar de verdad usa la versión opcional.
Si quieres esta entrada en un archivo (HTML o Word) para subirla directamente, dímelo.
Próximo chisme: el micro:bit recibirá órdenes del móvil.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Chisme 19 · El cuenta pasos del Lince 2</title>
<style>
:root{
--bg:#eef2ef; --ink:#10201c; --soft:#4d625b; --card:#ffffff; --line:#cfdad5;
--pine:#1f5b4a; --slow:#2563a8; --fast:#b4501a; --ok:#1f7a4d;
box-sizing:border-box;
padding-top:env(safe-area-inset-top,0px); padding-bottom:env(safe-area-inset-bottom,0px);
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
--bg:#0f1a17; --ink:#e8f1ed; --soft:#9db3aa; --card:#172622; --line:#2a3d37;
--pine:#5fc4a2; --slow:#7db3ee; --fast:#f0a06a; --ok:#6fd89f;}}
:root[data-theme="dark"]{--bg:#0f1a17; --ink:#e8f1ed; --soft:#9db3aa; --card:#172622; --line:#2a3d37;
--pine:#5fc4a2; --slow:#7db3ee; --fast:#f0a06a; --ok:#6fd89f;}
*{box-sizing:border-box}
html{scroll-padding-top:env(safe-area-inset-top,0px)}
body{margin:0;background:var(--bg);color:var(--ink);font:17px/1.45 "Trebuchet MS","Segoe UI",system-ui,sans-serif}
main{max-width:30rem;margin:0 auto;padding:1.2rem 1rem 2rem}
h1{font-size:1.3rem;margin:.2rem 0 .1rem}
.sub{color:var(--soft);margin:0 0 1rem;font-size:.95rem}
button{font:inherit;font-weight:700;border:2px solid var(--pine);background:var(--pine);color:var(--bg);
border-radius:.8rem;padding:.75rem 1.1rem;min-height:3rem;cursor:pointer}
button:disabled{opacity:.55;cursor:wait}
button.alt{background:transparent;color:var(--pine)}
button:focus-visible,input:focus-visible{outline:3px solid var(--slow);outline-offset:2px}
#estado{margin:.8rem 0 .2rem;color:var(--soft);min-height:1.5em}
#ayuda{margin:0 0 .6rem;color:var(--soft);font-size:.9rem;min-height:1.2em}
.ring{position:relative;width:min(78vw,18rem);aspect-ratio:1;margin:1rem auto;border-radius:50%;
border:.5rem solid var(--line);display:grid;place-items:center;text-align:center;
transition:border-color .2s}
.ring.ok{border-color:var(--ok)} .ring.slow{border-color:var(--slow)} .ring.fast{border-color:var(--fast)}
.ring.beat::after,.ring.step::before{content:"";position:absolute;inset:-.5rem;border-radius:50%;
border:.5rem solid var(--pine);opacity:0;animation:p .45s ease-out}
.ring.step::before{border-color:var(--ink)}
@keyframes p{from{opacity:.7;transform:scale(1)}to{opacity:0;transform:scale(1.12)}}
@media (prefers-reduced-motion:reduce){.ring::after,.ring::before{animation:none!important}}
.pasos{font-size:4.6rem;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}
.lbl{color:var(--soft);font-size:.95rem}
.fila{display:flex;gap:.8rem;justify-content:space-between;margin:1rem 0}
.dato{flex:1;background:var(--card);border:1px solid var(--line);border-radius:.8rem;padding:.7rem .9rem}
.dato b{display:block;font-size:2rem;font-variant-numeric:tabular-nums}
#aviso{font-weight:700;text-align:center;min-height:1.6em;margin:.4rem 0 1rem}
.objetivo{background:var(--card);border:1px solid var(--line);border-radius:.8rem;padding:.9rem}
input[type=range]{width:100%;margin:.6rem 0;accent-color:var(--pine)}
.botones{display:flex;gap:.6rem;flex-wrap:wrap}
</style>
</head>
<body>
<main>
<h1>El cuenta pasos del Lince 2</h1>
<p class="sub">Micro:bit en el tobillo, móvil en la mano.</p>
<div class="botones">
<button id="conectar">Conectar micro:bit</button>
<button id="cero" class="alt">Poner a cero</button>
</div>
<div id="estado" role="status">Sin conectar.</div>
<div id="ayuda"></div>
<div class="ring" id="ring">
<div><div class="pasos" id="pasos">0</div><div class="lbl">pasos</div></div>
</div>
<div class="fila">
<div class="dato"><span class="lbl">Cadencia real</span><b id="cad">0</b><span class="lbl">pasos/min</span></div>
<div class="dato"><span class="lbl">Ritmo objetivo</span><b id="obj">100</b><span class="lbl">pasos/min</span></div>
</div>
<div id="aviso" aria-live="polite"></div>
<div class="objetivo">
<label for="bpm">Ritmo objetivo del metrónomo</label>
<input type="range" id="bpm" min="60" max="140" value="100">
<button id="metro" class="alt">Activar metrónomo</button>
</div>
</main>
<script>
const UART_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const UART_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // la micro:bit envía por aquí
const $ = id => document.getElementById(id);
let ultimoN = 0, base = 0, ventana = [], ultimoT = 0, cadencia = 0, buffer = "", objetivo = 100;
let dispositivo = null, caracteristica = null, conectando = false;
const espera = ms => new Promise(r => setTimeout(r, ms));
let paso = "", diag = "";
function estado(t, ayuda){ if (!/^Error/.test(t)) paso = t; $("estado").textContent = t; $("ayuda").textContent = ayuda || ""; }
function flash(cls){ const r=$("ring"); r.classList.remove(cls); void r.offsetWidth; r.classList.add(cls); }
function pintar(){
$("pasos").textContent = Math.max(0, ultimoN - base);
$("cad").textContent = Math.round(cadencia);
const aviso = $("aviso"), ring = $("ring");
ring.classList.remove("ok","slow","fast");
if (cadencia <= 0){ aviso.textContent = ""; return; }
const d = cadencia - objetivo;
if (Math.abs(d) <= 5){ aviso.textContent = "Vas al ritmo"; ring.classList.add("ok"); }
else if (d < 0){ aviso.textContent = "Un poco más rápido"; ring.classList.add("slow"); }
else { aviso.textContent = "Un poco más despacio"; ring.classList.add("fast"); }
}
function linea(texto){
const m = texto.match(/\d+/); // vale "5", "5,x" y también "Pasos: 5"
if (!m) return;
const n = parseInt(m[0], 10);
if (n < ultimoN){ ventana = []; base = 0; } // la micro:bit se ha puesto a cero (botón A)
ultimoN = n;
const t = performance.now();
ultimoT = t;
ventana.push({t, n});
if (ventana.length > 6) ventana.shift();
if (ventana.length >= 2){
const a = ventana[0], b = ventana[ventana.length-1];
cadencia = (b.n - a.n) / (b.t - a.t) * 60000;
}
flash("step"); pintar();
}
function recibir(ev){
buffer += new TextDecoder().decode(ev.target.value);
let i;
while ((i = buffer.indexOf("\n")) >= 0){
linea(buffer.slice(0, i).trim());
buffer = buffer.slice(i + 1);
}
}
setInterval(() => { // si no hay pasos durante 3 s, la cadencia vuelve a 0
if (cadencia > 0 && performance.now() - ultimoT > 3000){ cadencia = 0; ventana = []; pintar(); }
}, 500);
/* Explica el error en cristiano */
function explicar(e){
const nombre = e && e.name ? e.name : "Error";
const msg = e && e.message ? e.message : String(e);
let ayuda = "";
if (nombre === "NotFoundError" && /cancel/i.test(msg)){
ayuda = "No has elegido ningún dispositivo. Pulsa Conectar y elige BBC micro:bit.";
} else if (nombre === "NotFoundError"){
ayuda = "La micro:bit no tiene el servicio UART. Revisa que el programa llame a bluetooth.startUartService() y que hayas copiado el .hex nuevo.";
} else if (nombre === "NotSupportedError"){
ayuda = "No se pudo activar el envío de datos. Suele deberse a una copia antigua de los servicios de la micro:bit guardada en el móvil: olvida la micro:bit en Ajustes → Bluetooth, borra la caché de la app del sistema «Bluetooth» y reinicia el móvil y la micro:bit.";
} else if (nombre === "NetworkError"){
ayuda = "No se ha podido abrir la conexión. Casi siempre es el emparejamiento: en MakeCode activa «No se requiere emparejamiento», vuelve a copiar el .hex, quita la micro:bit de los dispositivos Bluetooth del sistema y reiníciala. Comprueba que no la tenga abierta otra app o pestaña.";
} else if (nombre === "SecurityError"){
ayuda = "La página debe abrirse con https:// (no desde un archivo local).";
} else if (nombre === "NotAllowedError"){
ayuda = "Permiso denegado. Permite el Bluetooth y los dispositivos cercanos para el navegador.";
}
estado("Error (" + nombre + "): " + msg,
"Falló en el paso: «" + paso + "». " + ayuda + (diag ? " Características encontradas: " + diag : ""));
}
/* Reintenta: la primera conexión GATT falla a menudo y la segunda funciona */
async function conectarGatt(dev){
let ultimoError;
for (let intento = 1; intento <= 4; intento++){
try {
estado("Conectando… (intento " + intento + " de 4)");
const srv = dev.gatt.connected ? dev.gatt : await dev.gatt.connect();
await espera(600); // la micro:bit necesita un momento antes de pedirle servicios
estado("Buscando el servicio UART…");
const svc = await srv.getPrimaryService(UART_SERVICE);
return svc;
} catch (e){
ultimoError = e;
if (e.name === "NotFoundError" || e.name === "SecurityError") break; // reintentar no ayuda
try { if (dev.gatt.connected) dev.gatt.disconnect(); } catch (_){}
await espera(800);
}
}
throw ultimoError;
}
$("conectar").onclick = async () => {
if (conectando) return;
if (!navigator.bluetooth){
estado("Este navegador no permite Bluetooth.", "Usa Chrome o Edge (en ordenador o en Android). Safari y Firefox no sirven.");
return;
}
conectando = true; $("conectar").disabled = true;
try {
estado("Buscando micro:bit…");
const dev = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [UART_SERVICE]
});
dispositivo = dev;
dev.addEventListener("gattserverdisconnected", () => {
caracteristica = null;
cadencia = 0; ventana = [];
pintar();
estado("Desconectado.", "Pulsa Conectar para volver a empezar.");
});
const svc = await conectarGatt(dev);
buffer = ""; ventana = []; cadencia = 0; diag = "";
estado("Leyendo las características del servicio…");
const chars = await svc.getCharacteristics();
diag = chars.map(c => c.uuid.slice(0, 8) + " [" +
Object.keys(c.properties).filter(k => c.properties[k]).join(", ") + "]").join(" · ");
// Primero la TX conocida, luego cualquier otra que avise por notify o indicate
const candidatas = chars
.filter(c => c.properties.notify || c.properties.indicate)
.sort((a, b) => (b.uuid === UART_TX) - (a.uuid === UART_TX));
if (!candidatas.length) throw new Error("El servicio UART no tiene ninguna característica que envíe datos.");
let ch = null, ultimoErr;
for (const c of candidatas){
c.addEventListener("characteristicvaluechanged", recibir);
for (let i = 1; i <= 2 && !ch; i++){
try {
estado("Activando notificaciones en " + c.uuid.slice(0, 8) + "… (intento " + i + ")");
await c.startNotifications();
ch = c;
} catch (e){ ultimoErr = e; await espera(1000); }
}
if (ch) break;
c.removeEventListener("characteristicvaluechanged", recibir);
}
if (!ch) throw ultimoErr;
caracteristica = ch;
estado("Conectado a " + (dev.name || "micro:bit") + ". ¡Camina!");
} catch (e) {
explicar(e);
} finally {
conectando = false; $("conectar").disabled = false;
}
};
$("cero").onclick = () => { base = ultimoN; ventana = []; cadencia = 0; pintar(); };
/* Metrónomo */
let ac = null, activo = false, proximo = 0, timer = null;
function bip(t){
const o = ac.createOscillator(), g = ac.createGain();
o.frequency.value = 880; g.gain.setValueAtTime(0.4, t); g.gain.exponentialRampToValueAtTime(0.001, t + 0.08);
o.connect(g).connect(ac.destination); o.start(t); o.stop(t + 0.1);
setTimeout(() => { flash("beat"); if (navigator.vibrate) navigator.vibrate(30); }, Math.max(0, (t - ac.currentTime) * 1000));
}
function planificar(){
while (proximo < ac.currentTime + 0.15){ bip(proximo); proximo += 60 / objetivo; }
}
$("metro").onclick = () => {
if (!activo){
ac = ac || new (window.AudioContext || window.webkitAudioContext)();
ac.resume(); proximo = ac.currentTime + 0.1; timer = setInterval(planificar, 25);
activo = true; $("metro").textContent = "Parar metrónomo";
} else {
clearInterval(timer); activo = false; $("metro").textContent = "Activar metrónomo";
}
};
$("bpm").oninput = e => { objetivo = +e.target.value; $("obj").textContent = objetivo; pintar(); };
pintar();
</script>
</body>
</html>
Etiqueta:acelerómetro, Bluetooth, cadencia, Chismes del Lince, cuenta pasos, makecode, micro:bit, steam, webapp



