Saltearse al contenido

Unidad 4

Introducción 📜

En esta unidad vas a aprender cómo enviar líneas de texto desde el micro:bit a un sketch en p5.js. Dichas líneas de textos tendrán información de varios sensores del micro:bit, separada por comas, y terminadas por un salto de línea. Adicionalmente, vas a practicar de nuevo la técnica de programación de máquinas de estados.

¿Qué aprenderás en esta unidad? 💡

Vas a enviar información desde el micro:bit a un sketch en p5.js. Para ello, usarás protocolos de comunicación ASCII. Antes de hacerlo, te pediré que explores algunas aplicaciones interactivas hechas con p5.js que te servirán de inspiración. Luego, en la fase de aplicación vas a seleccionar una de esas aplicaciones y la modificarás para que puedas controlarla con el micro:bit; sin embargo, antes de hacerlo, vas a ver cómo lo puedes lograr en la fase de investigación.

Actividad 01

Algunos ejemplos inspiradores

Esta actividad puede tomarte un poco más de una hora, ¿Vale? Todo dependa de la curiosidad que tengas.

🎯 Enunciado: te voy a mostrar algunos ejemplos interesantes y fuentes de inspiración que pueden ser de utilidad a la hora de diseñar tus propias aplicaciones interactivas con p5.js.

Al explorar este sitio verifica si el ejemplo está hecho con p5.js siguiendo estos pasos:

  1. Dale click al tercer ícono de la parte superior derecha.

tercer icono

  1. Luego en esta parte de la página:

p5.js

  1. Selecciona el ícono de configuración:

p5.js

  1. Aquí puedes verificar si el modo del sketch es p5.js:

p5.js

Nota también que en esta parte de la página puedes verificar si el código requiere alguna biblioteca adicional y archivos para su correcto funcionamiento (pestaña FILES al lado de SKETCH).

Otros sitios que te pueden servir de inspiración:

📤 Entrega: de cada uno de estos sitios selecciona un ejemplo que te haya llamado la atención. Luego, en tu entrega, realiza lo siguiente:

  • Copia el enlace del ejemplo.
  • Describe brevemente qué te llamó la atención del ejemplo.
  • Trata de entender cómo está hecho el sketch. ¿Qué funciones de p5.js se están utilizando? ¿Qué técnicas de programación se están aplicando? Puedes buscar en la documentación de p5.js para entender mejor el código.
  • Vas a crear una cuenta en p5.js (tal vez ya la tienes) y vas a recrear en el editor de p5.js el ejemplo. Realiza una modificación a cada ejemplo y describe qué cambios hiciste y por qué.
  • Comparte el enlace de tu sketch en p5.js. ¿Cómo? Una vez crees la cuenta y salves el sketch recreado, la url de tu sketch aparecerá en la parte superior de la página. Copia y pega esa url en tu entrega.

Por ejemplo, mira este ensayo que yo hice:

En openprocessing encontré este ejemplo.
Lo recree en el editor de p5.js y cambié la imagen de fondo: mi versión.

🚀 Tu solución:

Sitio: p5.js Examples – Draw Line

🔗 Enlace del ejemplo original:
https://p5js.org/examples/animation-and-variables-drawing-lines/

1. ¿Qué me llamó la atención?

  • Interactividad libre: permite dibujar con el mouse de forma espontánea, como si fuera un cuaderno en blanco.
  • Simplicidad potente: con muy pocas líneas de código ya tienes un lienzo interactivo ready-to-go.
  • Posibilidades de personalización: es muy fácil cambiar grosor, color, mezcla de modos, añadir efectos sonoros, etc.

2. Análisis del sketch original

  • Funciones principales:
    • setup() para crear el lienzo (createCanvas) y establecer propiedades iniciales (fondo, ancho de trazo).
    • draw() (o mouseDragged()) para dibujar continuamente mientras el ratón está presionado.
  • Interacción:
    • mouseIsPressed (o mouseDragged()) detecta el arrastre.
    • pmouseX, pmouseY capturan la posición anterior del ratón para trazar líneas suaves.
  • Dibujo:
    • stroke() define el color de línea.
    • strokeWeight() fija el grosor del trazo.
    • line(x1, y1, x2, y2) dibuja la línea desde la posición anterior a la actual.

Aquí tienes una versión más creativa de tu sketch “drawLine”, que añade varios elementos nuevos para hacerlo único:

let mode = 0; // 0 = líneas, 1 = elipses
let hueOffset = 0; // desplazamiento de color con el tiempo
let fadeAlpha = 20; // cantidad de desvanecimiento del fondo
function setup() {
createCanvas(710, 400);
background(0);
colorMode(HSB);
strokeWeight(10);
noFill();
describe('Lienzo interactivo con líneas y elipses de colores que cambian con el mouse y el teclado');
}
function draw() {
// Efecto de fondo que se desvanece lentamente
push();
noStroke();
fill(0, 0, 0, fadeAlpha);
rect(0, 0, width, height);
pop();
// Acentuamos el desplazamiento de color con el tiempo
hueOffset = (hueOffset + 0.5) % 360;
if (mouseIsPressed) {
let speed = dist(pmouseX, pmouseY, mouseX, mouseY);
let dynamicWeight = map(speed, 0, 50, 5, 30);
strokeWeight(dynamicWeight);
// calculamos un color dinámico
let h = (mouseX - mouseY + hueOffset + 360) % 360;
stroke(h, 80, 90);
if (mode === 0) {
// Línea con ligera variación de ruido para un trazo orgánico
let nx = noise(frameCount * 0.01) * 5 - 2.5;
let ny = noise(frameCount * 0.01 + 100) * 5 - 2.5;
line(pmouseX + nx, pmouseY + ny, mouseX + nx, mouseY + ny);
} else {
// Elipses que siguen al cursor
let d = map(mouseX, 0, width, 10, 100);
ellipse(mouseX, mouseY, d, d * 0.6);
}
}
}
function keyPressed() {
// Cambiar entre modo línea y modo elipse con la tecla SPACE
if (key === ' ') {
mode = 1 - mode;
}
// Limpiar pantalla con 'C'
if (key === 'C' || key === 'c') {
background(0);
}
}

