Correo: [email protected] - Teléfono: +56979962128

En esta guía, exploraremos los fundamentos de la programación asincrónica en MicroPython con la Raspberry Pi Pico utilizando el módulo asyncio. Aprenderás a ejecutar múltiples tareas de forma concurrente, creando la ilusión de multitarea y evitando bloquear tu código durante tareas de larga duración.
Raspberry Pi Pico Asynchronous Programming – Run Multiple Tasks (MicroPython)
   

Por ejemplo, tu programa puede estar esperando la respuesta de un servidor y, al mismo tiempo, ser capaz de realizar otras tareas como comprobar si se ha presionado un botón o hacer parpadear un LED.

La programación asincrónica es sumamente útil en proyectos que incluyen: interactuar con bases de datos, comunicarse a través de redes (como al solicitar datos de un servidor o cuando la Pico actúa como servidor web), leer datos de sensores, mostrar información en una pantalla, recibir entradas de usuarios y mucho más.

Requisitos Previos

Antes de continuar, asegúrate de cumplir con los siguientes requisitos:

Firmware de MicroPython

Para seguir este tutorial, es necesario que tengas instalado el firmware de MicroPython en tu placa Raspberry Pi Pico. También necesitarás un IDE (Entorno de Desarrollo Integrado) para escribir y cargar el código en tu placa.

micorpython logo

El IDE recomendado para trabajar con MicroPython en la Raspberry Pi Pico es Thonny IDE. Es ligero, gratuito y está diseñado específicamente para principiantes en microcontroladores.

Puedes seguir este tutorial básico si aún no tienes todo configurado:

   

Introducción a la Programación Asincrónica

La programación asincrónica es una técnica que permite que tu programa inicie una tarea que podría durar mucho tiempo y, aun así, siga respondiendo a otros eventos mientras esa tarea se ejecuta, en lugar de tener que esperar a que la tarea finalice.

Esto se logra ejecutando las tareas de manera no bloqueante y utilizando funciones (como las corrutinas en MicroPython) para manejar los resultados. De esta forma, el programa puede continuar ejecutando otras tareas mientras espera los resultados de la tarea asincrónica. Por el contrario, en la programación sincrónica, cada tarea debe esperar a que la anterior se complete antes de comenzar.


En resumen…

Característica Programación Sincrónica Programación Asincrónica
Arquitectura Bloqueante: Cada operación espera a que termine la anterior. No bloqueante: La ejecución de una tarea no depende de otra.
Orden Estrictamente secuencial. Las tareas pueden avanzar de forma concurrente.
Eficiencia Menor; el procesador “desperdicia” tiempo esperando. Mayor; el procesador aprovecha los tiempos de espera.
Respuesta El sistema puede parecer “congelado” durante tareas largas. El sistema se mantiene responsivo (ej. detecta botones siempre).
 
Synchronous vs Asynchronous Programming
     

El Módulo asyncio de MicroPython

MicroPython proporciona el módulo asyncio, el cual es un framework de E/S asincrónica ligero inspirado en el módulo asyncio de Python. Puedes consultar todos los detalles sobre este módulo de MicroPython en el siguiente enlace: Documentación del módulo asyncio de MicroPython

Echemos un vistazo rápido a algunos conceptos básicos que necesitas saber para empezar a usar asyncio para la programación asincrónica:

  • Event Loop (Bucle de eventos): es un bucle que comprueba continuamente eventos (tareas o corrutinas) y los ejecuta.

  • Tasks (Tareas): unidades individuales de trabajo o corrutinas que están programadas para ejecutarse de forma concurrente dentro del bucle de eventos.

  • Asynchronous Functions (Funciones asincrónicas): también conocidas como corrutinas, estas son funciones que pueden ser pausadas y reanudadas sin bloquear otras operaciones, permitiendo la ejecución concurrente de múltiples tareas.

  • await: es una palabra clave utilizada dentro de las corrutinas para pausar la ejecución de la corrutina actual hasta que un evento u operación específica se complete, permitiendo que otras corrutinas se ejecuten mientras tanto.

Ahora, veamos con más detalle cada uno de esos conceptos, el flujo de trabajo y los métodos del módulo asyncio para escribir un programa asincrónico.

Event Loop (Bucle de eventos)

Un bucle de eventos es el núcleo de la programación asincrónica. Es un bucle que comprueba continuamente eventos (tareas o corrutinas) y los ejecuta. En asyncio, creas un bucle de eventos utilizando asyncio.get_event_loop().

La siguiente línea crea una instancia del bucle de eventos llamada loop. Puedes usar ese bucle para gestionar y programar tareas asincrónicas.

