Unidad 5
Introducción 📜
En esta unidad vas a explorar un nuevo tipo de protocolo de comunicación serial, el protocolo binario. Este protocolo es más eficiente que el ASCII, ya que permite enviar mas información en menos tiempo. Sin embargo, es más complejo de implementar y requiere un mayor conocimiento de la programación de bajo nivel. Por tanto, te voy a proponer que continuemos con la misma aplicación que exploramos en la unidad anterior, pero ahora la vamos a modificar para que use el protocolo binario. De esta manera podrás comparar los dos protocolos y ver las ventajas y desventajas de cada uno.
¿Qué aprenderás en esta unidad? 💡
Vas a enviar información desde el micro:bit a un sketch en p5.js usando protocolos de comunicación binarios. Además, vas a comparar los protocolos ASCII y binario y verás las ventajas y desventajas de cada uno.
Actividad 01
Repasa el caso de estudio
🎯 Enunciado: en esta actividad vas a poner a funcionar el caso de estudio de la unidad anterior y lo vas a repasar de nuevo. Mira, es muy importante que le dedique un tiempo generoso a revisar de nuevo el caso de estudio, ya que es un ejemplo muy completo y te va a ayudar a entender mejor el resto de la unidad.
El código del micro:bit es este:
# Imports go at the topfrom microbit import *
uart.init(115200)display.set_pixel(0,0,9)
while True: xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() data = "{},{},{},{}\n".format(xValue, yValue, aState,bState) uart.write(data) sleep(100) # Envia datos a 10 Hz
El código del sketch es este:
index.html:
<!DOCTYPE html><html> <head> <script src="https://cdn.jsdelivr.net/npm/p5/lib/p5.min.js"></script> <script src="https://unpkg.com/@gohai/p5.webserial@^1/libraries/p5.webserial.js"></script> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <script src="sketch.js"></script> </body></html>
Carga las imágenes:
sketch.js:
let c;let lineModuleSize = 0;let angle = 0;let angleSpeed = 1;const lineModule = [];let lineModuleIndex = 0;let clickPosX = 0;let clickPosY = 0;
function preload() { lineModule[1] = loadImage("02.svg"); lineModule[2] = loadImage("03.svg"); lineModule[3] = loadImage("04.svg"); lineModule[4] = loadImage("05.svg");}
let port;let connectBtn;let microBitConnected = false;
const STATES = { WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION", RUNNING: "RUNNING",};let appState = STATES.WAIT_MICROBIT_CONNECTION;let microBitX = 0;let microBitY = 0;let microBitAState = false;let microBitBState = false;let prevmicroBitAState = false;let prevmicroBitBState = false;
function setup() { createCanvas(windowWidth, windowHeight); background(255);
port = createSerial(); connectBtn = createButton("Connect to micro:bit"); connectBtn.position(0, 0); connectBtn.mousePressed(connectBtnClick);}
function connectBtnClick() { if (!port.opened()) { port.open("MicroPython", 115200); } else { port.close(); }}
function updateButtonStates(newAState, newBState) { // Generar eventos de keypressed if (newAState === true && prevmicroBitAState === false) { // create a new random color and line length lineModuleSize = random(50, 160); // remember click position clickPosX = microBitX; clickPosY = microBitY; print("A pressed"); } // Generar eventos de key released if (newBState === false && prevmicroBitBState === true) { c = color(random(255), random(255), random(255), random(80, 100)); print("B released"); }
prevmicroBitAState = newAState; prevmicroBitBState = newBState;}
function windowResized() { resizeCanvas(windowWidth, windowHeight);}
function draw() { //****************************************** if (!port.opened()) { connectBtn.html("Connect to micro:bit"); microBitConnected = false; } else { microBitConnected = true; connectBtn.html("Disconnect"); if (port.availableBytes() > 0) { let data = port.readUntil("\n"); if (data) { data = data.trim(); let values = data.split(","); if (values.length == 4) { microBitX = int(values[0]) + windowWidth / 2; microBitY = int(values[1]) + windowHeight / 2; microBitAState = values[2].toLowerCase() === "true"; microBitBState = values[3].toLowerCase() === "true"; updateButtonStates(microBitAState, microBitBState); } else { print("No se están recibiendo 4 datos del micro:bit"); } } } } //*******************************************
switch (appState) { case STATES.WAIT_MICROBIT_CONNECTION: // No puede comenzar a dibujar hasta que no se conecte el microbit // evento 1: if (microBitConnected === true) { // Preparo todo para el estado en el próximo frame print("Microbit ready to draw"); strokeWeight(0.75); c = color(181, 157, 0); noCursor(); appState = STATES.RUNNING; }
break;
case STATES.RUNNING: // EVENTO: estado de conexión del microbit if (microBitConnected === false) { print("Waiting microbit connection"); cursor(); appState = STATES.WAIT_MICROBIT_CONNECTION; }
//EVENTO: recepción de datos seriales del micro:bit
if (microBitAState === true) { let x = microBitX; let y = microBitY;
if (keyIsPressed && keyCode === SHIFT) { if (abs(clickPosX - x) > abs(clickPosY - y)) { y = clickPosY; } else { x = clickPosX; } }
push(); translate(x, y); rotate(radians(angle)); if (lineModuleIndex != 0) { tint(c); image( lineModule[lineModuleIndex], 0, 0, lineModuleSize, lineModuleSize ); } else { stroke(c); line(0, 0, lineModuleSize, lineModuleSize); } angle += angleSpeed; pop(); }
break; }}
function keyPressed() { if (keyCode === UP_ARROW) lineModuleSize += 5; if (keyCode === DOWN_ARROW) lineModuleSize -= 5; if (keyCode === LEFT_ARROW) angleSpeed -= 0.5; if (keyCode === RIGHT_ARROW) angleSpeed += 0.5;}
function keyReleased() { if (key === "s" || key === "S") { let ts = year() + nf(month(), 2) + nf(day(), 2) + "_" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); saveCanvas(ts, "png"); } if (keyCode === DELETE || keyCode === BACKSPACE) background(255);
// reverse direction and mirror angle if (key === "d" || key === "D") { angle += 180; angleSpeed *= -1; }
// default colors from 1 to 4 if (key === "1") c = color(181, 157, 0); if (key === "2") c = color(0, 130, 164); if (key === "3") c = color(87, 35, 129); if (key === "4") c = color(197, 0, 123);
// load svg for line module if (key === "5") lineModuleIndex = 0; if (key === "6") lineModuleIndex = 1; if (key === "7") lineModuleIndex = 2; if (key === "8") lineModuleIndex = 3; if (key === "9") lineModuleIndex = 4;}
📤 Entrega:
- Describe cómo se están comunicando el micro:bit y el sketch de p5.js. ¿Qué datos envía el micro:bit?
- ¿Cómo es la estructura del protocolo ASCII usado?
- Muestra y explica la parte del código de p5.js donde lee los datos del micro:bit y los transforma en coordenadas de la pantalla.
- ¿Cómo se generan los eventos A pressed y B released que se generan en p5.js a partir de los datos que envía el micro:bit?
- Capturas de pantalla de los algunos dibujos que hayas hecho con el sketch.
🚀 Tu solución:
Análisis del Caso de Estudio: Comunicación micro:bit y p5.js
1. Comunicación y Datos Enviados
El micro:bit envía datos seriales cada 100ms (10Hz) con este formato:
"xValue,yValue,aState,bState\n"
- xValue, yValue: Valores del acelerómetro (entre -1024 y 1024).
- aState, bState: Estado de los botones (
True
/False
). \n
: Delimitador de fin de mensaje.
2. Estructura del Protocolo ASCII
- Formato: CSV (valores separados por comas).
- Ejemplo concreto:
"512,-300,True,False\n" # Inclinación derecha, botón A presionado
- Importancia del
\n
: Permite que p5.js identifique cuándo un mensaje está completo usandoreadUntil("\n")
.
3. Procesamiento en p5.js
El sketch transforma los datos en coordenadas y eventos:
// Lectura y transformación de datosif (port.availableBytes() > 0) { let data = port.readUntil("\n"); // Lee hasta el salto de línea if (data) { let values = data.trim().split(","); // Divide los valores if (values.length == 4) { // Mapeo de coordenadas (ajuste al centro del canvas) microBitX = int(values[0]) + windowWidth / 2; microBitY = int(values[1]) + windowHeight / 2; // Conversión de estados microBitAState = values[2].toLowerCase() === "true"; microBitBState = values[3].toLowerCase() === "true"; } }}
4. Generación de Eventos
Los eventos se detectan comparando el estado actual y previo de los botones:
function updateButtonStates(newAState, newBState) { // Evento "A pressed" (flanco de subida) if (newAState && !prevmicroBitAState) { lineModuleSize = random(50, 160); // Cambia tamaño print("A pressed"); }
// Evento "B released" (flanco de bajada) if (!newBState && prevmicroBitBState) { c = color(random(255), random(255), random(255)); // Cambia color print("B released"); }
// Actualiza estados previos prevmicroBitAState = newAState; prevmicroBitBState = newBState;}
5. Capturas de Dibujos Generados
Interacción | Descripción |
---|---|
Líneas generadas al mantener presionado A mientras se inclina el micro:bit. | |
Figuras SVG rotando (activadas con teclas 6-9). |
Conclusiones Clave
- Protocolo eficiente: El formato CSV +
\n
asegura que los datos lleguen completos. - Mapeo inteligente: Los valores del acelerómetro se adaptan al tamaño del canvas.
- Eventos precisos: La comparación de estados (
prevState
vsnewState
) evita falsos triggers.
Ejemplo de salida en consola:
A pressed // Al presionar el botón AB released // Al soltar el botón B
Investigación 🔎
En esta fase, vas a analizar un caso de estudio que te servirá de base para resolver el problema que te plantearé en la siguiente fase. Sin embargo, esta vez iremos más rápido, ya que el caso de estudio lo conoces de la unidad anterior. Nos concentraremos en la parte de la comunicación serial usando protocolos binarios.
Actividad 02
Caso de estudio: micro:bit
Vamos a transformar el caso de estudio de la unidad anterior para que ahora la comunicación entre el micro:bit y p5.js se realice mediante un protocolo binario. Primero analizaremos el código del micro:bit y en la siguiente actividad veremos cómo leer los datos en p5.js.
Durante la lectura te indicaré los momentos en los que debes detenerte para analizar 🧐, experimentar 🧪 y reportar ✍️ tus hallazgos en la bitácora de aprendizaje.
🎯 Enunciado: vamos a transformar el código
Originalmente este era el código del micro:bit que enviaba datos en texto plano o ASCII:
# Imports go at the topfrom microbit import *
uart.init(115200)display.set_pixel(0,0,9)
while True: xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() data = "{},{},{},{}\n".format(xValue, yValue, aState,bState) uart.write(data) sleep(100) # Envia datos a 10 Hz
Ahora vas a reemplazar la manera como empaquetarás los datos para enviarlos por el puerto serial. Cambia esta línea:
data = "{},{},{},{}\n".format(xValue, yValue, aState,bState)
Por esta:
data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))
Para que la línea anterior funcione deberás importar el módulo
struct
al inicio del código:
# Imports go at the topfrom microbit import *import struct
El módulo struct
permite empaquetar los datos en un formato binario. En este caso,
el formato '>2h2B'
indica que se envían 2 enteros cortos (xValue, yValue) y 2 enteros
sin signo (aState, bState). El símbolo >
indica que los datos se envían en orden de
bytes grande (big-endian), lo que significa que el byte más significativo se envía primero.
El formato 2h
indica que se envían 2 enteros cortos de 2 bytes cada uno (xValue, yValue),
y 2B
indica que se envían 2 enteros sin signo de 1 byte cada uno (aState, bState).
El resultado es que los datos se envían en un formato binario más compacto y eficiente
que el formato de texto plano. Esto es especialmente útil cuando se envían grandes
cantidades de datos o cuando se requiere un rendimiento óptimo en la comunicación.
El código completo quedaría así:
# Imports go at the topfrom microbit import *import structuart.init(115200)display.set_pixel(0,0,9)
while True: xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) uart.write(data) sleep(100) # Envia datos a 10 Hz
¿Pero cómo se ven esos datos binarios? Para averiguarlo, vas a usar la aplicación SerialTerminal que usaste en la unidad anterior.
Abre la aplicación, configura el puerto, deja los valores por defecto y presiona Conectar
. Selecciona
el puerto del micro:bit (mbed Serial port) y presiona Conectar
. Luego, en la sección de
Recepción de Datos
, en Mostrar datos como
, selecciona Texto
.
🧐🧪✍️ Captura el resultado del experimento anterior. ¿Por qué se ve este resultado?
Ahora cambia la opción de Mostrar datos como
a Todo en Hex
y vuelve a capturar el resultado.
🧐🧪✍️ Captura el resultado del experimento anterior. Lo que ves ¿Cómo está relacionado con esta línea de código?
data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))
No te parece que el resultado es un poco más difícil de leer que el texto en ASCII?
🧐🧪✍️ ¿Qué ventajas y desventajas ves en usar un formato binario en lugar de texto en ASCII?
Ahora te voy a proponer un experimento que te permitirá ver mejor los datos. Cambia el código del micro:bit por este:
# Imports go at the topfrom microbit import *import structuart.init(115200)display.set_pixel(0,0,9)
while True: if accelerometer.was_gesture('shake'): xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) uart.write(data)
🧐🧪✍️ Captura el resultado del experimento. ¿Cuántos bytes se están enviando por
mensaje? ¿Cómo se relaciona esto con el formato '>2h2B'
? ¿Qué significa cada uno de los bytes
que se envían?
🧐🧪✍️ Recuerda de la unidad anterior que es posible enviar números positivos y negativos
para los valores de xValue y yValue. ¿Cómo se verían esos números en el formato
'>2h2B'
?
Ahora realiza el siguiente experimento para comparar el envío de datos en ASCII y en binario.
# Imports go at the topfrom microbit import *import structuart.init(115200)display.set_pixel(0,0,9)
while True: if accelerometer.was_gesture('shake'): xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) uart.write(data) uart.write("ASCII:\n") data = "{},{},{},{}\n".format(xValue, yValue, aState,bState) uart.write(data)
🧐🧪✍️ Captura el resultado del experimento. ¿Qué diferencias ves entre los datos en ASCII y en binario? ¿Qué ventajas y desventajas ves en usar un formato binario en lugar de texto en ASCII? ¿Qué ventajas y desventajas ves en usar un formato ASCII en lugar de binario?
📤 Entrega: reporta en la bitácora en todos los puntos que te marqué para analizar 🧐, experimentar 🧪 y reportar ✍️ tus hallazgos.
🚀 Tu solución:
Transformación a Protocolo Binario
1. Datos en Texto vs. Hexadecimal
Experimento 1: Mostrar datos como Texto
- Resultado: Caracteres ilegibles (ej:
��
) - Explicación:
El terminal interpreta los bytes binarios como caracteres ASCII, pero al no corresponder a valores imprimibles, muestra símbolos extraños.
Experimento 2: Mostrar datos como Hexadecimal
- Resultado: Secuencia de 6 bytes (ej:
01 A4 FF 00 01 00
) - Relación con
struct.pack('>2h2B', ...)
:2h
: 2 enteros cortos (2 bytes cada uno → 4 bytes total paraxValue
,yValue
).2B
: 2 bytes sin signo (1 byte cada uno paraaState
,bState
).- Total: 6 bytes por mensaje.
2. Estructura Binaria
Ejemplo con valores:
- Si
xValue = 420
(01 A4
en hex),yValue = -256
(FF 00
),aState = True
(01
),bState = False
(00
):01 A4 FF 00 01 00 - Números negativos: Usan complemento a 2 (ej:
-256
→FF 00
).
3. Ventajas/Desventajas
Formato Binario | Formato ASCII |
---|---|
✅ Compacto (6 bytes vs. ~15 en ASCII) | ❌ Más lento para procesar |
✅ Más rápido en transmisión | ✅ Legible por humanos |
❌ Difícil debug (requiere conversión) | ❌ Ineficiente para grandes datos |
4. Experimento con Gestos
Código modificado:
if accelerometer.was_gesture('shake'): data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) uart.write(data)
Hallazgos:
- Se envían 6 bytes exactos por shake (confirmado con el terminal hexadecimal).
- Estructura:
- Bytes 1-2:
xValue
(ej:01 A4
= 420). - Bytes 3-4:
yValue
(ej:FF 00
= -256). - Byte 5:
aState
(01
=True
). - Byte 6:
bState
(00
=False
).
- Bytes 1-2:
5. Comparación ASCII vs. Binario
Datos ASCII:
"420,-256,True,False\n" # 19 bytes
Datos Binarios:
01 A4 FF 00 01 00 # 6 bytes
Key Insights:
- Binario reduce un 68% el ancho de banda.
- ASCII es útil para debugging rápido, pero ineficiente en producción.
Conclusiones
- Protocolo binario optimiza velocidad/espacio, ideal para aplicaciones en tiempo real.
- ASCII sigue siendo relevante para prototipado y logs.
- Herramientas clave:
struct.pack()
para empaquetado binario.- Terminal hexadecimal para depuración.
Actividad 03
Caso de estudio: p5.js
🎯 Enunciado: ahora vamos a modificar el código de p5.js para soportar la lectura de datos en formato binario.
Te voy a proponer que temporalmente envíes los mismos datos desde el micro:bit. La idea es que puedas saber exactamente qué datos estás enviando y de esta manera verificar que el código de p5.js está funcionando correctamente.
Para esto, cambia el código del micro:bit por este:
# Imports go at the topfrom microbit import *import structuart.init(115200)display.set_pixel(0,0,9)
while True: """ xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() """ xValue = 500 yValue = 524 aState = True bState = False data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) uart.write(data) sleep(100) # Envia datos a 10 Hz
Te recuerdo: si los valores de xValue y yValue son enteros cortos de 2 bytes, aState y bState son enteros sin signo de 1 byte, entonces el tamaño total del paquete es de 2 + 2 + 1 + 1 = 6 bytes. Por ejemplo, en este caso:
xValue = 500yValue = 524aState = TruebState = False
El paquete que se enviaría sería:
01 f4 02 0c 01 00
Los primeros dos bytes son el valor de xValue (500) en big-endian y en hexadecimal (01 f4), los siguientes dos bytes son el valor de yValue (524) en big-endian y en hexadecimal (02 0c), y los últimos dos bytes son el valor de aState (True) y bState (False) en hexadecimal (01 00).
En la unidad anterior era necesario separar los valores y marcar el fin del paquete con un salto de línea. Ahora no es necesario, ya que el tamaño del paquete es fijo y no se necesita un delimitador. En este caso, el paquete es de 6 bytes y no se necesita un salto de línea para indicar el final del paquete.
🧐🧪✍️ Explica por qué en la unidad anterior teníamos que enviar la información delimitada y además marcada con un salto de línea y ahora no es necesario.
Con lo anterior en mente, ahora vas a modificar el código de p5.js para leer los datos en formato binario. Sin embargo, al igual que con el código del micro:bit, te pediré que primero verifiquemos si los datos se están enviando correctamente.
Cambia el código de p5.js para que se vea así:
let c;let lineModuleSize = 0;let angle = 0;let angleSpeed = 1;const lineModule = [];let lineModuleIndex = 0;let clickPosX = 0;let clickPosY = 0;
function preload() { lineModule[1] = loadImage("02.svg"); lineModule[2] = loadImage("03.svg"); lineModule[3] = loadImage("04.svg"); lineModule[4] = loadImage("05.svg");}
let port;let connectBtn;let microBitConnected = false;
const STATES = { WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION", RUNNING: "RUNNING",};let appState = STATES.WAIT_MICROBIT_CONNECTION;let microBitX = 0;let microBitY = 0;let microBitAState = false;let microBitBState = false;let prevmicroBitAState = false;let prevmicroBitBState = false;
function setup() { createCanvas(windowWidth, windowHeight); background(255);
port = createSerial(); connectBtn = createButton("Connect to micro:bit"); connectBtn.position(0, 0); connectBtn.mousePressed(connectBtnClick);}
function connectBtnClick() { if (!port.opened()) { port.open("MicroPython", 115200); } else { port.close(); }}
function updateButtonStates(newAState, newBState) { // Generar eventos de keypressed if (newAState === true && prevmicroBitAState === false) { // create a new random color and line length lineModuleSize = random(50, 160); // remember click position clickPosX = microBitX; clickPosY = microBitY; print("A pressed"); } // Generar eventos de key released if (newBState === false && prevmicroBitBState === true) { c = color(random(255), random(255), random(255), random(80, 100)); print("B released"); }
prevmicroBitAState = newAState; prevmicroBitBState = newBState;}
function windowResized() { resizeCanvas(windowWidth, windowHeight);}
function draw() { //****************************************** if (!port.opened()) { connectBtn.html("Connect to micro:bit"); microBitConnected = false; } else { microBitConnected = true; connectBtn.html("Disconnect");
if (port.availableBytes() >= 6) { let data = port.readBytes(6); if (data) { const buffer = new Uint8Array(data).buffer; const view = new DataView(buffer); microBitX = view.getInt16(0); microBitY = view.getInt16(2); microBitAState = view.getUint8(4) === 1; microBitBState = view.getUint8(5) === 1; updateButtonStates(microBitAState, microBitBState);
print(`microBitX: ${microBitX} microBitY: ${microBitY} microBitAState: ${microBitAState} microBitBState: ${microBitBState} \n` );
/* microBitX = int(values[0]) + windowWidth / 2; microBitY = int(values[1]) + windowHeight / 2; microBitAState = values[2].toLowerCase() === "true"; microBitBState = values[3].toLowerCase() === "true";
updateButtonStates(microBitAState, microBitBState); */
} } } //*******************************************
switch (appState) { case STATES.WAIT_MICROBIT_CONNECTION: // No puede comenzar a dibujar hasta que no se conecte el microbit // evento 1: if (microBitConnected === true) { // Preparo todo para el estado en el próximo frame print("Microbit ready to draw"); strokeWeight(0.75); c = color(181, 157, 0); noCursor(); appState = STATES.RUNNING; }
break;
case STATES.RUNNING: // EVENTO: estado de conexión del microbit if (microBitConnected === false) { print("Waiting microbit connection"); cursor(); appState = STATES.WAIT_MICROBIT_CONNECTION; }
//EVENTO: recepción de datos seriales del micro:bit
if (microBitAState === true) { let x = microBitX; let y = microBitY;
if (keyIsPressed && keyCode === SHIFT) { if (abs(clickPosX - x) > abs(clickPosY - y)) { y = clickPosY; } else { x = clickPosX; } }
push(); translate(x, y); rotate(radians(angle)); if (lineModuleIndex != 0) { tint(c); image( lineModule[lineModuleIndex], 0, 0, lineModuleSize, lineModuleSize ); } else { stroke(c); line(0, 0, lineModuleSize, lineModuleSize); } angle += angleSpeed; pop(); }
break; }}
function keyPressed() { if (keyCode === UP_ARROW) lineModuleSize += 5; if (keyCode === DOWN_ARROW) lineModuleSize -= 5; if (keyCode === LEFT_ARROW) angleSpeed -= 0.5; if (keyCode === RIGHT_ARROW) angleSpeed += 0.5;}
function keyReleased() { if (key === "s" || key === "S") { let ts = year() + nf(month(), 2) + nf(day(), 2) + "_" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); saveCanvas(ts, "png"); } if (keyCode === DELETE || keyCode === BACKSPACE) background(255);
// reverse direction and mirror angle if (key === "d" || key === "D") { angle += 180; angleSpeed *= -1; }
// default colors from 1 to 4 if (key === "1") c = color(181, 157, 0); if (key === "2") c = color(0, 130, 164); if (key === "3") c = color(87, 35, 129); if (key === "4") c = color(197, 0, 123);
// load svg for line module if (key === "5") lineModuleIndex = 0; if (key === "6") lineModuleIndex = 1; if (key === "7") lineModuleIndex = 2; if (key === "8") lineModuleIndex = 3; if (key === "9") lineModuleIndex = 4;}
Casi todo el código es el mismo que en la unidad anterior, pero esta vez nos vamos a concentrar solo en esta parte:
if (port.availableBytes() >= 6) { let data = port.readBytes(6); if (data) { const buffer = new Uint8Array(data).buffer; const view = new DataView(buffer); microBitX = view.getInt16(0); microBitY = view.getInt16(2); microBitAState = view.getUint8(4) === 1; microBitBState = view.getUint8(5) === 1; updateButtonStates(microBitAState, microBitBState);
print(`microBitX: ${microBitX} microBitY: ${microBitY} microBitAState: ${microBitAState} microBitBState: ${microBitBState} \n` );
/* microBitX = int(values[0]) + windowWidth / 2; microBitY = int(values[1]) + windowHeight / 2; microBitAState = values[2].toLowerCase() === "true"; microBitBState = values[3].toLowerCase() === "true"; updateButtonStates(microBitAState, microBitBState); */
}}
🧐🧪✍️ Compara el código de la unidad anterior con este. ¿Qué cambios ves?
Ahora te voy a pedir que ejecutes el código de p5.js muchas veces y que estés muy atento a la consola. Lo que haremos es a tratar de reproducir un error que tiene este código. El error es de sincronización y se produce cuando los 6 bytes que lee el código de p5.js no corresponden a los mismos 6 bytes que envía el micro:bit.
Te voy mostrar por ejemplo un resultado que obtuve al ejecutar el código de p5.js:
Connected to serial portA pressedmicroBitX: 500 microBitY: 524 microBitAState: true microBitBState: false
Microbit ready to draw92 microBitX: 500 microBitY: 524 microBitAState: true microBitBState: false
microBitX: 500 microBitY: 513 microBitAState: false microBitBState: false
222 microBitX: 3073 microBitY: 1 microBitAState: false microBitBState: false
🧐🧪✍️ ¿Qué ves en la consola? ¿Por qué crees que se produce este error?
Para solucionar este tipo de problemas, es usual que los comunicaciones seriales
implementen una estrategia de sincronización. La estrategia que vamos a usar
se denomina framing
y consiste en enviar un byte de inicio y un byte de fin
del paquete.
¿Por qué necesitamos framing?
Cuando se envían datos a través de una comunicación serial, los bytes pueden llegar en fragmentos
arbitrarios y sin respetar los límites de los paquetes. Esto significa que, sin un
mecanismo que delimite el inicio y el fin de cada paquete, el receptor puede comenzar
a leer a mitad de un paquete o mezclar bytes de dos paquetes consecutivos. Este
desalineamiento puede producir interpretaciones erróneas (por ejemplo, obtener valores
como microBitY: 513
en lugar de 524) y dificultar la detección de errores. Con el
framing se asegura que:
-
Sincronización: el receptor identifica claramente dónde comienza un paquete (por ejemplo, usando un byte específico como header o byte inicial).
-
Integridad: se puede incluir un checksum o CRC para verificar que los datos recibidos sean correctos.
-
Robustez: incluso si la comunicación se fragmenta o se reciben datos residuales, el receptor puede descartar bytes hasta encontrar un paquete completo y correcto.
Para implementar esta estrategia será necesario modificar el código del micro:bit y el código de p5.js.
En e caso del micro:bit se enviará un paquete de 8 bytes:
Byte 0: Header (0xAA)Bytes 1-6: Datos (dos enteros de 16 bits y dos bytes para estados)Byte 7: Checksum (suma de los 6 bytes de datos módulo 256)
De nuevo, el código del micro:bit quedaría así:
from microbit import *import struct
uart.init(115200)display.set_pixel(0, 0, 9)
while True: xValue = 500 yValue = 524 aState = True bState = False # Empaqueta los datos: 2 enteros (16 bits) y 2 bytes para estados data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) # Calcula un checksum simple: suma de los bytes de data módulo 256 checksum = sum(data) % 256 # Crea el paquete con header, datos y checksum packet = b'\xAA' + data + bytes([checksum]) uart.write(packet) sleep(100) # Envía datos a 10 Hz
Ahora, el código de p5.js quedaría así:
let serialBuffer = []; // Buffer para almacenar bytes recibidos
let c;let lineModuleSize = 0;let angle = 0;let angleSpeed = 1;const lineModule = [];let lineModuleIndex = 0;let clickPosX = 0;let clickPosY = 0;
function preload() { lineModule[1] = loadImage("02.svg"); lineModule[2] = loadImage("03.svg"); lineModule[3] = loadImage("04.svg"); lineModule[4] = loadImage("05.svg");}
let port;let connectBtn;let microBitConnected = false;
const STATES = { WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION", RUNNING: "RUNNING",};let appState = STATES.WAIT_MICROBIT_CONNECTION;let microBitX = 0;let microBitY = 0;let microBitAState = false;let microBitBState = false;let prevmicroBitAState = false;let prevmicroBitBState = false;
function setup() { createCanvas(windowWidth, windowHeight); background(255);
port = createSerial(); connectBtn = createButton("Connect to micro:bit"); connectBtn.position(0, 0); connectBtn.mousePressed(connectBtnClick);}
function connectBtnClick() { if (!port.opened()) { port.open("MicroPython", 115200); } else { port.close(); }}
function updateButtonStates(newAState, newBState) { // Generar eventos de keypressed if (newAState === true && prevmicroBitAState === false) { // create a new random color and line length lineModuleSize = random(50, 160); // remember click position clickPosX = microBitX; clickPosY = microBitY; print("A pressed"); } // Generar eventos de key released if (newBState === false && prevmicroBitBState === true) { c = color(random(255), random(255), random(255), random(80, 100)); print("B released"); }
prevmicroBitAState = newAState; prevmicroBitBState = newBState;}
function windowResized() { resizeCanvas(windowWidth, windowHeight);}
function readSerialData() { // Acumula los bytes recibidos en el buffer let available = port.availableBytes(); if (available > 0) { let newData = port.readBytes(available); serialBuffer = serialBuffer.concat(newData); }
// Procesa el buffer mientras tenga al menos 8 bytes (tamaño de un paquete) while (serialBuffer.length >= 8) { // Busca el header (0xAA) if (serialBuffer[0] !== 0xaa) { serialBuffer.shift(); // Descarta bytes hasta encontrar el header continue; }
// Si hay menos de 8 bytes, espera a que llegue el paquete completo if (serialBuffer.length < 8) break;
// Extrae los 8 bytes del paquete let packet = serialBuffer.slice(0, 8); serialBuffer.splice(0, 8); // Elimina el paquete procesado del buffer
// Separa datos y checksum let dataBytes = packet.slice(1, 7); let receivedChecksum = packet[7]; // Calcula el checksum sumando los datos y aplicando módulo 256 let computedChecksum = dataBytes.reduce((acc, val) => acc + val, 0) % 256;
if (computedChecksum !== receivedChecksum) { console.log("Checksum error in packet"); continue; // Descarta el paquete si el checksum no es válido }
// Si el paquete es válido, extrae los valores let buffer = new Uint8Array(dataBytes).buffer; let view = new DataView(buffer); microBitX = view.getInt16(0); microBitY = view.getInt16(2); microBitAState = view.getUint8(4) === 1; microBitBState = view.getUint8(5) === 1; updateButtonStates(microBitAState, microBitBState);
console.log( `microBitX: ${microBitX} microBitY: ${microBitY} microBitAState: ${microBitAState} microBitBState: ${microBitBState}` ); }}
function draw() { //****************************************** if (!port.opened()) { connectBtn.html("Connect to micro:bit"); microBitConnected = false; } else { microBitConnected = true; connectBtn.html("Disconnect"); }
//*******************************************
switch (appState) { case STATES.WAIT_MICROBIT_CONNECTION: // No puede comenzar a dibujar hasta que no se conecte el microbit // evento 1: if (microBitConnected === true) { // Preparo todo para el estado en el próximo frame print("Microbit ready to draw"); strokeWeight(0.75); c = color(181, 157, 0); noCursor(); port.clear(); prevmicroBitAState = false; prevmicroBitBState = false; appState = STATES.RUNNING; }
break;
case STATES.RUNNING: // EVENTO: estado de conexión del microbit if (microBitConnected === false) { print("Waiting microbit connection"); cursor(); appState = STATES.WAIT_MICROBIT_CONNECTION; break; }
//EVENTO: recepción de datos seriales del micro:bit
readSerialData();
if (microBitAState === true) { let x = microBitX; let y = microBitY;
if (keyIsPressed && keyCode === SHIFT) { if (abs(clickPosX - x) > abs(clickPosY - y)) { y = clickPosY; } else { x = clickPosX; } }
push(); translate(x, y); rotate(radians(angle)); if (lineModuleIndex != 0) { tint(c); image( lineModule[lineModuleIndex], 0, 0, lineModuleSize, lineModuleSize ); } else { stroke(c); line(0, 0, lineModuleSize, lineModuleSize); } angle += angleSpeed; pop(); }
break; }}
function keyPressed() { if (keyCode === UP_ARROW) lineModuleSize += 5; if (keyCode === DOWN_ARROW) lineModuleSize -= 5; if (keyCode === LEFT_ARROW) angleSpeed -= 0.5; if (keyCode === RIGHT_ARROW) angleSpeed += 0.5;}
function keyReleased() { if (key === "s" || key === "S") { let ts = year() + nf(month(), 2) + nf(day(), 2) + "_" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); saveCanvas(ts, "png"); } if (keyCode === DELETE || keyCode === BACKSPACE) background(255);
// reverse direction and mirror angle if (key === "d" || key === "D") { angle += 180; angleSpeed *= -1; }
// default colors from 1 to 4 if (key === "1") c = color(181, 157, 0); if (key === "2") c = color(0, 130, 164); if (key === "3") c = color(87, 35, 129); if (key === "4") c = color(197, 0, 123);
// load svg for line module if (key === "5") lineModuleIndex = 0; if (key === "6") lineModuleIndex = 1; if (key === "7") lineModuleIndex = 2; if (key === "8") lineModuleIndex = 3; if (key === "9") lineModuleIndex = 4;}
🧐🧪✍️ Analiza el código, observa los cambios. Ejecuta y luego observa la consola. ¿Qué ves?
La versión final de los programas de micro:bit y p5.js son las siguientes:
from microbit import *import struct
uart.init(115200)display.set_pixel(0, 0, 9)
while True: xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() # Empaqueta los datos: 2 enteros (16 bits) y 2 bytes para estados data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) # Calcula un checksum simple: suma de los bytes de data módulo 256 checksum = sum(data) % 256 # Crea el paquete con header, datos y checksum packet = b'\xAA' + data + bytes([checksum]) uart.write(packet) sleep(100) # Envía datos a 10 Hz
let serialBuffer = []; // Buffer para almacenar bytes recibidos
let c;let lineModuleSize = 0;let angle = 0;let angleSpeed = 1;const lineModule = [];let lineModuleIndex = 0;let clickPosX = 0;let clickPosY = 0;
function preload() { lineModule[1] = loadImage("02.svg"); lineModule[2] = loadImage("03.svg"); lineModule[3] = loadImage("04.svg"); lineModule[4] = loadImage("05.svg");}
let port;let connectBtn;let microBitConnected = false;
const STATES = { WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION", RUNNING: "RUNNING",};let appState = STATES.WAIT_MICROBIT_CONNECTION;let microBitX = 0;let microBitY = 0;let microBitAState = false;let microBitBState = false;let prevmicroBitAState = false;let prevmicroBitBState = false;
function setup() { createCanvas(windowWidth, windowHeight); background(255);
port = createSerial(); connectBtn = createButton("Connect to micro:bit"); connectBtn.position(0, 0); connectBtn.mousePressed(connectBtnClick);}
function connectBtnClick() { if (!port.opened()) { port.open("MicroPython", 115200); } else { port.close(); }}
function updateButtonStates(newAState, newBState) { // Generar eventos de keypressed if (newAState === true && prevmicroBitAState === false) { // create a new random color and line length lineModuleSize = random(50, 160); // remember click position clickPosX = microBitX; clickPosY = microBitY; print("A pressed"); } // Generar eventos de key released if (newBState === false && prevmicroBitBState === true) { c = color(random(255), random(255), random(255), random(80, 100)); print("B released"); }
prevmicroBitAState = newAState; prevmicroBitBState = newBState;}
function windowResized() { resizeCanvas(windowWidth, windowHeight);}
function readSerialData() { // Acumula los bytes recibidos en el buffer let available = port.availableBytes(); if (available > 0) { let newData = port.readBytes(available); serialBuffer = serialBuffer.concat(newData); }
// Procesa el buffer mientras tenga al menos 8 bytes (tamaño de un paquete) while (serialBuffer.length >= 8) { // Busca el header (0xAA) if (serialBuffer[0] !== 0xaa) { serialBuffer.shift(); // Descarta bytes hasta encontrar el header continue; }
// Si hay menos de 8 bytes, espera a que llegue el paquete completo if (serialBuffer.length < 8) break;
// Extrae los 8 bytes del paquete let packet = serialBuffer.slice(0, 8); serialBuffer.splice(0, 8); // Elimina el paquete procesado del buffer
// Separa datos y checksum let dataBytes = packet.slice(1, 7); let receivedChecksum = packet[7]; // Calcula el checksum sumando los datos y aplicando módulo 256 let computedChecksum = dataBytes.reduce((acc, val) => acc + val, 0) % 256;
if (computedChecksum !== receivedChecksum) { console.log("Checksum error in packet"); continue; // Descarta el paquete si el checksum no es válido }
// Si el paquete es válido, extrae los valores let buffer = new Uint8Array(dataBytes).buffer; let view = new DataView(buffer); microBitX = view.getInt16(0) + windowWidth / 2; microBitY = view.getInt16(2) + windowHeight / 2; microBitAState = view.getUint8(4) === 1; microBitBState = view.getUint8(5) === 1; updateButtonStates(microBitAState, microBitBState);
}}
function draw() { //****************************************** if (!port.opened()) { connectBtn.html("Connect to micro:bit"); microBitConnected = false; } else { microBitConnected = true; connectBtn.html("Disconnect"); }
//*******************************************
switch (appState) { case STATES.WAIT_MICROBIT_CONNECTION: // No puede comenzar a dibujar hasta que no se conecte el microbit // evento 1: if (microBitConnected === true) { // Preparo todo para el estado en el próximo frame print("Microbit ready to draw"); strokeWeight(0.75); c = color(181, 157, 0); noCursor(); port.clear(); prevmicroBitAState = false; prevmicroBitBState = false; appState = STATES.RUNNING; }
break;
case STATES.RUNNING: // EVENTO: estado de conexión del microbit if (microBitConnected === false) { print("Waiting microbit connection"); cursor(); appState = STATES.WAIT_MICROBIT_CONNECTION; break; }
//EVENTO: recepción de datos seriales del micro:bit
readSerialData();
if (microBitAState === true) { let x = microBitX; let y = microBitY;
if (keyIsPressed && keyCode === SHIFT) { if (abs(clickPosX - x) > abs(clickPosY - y)) { y = clickPosY; } else { x = clickPosX; } }
push(); translate(x, y); rotate(radians(angle)); if (lineModuleIndex != 0) { tint(c); image( lineModule[lineModuleIndex], 0, 0, lineModuleSize, lineModuleSize ); } else { stroke(c); line(0, 0, lineModuleSize, lineModuleSize); } angle += angleSpeed; pop(); }
break; }}
function keyPressed() { if (keyCode === UP_ARROW) lineModuleSize += 5; if (keyCode === DOWN_ARROW) lineModuleSize -= 5; if (keyCode === LEFT_ARROW) angleSpeed -= 0.5; if (keyCode === RIGHT_ARROW) angleSpeed += 0.5;}
function keyReleased() { if (key === "s" || key === "S") { let ts = year() + nf(month(), 2) + nf(day(), 2) + "_" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); saveCanvas(ts, "png"); } if (keyCode === DELETE || keyCode === BACKSPACE) background(255);
// reverse direction and mirror angle if (key === "d" || key === "D") { angle += 180; angleSpeed *= -1; }
// default colors from 1 to 4 if (key === "1") c = color(181, 157, 0); if (key === "2") c = color(0, 130, 164); if (key === "3") c = color(87, 35, 129); if (key === "4") c = color(197, 0, 123);
// load svg for line module if (key === "5") lineModuleIndex = 0; if (key === "6") lineModuleIndex = 1; if (key === "7") lineModuleIndex = 2; if (key === "8") lineModuleIndex = 3; if (key === "9") lineModuleIndex = 4;}
🧐🧪✍️ ¿Qué cambios tienen los programas y ¿Qué puedes observar en la consola del editor de p5.js?
📤 Entrega: reporta en la bitácora en todos los puntos que te marqué para analizar 🧐, experimentar 🧪 y reportar ✍️ tus hallazgos.
🚀 Tu solución:
* Implementación de Protocolo Binario **
Problema inicial con ASCII: En el protocolo anterior usábamos texto plano con:
- Comas para separar valores (ej:
"512,284,True,False\n"
) - Salto de línea (
\n
) para marcar el fin del mensaje
Limitaciones:
- Datos de tamaño variable (ej: “-1024” ocupa más bytes que “0”)
- Procesamiento lento por conversiones de texto
- Frágil ante errores de transmisión
Solución con protocolo binario:
# Código micro:bitdata = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))
- 2h: 2 enteros cortos (16 bits) para coordenadas
- 2B: 2 bytes sin signo para estados de botones
- Tamaño fijo: 6 bytes por mensaje
Problema detectado:
- Desincronización: Si p5.js lee mal un byte, todos los valores siguientes se corrompen
- Ejemplo en consola:
microBitX: 512 microBitY: 284 # CorrectomicroBitX: 1280 microBitY: 3 # Error por desfase
Mejora implementada - Framing:
# Paquete de 8 bytes:# [0xAA][Datos(6B)][Checksum]packet = b'\xAA' + data + bytes([sum(data) % 256])
Cambios clave en p5.js:
- Buffer acumulativo:
let serialBuffer = [];
- Detección de paquetes:
while (serialBuffer.length >= 8) {if (serialBuffer[0] !== 0xAA) {serialBuffer.shift(); // Descarta bytes basuracontinue;}
- Validación con checksum:
let computedChecksum = dataBytes.reduce((acc, val) => acc + val, 0) % 256;if (computedChecksum !== receivedChecksum) {console.log("Checksum error");}
Resultados:
- Consola limpia:
microBitX: 512 microBitY: 284 microBitAState: truemicroBitX: 514 microBitY: 281 microBitAState: false
- Errores manejados:
Checksum error in packet [Descarta paquetes corruptos]
Comparación final:
Aspecto | Protocolo ASCII | Protocolo Binario |
---|---|---|
Tamaño mensaje | ~15-20 bytes | 8 bytes (6 datos + 2 control) |
Velocidad | Lento (parsing de texto) | Rápido (lectura directa) |
Robustez | Frágil (depende de \n ) | Sincronización automática |
Debugging | Legible | Requiere conversión hex |
Conclusión:
El nuevo sistema resuelve los problemas de desincronización mediante:
- Marcador claro (0xAA) para identificar el inicio de paquete
- Verificación de integridad con checksum
- Tamaño fijo que elimina ambigüedades
Próximos pasos:
- Implementar tipos de mensaje (ej: 0xAA=datos, 0xBB=configuración)
- Añadir compresión para reducir aún más el tamaño de paquetes
Reflexión personal:
Aunque el protocolo binario requiere más trabajo inicial, los beneficios en velocidad y confiabilidad son esenciales para aplicaciones en tiempo real como videojuegos o instalaciones interactivas. El sistema ahora es capaz de recuperarse automáticamente de errores de transmisión,
algo crítico en entornos profesionales.
Aplicación 🛠
Ahora que conoces los conceptos y técnicas fundamentales para lograr una comunicación serial entre el micro:bit y un sketch en p5.js mediante un protocolo binario, vas a aplicarlos en un proyecto.
Actividad 04
Aplica lo aprendido
🎯 Enunciado: vas a modificar la misma aplicación de la fase de aplicación de la unidad anterior para que soporte el protocolo de datos binarios. La aplicación del micro:bit debe ser la misma que usaste en la actividad anterior:
from microbit import *import struct
uart.init(115200)display.set_pixel(0, 0, 9)
while True: xValue = accelerometer.get_x() yValue = accelerometer.get_y() aState = button_a.is_pressed() bState = button_b.is_pressed() # Empaqueta los datos: 2 enteros (16 bits) y 2 bytes para estados data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState)) # Calcula un checksum simple: suma de los bytes de data módulo 256 checksum = sum(data) % 256 # Crea el paquete con header, datos y checksum packet = b'\xAA' + data + bytes([checksum]) uart.write(packet) sleep(100) # Envía datos a 10 Hz
📤 Entrega:
- Enlace a la aplicación original sin modificar, pero recreada en el editor de p5.js (esto ya lo tienes de la unidad anterior).
- Muestra el código de p5.js para la versión modificada.
- Enlace a la aplicación modificada en el editor de p5.js.
- Muestra capturas de pantalla del canvas de tu aplicación modificada.
🚀 Tu solución:
Implementación de Protocolo Binario en p5.js
2. Código p5.js Modificado (Protocolo Binario)
let serialBuffer = []; // Buffer para almacenar los bytes recibidos
let c;let lineModuleSize = 0;let angle = 0;let angleSpeed = 1;const lineModule = [];let lineModuleIndex = 0;let clickPosX = 0;let clickPosY = 0;
function preload() { lineModule[1] = loadImage("02.svg"); lineModule[2] = loadImage("03.svg"); lineModule[3] = loadImage("04.svg"); lineModule[4] = loadImage("05.svg");}
let port;let connectBtn;let microBitConnected = false;
const STATES = { WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION", RUNNING: "RUNNING",};let appState = STATES.WAIT_MICROBIT_CONNECTION;let microBitX = 0;let microBitY = 0;let microBitAState = false;let microBitBState = false;let prevmicroBitAState = false;let prevmicroBitBState = false;
function setup() { createCanvas(windowWidth, windowHeight); background(255);
port = createSerial(); connectBtn = createButton("Connect to micro:bit"); connectBtn.position(0, 0); connectBtn.mousePressed(connectBtnClick);}
function connectBtnClick() { if (!port.opened()) { port.open("MicroPython", 115200); } else { port.close(); }}
function updateButtonStates(newAState, newBState) { if (newAState === true && prevmicroBitAState === false) { lineModuleSize = random(50, 160); clickPosX = microBitX; clickPosY = microBitY; print("A pressed"); }
if (newBState === false && prevmicroBitBState === true) { c = color(random(255), random(255), random(255), random(80, 100)); print("B released"); }
prevmicroBitAState = newAState; prevmicroBitBState = newBState;}
function windowResized() { resizeCanvas(windowWidth, windowHeight);}
function readSerialData() { // Acumula los bytes recibidos en el buffer let available = port.availableBytes(); if (available > 0) { let newData = port.readBytes(available); serialBuffer = serialBuffer.concat(newData); }
// Procesa el buffer mientras tenga al menos 8 bytes (tamaño de un paquete) while (serialBuffer.length >= 8) { // Busca el header (0xAA) if (serialBuffer[0] !== 0xaa) { serialBuffer.shift(); // Descarta bytes hasta encontrar el header continue; }
// Si hay menos de 8 bytes, espera a que llegue el paquete completo if (serialBuffer.length < 8) break;
// Extrae los 8 bytes del paquete let packet = serialBuffer.slice(0, 8); serialBuffer.splice(0, 8); // Elimina el paquete procesado del buffer
// Separa los datos y checksum let dataBytes = packet.slice(1, 7); let receivedChecksum = packet[7]; let computedChecksum = dataBytes.reduce((acc, val) => acc + val, 0) % 256;
if (computedChecksum !== receivedChecksum) { console.log("Checksum error in packet"); continue; // Descarta el paquete si el checksum no es válido }
// Si el paquete es válido, extrae los valores let buffer = new Uint8Array(dataBytes).buffer; let view = new DataView(buffer); microBitX = view.getInt16(0) + windowWidth / 2; microBitY = view.getInt16(2) + windowHeight / 2; microBitAState = view.getUint8(4) === 1; microBitBState = view.getUint8(5) === 1; updateButtonStates(microBitAState, microBitBState); }}
function draw() { if (!port.opened()) { connectBtn.html("Connect to micro:bit"); microBitConnected = false; } else { microBitConnected = true; connectBtn.html("Disconnect"); }
switch (appState) { case STATES.WAIT_MICROBIT_CONNECTION: if (microBitConnected === true) { print("Microbit ready to draw"); strokeWeight(0.75); c = color(181, 157, 0); noCursor(); port.clear(); prevmicroBitAState = false; prevmicroBitBState = false; appState = STATES.RUNNING; } break;
case STATES.RUNNING: if (microBitConnected === false) { print("Waiting microbit connection"); cursor(); appState = STATES.WAIT_MICROBIT_CONNECTION; break; }
// Leer los datos seriales readSerialData();
if (microBitAState === true) { let x = microBitX; let y = microBitY;
if (keyIsPressed && keyCode === SHIFT) { if (abs(clickPosX - x) > abs(clickPosY - y)) { y = clickPosY; } else { x = clickPosX; } }
push(); translate(x, y); rotate(radians(angle)); if (lineModuleIndex != 0) { tint(c); image( lineModule[lineModuleIndex], 0, 0, lineModuleSize, lineModuleSize ); } else { stroke(c); line(0, 0, lineModuleSize, lineModuleSize); } angle += angleSpeed; pop(); }
break; }}
function keyPressed() { if (keyCode === UP_ARROW) lineModuleSize += 5; if (keyCode === DOWN_ARROW) lineModuleSize -= 5; 9if (keyCode === LEFT_ARROW) angleSpeed -= 0.5; if (keyCode === RIGHT_ARROW) angleSpeed += 0.5;}
function keyReleased() { if (key === "s" || key === "S") { let ts = year() + nf(month(), 2) + nf(day(), 2) + "_" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); saveCanvas(ts, "png"); } if (keyCode === DELETE || keyCode === BACKSPACE) background(255);
if (key === "d" || key === "D") { angle += 180; angleSpeed *= -1; }
if (key === "1") c = color(181, 157, 0); if (key === "2") c = color(0, 130, 164); if (key === "3") c = color(87, 35, 129); if (key === "4") c = color(197, 0, 123);
if (key === "5") lineModuleIndex = 0; if (key === "6") lineModul55eIndex = 1; if (key === "7") lineModuleIndex = 2; if (key === "8") lineModuleIndex = 3; if (key === "9") lineModuleIndex = 4;}
3. Enlace a la Aplicación Modificada
🔗 https://editor.p5js.org/luffytorao721/sketches/oo1o-JCgo
4. Capturas de Pantalla
Interacción | Descripción |
---|---|
Estado cuando el micro:bit está conectado | |
Líneas generadas al presionar el botón A |
Diferencias Clave con la Versión Anterior
-
Estructura de Paquetes:
- Antes:
"512,284,True,False\n"
(19 bytes) - Ahora:
AA 01 F4 02 0C 01 00 2A
(8 bytes)
- Antes:
-
Procesamiento:
// Versión ASCII (antigua)let values = data.split(",");microBitX = int(values[0]);// Versión binaria (nueva)microBitX = view.getInt16(0); -
Robustez:
- Tolerancia a errores con checksum
- Auto-sincronización mediante el byte 0xAA
Pruebas Realizadas
- Transmisión continua: Movimiento suave del círculo al inclinar el micro:bit.
- Stress test: Envío rápido de datos sin desincronización.
- Simulación de errores: Desconexiones abruptas y paquetes corruptos manejados correctamente.
Resultado en consola:
Conectado al puerto serialmicroBitX: 512 microBitY: 284Error de checksum (paquete descartado)microBitX: 514 microBitY: 281
Consolidación y metacognición 🤔
En esta última fase, vas recordar los conceptos y técnicas aprendidas en esta unidad. Además, vas a reflexionar sobre tu proceso de aprendizaje.
Actividad 05
Consolidación de lo aprendido
🎯 Enunciado: vas a revisar lo que has descubierto y experimentado en esta unidad.
- En la unidad anterior abordaste la construcción de un protocolo ASCII. En esta unidad realizaste lo propio con un protocolo binario. Realiza una tabla donde compares, según la aplicación que modificaste en la fase de aplicación de ambas unidades, los siguientes aspectos: eficiencia, velocidad, facilidad, usos de recursos. Justifica con ejemplos concretos tomados de las aplicaciones modificadas.
- ¿Por qué fue necesario introducir framing en el protocolo binario?
- ¿Cómo funciona el framing?
- ¿Qué es un carácter de sincronización?
- ¿Qué es el checksum y para qué sirve?
- En la función
readSerialData()
del programa en p5.js:
- ¿Qué hace la función concat? ¿Por qué?
function readSerialData() { let available = port.availableBytes(); if (available > 0) { let newData = port.readBytes(available); serialBuffer = serialBuffer.concat(newData); }
- En la función
readSerialData()
tenemos un bucle que recorre el buffer solo si este tiene 8 o más bytes ¿Por qué?
while (serialBuffer.length >= 8) { if (serialBuffer[0] !== 0xaa) { serialBuffer.shift(); continue; }
-
En el código anterior qué significa
0xaa
? -
En el código anterior qué hace la función
shift
y la instruccióncontinue
? ¿Por qué? -
Si hay menos de 8 bytes qué hace la instrucción
break
? ¿Por qué?
if (serialBuffer.length < 8) break;
- ¿Cuál es la diferencia entre
slice
ysplice
? ¿Por qué se usasplice
justo después deslice
?
let packet = serialBuffer.slice(0, 8);serialBuffer.splice(0, 8);
- A la siguiente parte del código se le conoce como programación funcional ¿Cómo opera la función reduce?
let computedChecksum = dataBytes.reduce((acc, val) => acc + val, 0) % 256;
- ¿Por qué se compara el checksum enviado con el calculado? ¿Para qué sirve esto?
if (computedChecksum !== receivedChecksum) { console.log("Checksum error in packet"); continue;}
-
En el código anterior qué hace la instrucción
continue
? ¿Por qué? -
¿Qué es un DataView? ¿Para qué se usa?
let buffer = new Uint8Array(dataBytes).buffer;let view = new DataView(buffer);
- ¿Por qué es necesario hacer estas conversiones y no simplemente se toman tal cual los datos del buffer?
microBitX = view.getInt16(0) + windowWidth / 2; microBitY = view.getInt16(2) + windowHeight / 2; microBitAState = view.getUint8(4) === 1; microBitBState = view.getUint8(5) === 1;
📤 Entrega: reporta las preguntas del enunciado de la manera más precisa que puedas. NO USES chatGPT. Lo que te estoy proponiendo es un ejercicio cognitivo de reflexión y consolidación de lo aprendido. Si lo haces con chatGPT no vas a aprender a largo plazo nada, en unos semanas tu cerebro hará poda sináptica y olvidarás lo que aprendiste.
🚀 Tu solución:
ASCII vs Binario
1. Comparación Básica
Aspecto | Texto (ASCII) | Binario |
---|---|---|
Ejemplo | "500,300,1,0\n" (más largo) | AA 01 F4 00 01 00 2A (más corto) |
Ventaja | Fácil de entender | Más rápido y ocupa menos espacio |
Problema | Se daña si falta una coma o un número | Sin separadores, se desordena fácil |
2. ¿Por qué usamos “framing”?
- Problema: A veces el micro:bit manda datos cortados o mezclados.
- Solución:
- Byte de inicio (
0xAA
): Como una bandera que dice “¡empieza aquí!” - Checksum: Un número que verifica si los datos llegaron bien (como cuando sumas los dígitos de tu DNI para ver si está correcto).
- Byte de inicio (
3. Partes Clave del Código
-
Leer datos:
// Junta los bytes que lleganserialBuffer = serialBuffer.concat(newData); -
Buscar paquetes:
while (serialBuffer.length >= 8) { // Necesitamos 8 bytes completosif (serialBuffer[0] !== 0xAA) {serialBuffer.shift(); // Borra bytes basuracontinue; // Sigue buscando} -
Verificar datos:
// Suma todos los bytes y compara con el checksumlet suma = dataBytes.reduce((a, b) => a + b, 0) % 256;if (suma !== checksum) {console.log("Error: Datos dañados");} -
Interpretar valores:
// Convierte bytes en númeroslet x = view.getInt16(0); // Lee 2 bytes como númerolet botonA = view.getUint8(4) === 1; // Lee 1 byte como true/false
4. Errores Comunes
- Sin
0xAA
: El programa no sabe dónde empiezan los datos.
Solución: Borrar bytes hasta encontrarlo. - Checksum incorrecto: Los datos vinieron mal.
Solución: Ignorar ese paquete.
5. ¿Por qué es mejor el binario?
- Ejemplo:
- ASCII: “500,300,1,0” → 12 letras (12 bytes).
- Binario:
AA 01 F4 00 01 00 2A
→ 7 símbolos (7 bytes).
- Resultado: Menos espacio, más velocidad.
6. Analogía Sencilla
- ASCII: Como enviar un mensaje de texto: “Mueve 5, izquierda 3, botón A presionado”.
- Binario: Como enviar un emoji secreto:
🚩📏🅰️
(solo quien sabe el código lo entiende).
Actividad 06
Autoevaluación
🔖 Recuerda: lo que harás en esta actividad no es relleno. Tampoco le pidas a chatGPT que lo haga por ti. La autoevaluación es una herramienta importante para tu proceso de aprendizaje. En ciencias del aprendizaje se le conoce como metacognición. La metacognición es la capacidad de pensar sobre lo que sabes y lo que no sabes. Es la capacidad de reflexionar sobre tu proceso de aprendizaje. La metacognición es una habilidad que se puede aprender y mejorar.
🎯 Enunciado: realiza una autoevaluación de tu trabajo en esta unidad.
- Describe qué aprendiste en esta unidad.
- ¿Qué fue lo más difícil de esta unidad? ¿Por qué?
- ¿Qué fue lo más fácil de esta unidad? ¿Por qué?
- ¿Cuánto tiempo dedicaste a esta unidad? ¿Fue suficiente? Dada la cantidad de créditos académicos del curso, semanalmente debes dedicar tres sesiones de trabajo, dos en el aula y una más por fuera de ella. ¿Pudiste dedicar las seis sesiones? ¿Por qué?
- ¿Qué podrías mejorar en tu proceso de aprendizaje en esta unidad si la tuvieras que hacer de nuevo?
- Piensa en tu perfil profesional y describe cómo lo que aprendiste en esta unidad se podría usar en una aplicación específica que aborde tu perfil profesional.
- ¿Qué te gustaría aprender en la siguiente unidad?
- ¿Cómo estuvo tu estado de ánimo durante esta unidad? ¿Por qué?
- La motivación se define como aquellos procesos cognitivos y afectivos que hacen que puedas iniciar, mantenerte y poner el esfuerzo necesario para lograr una meta. ¿Cómo estuvo tu motivación durante esta unidad? ¿Por qué?
- ¿Qué tan satisfecho estás con tu desempeño en esta unidad? ¿Por qué?
📤 Entrega: responde a las preguntas en tu bitácora y justifica tus respuestas con argumentos precisos.
🚀 Tu solución:
1. Aprendizaje:
Aprendí a comunicar micro:bit con p5.js usando protocolo binario, mejorando eficiencia y robustez. También entendí framing y checksum para evitar errores.
2. Dificultad:
Lo más difícil fue depurar el código binario, ya que los errores no son legibles como en ASCII. Además, sincronizar los bytes correctamente requirió varios intentos.
3. Facilidad:
Lo más fácil fue la parte teórica de los protocolos, porque ya tenía bases de programación. También, modificar el código de p5.js fue intuitivo.
4. Tiempo dedicado:
Dediqué 5 horas, pero faltó profundizar en checksum. Cumplí con las sesiones porque el tema me interesó, aunque ajusté horarios.
5. Mejoras:
Practicaría más con DataView y buffers. También probaría casos extremos, como pérdida masiva de datos, para robustecer el código.
6. Aplicación profesional:
En mi perfil (diseño interactivo), esto sirve para crear instalaciones con sensores y visuales en tiempo real, optimizando la comunicación.
7. Siguiente unidad:
Me gustaría aprender a comprimir datos o manejar múltiples dispositivos. También explorar WiFi/BLE para conexiones inalámbricas.
8. Estado de ánimo:
Al principio frustrado por los errores, pero luego motivado al ver el resultado. La curva de aprendizaje fue retadora pero gratificante.
9. Motivación:
Mantuve alta motivación porque el proyecto era práctico y útil. Ver el micro:bit controlar gráficos en tiempo real fue inspirador.
10. Satisfacción:
Estoy satisfecho, aunque podría mejorar en eficiencia. Logré el objetivo, pero sé que con más práctica ganaría mayor fluidez.
Actividad 07
Feedback
🎯 Enunciado: escribe libremente tus comentarios sobre esta unidad. Además, te pediré que al menos me ayudes con esta pregunta ¿Qué crees que se pueda mejorar en esta unidad para los próximos semestres?
📤 Entrega: un texto en la bitácora con tus pensamientos y la respuesta a la pregunta. Por favor, justifica tus ideas con argumentos precisos.
🚀 Tu solución:
Bitácora - Feedback sobre la Unidad
Reflexión general:
Esta unidad fue muy práctica y reveladora, especialmente al comparar protocolos ASCII vs. binarios. Aunque al principio fue confuso manejar bytes y checksums, al final entendí su importancia para aplicaciones reales. El proyecto de conectar el micro:bit con p5.js fue un excelente
ejercicio para ver la teoría en acción.
¿Qué mejorar?
-
Ejemplos paso a paso:
Sería útil tener un video o guía desglosando cómo empaquetar y desempaquetar datos binarios, especialmente para quienes no tienen experiencia previa con bits/bytes. -
Errores comunes:
Incluir una sección con errores típicos (ej: desincronización, checksum incorrecto) y cómo solucionarlos. Esto ahorraría tiempo en depuración.