¿Qué hace diferente esta versión?

  1. Fondo con desvanecimiento suave
    Cada draw() dibuja un rectángulo semitransparente negro, lo que genera un efecto de “desvanecimiento” de los trazos anteriores y crea estelas suaves.

  2. Grosor dinámico según la velocidad del mouse
    El grosor del trazo (strokeWeight) varía entre 5 y 30 dependiendo de cuán rápido se mueva el ratón, haciendo que movimientos rápidos generen trazos más gruesos y lentos más finos.

  3. Dos modos de dibujo

    • Modo líneas: líneas con un ligero desplazamiento basado en noise() para un acabado más orgánico.
    • Modo elipses: elipses cuyo tamaño depende de la posición del mouse, creando formas más divertidas.
      Cambias de modo presionando ESPACIO.
  4. Control de limpieza
    Presionando C (o c) limpias el lienzo y vuelves a arrancar con fondo negro.

  5. Color en HSB con desplazamiento continuo
    El matiz se va desplazando lentamente con cada frame (hueOffset), de modo que incluso si se mantiene el mouse quieto, el color evoluciona con el tiempo.


🔗 Enlace de mi versión:
https://editor.p5js.org/luffytorao721/sketches/5hZA-aadG

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.

Actividad 02

Caso de estudio: exploración inicial

🎯 Enunciado: en esta actividad vas a explorar un ejemplo interesante de generación de arte digital con p5.js. El ejemplo es tomado del sitio Generative Design.

Lo que te pediré es que explores el código fuente de este sketch y trates de entender cómo está hecho.

Te ruego que hagas el mayor esfuerzo para explorar y entender el código. Si tienes dudas, no dudes en preguntarme.

📤 Entrega: explica cómo funciona este sketch, asegúrate de explorar toda su funcionalidad y comprender qué hace cada parte del código. Realiza varios dibujos y explica cómo los lograste.

🚀 Tu solución:

// P_2_3_1_02
//
// Generative Gestaltung – Creative Coding im Web
// ISBN: 978-3-87439-902-9, First Edition, Hermann Schmidt, Mainz, 2018
// Benedikt Groß, Hartmut Bohnacker, Julia Laub, Claudius Lazzeroni
// with contributions by Joey Lee and Niels Poldervaart
// Copyright 2018
//
// http://www.generative-gestaltung.de
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* draw tool. draw with a rotating element (svg file).
*
* MOUSE
* drag : draw
*
* KEYS
* 1-4 : switch default colors
* 5-9 : switch brush element
* delete/backspace : clear screen
* d : reverse direction and mirrow angle
* space : new random color
* arrow left : rotaion speed -
* arrow right : rotaion speed +
* arrow up : module size +
* arrow down : module size -
* shift : limit drawing direction
* s : save png
*/
'use strict';
var c;
var lineModuleSize = 0;
var angle = 0;
var angleSpeed = 1;
var lineModule = [];
var lineModuleIndex = 0;
var clickPosX = 0;
var clickPosY = 0;
function preload() {
lineModule[1] = loadImage('data/02.svg');
lineModule[2] = loadImage('data/03.svg');
lineModule[3] = loadImage('data/04.svg');
lineModule[4] = loadImage('data/05.svg');
}
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
//cursor(CROSS);
noCursor();
strokeWeight(0.75);
c = color(181, 157, 0);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function draw() {
if (mouseIsPressed && mouseButton == LEFT) {
var x = mouseX;
var y = mouseY;
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();
}
}
function mousePressed() {
// create a new random color and line length
lineModuleSize = random(50, 160);
// remember click position
clickPosX = mouseX;
clickPosY = mouseY;
}
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') saveCanvas(gd.timestamp(), 'png');
if (keyCode == DELETE || keyCode == BACKSPACE) background(255);
// reverse direction and mirror angle
if (key == 'd' || key == 'D') {
angle += 180;
angleSpeed *= -1;
}
// change color
if (key == ' ') c = color(random(255), random(255), random(255), random(80, 100));
// 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;
}

1. Variables globales

  • c: guarda el color actual con el que se va a dibujar o tintar el SVG.
  • lineModuleSize: controla el tamaño (escala) del elemento que se va a dibujar.
  • angle y angleSpeed: llevan la cuenta del ángulo de rotación y la velocidad a la que gira cada elemento.
  • lineModule[]: array donde se cargan varios archivos SVG (en preload).
  • lineModuleIndex: índice que indica qué SVG (o la opción “línea simple”) está seleccionado.
  • clickPosX, clickPosY: memoria de la posición en la que se hizo clic, usada para permitir el “lockeo” horizontal/vertical con Shift.

2. preload()

Antes de inicializar el sketch, el autor carga cuatro archivos SVG en lineModule[1]…[4]. Esto garantiza que las imágenes estén disponibles en memoria al dibujar, sin retrasos.


3. setup()

  • Llama a createCanvas(windowWidth, windowHeight) para ocupar toda la ventana.
  • Pinta el fondo de blanco (background(255)) y oculta el cursor con noCursor() para darle más protagonismo al elemento rotativo.
  • Ajusta el grosor de trazo a 0.75 píxeles (strokeWeight(0.75)).
  • Inicializa c con un color ocre (color(181,157,0)).

4. windowResized()

Cada vez que la ventana cambia de tamaño, resizeCanvas actualiza el lienzo para que siga llenando toda la pantalla.


5. draw()

Dentro de este bucle continuo:

  1. Detección de dibujo
    Solo se ejecuta si el botón izquierdo del ratón está presionado.

  2. Shift‑lock
    Si también se mantiene Shift, compara la distancia arrastrada en X y en Y desde el clic inicial y anula el eje con menor desplazamiento para trazar perfectamente horizontal o vertical.

  3. Transformaciones
    Con push()/pop() se traslada el origen al punto de dibujo (translate(mouseX, mouseY)) y se gira el sistema de coordenadas según angle.

  4. Dibujo condicional

    • Si lineModuleIndex > 0, pinta el SVG correspondiente usando tint(c) y image(...) escalado a lineModuleSize.
    • Si lineModuleIndex == 0, dibuja una línea diagonal de largo lineModuleSize con stroke(c) y line(0,0, lineModuleSize, lineModuleSize).
  5. Rotación
    Tras dibujar, suma angleSpeed al ángulo acumulado para que en el siguiente frame el módulo gire un poco más.


6. mousePressed()

Al pulsar el ratón:

  • Se asigna un tamaño aleatorio a lineModuleSize (entre 50 y 160px).
  • Se almacena la posición (mouseX, mouseY) en clickPosX/clickPosY para el comportamiento con Shift.