loop = asyncio.get_event_loop()

Creación de Tareas

En la programación asincrónica, las tareas representan unidades de trabajo. Creas tareas para ejecutar corrutinas de forma concurrente. Las corrutinas son funciones definidas con la palabra clave async, que pueden ser pausadas y reanudadas, permitiendo la programación asincrónica.

Por ejemplo:

async def blink_led():
    # Código para parpadear un LED

Luego, podemos usar loop.create_task() para programar esta corrutina como una tarea para ser ejecutada por el bucle de eventos.

loop.create_task(blink_led())

Ejecución del Bucle de Eventos

Una vez que hayas creado las tareas, inicias el bucle de eventos para ejecutarlas. El bucle comprobará continuamente las tareas programadas y las ejecutará. La siguiente línea inicia el bucle de eventos, como hemos visto anteriormente.

loop = asyncio.get_event_loop()
 

Después, loop.run_forever() hace que se ejecute indefinidamente, comprobando constantemente si hay tareas para ejecutar.

loop.run_forever()
 

Funciones Asincrónicas – async def

Una función asincrónica se define utilizando la sintaxis async def. Estas funciones, también conocidas como corrutinas, pueden ser pausadas con la palabra clave await, permitiendo que otras corrutinas se ejecuten mientras tanto. Por ejemplo:

async def blink_led():
    while True:
        led.toggle()  # Cambia el estado del LED
        await asyncio.sleep(1)
 

Aquí, blink_led() es una función asincrónica. Alterna un LED y luego se pausa durante 1 segundo usando await asyncio.sleep(1). Durante esta pausa, otras tareas pueden ejecutarse.

Retraso Asincrónico – asyncio.sleep()

asyncio.sleep() es una corrutina proporcionada por el módulo asyncio y se utiliza para introducir un retraso en la ejecución de una corrutina por una duración especificada sin bloquear todo el bucle de eventos. Por lo tanto, para crear una función asincrónica, debes reemplazar todos tus time.sleep() con asyncio.sleep().

  • Cuando se llama a asyncio.sleep() dentro de una corrutina, se suspende temporalmente la ejecución de esa corrutina, permitiendo que otras corrutinas se ejecuten mientras tanto.

  • El bucle de eventos continúa ejecutándose mientras la corrutina está pausada, comprobando otras tareas y eventos.

  • Después de la duración especificada (segundos), la corrutina pausada reanuda la ejecución desde el punto donde se llamó a asyncio.sleep().

await asyncio.sleep() es una forma no bloqueante de ceder el control a otras corrutinas en el bucle de eventos sin introducir ningún retraso real. Permite efectivamente que otras corrutinas se ejecuten de inmediato.

await

La palabra clave await se utiliza dentro de las corrutinas para indicar un punto donde la corrutina puede ser suspendida temporalmente hasta que se complete un evento específico.

En el siguiente ejemplo, la corrutina se pausa durante 1 segundo sin bloquear todo el bucle de eventos.

await asyncio.sleep(1)

 

En Resumen…

Cuando ejecutas un programa asincrónico, el bucle de eventos se ejecuta continuamente, procesando las tareas programadas de forma concurrente. Cada corrutina tiene su turno para ejecutarse, y las sentencias await permiten que el bucle de eventos cambie entre tareas, creando la apariencia de paralelismo.

Como resultado, puedes realizar múltiples tareas de forma concurrente sin tener que esperar a que cada una se complete antes de pasar a la siguiente.

Ejemplo Básico de un Programa Asincrónico

Ahora que conoces los conceptos básicos de la programación asincrónica y los métodos básicos del módulo asyncio, vamos a crear un ejemplo sencillo para aplicar los conceptos aprendidos.

Crearemos dos corrutinas diferentes. Cada corrutina hará parpadear un LED diferente a un ritmo diferente.

  • LED Verde Pasto: GPIO 20 >> parpadea cada dos segundos;

  • LED Azul Marino: GPIO 19 >> parpadea cada medio segundo.

Para comprobar visualmente el resultado final, conecta dos LEDs a la Raspberry Pi Pico, uno al GPIO 20 y otro al GPIO 19.

Raspberry Pi Pico Blinking Two LEDs Asynchronously - Circuit

Este es el código de ejemplo:

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-raspberry-pi-pico-asynchronous-programming/

import asyncio
from machine import Pin

green_led_pin = 20
green_led = Pin(green_led_pin, Pin.OUT)
blue_led_pin = 19
blue_led = Pin(blue_led_pin, Pin.OUT)

