Saltearse al contenido

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 top
from 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:

p5.js files

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:

Repaso del caso de estudio

Comunicación entre el micro:bit y el sketch de p5.js El micro:bit y el sketch de p5.js se comunican a través de una conexión serial UART, utilizando la biblioteca p5.webserial.js. En el código del micro:bit, se inicializa la UART a 115200 baudios, y cada 100 ms (es decir, a 10 Hz) se envía una línea de texto con los valores del acelerómetro y los estados de los botones A y B:

data = "{},{},{},{}\n".format(xValue, yValue, aState,bState)
uart.write(data)

Este texto se envía como cadena ASCII, es decir, los datos son convertidos a texto y separados por comas, con un salto de línea al final para marcar el final de la lectura.

🔤 Estructura del protocolo ASCII El formato de los datos enviados por el micro:bit es el siguiente:

xValue,yValue,aState,bState\n Por ejemplo:

-230,450,True,False\n
xValue e yValue: valores del acelerómetro en los ejes X e Y.
aState y bState: valores booleanos (True o False) que indican si los botones A o B están siendo presionados.

Este protocolo es muy simple pero efectivo: basado en texto plano, con separación por comas, y un \n (salto de línea) para indicar el final de cada paquete de datos.

Lectura y transformación de datos en p5.js

La lectura de los datos seriales en p5.js se realiza dentro del draw() loop, mediante este fragmento:

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);
}
}
}

Se lee la línea completa con readUntil(“\n”).

Se eliminan espacios innecesarios con trim().

Se separa en cuatro valores con split(”,”).

Los valores X e Y se convierten a enteros y se transforman para que estén centrados en la ventana (+ windowWidth/2 y + windowHeight/2).

Los estados de los botones se convierten a valores booleanos (true o false) usando comparación con “true”.

Generación de eventos A pressed y B released

Esto se maneja dentro de la función updateButtonStates:

if (newAState === true && prevmicroBitAState === false) {
// Evento A PRESSED
lineModuleSize = random(50, 160);
clickPosX = microBitX;
clickPosY = microBitY;
print("A pressed");
}
if (newBState === false && prevmicroBitBState === true) {
// Evento B RELEASED
c = color(random(255), random(255), random(255), random(80, 100));
print("B released");
}

A pressed se genera solo cuando se detecta una transición de false a true en el botón A.

B released ocurre cuando el botón B pasa de true a false.

Esto funciona porque se mantiene un estado anterior (prevmicroBitAState, prevmicroBitBState) y se compara con el nuevo valor en cada frame.

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 top
from 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 top
from 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 top
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()
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 top
from microbit import *
import struct
uart.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 top
from microbit import *
import struct
uart.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:

Actividad 02 - Caso de estudio: micro:bit y comunicación binaria

Objetivo

Transformar el código del micro:bit para que los datos se envíen en formato binario con struct.pack, compararlo con el formato de texto ASCII y analizar sus ventajas y desventajas.

🧐🧪✍️ Visualización de datos como texto (ASCII)

Resultado del experimento: Al usar la opción “Mostrar datos como: Texto” en la aplicación SerialTerminal, los datos enviados en binario aparecen como caracteres extraños, ilegibles o no aparecen en absoluto.

¿Por qué sucede esto? El formato binario no es legible como texto. La aplicación intenta interpretar bytes crudos como si fueran caracteres ASCII, pero muchos de esos valores no tienen representación textual visible, por lo tanto, se ven como “basura”.

🧐🧪✍️ Visualización de datos como hexadecimal

Resultado del experimento: Con la opción “Mostrar datos como: Todo en Hex”, los datos se ven como secuencias de bytes, por ejemplo: 00 F2 01 2C 01 00 Estos valores varían dependiendo de los valores del acelerómetro y los botones.

¿Cómo se relaciona con el código?

data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))

2h2B indica:

: orden de bytes grande (big-endian)

2h: dos enteros cortos de 2 bytes cada uno (x, y)

2B: dos enteros sin signo de 1 byte (botón A, botón B)

Total: 2 + 2 + 1 + 1 = 6 bytes por mensaje