7. Manejo de teclado

  • Flechas Arriba/Abajo: ajustan lineModuleSize incrementalmente.
  • Flechas Izquierda/Derecha: modifican angleSpeed para acelerar o ralentizar la rotación.
  • Tecla D: invierte la dirección de giro y añade 180° al ángulo, creando un efecto espejo.
  • Espacio: genera un color aleatorio para c con algo de transparencia.
  • Teclas 1–4: restauran colores por defecto predefinidos.
  • Teclas 5–9: seleccionan si dibujar la línea simple (5) o uno de los cuatro SVG (69).
  • Suprimir/Backspace: limpia el fondo (vuelve al blanco).
  • S: guarda una captura del canvas como PNG.

Actividad 03

Caso de estudio: micro:bit

Desde esta actividad hasta la fase de aplicación, te voy a guiar para que transformes y adaptes el caso de estudio para lograr controlar partes de este con el micro:bit. Primero te mostraré cómo transmitir información desde el micro:bit.

🎯 Enunciado: analiza el código del micro:bit que te permitirá enviar información a un sketch en p5.js.

Vas a analizar lentamente el siguiente código del micro:bit

# 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
  • Programa el micro:bit con este código y luego abre la aplicación SerialTerminal para ver los datos que se están enviando.

  • ¿Qué información se está enviando? ¿Cómo se está enviando? Qué significa esta parte del código:

"{},{},{},{}\n".format(xValue, yValue, aState,bState)
  • Observa en la aplicación SerialTerminal cómo se ven los datos que se están enviando. ¿Qué puedes inferir de la estructura de los datos?
  • ¿Por qué se separan los datos con comas y se termina con un salto de línea?
  • ¿Qué crees que pasaría si no se separan los datos con comas y no terminan con un salto de línea?
  • Para qué crees que se usa la función sleep(100)? ¿Qué pasaría si no se usara?
  • Observa cómo cambian los valores de xValue y yValue a medida que el micro:bit se inclina hacia la izquierda, derecha, adelante y atrás. ¿Qué valores toman xValue y yValue en cada caso?
  • ¿Qué valores toman aState y bState cuando presionas los botones A y B?
  • Observa qué ocurre si en vez de is_pressed() usas was_pressed(). ¿Qué diferencias encuentras?

Finalmente, analiza este asunto: si el micro:bit tiene los siguientes datos xValue: 969, yValue: 652, aState: True, bState: False ¿Qué bytes se enviarían por el puerto serial? Piensa, primero piensa por favor, y luego verifica con la aplicación SerialTerminal. Ten presente que en Mostrar datos como puedes ver los bytes que se están enviando mediante Todo en HEX.

📤 Entrega: reporta los experimentos y hallazgos que vas encontrando a medida que analizas el código y responde las preguntas que te voy haciendo en el enunciado.

🚀 Tu solución:

Actividad 03: Caso de Estudio - Comunicación Serial con micro:bit

🔍 Análisis del Código

El código del micro:bit envía datos del acelerómetro (x, y) y el estado de los botones (A, B) a través del puerto serial (UART) en formato de texto.

Estructura del código:

data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)
uart.write(data)
  • "{},{},{},{}\n": Crea una cadena con 4 valores separados por comas y un salto de línea (\n).
    • Ejemplo: "969,652,True,False\n".
  • uart.write(data): Envía la cadena como bytes a través del puerto serial.

📡 Datos Enviados y su Estructura

  1. ¿Qué información se envía?

    • xValue, yValue: Valores del acelerómetro (rango: -1024 a +1024).
    • aState, bState: True/False según si los botones A/B están presionados.
  2. Formato de los datos:

    • Separados por comas: Para que p5.js pueda interpretar cada valor individualmente (ej: split(",")).
    • Salto de línea (\n): Indica el fin de un paquete de datos.
  3. ¿Qué pasa si no se usan comas o \n?

    • Sin comas: p5.js no podría distinguir entre xValue y yValue.
    • Sin \n: El programa receptor no sabría cuándo termina un conjunto de datos.

⚙️ Experimentos y Hallazgos

PreguntaRespuestaEvidencia
¿Para qué sirve sleep(100)?Limita la frecuencia de envío a 10 Hz (100 ms). Sin él, el micro:bit saturaría el puerto serial.Sin sleep, SerialTerminal muestra datos demasiado rápido y se pierden.
Valores de xValue/yValue al inclinar:- Izquierda: xValue negativo. Derecha: xValue positivo.
- Adelante: yValue negativo. Atrás: yValue positivo.
Al inclinar, los valores cambian entre -1024 y +1024.
aState y bState:True si el botón está presionado; False si no.SerialTerminal muestra True,False al presionar solo A.
was_pressed() vs is_pressed():was_pressed() detecta pulsaciones únicas, mientras que is_pressed() lee el estado actual.Con was_pressed(), SerialTerminal solo muestra True una vez por pulsación.
Bytes enviados para 969,652,True,False:Se envía la cadena como ASCII:
"969,652,True,False\n" → Hex: 39 36 39 2C 36 35 32 2C 54 72 75 65 2C 46 61 6C 73 65 0A
Verificado en SerialTerminal en modo HEX.

  1. El formato CSV + \n es clave para que p5.js interprete los datos correctamente.
  2. sleep(100) evita sobrecargar la comunicación serial.
  3. was_pressed() es útil para detectar pulsaciones únicas (ej: inicio de un gesto).

Puerto serial

image


Hex

image

Actividad 04

Caso de estudio: p5.js

Esta actividad será larga, te tomará posiblemente una o más sesiones de trabajo. Te pido que a medida que la recorres vayas tomando notas de tus hallazgos y experimentos en la bitácora. Tómate tu tiempo para analizar y comprender cada parte del código. La idea no es leer de corrido el código, sino analizarlo ¿Cómo? Haciendo experimentos, modificando partes del código, ejecutando y observando los resultados. A esto se le llama aprendizaje activo.

🎯 Enunciado: en esta actividad vas a modificar diferentes partes del código original para que puedas controlar ciertas partes de este con el micro:bit.

  • Crea un nuevo proyecto p5.js.
  • Modifica el archivo index.html así:
<!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>
  • Compara el código original del caso de estudio con el anterior. ¿Qué notas de diferente?

La aplicación requiere algunas imágenes que puedes descargar de la aplicación original. ¿Cómo? Lo primero es autenticarte en el editor de p5.js. Luego abre el sketch original y realiza una modificación simple. Guarda el sketch. Nota que ahora este código está en tu cuenta. Descarga las imágenes, ingresando al menú File. Finalmente, carga las imágenes a la carpeta de tu proyecto p5.js así:

p5.js files

Ahora modifica el archivo sketch.js así:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
}

Ejecuta el sketch. Si no tienes errores podrás continuar. Reflexiona ¿Para qué se usan estas imágenes? ¿Qué representan? Revisa de nuevo el sketch original y analiza.