# Define coroutine function
async def blink_green_led():
    while True:
        green_led.toggle()
        await asyncio.sleep(2) 

# Define coroutine function
async def blink_blue_led():
    while True:
        blue_led.toggle()
        await asyncio.sleep(0.5)

# Define the main function to run the event loop
async def main():
    # Create tasks for blinking two LEDs concurrently
    asyncio.create_task(blink_green_led())
    asyncio.create_task(blink_blue_led())

# Create and run the event loop
loop = asyncio.get_event_loop()  
loop.create_task(main())  # Create a task to run the main function
loop.run_forever()  # Run the event loop indefinitely
   

Como funciona el código

Echemos un vistazo rápido al código.

Creamos dos corrutinas. Una para cada LED:

# Definir la función de la corrutina
async def blink_green_led():
    while True:
        green_led.toggle()
        await asyncio.sleep(2) 

# Definir la función de la corrutina
async def blink_blue_led():
    while True:
        blue_led.toggle()
        await asyncio.sleep(0.5)
 
Dentro de cada corrutina, alternamos el estado del LED en un bucle y esperamos (espera asincrónica) durante un intervalo específico await asyncio.sleep(0.5).  

Creamos otra corrutina llamada main() que sirve como un punto central donde puedes organizar y coordinar la ejecución de esas tareas.

En la corrutina main(), creamos tareas para las funciones blink_green_led() y blink_blue_led() para que se ejecuten de forma concurrente.

# Definir la función principal para ejecutar el bucle de eventos
async def main():
    # Crear tareas para hacer parpadear dos LEDs concurrentemente
    asyncio.create_task(blink_green_led())
    asyncio.create_task(blink_blue_led()

Luego, creamos un bucle de eventos (event loop).

loop = asyncio.get_event_loop()
 

El loop.create_task(main()) crea una tarea para ejecutar la función de corrutina main().

loop.create_task(main()) # Crear una tarea para ejecutar la función principal
 

Finalmente, el método run_forever() comenzará a ejecutar el bucle de eventos indefinidamente. El bucle de eventos comprueba continuamente las tareas para ejecutar, las ejecuta de forma concurrente y gestiona su ejecución.

loop.run_forever() # Ejecutar el bucle de eventos indefinidamente
 

Probando el código

Ejecuta el código anterior en tu Raspberry Pi Pico. El resultado serán dos LEDs parpadeando a ritmos diferentes.

Raspberry Pi Pico Asynchronous Programming - Blink Multiple LEDs - DemonstrationRaspberry Pi Pico Asynchronous Programming - Blink Multiple LEDs - Demonstration

Raspberry Pi Pico Asynchronous Programming - Blink Multiple LEDs - DemonstrationRaspberry Pi Pico Asynchronous Programming - Blink Multiple LEDs - Demonstration

Conclusión

Este es solo un ejemplo sencillo para mostrarte cómo escribir un programa asincrónico. Ahora, deberías entender que, en lugar de simplemente hacer parpadear un LED, puedes realizar tareas más complejas como leer un archivo, solicitar datos de internet, gestionar un servidor web con múltiples clientes y mucho más.

 

Preguntas frecuentes

¿Qué es la programación asincrónica en MicroPython?

Es una forma de ejecutar varias tareas de manera concurrente, creando la ilusión de multitarea y evitando que el programa se bloquee durante operaciones largas. En MicroPython se implementa con el módulo asyncio.

¿Para qué sirve asyncio en la Raspberry Pi Pico?

Para que el programa pueda estar esperando la respuesta de un servidor y al mismo tiempo comprobar si se presionó un botón o hacer parpadear un LED, en lugar de quedarse detenido hasta que llegue la respuesta.

¿Qué es el event loop?

Es el bucle de eventos que coordina las tareas asincrónicas: decide cuál se ejecuta en cada momento y les va cediendo el control a medida que unas quedan esperando. Es el corazón de asyncio.

¿Cómo se define una función asincrónica?

Con async def en lugar de def. Dentro de ella se usan retrasos asincrónicos en vez de los bloqueantes, de modo que mientras esa función espera, el bucle de eventos pueda ejecutar otras tareas.

¿Es lo mismo asincrónico que multinúcleo?

No. La programación asincrónica reparte el tiempo de un mismo núcleo entre varias tareas que pasan buena parte del tiempo esperando; no ejecuta código en paralelo real. Por eso se habla de la "ilusión" de multitarea.

Aprende esto en un curso

Si prefieres verlo en vivo y con alguien que resuelva tus dudas, estos cursos presenciales de MCI Educación cubren el tema de este tutorial: