Chisme 150 · La caja negra del Lince: datalogger con micro:bit

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

ColumnaQué esDe dónde sale
fecha, horaFecha y hora realesReloj DS3231
temp_microbitTemperatura en °CChip de la micro:bit (se calienta un poco al funcionar)
temp_relojTemperatura en °C (cuartos de grado)Sensor interno del DS3231
luzLuz media (0-255)Matriz de LEDs de la micro:bit (poco sensible: en interior con poca luz da 0)
sonido_maxSonido más alto del intervalo (0-255)Micrófono de la micro:bit V2 (solo el nivel, no graba audio)
movimientoCuánto ha variado la aceleración (mg)Acelerómetro
magnetico_uTIntensidad 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.

micro:bit V2 Keyestudio 2019V2G cabecera I2C → DS3231 SCLSDA+− Puente V2 en 3,3 V
DS3231Placa de ampliación
SCL19
SDA20
+V2
−G
Importante: el puente de V2 tiene que estar en 3,3 V. Este módulo une SDA y SCL a su alimentación: con 5 V, esas líneas llegarían a 5 V a la micro:bit, que solo admite 3,3 V.

2 · Preparar MakeCode

  1. Abre makecode.microbit.org en Chrome o Edge y crea un proyecto nuevo.
  2. 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'».
  3. Pasa a la pestaña JavaScript, borra lo que haya y pega el programa completo (botón «Copiar» de abajo).
  4. 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.

  1. En el programa busca la línea // ponerFechaHora(24, 10, 2026, 13, 45, 0).
  2. 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.
  3. Pulsa Descargar justo cuando llegue esa hora.
  4. 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.
  5. 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: true si está grabando y false si 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.
  • borrarPedido y ultimaFila: 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). bcdADecimal lo convierte en número normal y decimalABcd hace lo contrario.
  • Los & 127, & 63 y & 31 son 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, pone grabando a true y enseña «1s» o «1m». Pone ultimaFila a 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.
  • fuerzaMax y fuerzaMin: la aceleración más alta y más baja. Su diferencia dice cuánto se ha movido o vibrado la placa.
  • luzSuma y luzCuenta: 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 intervalo desde 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:bitPrepara el registro; no graba todavía☺ (o «HORA?» si el reloj no está en hora)
Botón AGraba una fila cada segundo (para probar)«1s» y luego un punto que parpadea en la esquina
Botón BGraba una fila cada minuto (para toda la noche)«1m» y el punto parpadea una vez por minuto
A+B a la vezPara de grabar (lo grabado se queda)✗
Logo dos veces en 3 sBorra todo el registro«?» y después una calavera
—La memoria se llenaCara 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).

Mostrar datos Dispositivo…

Filas guardadas: 0

fechahoratemp_microbittemp_relojluzsonido_maxmovimientomagnetico_uT
¿Un fantasma? Pulsa «Acercar el móvil» mientras grabas y mira la columna 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

  1. Para la grabación con A+B.
  2. Conecta la micro:bit al ordenador con el USB. Aparece una unidad llamada MICROBIT.
  3. Abre el archivo MY_DATA con doble clic: se abre en el navegador con la tabla y una gráfica.
  4. Pulsa Download (descargar) para guardar los datos como CSV, o cópialos a una hoja de cálculo.
  5. Copia el CSV a una carpeta de tu ordenador y cámbiale el nombre, por ejemplo sierra-bermeja-24-10-2026.csv.
  6. 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.