Recuerda que en el código del micro:bit, cada 100 ms se están enviando datos; sin embargo, la aplicación no podrá comenzar a usar dichos datos antes de que el usuario de manera explícita conecte el micro:bit a la aplicación.

Entonces tienes un problema en el cual una aplicación se comporta diferente dependiente del ESTADO en el que se encuentra. ¿Cómo puedes solucionar este tipo de problema? Ya lo sabes: máquinas de estado.

Modifica el archivo sketch.js para que puedas controlar el estado de la aplicación:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
}
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Recuerda que en una aplicación p5.js, la función setup() se llama solo una vez. Luego se llamará la función draw() cada 17 ms que equivale a 60 fps. No olvides entonces que cada vez que draw() se llama, la aplicación evalúa el valor de appState para decidir qué hará en ese frame, o dicho de otra manera qué hará en el estado actual.

¿Recuerdas que en las unidades anteriores teníamos un pseudoestado llamado INIT? Se llama pseudoestado porque cuando la aplicación está en este realmente NO ESTÁ ESPERANDO nada. ¿Puedes notar que no tenemos a INIT en este caso? Pero la verdad si está, solo que no como antes, la función setup() está haciendo esa función. ¿Lo ves?

Ahora es momento de programar el comportamiento del estado STATES.WAIT_MICROBIT_CONNECTION.
Recuerda que la aplicación en este estado, en cada frame, simplemente está esperando a que el usuario conecte el micro:bit. Observa el código, agregaré algunas variables:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
//*********************************
// Código para soportar el serial
let port;
let connectBtn;
let microBitConnected = false;
//*********************************
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
}
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

Observa que port, connectBtn y microBitConnected son variables globales. Se necesitan así porque las vas a manipular en cualquier función del programa. No olvides que las variables que declaremos dentro de las funciones solo serán visibles dentro de la función, no por fuera.

Ahora añadiremos en la función setup el código que permitirá conectarse al micro:bit:

const lineModule = [];
function preload() {
lineModule[1] = loadImage("02.svg");
lineModule[2] = loadImage("03.svg");
lineModule[3] = loadImage("04.svg");
lineModule[4] = loadImage("05.svg");
}
//*********************************
// Código para soportar el serial
let port;
let connectBtn;
let microBitConnected = false;
//*********************************
const STATES = {
WAIT_MICROBIT_CONNECTION: "WAITMICROBIT_CONNECTION",
RUNNING: "RUNNING",
};
let appState = STATES.WAIT_MICROBIT_CONNECTION;
function setup() {
createCanvas(windowWidth, windowHeight);
background(255);
//****************************************************
// Adición del serial
port = createSerial();
connectBtn = createButton("Connect to micro:bit");
connectBtn.position(0, 0);
connectBtn.mousePressed(connectBtnClick);
//****************************************************
}
function draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}
  • createSerial(): crea el objeto que representará la conexión serial y devolverá la dirección de memoria de este objeto que se almacenará en la variable port.
  • createButton(): crea un objeto que representará a un botón en la aplicación. La dirección de este objeto quedará almacenada en la variable connectBtn.
  • Observa que por medio de connectBtn, que contiene la dirección del objeto, se manipulará la posición del objeto y el comportamiento de este al presionarlo con el mouse mousePressed. Nota que al método mousePressed() le estás pasando el nombre de una función connectBtnClick. De esta manera al presionar el botón se llamará dicha función.

Añadimos entonces la función connectBtnClick():

const lineModule = [];
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;
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 draw() {
switch (appState) {
case STATES.WAIT_MICROBIT_CONNECTION:
break;
case STATES.RUNNING:
break;
}
}

¿Qué pasaría ahora si das click al botón? Observa que en la función se verifica si el puerto serial no está abierto. De ser así se abren con el método open(“MicroPython”, 115200). De lo contrario el puerto se cerrará.

¿Para qué abres el puerto serial? Se abre para poder recibir los datos del micro:bit, es decir, para conectarte al micro:bit y poder recibir los datos que está enviando en esta parte de su código:

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

Verifica el funcionamiento de la aplicación hasta este momento. Todo debería estar bien.

Una vez ejecutes el programa nota que debe aparecer en la esquina superior izquierda el botón para conectarse al micro:bit: Connect to micro:bit. Observa la consola para que puedas ver los mensajes que te saldrán.

Observa detenidamente algo interesante. Una vez te conectas al micro:bit el botón sigue con el texto: Connect to micro:bit, pero ya estás conectado. Es necesario informar al usuario que la funcionalidad del botón cambiará.

Para lograr lo anterior, al inicio de draw() y por tanto en cada frame se comprobará si el puerto está abierto o cerrado. Esto permitirá cambiar el texto del botón y además permitirá generar el evento microBitConnected para informarle al resto de la aplicación el estado del puerto.

const lineModule = [];
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;
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 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:
break;
case STATES.RUNNING:
break;
}
}

Ejecuta de nuevo la aplicación y verifica que funciona correctamente.

Ahora vamos a añadir la parte del código que lee los datos del micro:bit.

const lineModule = [];
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;
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 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:
break;
case STATES.RUNNING:
break;
}
}

Nota que solo se leen datos del micro:bit si el puerto está abierto ¿Por qué? ¿Podrías leer datos si el puerto está cerrado? ¿Qué pasaría si el puerto está cerrado y el micro:bit envía datos?

Ahora te pediré que te concentres. Para leer los datos que vienen del micro:bit, la aplicación primero pregunta si al menos hay un dato disponible para leer. Piensa que hay una parte del código de la biblioteca p5.webserial que se encarga de recibir los datos (como si fuera el portero de un edificio) y tu lo único que tienes que preguntar es si al menos ya tienes un dato para leer.

if (port.availableBytes() > 0)

​ Una vez sabes que al menos hay un dato, te quedas esperando que esté completa la LINEA que contiene todos los datos:

let data = port.readUntil("\n");

​​ Pero ¿Cómo sabes que ya está completa la línea? La función readUntil esperará a que llegue el byte que representa el fin de línea: "\n". Por tanto, el micro:bit tendrá que MARCAR esto en cada paquete de datos que envíe:

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

¿Puedes verlo? ¿Qué pasaría si el micro:bit no envía el "\n"?

Una vez recibes el paquete completo que envió el micro:bit, verificas si el dato es válido:

if (data)

​ Y procedes a eliminar de los datos el "\n", ya que este solo lo necesitas para marcar el fin del paquete de datos:

data = data.trim();

