Una caja negra para Sierra Bermeja. La micro:bit V2 puede guardar datos en su memoria aunque la desenchufes. Con un reloj DS3231 cada fila lleva la fecha y la hora reales. La dejaremos grabando toda la noche: temperatura, luz, sonido, movimiento y campo magnético. Al día siguiente, los datos en una hoja de cálculo.
Esta página lo explica todo: conexión, poner el reloj en hora, el programa parte por parte, cómo se usa, cómo sacar los datos y un simulador para practicar.
Qué guarda cada fila
| Columna | Qué es | De dónde sale |
|---|---|---|
| fecha, hora | Fecha y hora reales | Reloj DS3231 |
| temp_microbit | Temperatura en °C | Chip de la micro:bit (se calienta un poco al funcionar) |
| temp_reloj | Temperatura en °C (cuartos de grado) | Sensor interno del DS3231 |
| luz | Luz media (0-255) | Matriz de LEDs de la micro:bit (poco sensible: en interior con poca luz da 0) |
| sonido_max | Sonido más alto del intervalo (0-255) | Micrófono de la micro:bit V2 (solo el nivel, no graba audio) |
| movimiento | Cuánto ha variado la aceleración (mg) | Acelerómetro |
| magnetico_uT | Intensidad del campo magnético (µT) | Brújula (magnetómetro) |
Privacidad: no se guarda ninguna voz ni imagen; el micrófono solo da un número de volumen.
1 · Conexión del reloj DS3231
El reloj va a la cabecera I2C de la derecha de la placa de ampliación (la que pone 20, 19, V2, G), con 4 cables Dupont hembra-hembra.
| DS3231 | Placa de ampliación |
|---|---|
| SCL | 19 |
| SDA | 20 |
| + | V2 |
| − | G |
2 · Preparar MakeCode
- Abre makecode.microbit.org en Chrome o Edge y crea un proyecto nuevo.
- Pulsa Extensiones (al final de la lista de categorías de bloques), busca datalogger y añádela. Sin ella, MakeCode marca en rojo «Cannot find name 'datalogger'».
- Pasa a la pestaña JavaScript, borra lo que haya y pega el programa completo (botón «Copiar» de abajo).
- Conecta la micro:bit por su propio USB y graba con Descargar (la primera vez: Siguiente → Emparejar ahora → BBC micro:bit CMSIS-DAP → Conectar → Descargar).
Este programa es largo para hacerlo con bloques: por eso lo escribimos en la pestaña JavaScript. Si vuelves a «Bloques» verás que MakeCode lo convierte en bloques, aunque ocupa mucho.
3 · Poner el reloj en hora (solo una vez)
El reloj DS3231 sale de fábrica con la fecha 01/01/2000. Hay que decirle qué hora es una sola vez; después la pila lo mantiene aunque lo desenchufes.
- En el programa busca la línea
// ponerFechaHora(24, 10, 2026, 13, 45, 0). - Borra las dos barras
//del principio y cambia los números: día, mes, año, hora, minuto y segundo de dentro de un minuto. - Pulsa Descargar justo cuando llegue esa hora.
- Vuelve a poner las
//y descarga otra vez. Si no, cada vez que la micro:bit se reinicie volvería a poner el reloj en aquella hora antigua. - Comprueba: si al encender sale «HORA?», el reloj sigue sin fecha.
4 · El programa, parte por parte
El programa completo está al final de esta sección. Aquí lo tienes troceado y explicado.
1 · Las variables: la memoria de trabajo
Antes de nada, el programa reserva variables: cajitas con nombre donde guarda números mientras funciona.
intervalo: cada cuántos milisegundos se guarda una fila (1000 = un segundo, 60000 = un minuto).grabando:truesi está grabando yfalsesi está parado.sonidoMax,fuerzaMax,fuerzaMin,luzSuma,luzCuenta: los «resúmenes» que se van llenando entre fila y fila.dia,mes,anio,hora,minuto,segundo: lo que nos dice el reloj.borrarPedidoyultimaFila: momentos (en ms) para saber cuándo pasó algo.
// Chisme 150 - La caja negra del Lince: micro:bit V2 + reloj DS3231 // Necesita la extensión "datalogger" let intervalo = 1000 let grabando = false let sonidoMax = 0 let fuerzaMax = 0 let fuerzaMin = 99999 let luzSuma = 0 let luzCuenta = 0 let dia = 0 let mes = 0 let anio = 0 let hora = 0 let minuto = 0 let segundo = 0 let borrarPedido = 0 let ultimaFila = 0
2 · Hablar con el reloj DS3231
La micro:bit no sabe qué hora es: cuenta milisegundos desde que se enciende y vuelve a cero cada vez que se reinicia. El DS3231 es un reloj de verdad con pila: sigue contando aunque desenchufes todo.
Hablan por I2C, un «bus» de dos cables (SDA = datos, SCL = reloj del bus). El DS3231 tiene la dirección 104 (0x68): es como su número de teléfono en el bus.
leerReloj(): le dice «empieza en el registro 0» y le pide 7 bytes: segundos, minutos, horas, día de la semana, día, mes y año.- El reloj guarda los números en BCD: cada cifra decimal en 4 bits. El 45 se guarda como 0100 0101 (un 4 y un 5).
bcdADecimallo convierte en número normal ydecimalABcdhace lo contrario. - Los
& 127,& 63y& 31son máscaras: borran bits que el reloj usa para otras cosas (por ejemplo, el modo 12/24 horas). ponerFechaHora(...): escribe la fecha y la hora en el reloj. Solo se usa una vez.temperaturaReloj(): el DS3231 tiene un termómetro interno (lo usa para corregir su cristal de cuarzo). Lo leemos del registro 17: la parte entera y los cuartos de grado.dos(n): pone un cero delante de los números de una cifra (7 → «07») para que la hora salga como 13:05:07.
// --- Reloj DS3231 (dirección I2C 104) ---
function bcdADecimal (b: number) {
return (b >> 4) * 10 + (b & 15)
}
function decimalABcd (d: number) {
return Math.idiv(d, 10) * 16 + d % 10
}
function dos (n: number) {
return n < 10 ? "0" + n : "" + n
}
function leerReloj () {
pins.i2cWriteNumber(104, 0, NumberFormat.UInt8BE)
let buf = pins.i2cReadBuffer(104, 7)
segundo = bcdADecimal(buf[0] & 127)
minuto = bcdADecimal(buf[1])
hora = bcdADecimal(buf[2] & 63)
dia = bcdADecimal(buf[4])
mes = bcdADecimal(buf[5] & 31)
anio = 2000 + bcdADecimal(buf[6])
}
function ponerFechaHora (d: number, me: number, a: number, h: number, mi: number, s: number) {
let b = pins.createBuffer(8)
b[0] = 0
b[1] = decimalABcd(s)
b[2] = decimalABcd(mi)
b[3] = decimalABcd(h)
b[4] = 1
b[5] = decimalABcd(d)
b[6] = decimalABcd(me)
b[7] = decimalABcd(a - 2000)
pins.i2cWriteBuffer(104, b)
}
function temperaturaReloj () {
pins.i2cWriteNumber(104, 17, NumberFormat.UInt8BE)
let t = pins.i2cReadBuffer(104, 2)
let entero = t[0]
if (entero > 127) {
entero = entero - 256
}
return entero + (t[1] >> 6) * 0.25
}3 · Los botones: empezar, parar y borrar
empezar(ms, texto): cambia el intervalo, ponegrabandoatruey enseña «1s» o «1m». PoneultimaFilaa 0 para que la primera fila se guarde enseguida.- A: graba una fila cada segundo (para probar).
- B: graba una fila cada minuto (para dejarlo toda la noche).
- A+B a la vez: para y enseña una ✗.
- Logo dos veces en menos de 3 segundos: borra todo el registro y vuelve a escribir los títulos de las columnas. La primera vez enseña «?» para que no se borre por accidente.
datalogger.onLogFull: si la memoria se llena, para y enseña una cara triste.
function empezar (ms: number, texto: string) {
intervalo = ms
grabando = true
basic.showString(texto)
basic.clearScreen()
ultimaFila = 0
}
// --- Botones ---
input.onButtonPressed(Button.A, function () {
empezar(1000, "1s")
})
input.onButtonPressed(Button.B, function () {
empezar(60000, "1m")
})
input.onButtonPressed(Button.AB, function () {
grabando = false
basic.showIcon(IconNames.No)
})
input.onLogoEvent(TouchButtonEvent.Pressed, function () {
if (input.runningTime() - borrarPedido < 3000) {
grabando = false
datalogger.deleteLog(datalogger.DeleteType.Full)
datalogger.setColumnTitles("fecha", "hora", "temp_microbit", "temp_reloj", "luz", "sonido_max", "movimiento", "magnetico_uT")
basic.showIcon(IconNames.Skull)
} else {
borrarPedido = input.runningTime()
basic.showString("?")
}
})
datalogger.onLogFull(function () {
grabando = false
basic.showIcon(IconNames.Sad)
})4 · Al iniciar: preparar el registro
- La línea
// ponerFechaHora(...)está desactivada con//. Solo se activa una vez para poner el reloj en hora (lo explicamos más abajo). includeTimestamp(None): no queremos la marca de tiempo de la micro:bit (se reinicia), usaremos la del reloj.mirrorToSerial(true): además de guardar, manda cada fila por el cable para verla en «Mostrar datos Dispositivo».setColumnTitles(...): los nombres de las 8 columnas.- Si el reloj dice año 2000, es que no está en hora: la micro:bit enseña «HORA?».
// --- Al iniciar ---
// PONER EL RELOJ EN HORA (solo una vez): quita las // de la línea de abajo, pon la fecha y hora
// de dentro de 1 minuto, descarga, y después vuelve a poner las // y descarga otra vez.
// ponerFechaHora(24, 10, 2026, 13, 45, 0)
datalogger.includeTimestamp(FlashLogTimeStampFormat.None)
datalogger.mirrorToSerial(true)
datalogger.setColumnTitles("fecha", "hora", "temp_microbit", "temp_reloj", "luz", "sonido_max", "movimiento", "magnetico_uT")
leerReloj()
if (anio == 2000) {
// el reloj no tiene fecha: avisamos
basic.showString("HORA?")
}
basic.showIcon(IconNames.Happy)5 · El muestreo rápido: 10 veces por segundo
Si solo miráramos el sonido una vez por minuto, un portazo que dure medio segundo casi nunca saldría. Por eso este bucle mira 10 veces por segundo y va guardando un resumen:
sonidoMax: el sonido más alto desde la última fila.fuerzaMaxyfuerzaMin: la aceleración más alta y más baja. Su diferencia dice cuánto se ha movido o vibrado la placa.luzSumayluzCuenta: para calcular la luz media.
Esto se llama muestrear: decidir cada cuánto se mira y qué se guarda de lo que se ha mirado.
// --- Muestreo rápido: 10 veces por segundo ---
basic.forever(function () {
sonidoMax = Math.max(sonidoMax, input.soundLevel())
let f = input.acceleration(Dimension.Strength)
fuerzaMax = Math.max(fuerzaMax, f)
fuerzaMin = Math.min(fuerzaMin, f)
luzSuma += input.lightLevel()
luzCuenta += 1
basic.pause(100)
})6 · La grabación: una fila cada intervalo
- Cada 100 ms comprueba si está grabando y si ya ha pasado el
intervalodesde la última fila. - Si toca, lee el reloj y guarda una fila con las 8 columnas usando
datalogger.log. - Vacía los resúmenes para empezar el siguiente intervalo.
- Enciende un instante el LED de la esquina: es la señal de que ha guardado.
Como comprueba cada 100 ms, de vez en cuando una fila llega unas décimas tarde y en modo segundo puede «saltarse» un segundo del reloj. En nuestra prueba real pasó de 13:36:08 a 13:36:10. No se pierden datos de sonido ni de movimiento, porque los resúmenes siguen acumulando.
// --- Grabación: una fila cada intervalo (se comprueba cada 100 ms) ---
basic.forever(function () {
if (grabando && input.runningTime() - ultimaFila >= intervalo) {
ultimaFila = input.runningTime()
leerReloj()
datalogger.log(
datalogger.createCV("fecha", dos(dia) + "/" + dos(mes) + "/" + anio),
datalogger.createCV("hora", dos(hora) + ":" + dos(minuto) + ":" + dos(segundo)),
datalogger.createCV("temp_microbit", input.temperature()),
datalogger.createCV("temp_reloj", temperaturaReloj()),
datalogger.createCV("luz", luzCuenta > 0 ? Math.round(luzSuma / luzCuenta) : 0),
datalogger.createCV("sonido_max", sonidoMax),
datalogger.createCV("movimiento", fuerzaMax - fuerzaMin),
datalogger.createCV("magnetico_uT", Math.round(input.magneticForce(Dimension.Strength)))
)
sonidoMax = 0
fuerzaMax = 0
fuerzaMin = 99999
luzSuma = 0
luzCuenta = 0
led.plot(4, 4)
basic.pause(50)
led.unplot(4, 4)
}
basic.pause(100)
})Programa completo
// Chisme 150 - La caja negra del Lince: micro:bit V2 + reloj DS3231
// Necesita la extensión "datalogger"
let intervalo = 1000
let grabando = false
let sonidoMax = 0
let fuerzaMax = 0
let fuerzaMin = 99999
let luzSuma = 0
let luzCuenta = 0
let dia = 0
let mes = 0
let anio = 0
let hora = 0
let minuto = 0
let segundo = 0
let borrarPedido = 0
let ultimaFila = 0
// --- Reloj DS3231 (dirección I2C 104) ---
function bcdADecimal (b: number) {
return (b >> 4) * 10 + (b & 15)
}
function decimalABcd (d: number) {
return Math.idiv(d, 10) * 16 + d % 10
}
function dos (n: number) {
return n < 10 ? "0" + n : "" + n
}
function leerReloj () {
pins.i2cWriteNumber(104, 0, NumberFormat.UInt8BE)
let buf = pins.i2cReadBuffer(104, 7)
segundo = bcdADecimal(buf[0] & 127)
minuto = bcdADecimal(buf[1])
hora = bcdADecimal(buf[2] & 63)
dia = bcdADecimal(buf[4])
mes = bcdADecimal(buf[5] & 31)
anio = 2000 + bcdADecimal(buf[6])
}
function ponerFechaHora (d: number, me: number, a: number, h: number, mi: number, s: number) {
let b = pins.createBuffer(8)
b[0] = 0
b[1] = decimalABcd(s)
b[2] = decimalABcd(mi)
b[3] = decimalABcd(h)
b[4] = 1
b[5] = decimalABcd(d)
b[6] = decimalABcd(me)
b[7] = decimalABcd(a - 2000)
pins.i2cWriteBuffer(104, b)
}
function temperaturaReloj () {
pins.i2cWriteNumber(104, 17, NumberFormat.UInt8BE)
let t = pins.i2cReadBuffer(104, 2)
let entero = t[0]
if (entero > 127) {
entero = entero - 256
}
return entero + (t[1] >> 6) * 0.25
}
function empezar (ms: number, texto: string) {
intervalo = ms
grabando = true
basic.showString(texto)
basic.clearScreen()
ultimaFila = 0
}
// --- Botones ---
input.onButtonPressed(Button.A, function () {
empezar(1000, "1s")
})
input.onButtonPressed(Button.B, function () {
empezar(60000, "1m")
})
input.onButtonPressed(Button.AB, function () {
grabando = false
basic.showIcon(IconNames.No)
})
input.onLogoEvent(TouchButtonEvent.Pressed, function () {
if (input.runningTime() - borrarPedido < 3000) {
grabando = false
datalogger.deleteLog(datalogger.DeleteType.Full)
datalogger.setColumnTitles("fecha", "hora", "temp_microbit", "temp_reloj", "luz", "sonido_max", "movimiento", "magnetico_uT")
basic.showIcon(IconNames.Skull)
} else {
borrarPedido = input.runningTime()
basic.showString("?")
}
})
datalogger.onLogFull(function () {
grabando = false
basic.showIcon(IconNames.Sad)
})
// --- Al iniciar ---
// PONER EL RELOJ EN HORA (solo una vez): quita las // de la línea de abajo, pon la fecha y hora
// de dentro de 1 minuto, descarga, y después vuelve a poner las // y descarga otra vez.
// ponerFechaHora(24, 10, 2026, 13, 45, 0)
datalogger.includeTimestamp(FlashLogTimeStampFormat.None)
datalogger.mirrorToSerial(true)
datalogger.setColumnTitles("fecha", "hora", "temp_microbit", "temp_reloj", "luz", "sonido_max", "movimiento", "magnetico_uT")
leerReloj()
if (anio == 2000) {
// el reloj no tiene fecha: avisamos
basic.showString("HORA?")
}
basic.showIcon(IconNames.Happy)
// --- Muestreo rápido: 10 veces por segundo ---
basic.forever(function () {
sonidoMax = Math.max(sonidoMax, input.soundLevel())
let f = input.acceleration(Dimension.Strength)
fuerzaMax = Math.max(fuerzaMax, f)
fuerzaMin = Math.min(fuerzaMin, f)
luzSuma += input.lightLevel()
luzCuenta += 1
basic.pause(100)
})
// --- Grabación: una fila cada intervalo (se comprueba cada 100 ms) ---
basic.forever(function () {
if (grabando && input.runningTime() - ultimaFila >= intervalo) {
ultimaFila = input.runningTime()
leerReloj()
datalogger.log(
datalogger.createCV("fecha", dos(dia) + "/" + dos(mes) + "/" + anio),
datalogger.createCV("hora", dos(hora) + ":" + dos(minuto) + ":" + dos(segundo)),
datalogger.createCV("temp_microbit", input.temperature()),
datalogger.createCV("temp_reloj", temperaturaReloj()),
datalogger.createCV("luz", luzCuenta > 0 ? Math.round(luzSuma / luzCuenta) : 0),
datalogger.createCV("sonido_max", sonidoMax),
datalogger.createCV("movimiento", fuerzaMax - fuerzaMin),
datalogger.createCV("magnetico_uT", Math.round(input.magneticForce(Dimension.Strength)))
)
sonidoMax = 0
fuerzaMax = 0
fuerzaMin = 99999
luzSuma = 0
luzCuenta = 0
led.plot(4, 4)
basic.pause(50)
led.unplot(4, 4)
}
basic.pause(100)
})
5 · Cómo se usa
| Haces… | Pasa… | En la pantalla |
|---|---|---|
| Enciendes la micro:bit | Prepara el registro; no graba todavía | ☺ (o «HORA?» si el reloj no está en hora) |
| Botón A | Graba una fila cada segundo (para probar) | «1s» y luego un punto que parpadea en la esquina |
| Botón B | Graba una fila cada minuto (para toda la noche) | «1m» y el punto parpadea una vez por minuto |
| A+B a la vez | Para de grabar (lo grabado se queda) | ✗ |
| Logo dos veces en 3 s | Borra todo el registro | «?» y después una calavera |
| — | La memoria se llena | Cara triste: para solo |
Para dejarla toda la noche: desconéctala del ordenador, ponle el portapilas (o un cargador USB), pulsa B y déjala donde no la muevan ni le dé el sol directo.
Capacidad (estimación): caben del orden de 2.000 filas de 8 columnas; a una fila por minuto, más de un día. Compruébalo: si sale la cara triste, se llenó.
6 · Simulador
Practica aquí sin placa. Pulsa los botones como en la micro:bit real y mueve los deslizadores para cambiar el «mundo». En modo minuto el simulador acelera el tiempo (un minuto pasa en un segundo).
Filas guardadas: 0
| fecha | hora | temp_microbit | temp_reloj | luz | sonido_max | movimiento | magnetico_uT |
|---|
magnetico_uT: sube de golpe. Los altavoces y otros componentes del móvil tienen imanes, y los aparatos eléctricos crean campos magnéticos. Antes de decir «hay una presencia», hay que buscar la causa más sencilla. Si alguien afirma algo extraordinario, que traiga pruebas.7 · Sacar los datos y enviarlos
- Para la grabación con A+B.
- Conecta la micro:bit al ordenador con el USB. Aparece una unidad llamada MICROBIT.
- Abre el archivo MY_DATA con doble clic: se abre en el navegador con la tabla y una gráfica.
- Pulsa Download (descargar) para guardar los datos como CSV, o cópialos a una hoja de cálculo.
- Copia el CSV a una carpeta de tu ordenador y cámbiale el nombre, por ejemplo
sierra-bermeja-24-10-2026.csv. - Para mandarlo por correo: escribe un correo nuevo, pulsa Adjuntar (el clip), elige el archivo CSV y envíalo a tu profesor.
Descargar un programa nuevo no borra los datos del todo (los esconde). Para borrarlos de verdad, toca el logo dos veces.
Lee y predice
Dejas la micro:bit grabando junto al portátil y el campo magnético marca 80 µT. ¿Hay un fantasma?
No: el campo de la Tierra en España ronda los 45 µT y los aparatos eléctricos y los imanes lo aumentan. Aleja la placa y repite: si baja, ya tienes la causa.¿Por qué el sonido se guarda como «máximo» y la luz como «media»?
Un ruido corto (un portazo) se perdería si hiciéramos la media; la luz cambia despacio y la media la representa mejor. Cada medida necesita su resumen.En los datos aparece 13:36:08 y luego 13:36:10. ¿Se ha perdido un segundo?
La fila llegó unas décimas tarde y el reloj ya marcaba el segundo siguiente. Los resúmenes no se pierden: siguen acumulando hasta la siguiente fila.Micro:bit V2 con la placa de ampliación Keyestudio y el reloj DS3231 de Keyestudio. Extensión datalogger de MakeCode.