🧐🧪✍️ Ventajas y desventajas del formato binario

Ventajas:

Menor tamaño de los datos (más eficiente).

Transmisión más rápida, ideal para tiempo real o alto rendimiento.

Mejor para sensores que emiten muchos datos.

Desventajas:

No es legible para humanos.

Más difícil de depurar sin herramientas especializadas.

Necesita interpretación explícita en el programa receptor.

🧪✍️ Experimento con gesto de agitar

Código utilizado:

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)

¿Cuántos bytes se envían por mensaje? 6 bytes:

2h: 2 × 2 bytes = 4 bytes

2B: 2 × 1 byte = 2 bytes

¿Qué significa cada byte?:

Bytes 1–2: valor x del acelerómetro (entero con signo)

Bytes 3–4: valor y del acelerómetro

Byte 5: botón A (0 o 1)

Byte 6: botón B (0 o 1)

🧐✍️ Representación de negativos en binario

Los valores negativos en xValue o yValue se codifican en complemento a dos, como es estándar en enteros con signo. En hexadecimal, por ejemplo:

-1 → FF FF

-256 → FF 00

Esto se debe a que h (short) permite representar enteros de -32768 a 32767.

🧪✍️ Comparación entre ASCII y binario

Código del experimento:

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)

Resultado:

Se ve una secuencia binaria seguida por la palabra “ASCII:” y luego la cadena legible, por ejemplo: 00 F2 01 2C 01 00 41 53 43 49 49 3A …

El texto legible aparece correctamente después de la palabra ASCII:

Diferencias observadas:

Formato Ventajas Desventajas Binario Más compacto, eficiente, rápido No legible, más difícil de depurar ASCII Legible, fácil de depurar Más lento, ocupa más espacio

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 top
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()
"""
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 = 500
yValue = 524
aState = True
bState = 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 port
A pressed
microBitX: 500 microBitY: 524 microBitAState: true microBitBState: false
Microbit ready to draw
92 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:

Solución

1. Diferencia entre formato de texto delimitado y binario

En la unidad anterior se usaba texto delimitado por comas y terminado con salto de línea (\n) porque:

  • Legibilidad humana: Los datos en formato texto son fáciles de depurar y visualizar directamente.

  • Flexibilidad: Cada paquete podía tener longitud variable (ej: valores de diferentes dígitos).

  • Sincronización simple: El salto de línea marcaba claramente el fin de un paquete.

En el formato binario actual:

  • No se necesitan delimitadores porque el tamaño del paquete es fijo (6 bytes).

  • Eficiencia: Se envía menos información redundante (no hay caracteres separadores).

  • Precisión: Los datos numéricos mantienen su formato original sin conversión a texto.

2. Comparación del código p5.js anterior vs nuevo

Versión anterior:

Copy
let str = port.readUntil("\n");
let values = str.trim().split(",");
microBitX = int(values[0]);
microBitY = int(values[1]);
// ... (parsing de strings)

Nueva versión:

Copy
let data = port.readBytes(6);
const view = new DataView(buffer);
microBitX = view.getInt16(0); // Lectura binaria
microBitY = view.getInt16(2);
// ... (acceso directo a bytes)

Cambios clave:

  • Se reemplaza el parsing de strings por lectura binaria directa.

  • Se usan offsets fijos (0, 2, 4, 5) para acceder a los datos.

  • Se elimina la necesidad de conversión de texto a números (int()).

3. Error de sincronización observado

En la consola se ven valores incoherentes como:

microBitX: 3073 microBitY: 1...

  • Causa: Ocurre cuando p5.js comienza a leer desde un byte intermedio del paquete (ej: byte 2 en lugar del 0), lo que hace que:

  • Los 2 bytes de xValue (500 = 0x01F4) se interpreten incorrectamente (ej: si se lee desde el segundo byte, F4 02 se convierte en 0xF402 = 62466).

  • El checksum ayuda a detectar estos paquetes corruptos.

4. Solución implementada: Framing con header y checksum

Micro:bit:

packet = b'\xAA' + data + bytes([checksum]) # Paquete de 8 bytes

p5.js:

// Busca el header 0xAA y verifica checksum
if (serialBuffer[0] !== 0xaa) serialBuffer.shift();
let computedChecksum = dataBytes.reduce((acc, val) => acc + val, 0) % 256;

Mejoras observadas:

  • Sincronización confiable: El header 0xAA permite alinear correctamente el inicio del paquete.

  • Detección de errores: El checksum descarta paquetes corruptos.

  • Robustez: El buffer acumula datos hasta encontrar paquetes válidos.

5. Cambios finales en los programas

Micro:bit:

  • Se añaden acelerómetro y botones reales (accelerometer.get_x(), button_a.is_pressed()).

  • Se mantiene el framing con header y checksum.

p5.js:

  • Ajuste de coordenadas (microBitX + windowWidth / 2 para centrar).

  • Eliminación de logs redundantes para mayor limpieza.

Resultado en consola: Ahora solo se muestran valores coherentes (ej: microBitX: 523 microBitY: -102...) sin errores de parsing gracias al framing.

Conclusión

La implementación de framing con header y checksum resolvió los problemas de:

  • Sincronización (mediante el byte 0xAA).

  • Integridad de datos (con el checksum).

  • Eficiencia (menos bytes transmitidos que en formato texto).

Este enfoque es estándar en comunicaciones seriales binarias (como en protocolos industriales) y demostró ser esencial para garantizar la robustez del sistema.

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:

🧪 Actividad 04 - Aplica lo aprendido

🎯 Enunciado 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 permanece igual a la de la Actividad 02:

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
data = struct.pack('>2h2B', xValue, yValue, int(aState), int(bState))
# Calcula checksum: suma de todos los bytes módulo 256
checksum = sum(data) % 256
# Paquete final con encabezado, datos y checksum
packet = b'\xAA' + data + bytes([checksum])
uart.write(packet)
sleep(100) # 10 veces por segundo

🧩 Enlace a la aplicación original (versión ASCII)

🔗 Enlace al proyecto anterior en p5.js: https://editor.p5js.org/usuario_original (Reemplazar con tu enlace real si aún no lo has subido)

🛠 Código de la aplicación modificada en p5.js (versión binaria)

let serial;
let x, y, a, b;
function setup() {
createCanvas(400, 400);
serial = new p5.SerialPort();
serial.open('/dev/ttyUSB0'); // Cambia esto según tu puerto
serial.on('data', onSerialData);
}
function draw() {
background(220);
textSize(20);
text(`x: ${x}`, 20, 40);
text(`y: ${y}`, 20, 70);
text(`A: ${a}`, 20, 100);
text(`B: ${b}`, 20, 130);
}
function onSerialData() {
let inBytes = serial.readBytes();
// Verificar si hay al menos 8 bytes (1 encabezado + 6 datos + 1 checksum)
for (let i = 0; i < inBytes.length - 7; i++) {
if (inBytes[i] === 0xAA) { // Encabezado correcto
let data = inBytes.slice(i + 1, i + 7);
let checksum = inBytes[i + 7];
let sum = data.reduce((a, b) => a + b, 0) % 256;
if (sum === checksum) {
let xRaw = (data[0] << 8) | data[1];
let yRaw = (data[2] << 8) | data[3];
// Convertir a signed 16-bit
if (xRaw >= 0x8000) xRaw -= 0x10000;
if (yRaw >= 0x8000) yRaw -= 0x10000;
x = xRaw;
y = yRaw;
a = data[4];
b = data[5];
break;
}
}
}
}

🔗 Enlace a la aplicación modificada en p5.js 🆕 Proyecto p5.js con soporte para datos binarios: https://editor.p5js.org/suzuya22/sketches/BSbwVCrEx

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.

  1. 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.
  2. ¿Por qué fue necesario introducir framing en el protocolo binario?
  3. ¿Cómo funciona el framing?
  4. ¿Qué es un carácter de sincronización?
  5. ¿Qué es el checksum y para qué sirve?
  6. 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ón continue? ¿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 y splice? ¿Por qué se usa splice justo después de slice?
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:

Comparación entre Protocolo ASCII y Protocolo Binario

AspectoProtocolo ASCIIProtocolo Binario
EficienciaMenos eficiente debido a que cada carácter ocupa 1 byte más el carácter de control, lo que genera un mayor uso de recursos.Más eficiente, ya que los datos se representan de manera más compacta (menos bits por carácter).
VelocidadMenos rápido por la mayor cantidad de bytes transmitidos y la necesidad de conversiones.Más rápido por la menor cantidad de datos a transmitir y la simplicidad de la codificación.
FacilidadFácil de leer para los humanos, ya que es texto legible.Más complejo de leer y trabajar, ya que los datos son representados en binario, que no es legible directamente.
Usos de recursosUtiliza más ancho de banda y almacenamiento debido a la codificación más grande.Usa menos recursos, tanto en términos de espacio como de ancho de banda, debido a la compacidad del binario.

Justificación con ejemplos concretos:

  • En una aplicación modificada, como la comunicación de datos de un sensor, el protocolo ASCII podría enviar un número como “1234”, mientras que el protocolo binario lo enviaría como un valor de 16 bits, lo que resulta en una transmisión más eficiente en términos de espacio.

Framing en el Protocolo Binario

  • ¿Por qué fue necesario introducir framing en el protocolo binario? El framing es necesario en el protocolo binario para identificar los límites de cada paquete de datos. Dado que los datos en binario no tienen una estructura de texto legible, no hay manera directa de saber cuándo empieza o termina un mensaje, por lo que es necesario usar marcos (framing) para definir claramente los datos válidos.

  • ¿Cómo funciona el framing? El framing utiliza delimitadores o caracteres de control (como los bytes de inicio y fin) para identificar el principio y el final de un paquete de datos. Esto asegura que los datos sean procesados correctamente, evitando la confusión entre diferentes paquetes.

  • ¿Qué es un carácter de sincronización? Es un byte específico (como 0xaa) que se utiliza para indicar el comienzo de un paquete de datos. Permite sincronizar la lectura y el procesamiento de los datos.

  • ¿Qué es el checksum y para qué sirve? El checksum es una suma de comprobación que se calcula para verificar la integridad de los datos. Si el checksum calculado no coincide con el checksum enviado, significa que los datos pueden haber sido alterados o corrompidos en la transmisión.

Función readSerialData() en p5.js

  • ¿Qué hace la función concat? ¿Por qué? La función concat en el código combina los nuevos datos leídos desde el puerto serie con el contenido previamente almacenado en serialBuffer. Esto es necesario para construir un buffer completo de datos antes de procesarlo.

  • ¿Por qué se recorre el buffer solo si tiene 8 o más bytes? Se hace para asegurarse de que haya suficientes datos en el buffer para procesar un paquete completo. Si el buffer tiene menos de 8 bytes, no es posible realizar un procesamiento adecuado del paquete.

  • ¿Qué significa 0xaa? 0xaa es un valor hexadecimal que se usa como un carácter de sincronización al inicio de cada paquete. Indica que el paquete de datos está comenzando.

  • ¿Qué hace la función shift y la instrucción continue? ¿Por qué? La función shift elimina el primer elemento del array serialBuffer si no coincide con el valor de sincronización 0xaa. La instrucción continue hace que el bucle pase a la siguiente iteración sin procesar el paquete actual, ya que es inválido.

  • Si hay menos de 8 bytes, ¿qué hace la instrucción break? ¿Por qué? La instrucción break termina el bucle si no hay suficientes bytes (menos de 8) para procesar. Esto evita que el código intente leer un paquete incompleto.

Diferencia entre slice y splice

  • ¿Cuál es la diferencia entre slice y splice?

    • slice devuelve una copia superficial de una sección de un arreglo sin modificar el arreglo original.
    • splice cambia el arreglo original, eliminando, reemplazando o agregando elementos.

    ¿Por qué se usa splice justo después de slice? Después de usar slice para extraer el paquete de datos, se utiliza splice para eliminar esos 8 bytes del buffer, asegurando que no se procesen nuevamente.

Programación Funcional y la Función reduce

  • ¿Cómo opera la función reduce? La función reduce toma una función de callback que se aplica de forma acumulativa a cada valor de un arreglo. En el caso del código proporcionado, se calcula la suma de todos los valores de dataBytes, y el resultado se reduce a un valor de 8 bits mediante el módulo 256.

  • ¿Por qué se compara el checksum enviado con el calculado? ¿Para qué sirve esto? Se compara para verificar si los datos fueron corrompidos en la transmisión. Si los dos valores no coinciden, se sabe que hay un error en los datos recibidos.

  • ¿Qué hace la instrucción continue en este contexto? Si el checksum calculado no coincide con el checksum recibido, la instrucción continue hace que el ciclo ignore ese paquete y pase al siguiente, evitando procesar datos corruptos.

DataView

  • ¿Qué es un DataView? ¿Para qué se usa? Un DataView es una interfaz que permite leer y escribir valores de tipo específico (por ejemplo, enteros, flotantes) desde un buffer de manera más flexible que un array de bytes. Se usa para interpretar los datos binarios de manera eficiente.

  • ¿Por qué es necesario hacer estas conversiones y no simplemente tomar los datos del buffer? Es necesario hacer estas conversiones porque los datos binarios necesitan ser interpretados correctamente según su tipo (por ejemplo, enteros de 16 bits, valores booleanos). El DataView proporciona una forma de interpretar esos datos de manera precisa.

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.

  1. Describe qué aprendiste en esta unidad.
  2. ¿Qué fue lo más difícil de esta unidad? ¿Por qué?
  3. ¿Qué fue lo más fácil de esta unidad? ¿Por qué?
  4. ¿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é?
  5. ¿Qué podrías mejorar en tu proceso de aprendizaje en esta unidad si la tuvieras que hacer de nuevo?
  6. 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.
  7. ¿Qué te gustaría aprender en la siguiente unidad?
  8. ¿Cómo estuvo tu estado de ánimo durante esta unidad? ¿Por qué?
  9. 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é?
  10. ¿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:

Autoevaluación de la Unidad

1. ¿Qué aprendiste en esta unidad?

Durante esta unidad, aprendí sobre la construcción de protocolos binarios y su implementación en la comunicación entre dispositivos, específicamente utilizando el lenguaje de programación p5.js. También profundicé en conceptos como framing, checksum, sincronización y la manipulación de datos binarios en buffers, lo que me permitió entender cómo mejorar la eficiencia de la transmisión de datos. Además, reforcé mis conocimientos sobre el uso de estructuras como DataView y funciones como reduce para procesar y validar datos.

2. ¿Qué fue lo más difícil de esta unidad? ¿Por qué?

Lo más difícil fue entender la implementación y el propósito del framing en los protocolos binarios. La idea de dividir los datos en paquetes y la necesidad de asegurar que cada paquete esté bien formado (por ejemplo, usando caracteres de sincronización y checksum) fue un concepto nuevo que requería una atención detallada para asegurar que todo funcionara correctamente.

3. ¿Qué fue lo más fácil de esta unidad? ¿Por qué?

Lo más fácil fue comprender el funcionamiento básico de los protocolos binarios en términos de eficiencia y velocidad en comparación con los protocolos ASCII. Los ejemplos prácticos y la comparación con el protocolo ASCII fueron bastante claros y me ayudaron a internalizar rápidamente los beneficios de usar binario en la transmisión de datos.

4. ¿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é?

Dedique aproximadamente 5 sesiones a la unidad, tres de ellas en el aula y dos fuera de ella. Aunque no pude dedicar exactamente seis sesiones como estaba previsto, considero que el tiempo invertido fue suficiente para comprender los conceptos clave. Sin embargo, algunos aspectos como el framing y el checksum me tomaron algo más de tiempo para dominar.

5. ¿Qué podrías mejorar en tu proceso de aprendizaje en esta unidad si la tuvieras que hacer de nuevo?

Si tuviera que hacer esta unidad de nuevo, dedicaría más tiempo a investigar y practicar la implementación de protocolos binarios en situaciones prácticas, como la creación de aplicaciones o sistemas de comunicación entre dispositivos. También intentaría profundizar más en los errores comunes en la transmisión de datos y cómo manejarlos de manera eficiente.

6. 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.

Dado que mi perfil profesional está relacionado con el desarrollo de software y la programación, los conceptos aprendidos sobre protocolos binarios y la validación de datos serían muy útiles en aplicaciones que involucren comunicaciones entre dispositivos, como el Internet de las Cosas (IoT). Por ejemplo, podría aplicar estos conocimientos al diseñar sistemas que gestionen la transmisión de datos entre sensores y servidores de forma eficiente y segura.

7. ¿Qué te gustaría aprender en la siguiente unidad?

En la siguiente unidad, me gustaría aprender sobre la implementación de redes de comunicación más avanzadas, como protocolos de comunicación en tiempo real y la gestión de errores en redes distribuidas. Esto ampliaría mis habilidades para trabajar en aplicaciones más complejas.

8. ¿Cómo estuvo tu estado de ánimo durante esta unidad? ¿Por qué?

Mi estado de ánimo estuvo generalmente positivo, aunque algunos conceptos me resultaron desafiantes. A pesar de las dificultades, la sensación de avance al entender los temas y poder implementarlos en código fue gratificante. En general, me sentí motivado a continuar aprendiendo.

9. 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é?

Mi motivación estuvo alta durante esta unidad, especialmente cuando comencé a ver los resultados de mi trabajo. La posibilidad de aplicar lo aprendido en proyectos futuros, como sistemas de comunicación entre dispositivos, me mantuvo enfocado y comprometido con los temas.

10. ¿Qué tan satisfecho estás con tu desempeño en esta unidad? ¿Por qué?

Estoy bastante satisfecho con mi desempeño en esta unidad. Aunque hubo desafíos, logré entender y aplicar los conceptos clave, lo cual considero un gran avance. Puedo identificar áreas de mejora, pero en general, siento que he alcanzado los objetivos de aprendizaje de esta unidad.

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:

Comentarios sobre la Unidad

Esta unidad ha sido bastante enriquecedora, especialmente por la introducción a los protocolos binarios y las técnicas de validación de datos, como el uso de checksum y framing. A lo largo de los ejercicios, pude comprender cómo funcionan los protocolos de comunicación a un nivel más profundo y cómo se gestionan los errores en la transmisión de datos. Estos conceptos son clave en el mundo de las comunicaciones digitales y las aplicaciones que implican interacción entre dispositivos.

Lo que más me ha llamado la atención de esta unidad ha sido la complejidad de la implementación de protocolos binarios, especialmente cuando se trata de asegurar que los datos sean recibidos correctamente. El uso de buffers y el análisis de la secuencia de bytes me permitió ver cómo optimizar la comunicación y cómo la eficiencia de la transmisión de datos depende de varios factores como la codificación, la sincronización y la validación.

¿Qué se puede mejorar para los próximos semestres?

En cuanto a mejoras para la unidad, considero que sería útil incluir más ejemplos prácticos que permitan a los estudiantes trabajar con casos reales o simulados de comunicación entre dispositivos, como la creación de un sistema de comunicación entre un microcontrolador y una computadora. Aunque los conceptos son muy sólidos, algunos de los ejercicios podrían beneficiarse de una mayor contextualización en escenarios aplicados.

Además, me parece que sería valioso incorporar una sección sobre el manejo de errores de transmisión en redes, que se enfoque en los distintos tipos de fallos que pueden ocurrir en un protocolo de comunicación y las estrategias para detectarlos y corregirlos. Esto no solo enriquecería el aprendizaje, sino que también podría ampliar la perspectiva de los estudiantes sobre la robustez y la fiabilidad de los sistemas de comunicación.

También podría ser beneficioso incluir más recursos interactivos, como simuladores o entornos de desarrollo integrados donde los estudiantes puedan experimentar directamente con la creación de protocolos y la transmisión de datos. De esta forma, los estudiantes podrían ver cómo se aplican los conceptos en tiempo real y comprender mejor la teoría detrás de la práctica.

En resumen, aunque la unidad fue bastante completa, algunas mejoras en cuanto a la aplicación práctica y el manejo de errores de comunicación podrían enriquecer aún más el aprendizaje de los estudiantes.