Ahora necesitas extraer de la cadena enviada cada uno de los datos, es decir, el valor de x, de y, el estado de A y de B:

let values = data.split(",");

Regresa al código del micro:bit:

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

Cada {} es reemplazada por el valor de las variables xValue, yValue, aState, bState respectivamente. Además, observa el carácter , que separa cada valor.

En resumen hasta ahora. El micro:bit al enviar esta cadena:

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

Está separando los valor por coma y marcando el fin del mensaje con un retorno de carro (\n) o enter.

A esto se le conoce como un PROTOCOLO. Si la aplicación en p5.js quiere recibir correctamente los datos tendrá que seguir el PROTOCOLO para poder extraer correctamente la información.

Retomemos:

let values = data.split(",");

Esta parte devuelve un ARREGLO de cadenas y la dirección de este arreglo se almacenará en values.

Considera ahora lo siguiente. Cuando estás comunicando dos aplicaciones es fundamental verificar la integridad de la información recibida. En este caso, el micro:bit está enviando 4 datos, por lo que la aplicación en p5.js debe verificar que efectivamente se recibieron los 4 datos:

if (values.length == 4) {

​ Para analizar lo que sigue debemos volver al código del micro:bit:

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

Como ya te dije antes, cada {} es reemplazada por el valor de las variables xValue, yValue, aState, bState respectivamente. Ten presente que toda la información está CODIFICAD en ASCII, es decir, si xValue es 100, realmente no estás enviando el byte que representa ese 100 sino que estás codificando cada número en ASCII. Por tanto, el 100 realmente se envía como tres bytes: 49, 48, 48.

Es por ello que esta parte del código:

microBitX = int(values[0]) + windowWidth/2;
microBitY = int(values[1]) + windowHeight/2;
microBitAState = values[2].toLowerCase() === "true";
microBitBState = values[3].toLowerCase() === "true";

Necesita convertir cada cadena en un número entero y además necesita convertir las cadenas que representan los estados de los botones en un valor booleano.

Te estarás preguntando ¿Por qué se suma windowWidth/2 y windowHeight/2 a los valores de x e y? Esta respuesta te toca analizarla a ti. No olvides escribir tus hallazgos en la bitácora.

Por último, la función updateButtonStates se encargará de actualizar el estado de los botones. Vamos a añadir esta función al código:

const lineModule = [];
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 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:
break;
case STATES.RUNNING:
break;
}
}

Ahora, si ejecutas la aplicación y conectas el micro:bit, podrás ver que al presionar el botón A se generará un evento de keypressed y al soltar el botón B se generará un evento de keyreleased. Además los valores de microBitX y microBitY se actualizarán con los valores que envía el micro:bit.

¿Cómo puedes verificar que los eventos de keypressed y keyreleased se están generando? Piensa en cómo puedes hacerlo y escribe tus hallazgos en la bitácora.

Ahora te pediré que analices el algoritmo updateButtonStates. ¿Qué hace? ¿Por qué es necesario almacenar el estado anterior de los botones? ¿Qué pasaría si no se almacenara el estado anterior?

Seguimos. Vamos a añadir comportamientos a cada uno de los estados de la aplicación. Recuerda, en un estado se espera la ocurrencia de uno o varios eventos. Eso puede generar acciones y posiblemente un cambio de estado.

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;

En cada frame se verifica si el micro:bit está conectado. Si es así, se prepara todo para el estado RUNNING. Te muestro el código completo incluyendo las variables de aplicación nuevas que utilizará el estado RUNNING, así como el resto del código del sketch original:

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

Observa el código original y el nuevo código. ¿Qué diferencias encuentras? ¿Qué pasó con algunos eventos del mouse? ¿Qué paso con la función relacionada con la barra de espacio del teclado?

Ejecuta la aplicación. Mira en la consola los mensajes que se generan. Nota en particular uno que dice: No se están recibiendo 4 datos del micro:bit ¿Qué significa esto? Analiza si este mensaje ocurre en varios frames o solo en uno. ¿Por qué? ¿Qué puedes hacer para solucionar este problema? (Ten presente que esta pregunta es abierta y no tiene una única respuesta).

Finalmente, juega con la aplicación y DIBUJA.

📤 Entrega: reporta los experimentos y hallazgos que vas encontrando a medida que analizas el código y responde las preguntas que te voy haciendo en el enunciado.

🚀 Tu solución:

Codigo ejemplo p5.js

https://p5js.org/examples/animation-and-variables-conditions/

Actividad 05: Integración de micro:bit con p5.js

https://editor.p5js.org/luffytorao721/sketches/w9oEIeKAE


2. Código Modificado para micro:bit

Cambios clave:

  • Control por acelerómetro: La posición X/Y del círculo se controla inclinando el micro:bit.
  • Botones A/B:
    • A: Cambia el color del círculo.
    • B: Reinicia la posición al centro.
let port, connectBtn;
let microBitConnected = false;
let microBitX, microBitY, microBitAState, microBitBState;
let circleX, circleY;
let circleSize = 50;
let circleColor;
function setup() {
createCanvas(800, 600);
circleX = width / 2;
circleY = height / 2;
circleColor = color(255, 0, 0);
// Configuración serial
port = createSerial();
connectBtn = createButton("Conectar micro:bit");
connectBtn.position(10, 10);
connectBtn.mousePressed(toggleMicrobitConnection);
}
function draw() {
background(240);
// Dibujar círculo
fill(circleColor);
noStroke();
ellipse(circleX, circleY, circleSize);
// Gestión de conexión
if (!port.opened()) {
connectBtn.html("Conectar micro:bit");
microBitConnected = false;
} else {
connectBtn.html("Desconectar");
microBitConnected = true;
readMicrobitData();
}
}
function toggleMicrobitConnection() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
function readMicrobitData() {
if (port.available() > 0) {
let data = port.readUntil("\n");
if (data) {
let values = data.trim().split(",");
if (values.length === 4) {
// Mapear acelerómetro (-1024 a 1024) a coordenadas del canvas
microBitX = map(int(values[0]), -1024, 1024, 0, width);
microBitY = map(int(values[1]), -1024, 1024, 0, height);
microBitAState = values[2] === "true";
microBitBState = values[3] === "true";
// Actualizar posición del círculo
circleX = microBitX;
circleY = microBitY;
// Botón A: Cambiar color
if (microBitAState) {
circleColor = color(random(255), random(255), random(255));
}
// Botón B: Reiniciar posición
if (microBitBState) {
circleX = width / 2;
circleY = height / 2;
}
}
}
}
}

3. Enlace al Sketch Modificado

p5.js Editor: https://editor.p5js.org/luffytorao721/sketches/8j9vPiOb2


4. Capturas de Pantalla

EstadoImagenDescripción
Esperando conexiónimageBotón “Conectar micro:bit” visible.
Círculo controlado por micro:bitimageEl círculo se mueve al inclinar el micro:bit.
Botón A presionadoEl círculo cambia de color aleatorio.
from microbit import *
import music
uart.init(baudrate=115200)
# Variables para controlar el cambio de color
color_index = 0
color_sequence = [(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255)] # Rojo, Verde, Azul, Amarillo, Magenta
while True:
x = accelerometer.get_x()
y = accelerometer.get_y()
a = button_a.was_pressed() # Cambiado a was_pressed para detectar pulsación única
b = button_b.is_pressed()
# Cambio de color con botón A
if a:
color_index = (color_index + 1) % len(color_sequence)
music.play(music.BA_DING) # Sonido de confirmación
display.show(Image.HAPPY) # Feedback visual
sleep(200)
display.clear()
# Envía datos incluyendo el componente de color actual
uart.write("{},{},{},{},{},{},{}\n".format(
x,
y,
a,
b,
color_sequence[color_index][0], # Componente R
color_sequence[color_index][1], # Componente G
color_sequence[color_index][2] # Componente B
))
sleep(100)

5. Funcionamiento Detallado

  1. Acelerómetro:

    • Inclinar el micro:bit izquierda/derecha mueve el círculo en el eje X.
    • Inclinar adelante/atrás mueve el círculo en el eje Y.
  2. Botones:

    • A: Genera un nuevo color RGB aleatorio.
    • B: Centra el círculo en el canvas.
  3. Conexión Serial:

    • El botón alterna entre conectar/desconectar el micro:bit.
    • Los datos se leen a 115200 baudios.

6. Posibles Mejoras

  • Límites del canvas: Evitar que el círculo salga del área visible.
  • Efectos visuales: Añadir trazo al mover el círculo.
  • Feedback auditivo: Sonido al presionar los botones.
  • Cambio color: no cambia aveces

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, vas a aplicarlos en un proyecto.

Actividad 05

El momento de aplicar lo aprendido

🎯 Enunciado: selecciona uno de los ejemplos que exploraste en la actividad 1 y realiza las modificaciones necesarias para que interactúe con el micro:bit. El micro:bit estará ejecutando este programa:

# 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

📤 Entrega:

  • Enlace a la aplicación original sin modificar, pero recreada en el editor de p5.js.
  • 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 05: Integración de micro:bit con p5.js


1. Aplicación Original

Enlace: https://p5js.org/examples/animation-and-variables-animation-with-events/ Descripción: Muestra un círculo que rebota en los bordes del canvas y cambia de color al hacer clic.


2. Código Modificado para micro:bit

Cambios clave:

  • Control por acelerómetro: La posición X/Y del círculo se controla inclinando el micro:bit.
  • Botones A/C:
    • A: Cambia el color del círculo.
let port, connectBtn;
let microBitConnected = false;
let microBitX, microBitY, microBitAState, microBitBState;
let circleX, circleY;
let circleSize = 50;
let circleColor;
function setup() {
createCanvas(800, 600);
// Initialize the circle in the center
circleX = width / 2;
circleY = height / 2;
circleColor = color(255, 0, 0); // Start with red color
// Setup serial connection
port = createSerial();
connectBtn = createButton("Conectar micro:bit");
connectBtn.position(10, 10);
connectBtn.mousePressed(toggleMicrobitConnection);
}
function draw() {
background(240);
// Draw the circle
fill(circleColor);
noStroke();
ellipse(circleX, circleY, circleSize);
// Manage micro:bit connection
if (!port.opened()) {
connectBtn.html("Conectar micro:bit");
microBitConnected = false;
} else {
connectBtn.html("Desconectar");
microBitConnected = true;
readMicrobitData();
}
}
function toggleMicrobitConnection() {
if (!port.opened()) {
port.open("MicroPython", 115200);
} else {
port.close();
}
}
function readMicrobitData() {
if (port.available() > 0) {
let data = port.readUntil("\n");
if (data) {
let values = data.trim().split(",");
if (values.length === 7) {
// Map accelerometer data to canvas coordinates
microBitX = map(int(values[0]), -1024, 1024, 0, width);
microBitY = map(int(values[1]), -1024, 1024, 0, height);
microBitAState = values[2] === "true";
microBitBState = values[3] === "true";
// Update circle position
circleX = microBitX;
circleY = microBitY;
// Button A: Change color
if (microBitAState) {
circleColor = color(int(values[4]), int(values[5]), int(values[6])); // Use the color components from micro:bit
}
// Button B: Reset position
if (microBitBState) {
circleX = width / 2;
circleY = height / 2;
}
}
}
}
}

Code Micro:bit

from microbit import *
import music
uart.init(baudrate=115200)
# Variables para controlar el cambio de color
color_index = 0
color_sequence = [(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255)] # Rojo, Verde, Azul, Amarillo, Magenta
while True:
x = accelerometer.get_x()
y = accelerometer.get_y()
a = button_a.was_pressed() # Cambiado a was_pressed para detectar pulsación única
b = button_b.is_pressed()
# Cambio de color con botón A
if a:
color_index = (color_index + 1) % len(color_sequence)
music.play(music.BA_DING) # Sonido de confirmación
display.show(Image.HAPPY) # Feedback visual
sleep(200)
display.clear()
# Envía datos incluyendo el componente de color actual
uart.write("{},{},{},{},{},{},{}\n".format(
x,
y,
a,
b,
color_sequence[color_index][0], # Componente R
color_sequence[color_index][1], # Componente G
color_sequence[color_index][2] # Componente B
))
sleep(100)

3. Enlace al Sketch Modificado

p5.js Editor: https://editor.p5js.org/luffytorao721/sketches/8j9vPiOb2


4. Capturas de Pantalla

EstadoImagenDescripción
Esperando conexiónimageBotón “Conectar micro:bit” visible.
Círculo controlado por micro:bitimageEl círculo se mueve al inclinar el micro:bit.
Botón A presionadoEl círculo cambia de color aleatorio.

5. Funcionamiento Detallado

  1. Acelerómetro:

    • Inclinar el micro:bit izquierda/derecha mueve el círculo en el eje X.
    • Inclinar adelante/atrás mueve el círculo en el eje Y.
  2. Botones:

    • A: Genera un nuevo color RGB aleatorio.
    • B: Centra el círculo en el canvas.
  3. Conexión Serial:

    • El botón alterna entre conectar/desconectar el micro:bit.
    • Los datos se leen a 115200 baudios.

6. Posibles Mejoras

  • Límites del canvas: Evitar que el círculo salga del área visible.
  • Efectos visuales: Añadir trazo al mover el círculo, el color aveces no cambia.
  • Feedback auditivo: Sonido al presionar los botones.

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 06

Consolidación de lo aprendido

Esta parte del proceso de aprendizaje es fundamental. Trata de hacerla de memoria, sin consultar las notas en la bitácora, y luego compara tus respuestas con tus notas. En términos de aprendizaje, este ejercicio se llama recuperación y es muy efectivo para fijar el conocimiento en la memoria a largo plazo.

🔖 Recuerda: trata de ser lo más específico posible en tus respuestas. Esta no es una actividad de relleno, sino una oportunidad para reflexionar sobre lo que has aprendido y fijar en memoria de largo plazo los conceptos y técnicas aprendidas.

🎯 Enunciado: en esta actividad vas a recordar los conceptos y técnicas aprendidas en esta unidad. Para ello te pediré que contestes en tu bitácora las siguientes preguntas y justifica lo que dices mostrando solo fragmentos de código de tus actividades donde estés aplicando los conceptos y técnicas.

  1. ¿Qué es un protocolo de comunicación y por qué es importante en la comunicación serial?
  2. ¿Por qué se separan los datos con comas en el protocolo ASCII que exploramos?
  3. ¿Por qué es necesario terminar los datos con un carácter que marque el fin del mensaje?
  4. ¿Por qué fue necesario usar una máquina de estados en la aplicación modificada de p5.js?
  5. ¿Cómo se formatean los datos en el micro:bit para ser enviados por el puerto serial?
  6. ¿Qué significa que los datos enviados por el micro:bit están codificados en ASCII?
  7. ¿Por qué es necesario en la aplicación de p5.js preguntar si hay bytes disponibles en el puerto serial antes de leerlos?
