Guía para la IA tutora · ESP32 + OLED en Wokwi · edición Halloween
El ESP32 y la pantalla OLED ya están listos en el simulador. Aquí aprendes a usarlos: encender Wokwi, pausar tu programa dentro del ESP32 y ver a la vez las variables y la pantalla. Después, practica con retos de Halloween.
Explica en español, da un solo paso, pide el resultado y espera antes del siguiente. Ante un fallo, pide el mensaje completo y cambia una sola cosa por vez.
Preguntas y pistas, no soluciones. No resuelve ejercicios ni exámenes, ni escribe lógica de robots sin permiso docente explícito. Esta demo diagnostica el entorno; no autoriza resolver el trabajo de Luka.
No flashear placas físicas, no tocar puertos USB, no instalar machine con pip. Para APIs nuevas verificar en Context7 /micropython/micropython o declarar la limitación.
Antes de empezar
Un pin es una patita metálica de la placa. Cada una tiene una etiqueta impresa (3V3, GND, 21…) y un trabajo. Conectar es unir con un cable el pin de un aparato con el pin correcto del otro. En nuestro montaje bastan 4 cables entre el ESP32 y la pantalla OLED.
diagram.json. Los colores de los cables son los que usa Wokwi. Los puntitos que se mueven muestran hacia dónde viajan la energía y los datos.Le da corriente a la pantalla. El ESP32 trabaja a 3,3 voltios, por eso usamos 3V3. Es como el enchufe.
El camino de regreso de la corriente. Sin él no hay circuito: la energía necesita ir y volver. Los dos aparatos deben compartir GND.
Por aquí viajan los mensajes: qué letras dibujar y dónde.
Marca el ritmo, como un metrónomo: en cada «tic» se lee un trocito del mensaje que va por SDA.
I2C es una «calle» de dos cables donde pueden vivir varios aparatos. Cada uno tiene su número de casa, la dirección. Nuestra OLED vive en la 0x3C (está en diagram.json). El ESP32 escribe a esa dirección y solo la pantalla responde.
El 21 no es «la patita 21 contando desde arriba»: es el pin que tiene 21 impreso al lado (GPIO 21). Busca la etiqueta, no cuentes.
print()?Los pines TX (enviar) y RX (recibir) van al monitor serie de Wokwi. Por ahí llega a tu pantalla el texto de print("vuelta", contador, …).
Así lo dice oled_demo.py. Si el cable está en el 21 pero el código dice otro número, el ESP32 habla por una patita donde no hay nadie escuchando.
from machine import Pin, I2C import ssd1306 i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000) display = ssd1306.SSD1306_I2C(128, 64, i2c)
sda=Pin(21) por Pin(19) sin mover el cable, ¿qué verás?La pantalla se queda negra: el ESP32 manda los datos por el 19, pero la OLED escucha en el 21. Código y cables ya no coinciden.
La pantalla no enciende. La corriente entra por VCC pero no tiene por dónde volver: el circuito queda abierto.
No. El ESP32 enviaría el ritmo por el cable de datos y los datos por el del ritmo. Hay que devolver los cables a su sitio o cambiar también el código.
Porque la línea 27 llama a mostrar_contador, que termina con show(): en ese momento los datos viajan por SDA y SCL y la pantalla se actualiza.
En Wokwi todo es simulado: puedes equivocarte con los cables sin romper nada. Con una placa real, desconecta la energía antes de mover cables. ¿Quieres saber qué hacen los otros 34 pines? Mira el mapa de pines al final.
Cada vez que vayas a programar
Todo ya está instalado. Este es el recorrido de cada sesión: abrir, encender el simulador, lanzar el programa, pausarlo y mirar qué pasa por dentro.
En VS Code: Archivo → Abrir carpeta y elige la carpeta luka-wokwi-oled-debug entera, no solo MicroPython-debug/.
Si hay novedades del profe, tráelas antes de empezar, desde la terminal de VS Code:
git pull
MicroPython-debug/wokwi.toml.Verás el ESP32 y la pantalla. Que esté negra es normal: todavía no se ha dibujado nada. Deja el simulador a la vista; si lo ocultas, Wokwi se puede pausar.
practica.py.Un mensaje de reintento «(1/3)» al arrancar es normal. Espera el resultado final.
Un breakpoint es una señal de «para aquí». Haz clic a la izquierda del número de línea: aparece un punto rojo. Cuando el programa llega a esa línea, se congela antes de ejecutarla.
Mientras está en pausa puedes mirar tres cosas a la vez:
Pon los breakpoints dentro de funciones, como practica(). En líneas sueltas fuera de una función, a veces no se detiene.
Pruébalo aquí con la demo. Está pausada en la línea 29 de oled_demo.py, justo después de dibujar:
Antes de pulsar F5, predice el siguiente valor de contador.
Antes de cada F5, di en voz alta qué esperas ver. Luego compara con Variables y con la pantalla.
El ESP32 simulado no guarda nada al apagarse: tu código vive en practica.py y se vuelve a enviar cada vez que lanzas.
| Lo que ves | Qué hacer |
|---|---|
Error de machine o la palabra debugpy | Se lanzó Python del PC, no el del ESP32. Detén y elige un perfil REMOTA GHI en el panel Ejecutar y depurar. |
| Falla la tarea antes de lanzar | No elijas Debug Anyway. Lee la terminal de la tarea y comprueba que Wokwi está encendido con MicroPython-debug/wokwi.toml. |
Lost connection to the board | Comprueba que el simulador está visible y sin pausar. Detén todo (paso 6) y vuelve a empezar desde el paso 2. |
| Pantalla negra o dibujo viejo | Si la pausa está antes de show(), es normal. Avanza con F10 hasta pasar show(). |
| Recargaste VS Code o reiniciaste Wokwi | La conexión anterior ya no sirve. Detén la depuración y lanza el perfil otra vez. |
| Nada de lo anterior | Copia el mensaje completo del error y enséñaselo a tu tutor. |
Después de la demo
Un poco de teoría, practicar, una idea nueva, practicar otra vez. Cada práctica usa lo de antes y le suma algo. Se trabaja en MicroPython-debug/practica.py.
git pull para traer practica.py.MicroPython-debug/practica.py.def practica(display): por el código de la práctica.Antes de ejecutar, pide a Luka que prediga qué verá. Da las pistas de una en una y solo cuando las pida o se atasque. No completes los huecos por Luka: si se equivoca, pregúntale qué esperaba ver y qué vio.
La OLED tiene 128 puntitos de ancho y 64 de alto. Cada puntito (píxel) tiene una dirección: x cuenta de izquierda a derecha y y de arriba hacia abajo. El (0, 0) es la esquina de arriba a la izquierda.
Tres órdenes bastan para empezar:
1 es «encendido».Piensa en una hoja: fill y text dibujan en la hoja; show() la pega en la ventana.
def practica(display): display.fill(0) display.text("Feliz Halloween", 0, 0, 1) display.text(___, 0, 20, 1) # escribe tu nombre display.show()
El texto va entre comillas, igual que "Feliz Halloween".
Si no aparece nada, revisa que show() siga al final.
Comprueba: arriba «Feliz Halloween» y debajo tu nombre. ¿Qué número cambiarías para bajar tu nombre hasta la última línea?
Además de letras, la pantalla puede dibujar puntos, líneas y rectángulos. Todos empiezan por la esquina de arriba a la izquierda (x, y).
Igual que con el texto: se dibuja en la hoja y se ve al llamar a show().
def practica(display): display.fill(0) display.rect(44, 20, 40, 36, 1) # cuerpo: empieza en y = 20 display.fill_rect(60, 12, 8, ___, 1) # tallo: empieza en y = 12 display.show()
El hueco es el alto del tallo.
El tallo empieza en y = 12 y debe tocar el cuerpo, que empieza en y = 20. ¿Cuántos puntitos hay entre los dos?
Comprueba: un cuadrado hueco con un tallo encima, sin espacio entre los dos. ¿Qué pasa si el alto es muy grande?
Cada letra mide 8 × 8 puntitos: caben 16 letras por línea (128 ÷ 8) y 8 líneas (64 ÷ 8). Para centrar un texto se resta lo que ocupa del ancho total y se reparte a los dos lados:
Ejemplo: «Hola» tiene 4 letras → (128 − 32) ÷ 2 = 48. Los espacios y signos también cuentan.
Con las formas pasa igual. El cuerpo de la calabaza empieza en x = 44 y mide 40: ocupa de 44 a 83. El ojo izquierdo empieza en x = 52, o sea 8 puntitos dentro del borde. El derecho debe terminar 8 antes del borde derecho; como mide 6 de ancho, también hay que restar esos 6:
def practica(display): display.fill(0) display.rect(44, 20, 40, 36, 1) # cuerpo display.fill_rect(60, 12, 8, 8, 1) # tallo display.fill_rect(52, 28, 6, 6, 1) # ojo izquierdo display.fill_rect(___, 28, 6, 6, 1) # ojo derecho display.hline(52, 46, ___, 1) # boca centrada display.text("BOO!", ___, 0, 1) # texto centrado arriba display.show()
Para el ojo derecho, usa la cuenta de la profundización.
La boca empieza 8 puntitos dentro del borde izquierdo (x = 52). Debe terminar 8 antes del borde derecho, en x = 75. ¿Cuántos puntitos hay de 52 a 75, contando los dos?
Para «BOO!», cuenta sus letras (el signo también) y usa la fórmula de centrar.
Comprueba: los dos ojos a la misma distancia de los lados, la boca centrada y «BOO!» centrado arriba. Reto: hazle dientes con pixel.
forCuando algo se repite, no hace falta escribirlo muchas veces. for repite las líneas que tiene debajo con sangría (4 espacios a la derecha).
for i in range(3): display.pixel(i * 10, 0, 1) # se repite 3 veces
range(3) le da a i los valores 0, 1 y 2: empieza en 0 y no llega al 3. En cada vuelta i vale distinto, así que i * 10 pone cada punto en otro sitio: x = 0, 10, 20.
def practica(display): display.fill(0) for i in range(___): # queremos 4 murciélagos display.fill_rect(i * 30 + 4, 20, 20, 10, 1) display.show()
range(n) repite n veces.
Pon un breakpoint en la línea de fill_rect y mira cuánto vale i en cada parada con F5.
Comprueba: 4 rectángulos en fila. Antes de ejecutar, predice: ¿en qué x empieza el tercero? ¿Por qué show() está fuera del for?
range con inicio, fin y paso, y esperartext() solo acepta texto.def practica(display): for numero in range(___, ___, -1): # 5, 4, 3, 2, 1 display.fill(0) display.text("Halloween en", 16, 10, 1) display.text(str(numero), 60, 30, 1) display.show() time.sleep(1) display.fill(0) display.text("BOO!", 48, 28, 1) display.show()
El primer hueco es donde empieza la cuenta.
El fin no se incluye. Si quieres que el último sea 1, ¿dónde tienes que parar?
Comprueba: 5, 4, 3, 2, 1 y luego «BOO!». ¿Por qué el «BOO!» está fuera del for?
Una variable es una caja con nombre que guarda un valor. Puede cambiar: dulces = dulces + 2 significa «lo que había en la caja, más 2, y lo guardo otra vez en la caja».
El depurador te deja ver la caja por dentro: en cada pausa, el panel Variables muestra cuánto vale ahora. Así compruebas si lo que pensabas es lo que pasa de verdad.
def practica(display): dulces = 0 for casa in range(1, 5): dulces = dulces + casa # cada casa da tantos dulces como su número display.fill(0) display.text("Casa " + str(casa), 0, 0, 1) display.text("Dulces: " + str(dulces), 0, 20, 1) display.show() time.sleep(1)
Esta vez no hay huecos: el reto es predecir. Antes de ejecutar, haz en papel una tabla con dos columnas, casa y dulces, y llénala vuelta por vuelta.
Primera vuelta: casa vale 1 y dulces valía 0. ¿Cuánto queda?
Pon un breakpoint en la línea de display.show() y compara tu tabla con el panel Variables en cada F5.
Comprueba: tu tabla coincide con Variables y con la pantalla en las 4 casas. Reto: ¿qué cambiarías para que cada casa dé siempre 2 dulces?
Hasta ahora todo se dibujaba en la hoja y se veía con show(). Algunas funciones son distintas: son órdenes directas a la pantalla y actúan al instante, sin show().
Por eso invertir es instantáneo: no cambia el dibujo de la hoja, cambia cómo la pantalla lo muestra.
def practica(display): display.fill(0) display.text("BOO!", 48, 28, 1) display.show() for vez in range(___): # parpadea 3 veces display.invert(1) time.sleep(0.5) display.invert(___) time.sleep(0.5)
Mira la profundización: un valor invierte, el otro devuelve la pantalla a la normalidad.
Pon un breakpoint en time.sleep(0.5) y avanza con F5: ¿cambia la pantalla aunque no haya show()?
Comprueba: «BOO!» parpadea 3 veces y termina con fondo negro. ¿Qué pasa si quitas el segundo time.sleep?
Junta lo aprendido en una sola escena: una cuenta regresiva, después aparece la calabaza con cara y, al final, la pantalla parpadea. Antes de escribir código, dibuja en papel la cuadrícula y apunta las coordenadas.
Comprueba: explica a tu tutor en qué orden pasan las cosas y por qué, usando un breakpoint para enseñarlo.
Para consultar
La placa del ejemplo es una ESP32 DevKit C V4 y tiene 38 pines, 19 por lado. Aquí está qué hace cada uno. Los colores dicen si puedes usarlo tranquilo en tus proyectos.
Todos los pines de datos trabajan a 3,3 V. Con una placa real, nunca les metas 5 V. En Wokwi puedes probar sin miedo: no se rompe nada.
| Pin | Lado | Qué hace | Tipo |
|---|---|---|---|
| 3V3 | izq. | Saca 3,3 voltios para alimentar sensores y pantallas.VCC de la OLED | Energía |
| EN | izq. | Reinicio. Si se une a GND, la placa se reinicia; es lo que hace el botón EN. No le conectes nada. | Cuidado al arrancar |
| VP | izq. | GPIO 36. Solo entrada: lee voltajes (analógico), por ejemplo un potenciómetro. | Solo entrada |
| VN | izq. | GPIO 39. Solo entrada analógica, igual que VP. | Solo entrada |
| 34 | izq. | Solo entrada, puede leer voltajes (analógico). No tiene resistencia interna de pull-up. | Solo entrada |
| 35 | izq. | Solo entrada analógica, igual que el 34. | Solo entrada |
| 32 | izq. | Libre. Además lee voltajes (analógico) y detecta el toque de un dedo (táctil). | Libres |
| 33 | izq. | Libre. Analógico y táctil, como el 32. | Libres |
| 25 | izq. | Libre. Tiene DAC: puede sacar voltajes intermedios, útil para sonidos sencillos. | Libres |
| 26 | izq. | Libre. También tiene DAC, como el 25. | Libres |
| 27 | izq. | Libre. Analógico y táctil. | Libres |
| 14 | izq. | Al encender manda una señal rápida (PWM) un momento. Después se puede usar normal. | Cuidado al arrancar |
| 12 | izq. | Pin de arranque: si está en alto al encender, la placa puede no arrancar. | Cuidado al arrancar |
| GND | izq. | Tierra. Todos los GND están unidos entre sí.GND de la OLED | Tierra |
| 13 | izq. | Libre. Analógico y táctil. | Libres |
| D2 | izq. | GPIO 9. Conectado a la memoria flash interna. No usar. | No tocar |
| D3 | izq. | GPIO 10. Memoria flash. No usar. | No tocar |
| CMD | izq. | GPIO 11. Memoria flash. No usar. | No tocar |
| 5V | izq. | 5 voltios que llegan del USB. Para aparatos de 5 V; ojo, los pines de datos solo aguantan 3,3 V. | Energía |
| GND | der. | Tierra, igual que los demás GND. | Tierra |
| 23 | der. | Libre. En el bus SPI hace de MOSI (datos que salen hacia el aparato). | Libres |
| 22 | der. | Libre. Muy usado como SCL (reloj) de I2C.SCL de la OLED | Libres |
| TX | der. | GPIO 1. Envía datos al PC: por aquí sale lo que escribes con print(). Mejor no usarlo. | Conexión con el PC |
| RX | der. | GPIO 3. Recibe datos del PC. Mejor no usarlo. | Conexión con el PC |
| 21 | der. | Libre. Muy usado como SDA (datos) de I2C.SDA de la OLED | Libres |
| GND | der. | Tierra. | Tierra |
| 19 | der. | Libre. En SPI hace de MISO (datos que llegan del aparato). | Libres |
| 18 | der. | Libre. En SPI hace de SCK (reloj). | Libres |
| 5 | der. | Pin de arranque. Se puede usar, pero sin forzarlo a alto o bajo al encender. En SPI suele ser CS. | Cuidado al arrancar |
| 17 | der. | Libre. Puede ser TX de un segundo puerto serie (UART2). | Libres |
| 16 | der. | Libre. Puede ser RX del segundo puerto serie (UART2). | Libres |
| 4 | der. | Libre. Analógico y táctil. | Libres |
| 0 | der. | Es el botón BOOT: si está en bajo al encender, la placa entra en modo de cargar firmware. | Cuidado al arrancar |
| 2 | der. | Pin de arranque: debe estar en bajo o libre para poder cargar firmware. | Cuidado al arrancar |
| 15 | der. | Pin de arranque: cambia los mensajes que la placa escribe al encender. | Cuidado al arrancar |
| D1 | der. | GPIO 8. Memoria flash. No usar. | No tocar |
| D0 | der. | GPIO 7. Memoria flash. No usar. | No tocar |
| CLK | der. | GPIO 6. Memoria flash. No usar. | No tocar |
Siguiente nivel
El ESP32 puede conversar con sensores, pantallas y otras placas. Cada uno habla un «idioma» (protocolo). Aquí verás cuatro, cada uno con su circuito en Wokwi, su código completo y una práctica. Todo está en la carpeta chips/ del repositorio (haz git pull si no la tienes).
| Protocolo | Cables de datos | ¿Cómo sabe a quién le habla? | Chip de ejemplo |
|---|---|---|---|
| Un cable | 1: datos | Solo hay un chip en ese cable | DHT22 (temperatura) |
| I2C | 2: SDA (datos) y SCL (reloj) | Cada chip tiene una dirección, como un número de casa | MPU6050 (movimiento) y la OLED |
| SPI | 3: MOSI (datos), SCK (reloj) y CS | Un cable CS por chip: «te hablo a ti» | MAX7219 (matriz de LEDs) |
| UART | 2: TX (envía) y RX (recibe), cruzados | Solo hay dos: uno en cada punta | Otra placa, un GPS, el PC |
Para cualquier chip, la receta siempre es la misma:
El DHT22 mide temperatura y humedad. Usa un solo cable de datos: el ESP32 le pide una medida y el sensor responde por el mismo cable, por turnos.
También lleva la OLED conectada como siempre (21, 22, 3V3 y GND) para mostrar los valores.
MicroPython ya trae el idioma del DHT22: se llama dht.
import dht from machine import Pin
Solo hay que decirle en qué pin está el cable de datos. Si el cable va al 4, el código dice 4.
sensor = dht.DHT22(Pin(4))
measure() hace la conversación por el cable: el ESP32 pregunta y el sensor contesta.
sensor.measure()
Después de medir, se leen los dos números. Entre medidas hay que esperar al menos 2 segundos.
temperatura = sensor.temperature() # grados Celsius humedad = sensor.humidity() # porcentaje
Pon un breakpoint en mostrar(...) y mira temperatura y humedad en Variables. Mientras corre la simulación, haz clic en el DHT22 de Wokwi: aparecen controles para cambiar la temperatura.
chips/1-dht22/termometro.py# Chip 1 · DHT22: el termómetro embrujado. # Protocolo de UN cable: el sensor y el ESP32 se turnan para hablar por el pin 4. import time import dht from machine import Pin, I2C import ssd1306 def preparar_pantalla(): i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000) return ssd1306.SSD1306_I2C(128, 64, i2c) def preparar_sensor(): # El DHT22 solo necesita saber en qué pin está su cable de datos. return dht.DHT22(Pin(4)) def leer(sensor): sensor.measure() # pide una medida nueva al sensor return sensor.temperature(), sensor.humidity() def mostrar(display, temperatura, humedad): display.fill(0) display.text("Termometro", 24, 0, 1) display.text("Temp: %.1f C" % temperatura, 0, 20, 1) display.text("Humedad: %d %%" % humedad, 0, 32, 1) if temperatura > 30: display.text("Calor de caldero", 0, 52, 1) display.show() def main(): display = preparar_pantalla() sensor = preparar_sensor() for vuelta in range(30): temperatura, humedad = leer(sensor) mostrar(display, temperatura, humedad) # breakpoint aquí para ver los valores time.sleep(2) # el DHT22 necesita 2 segundos entre medidas if __name__ == "__main__": main()
chips/1-dht22/wokwi.toml y Start Simulation. Luego, en Ejecutar y depurar, elige Chip 1 · DHT22 termómetro — REMOTA GHI.En termometro.py, dentro de mostrar, añade un aviso cuando haga frío:
if temperatura > 30: display.text("Calor de caldero", 0, 52, 1) elif temperatura < ___: display.text(___, 0, 52, 1)
elif significa «si no pasó lo de arriba, pero pasa esto otro».
Elige un número de grados que te parezca frío, por ejemplo menos de 5.
El texto va entre comillas y no puede pasar de 16 letras. Sin tildes: la OLED no las tiene.
Comprueba: haz clic en el DHT22 en Wokwi y baja la temperatura. ¿En qué valor exacto cambia el mensaje? ¿Qué pasa justo en ese número?
El MPU6050 mide inclinación y movimiento. Habla I2C, el mismo idioma que la OLED, así que comparte los mismos dos cables: SDA en el 21 y SCL en el 22. ¿Cómo sabe cada chip que le hablan a él? Por su dirección: la OLED es la 0x3C y el MPU6050 la 0x68.
Un bus para los dos chips, con los pines del reloj y los datos.
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
scan() pregunta en todas las direcciones y devuelve las que contestan. Las da en decimal: 60 es 0x3C y 104 es 0x68.
print(i2c.scan()) # [60, 104]Los chips I2C guardan su configuración en registros, cajitas numeradas. El MPU6050 arranca dormido: se despierta escribiendo 0 en su registro 0x6B.
i2c.writeto_mem(0x68, 0x6B, b"\x00") # (dirección, registro, dato)
La aceleración está en 6 bytes a partir del registro 0x3B: dos por cada eje X, Y, Z.
datos = i2c.readfrom_mem(0x68, 0x3B, 6)
Cada eje viene en dos bytes que se juntan en un número con signo. Dividido entre 16384 da la aceleración en «g» (1 g es la gravedad).
ax = con_signo(datos[0], datos[1]) / 16384
Mira la consola: debe imprimir [60, 104]. Pon un breakpoint en mostrar(...) y mira ax y ay. Para «inclinar» la varita, cambia accelX y accelY en chips/2-mpu6050/diagram.json y vuelve a iniciar la simulación.
chips/2-mpu6050/varita.py# Chip 2 · MPU6050: la varita mágica. # Protocolo I2C: comparte los cables SDA (21) y SCL (22) con la OLED. # Cada chip tiene su dirección: OLED = 0x3C (60), MPU6050 = 0x68 (104). import time from machine import Pin, I2C import ssd1306 MPU = 0x68 # dirección del MPU6050 en el bus I2C REG_ENCENDIDO = 0x6B # registro PWR_MGMT_1: 0 = despierto REG_ACELERACION = 0x3B # aquí empiezan los 6 bytes de aceleración X, Y, Z def preparar_bus(): return I2C(0, scl=Pin(22), sda=Pin(21), freq=400000) def despertar_mpu(i2c): # Al encender, el MPU6050 está dormido: se despierta escribiendo 0 en su registro. i2c.writeto_mem(MPU, REG_ENCENDIDO, b"\x00") def con_signo(alto, bajo): # Junta dos bytes en un número que puede ser negativo (−32768 a 32767). valor = (alto << 8) | bajo if valor > 32767: valor -= 65536 return valor def leer_aceleracion(i2c): datos = i2c.readfrom_mem(MPU, REG_ACELERACION, 6) ax = con_signo(datos[0], datos[1]) / 16384 # 16384 = 1 g en el rango de ±2 g ay = con_signo(datos[2], datos[3]) / 16384 az = con_signo(datos[4], datos[5]) / 16384 return ax, ay, az def mostrar(display, ax, ay): display.fill(0) display.text("Varita magica", 12, 0, 1) x = 60 + int(ax * 50) y = 34 + int(ay * 20) x = max(0, min(120, x)) y = max(12, min(56, y)) display.fill_rect(x, y, 8, 8, 1) # el fantasma se mueve al inclinar display.show() def main(): i2c = preparar_bus() print("Chips encontrados:", i2c.scan()) # debería mostrar [60, 104] display = ssd1306.SSD1306_I2C(128, 64, i2c) despertar_mpu(i2c) for vuelta in range(100): ax, ay, az = leer_aceleracion(i2c) mostrar(display, ax, ay) # breakpoint aquí para ver ax, ay time.sleep(0.2) if __name__ == "__main__": main()
chips/2-mpu6050/wokwi.toml y Start Simulation. Luego, en Ejecutar y depurar, elige Chip 2 · MPU6050 varita — REMOTA GHI.En varita.py, dentro de main, cambia la línea del print por esta comprobación:
chips = i2c.scan()
print("Chips encontrados:", chips)
if ___ not in chips:
print("No encuentro la varita: revisa SDA y SCL")Busca la dirección del MPU6050: está guardada en una variable al principio del archivo.
x in lista pregunta si x está en la lista; not in, si NO está.
Para probar que funciona, en diagram.json borra la conexión de imu1:SDA y vuelve a iniciar.
Comprueba: con todo conectado no aparece el aviso; sin el cable SDA del MPU6050, sí. Reto: haz que el fantasma se mueva al revés cuando inclinas.
El MAX7219 controla una matriz de 8 × 8 LEDs. Habla SPI: un cable de reloj, uno de datos y uno llamado CS que funciona como levantar la mano: mientras está en 0, el chip escucha.
Se indica el reloj (sck) y los datos de salida (mosi). La velocidad es cuántos bits por segundo se envían.
spi = SPI(2, baudrate=1000000, sck=Pin(18), mosi=Pin(23))
CS empieza en 1: «no te estoy hablando».
cs = Pin(5, Pin.OUT, value=1)
Cada orden son dos bytes: el número del registro y su valor, siempre entre bajar y subir CS.
cs.value(0) # levantar la mano spi.write(bytes([registro, valor])) # enviar la orden cs.value(1) # el chip la guarda
Al arrancar se le dice cómo trabajar: usar las 8 filas, dibujar punto por punto, el brillo, y encender.
escribir(spi, cs, 0x0B, 7) # usar las 8 filas escribir(spi, cs, 0x09, 0) # punto por punto escribir(spi, cs, 0x0A, 3) # brillo (0 a 15) escribir(spi, cs, 0x0C, 1) # encender
Las filas son los registros 1 a 8. Cada fila es un número binario: cada 1 es un LED encendido.
escribir(spi, cs, 1, 0b00011000) # fila 1: dos LEDs en el centroPon un breakpoint en dibujar(...) y avanza con F5: la matriz cambia entre calabaza y fantasma. Si el dibujo sale girado o como en un espejo, es por cómo está montada la matriz: prueba "layout": "fc16" en los attrs de la matriz en diagram.json.
chips/3-max7219/calabaza_leds.py# Chip 3 · MAX7219: la calabaza de LEDs (matriz de 8 × 8). # Protocolo SPI: reloj (18), datos (23) y un pin de "te hablo a ti" llamado CS (5). import time from machine import Pin, SPI REG_DECODIFICAR = 0x09 # 0 = dibujamos punto por punto, sin números automáticos REG_BRILLO = 0x0A # de 0 (tenue) a 15 (máximo) REG_FILAS = 0x0B # cuántas filas usar: 7 = las 8 (de 0 a 7) REG_ENCENDIDO = 0x0C # 1 = encendido REG_PRUEBA = 0x0F # 1 = enciende todo para probar CALABAZA = [ 0b00011000, 0b01111110, 0b11111111, 0b10111101, 0b11111111, 0b10100101, 0b11011011, 0b01111110, ] FANTASMA = [ 0b00111100, 0b01111110, 0b11011011, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b10101010, ] def preparar_spi(): spi = SPI(2, baudrate=1000000, sck=Pin(18), mosi=Pin(23)) cs = Pin(5, Pin.OUT, value=1) # CS en 1 = "no te estoy hablando" return spi, cs def escribir(spi, cs, registro, valor): cs.value(0) # 1. levantar la mano: "te hablo a ti" spi.write(bytes([registro, valor])) # 2. enviar dirección del registro y valor cs.value(1) # 3. bajar la mano: el chip guarda el dato def iniciar(spi, cs): escribir(spi, cs, REG_PRUEBA, 0) escribir(spi, cs, REG_FILAS, 7) escribir(spi, cs, REG_DECODIFICAR, 0) escribir(spi, cs, REG_BRILLO, 3) escribir(spi, cs, REG_ENCENDIDO, 1) def dibujar(spi, cs, filas): for numero in range(8): escribir(spi, cs, numero + 1, filas[numero]) # las filas son los registros 1 a 8 def main(): spi, cs = preparar_spi() iniciar(spi, cs) for vez in range(6): dibujar(spi, cs, CALABAZA) # breakpoint aquí time.sleep(1) dibujar(spi, cs, FANTASMA) time.sleep(1) print("Fin del espectáculo") if __name__ == "__main__": main()
chips/3-max7219/wokwi.toml y Start Simulation. Luego, en Ejecutar y depurar, elige Chip 3 · MAX7219 calabaza — REMOTA GHI.Añade esta figura debajo de FANTASMA y complétala. Dibújala antes en papel en una cuadrícula de 8 × 8.
MURCIELAGO = [
0b00000000,
0b10000001,
0b11000011,
0b11111111,
0b___, # cuerpo: 8 cifras, 1 = encendido
0b00100100,
0b00000000,
0b00000000,
]Luego, en main, añade dibujar(spi, cs, MURCIELAGO) y un time.sleep(1) dentro del for, y cambia el brillo:
escribir(spi, cs, REG_BRILLO, ___) # de 0 a 15
Cada fila tiene exactamente 8 cifras, solo ceros y unos.
El cuerpo del murciélago va en el centro: los LEDs de los extremos de esa fila, apagados.
Para el brillo, cualquier número de 0 a 15. ¿Se nota la diferencia entre 1 y 15?
Comprueba: aparecen calabaza, fantasma y murciélago. ¿Qué pasa si una fila tiene 9 cifras?
UART (puerto serie) es como un teléfono: TX habla y RX escucha. Entre dos placas se cruzan: el TX de una va al RX de la otra, y comparten GND. Es el mismo idioma con el que el ESP32 te manda los print(). En este circuito hay un solo ESP32, así que el cable sale por el 17 y vuelve a entrar por el 16: el ESP32 se habla a sí mismo.
Se elige el UART 2 (el 0 lo usa el PC), la velocidad y los pines. Los dos lados tienen que usar la misma velocidad; si no, llegan letras sin sentido.
uart = UART(2, baudrate=9600, tx=17, rx=16)
write manda el texto letra por letra por el cable TX.
uart.write("BOO")any() dice cuántos bytes están esperando en RX.
time.sleep(0.1) if uart.any(): mensaje = uart.read().decode() # bytes → texto
Pon un breakpoint en mostrar(...) y compara palabra con llegada en Variables. La OLED muestra lo enviado y lo recibido.
chips/4-uart/mensaje_secreto.py# Chip 4 · UART: el mensaje secreto. # Protocolo UART (puerto serie): un cable para enviar (TX, 17) y otro para recibir (RX, 16). # En este circuito el cable sale del 17 y vuelve a entrar por el 16: el ESP32 se habla a sí mismo. # Con dos placas reales, el TX de una va al RX de la otra (y comparten GND). import time from machine import Pin, I2C, UART import ssd1306 def preparar_pantalla(): i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000) return ssd1306.SSD1306_I2C(128, 64, i2c) def preparar_uart(): # UART 2, velocidad 9600 bits por segundo. Los dos lados deben usar la MISMA velocidad. return UART(2, baudrate=9600, tx=17, rx=16) def enviar(uart, texto): uart.write(texto) def recibir(uart): time.sleep(0.1) # dar tiempo a que lleguen las letras if uart.any(): return uart.read().decode() return "" def mostrar(display, enviado, recibido): display.fill(0) display.text("Mensaje secreto", 4, 0, 1) display.text("Envie: " + enviado, 0, 24, 1) display.text("Llego: " + recibido, 0, 40, 1) display.show() def main(): display = preparar_pantalla() uart = preparar_uart() for palabra in ["BOO", "Dulce", "Truco"]: enviar(uart, palabra) llegada = recibir(uart) mostrar(display, palabra, llegada) # breakpoint aquí time.sleep(1) if __name__ == "__main__": main()
chips/4-uart/wokwi.toml y Start Simulation. Luego, en Ejecutar y depurar, elige Chip 4 · UART mensaje secreto — REMOTA GHI.En mensaje_secreto.py, añade tu propia palabra a la lista:
for palabra in ["BOO", "Dulce", "Truco", ___]:
La palabra va entre comillas. Recuerda el límite: «Envie: » ya ocupa 7 letras de las 16.
Después, abre chips/4-uart/diagram.json, borra la conexión esp:17 → esp:16 y vuelve a iniciar. Predice antes qué mostrará «Llego:».
Comprueba: tu palabra aparece en «Envie» y en «Llego». Sin el cable, ¿qué llega? ¿Por qué el programa no se queda esperando para siempre?
Con tu tutor, diseña un circuito con DHT22 y MAX7219 a la vez: si hace calor, la matriz muestra la calabaza; si hace frío, el fantasma. Antes de programar, haz la tabla de pines: ¿alguno se repite? Revisa el mapa de pines para elegir.
Estos circuitos y programas se prepararon con la misma configuración de la demo, pero aún no se han probado en tu Wokwi. Si algo no responde, revisa primero los cables con la receta de 5 pasos y avisa a tu tutor con el mensaje completo.