if (port.availableBytes() > 0) {
let data = port.readUntil("\n");

¿Qué pasa si esto no se hace?
8. ¿Cómo se elimina el retorno de carro o salto de línea de un string en p5.js?
9. Si una cadena tiene información separada por espacios y quieres dividir dicha información en varias cadenas individuales ¿Qué función de p5.js usarías?
10. Por qué es necesario en la aplicación del caso de estudio convertir las cadenas a números enteros antes de usarlas en el sketch de p5.js?
11. Si el micro:bit tiene los siguientes datos xValue: 123, yValue: 756, aState: False, bState: True ¿Qué bytes se enviarían por el puerto serial?
12. ¿Qué aprendiste nuevo del micro:bit que no sabías antes?
13. ¿Qué aprendiste nuevo de p5.js que no sabías antes?

📤 Entrega: responde estas preguntas en tu bitácora y justifica tus respuestas mostrando fragmentos de código.

🚀 Tu solución:

Bitácora - Consolidación de Conceptos

1. Protocolo de comunicación

Es un conjunto de reglas para intercambiar datos. En comunicación serial es esencial porque:

  • Define cómo se estructuran los datos
  • Permite que ambos dispositivos (micro:bit y p5.js) interpreten correctamente la información
# En micro:bit (formato: x,y,A,B\n)
data = "{},{},{},{}\n".format(xValue, yValue, aState, bState)

2. Separación por comas

Las comas actúan como delimitadores para:

  • Identificar valores individuales en una cadena
  • Permitir el uso de split() en p5.js
// En p5.js:
let values = data.trim().split(","); // Divide "123,456,true,false" en array

3. Carácter de fin de mensaje (\n)

Es necesario porque:

  • Indica cuándo un paquete de datos está completo
  • Permite usar readUntil("\n") para lectura sincronizada
let data = port.readUntil("\n"); // Lee hasta encontrar salto de línea

4. Máquina de estados

Se usó para manejar:

  • Conexión/desconexión del micro:bit
  • Transiciones entre modos de operación
const STATES = {
WAIT_CONNECTION: "WAIT",
DRAWING: "DRAW"
};

5. Formateo en micro:bit

Los datos se convierten a string con formato fijo:

uart.write("{},{},{},{}\n".format(x, y, a, b))

6. Codificación ASCII

Significa que los números se envían como caracteres (ej: 123 → “1”,“2”,“3”), no como valores binarios.

# El valor 123 se envía como tres bytes: 49, 50, 51 (ASCII de '1','2','3')

7. Verificación de bytes disponibles

Evita bloquear el programa si no hay datos:

if (port.availableBytes() > 0) { // Solo leer si hay datos
let data = port.readUntil("\n");
}

Sin esto: El programa podría congelarse esperando datos.

8. Eliminar retorno de carro

Se usa trim():

data = data.trim(); // Elimina \n y espacios

9. División por espacios

Se usaría split() con espacio como delimitador:

let partes = cadena.split(" "); // Para "hola mundo" → ["hola", "mundo"]

10. Conversión a números

Los datos llegan como texto (ej: “123”), pero necesitamos valores numéricos para cálculos:

microBitX = int(values[0]); // Convierte "123" → 123

11. Bytes enviados

Para 123,756,False,True\n se enviarían (en hexadecimal):

31 32 33 2C 37 35 36 2C 46 61 6C 73 65 2C 54 72 75 65 0A

(ASCII de: 1,2,3,,,7,5,6,,F,a,l,s,e,,T,r,u,e,\n)

12. Aprendizaje micro:bit

  • Uso de uart.init() para configuración serial
  • Envío continuo de datos con while True
uart.init(baudrate=115200)
while True:
uart.write(data)
sleep(100)

13. Aprendizaje p5.js

  • Uso de createSerial() para comunicación serial en navegador
  • Mapeo de valores con map() para coordenadas:
x = map(valor, -1024, 1024, 0, width); // Adapta rango del acelerómetro

Reflexión final:
Esta unidad me ayudó a entender cómo diseñar protocolos simples pero robustos para comunicación entre dispositivos, y la importancia de la sincronización en sistemas embebidos. Los conceptos de máquinas de estados y parsing de datos son aplicables a muchos otros proyectos.

Actividad 07

Autoevaluación

🎯 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 te puede ser útil en el futuro en tu vida 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 - Unidad de Comunicación Serial entre micro:bit y p5.js

1. ¿Qué aprendí en esta unidad?

  • Protocolos de comunicación serial: Entendí cómo estructurar datos (formato CSV + \n) para que p5.js y micro:bit se comuniquen eficientemente.
    # Código micro:bit (formato: x,y,A,B\n)
    data = "{},{},{},{}\n".format(x, y, a, b)
  • Máquinas de estados: Aprendí a gestionar flujos complejos (ej: conexión, dibujo, desconexión).
    const STATES = { WAIT_CONNECTION: "WAIT", RUNNING: "RUN" };
  • Mapeo de valores: Convertir datos del acelerómetro (-1024 a 1024) a coordenadas de pantalla.
    x = map(valor, -1024, 1024, 0, width);

Puntuación: 4/5 (Falta profundizar en manejo de errores avanzados)


2. ¿Qué fue lo más difícil?

  • Sincronización serial: Entender por qué los datos se corrompían si no se usaba \n como delimitador.
  • Detección de pulsaciones: Implementar lógica para detectar el flanco de subida (was_pressed vs is_pressed).

Dificultad: 4/5 (Requiere comprensión de timing y buffers)


3. ¿Qué fue lo más fácil?

  • Integración visual en p5.js: Dibujar círculos y actualizar su posición con los datos del micro:bit.
    ellipse(microBitX, microBitY, 50);

Facilidad: 5/5 (Similar a otros proyectos previos con p5.js)


4. Tiempo dedicado

  • Total: 5 sesiones (3 en clase, 2 fuera).
  • ¿Fue suficiente?: 3/5 (Necesité tiempo extra para depurar la comunicación serial)

5. ¿Qué mejorarías?

  • Pruebas incrementales: Validar cada función (ej: conexión, parsing) por separado antes de integrarlas.
  • Documentar errores: Registrar soluciones a problemas comunes (ej: “No se están recibiendo 4 datos”).

Mejora potencial: +1.5 puntos en eficiencia.


Revisión de Aplicación Profesional para Ingeniería de Entretenimiento Digital

6. Aplicación en Entretenimiento Digital

Como futuro ingeniero en entretenimiento digital, lo aprendido en esta unidad se aplica directamente a:

  1. Desarrollo de Interfaces Físicas para Juegos:

    • Creación de controles personalizados usando micro:bit (ej: mandos con gestos o botones físicos para juegos indie).
    # Ejemplo: Detección de gestos para controlar personajes
    if accelerometer.current_gesture() == "shake":
    uart.write("JUMP\n")
  2. Experiencias Interactivas Inmersivas:

    • Integración de sensores físicos (acelerómetro, botones) con entornos virtuales en p5.js/Three.js para instalaciones artísticas.
    // En p5.js: Mover objetos 3D con datos del micro:bit
    if (microBitAState) avatar.jump();
  3. Prototipado Rápido de Dispositivos:

    • Validación de mecánicas interactivas para parques temáticos o escape rooms (ej: puzzles que requieren inclinar un dispositivo).
  4. Educación Tecnológica:

    • Diseño de talleres sobre interacción hardware/software para niños o adolescentes.

Relevancia: 5/5
(Estas habilidades son clave para innovar en gaming, realidad aumentada y experiencias interactivas multisensoriales).


Ejemplo Concreto: Juego con Controles Físicos

Caso de Uso: Un juego de plataformas donde:

  • Inclinar el micro:bit mueve al personaje.
  • Botón A salta.
  • Botón B activa habilidades.

Código micro:bit:

while True:
x = accelerometer.get_x()
a = button_a.was_pressed()
b = button_b.was_pressed()
uart.write(f"{x},{a},{b}\n")
sleep(50)

Código p5.js:

function readMicrobitData() {
let data = port.readUntil("\n");
let [x, a, b] = data.split(",");
personaje.velocityX = map(x, -1024, 1024, -5, 5);
if (a === "true") personaje.jump();
if (b === "true") personaje.usePower();
}

¿Te gustaría que desarrolle otro ejemplo aplicado a tu campo? Por ejemplo: controlar efectos visuales en vivo con sensores o crear instrumentos musicales digitales.


7. ¿Qué te gustaría aprender después?

  • Comunicación inalámbrica (radio/Wi-Fi) entre micro:bit y p5.js.
  • Procesamiento de señales: Filtrar datos del acelerómetro para movimientos más suaves.

8. Estado de ánimo

  • Inicio: 3/5 (Frustración con errores de conexión)
  • Final: 4.5/5 (Satisfacción al ver el círculo moverse con el micro:bit)

9. Motivación

  • Mantenimiento: 4/5 (La curiosidad por hacer funcionar el sistema me impulsó)
  • Dificultades: Bajó al ver errores seriales, pero se recuperó con la solución.

10. Satisfacción con desempeño

  • Resultado final: 4/5 (Logré los objetivos, pero el código podría ser más modular)
  • Autoexigencia: Quise implementar más efectos visuales (ej: trail de partículas).

Promedio final: 4.1/5

Reflexión: Esta unidad consolidó mi capacidad para integrar hardware y software, aunque debo mejorar en documentación y pruebas tempranas. Estoy motivado para proyectos más complejos con comunicación inalámbrica.

Actividad 08

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 respuestas con argumentos precisos.

🚀 Tu solución:

Reflexión fresca sobre la unidad

Esta unidad me dejó con buen sabor de boca – eso de hacer “hablar” al micro:bit con p5.js tiene algo mágico. Pero para darle más chispa a futuro, se me ocurren estas dinámicas innovadoras:

1. “Hackathons” express (45 min)

  • Propuesta: Al final de la unidad, hacer equipos para crear un prototipo funcional (ej: controlar un instrumento digital o mini-juego)
  • Diferencial: Premiar no solo el código, sino la experiencia de usuario (qué tan intuitiva/intensa es la interacción)
  • Ejemplo:
# En micro:bit - Si inclinas > 500, dispara evento
if accelerometer.get_x() > 500:
uart.write("POWER_UP\n")

2. Sesión de “remix colaborativo”

  • Cada estudiante modifica el código base (ej: añade un botón C virtual en p5.js) y el siguiente lo mejora (feedback en cadena)

3. Micro-proyectos con restricciones creativas

  • Desafíos como: “Usa solo el eje Y del acelerómetro para controlar 2 elementos simultáneos”
  • Técnica aplicada:
// En p5.js - Un valor controla posición y tamaño
let y = map(microBitY, -1024, 1024, 0, height);
ellipse(width/2, y, y/2);

4. Conexión con artistas digitales

  • Invitar a creadores que usen micro:bit en instalaciones para mostrar aplicaciones reales fuera del aula

Lo que más me motivó:
Ver que un concepto técnico (serial communication) se transforma en interacción tangible. Con estas dinámicas, ese “click” podría ser aún más memorable.

pues… sería solo eso.