Aproximación a la multitarea en Arduino. Blink sin delay


En esta entrada vamos a ver distintas aproximaciones a la ejecución multitarea en un procesador como Arduino o, como se le conoce habitualmente, el problema de blink sin delay.

En primer lugar, vamos a rebajar las expectativas respecto a este comportamiento “asíncrono”. En un procesador de pequeño tamaño como Arduino, con un único núcleo y sin sistema operativo, la ejecución de dos tareas simultáneas es imposible.

Cuando nos referimos a “multitarea” o “comportamiento asíncrono” en realidad nos estamos refiriendo a la posibilidad de temporizar tareas de forma no bloqueante. Es decir, ejecutar una o varias tareas cada cierto tiempo, sin que ello suponga que no podamos hacer nada más.

Lo vamos a entender mucho mejor si lo ilustramos con el ejemplo de blink sin delay, así que vamos a dejar de hablar y meternos en harina (en el código, más bien).

Blink con delay

Empezamos recordando el archiconocido Blink, el equivalente al “Hola mundo” en el mundo de Arduino, que simplemente hace parpadear el Led de la placa cada segundo.

Como sabemos, empleamos la función delay para marcar los tiempos. El problema es que delay es una espera bloqueante, es decir, el procesador detiene el bucle de control principal durante este tiempo.

¿Qué pasa si queremos realizar otras funciones mientras parpadea el Led, como leer un sensor, recibir datos por puerto serie, activar un motor? ¿Por hacer parpadear un Led tengo que parar toda la placa?

Bueno, si esto fuera así los procesadores no serían muy interesantes. Tenemos varios mecanismos para lidiar con esto. Entre ellos están las interrupciones y los timers, pero estos están indicados para funciones más específicas.

Además, en un procesador como Arduino los timers e interrupciones son recursos valiosos y escasos. Por no decir que modificarlos puede provocar conflictos con otras funciones y librerías.

Blink sin delay

Si no tenemos unos requisitos temporales estrictos, si lo único que necesitamos es temporizar una serie de funciones que se ejecuten en un determinado instante, lo más sencillo (y habitual) es hacer una aproximación basada en el tiempo transcurrido entre eventos.

En primer lugar, como no nos apetece dejarnos los ojos mirando un Led, vamos a modificar el ejemplo de Blink para que en lugar de parpadear un Led muestre por puerto serie “ON”, “OFF”. Así podemos aprovechar para mostrar también los tiempos de disparo.

También vamos a cambiar el encendido y apagado del Led por una función toggleLed(), que simplemente cambia el estado del Led de encendido a apagado. Hacemos esto únicamente para que los ejemplos sean más sencillos, porque tenemos llamada a una única acción.

El código sería el siguiente, que básicamente es idéntico en funciones al anterior Blink con unos cambios mínimos para que sea más fácil ilustrar el ejemplo.

Y aquí tenemos la salida del sistema.

Ahora vamos a ver cómo hacer lo mismo que el código anterior pero sin usar la función delay(), es decir, el esperado blink sin delay.

La idea general es que, en lugar de detener la ejecución durante un determinado tiempo, vamos a dejar que este corra normalmente. En un cierto punto comprobamos el tiempo transcurrido entre disparos. Si el tiempo transcurrido es mayor que el intervalo deseado, realizaremos la acción establecida. En este ejemplo, cambiar el estado del Led con toggleLed().

Y esta es la respuesta del sistema.

Por supuesto esto tiene varias consecuencias, como que tenemos un mayor consumo energético porque el bucle de control se ejecuta continuamente, sin entrar en un estado de baja energía.

Por otro lado, si tenemos tareas con alto tiempo de ejecución, puede causar que nuestra tarea temporizada se retrase porque el procesador esté ocupado en el momento en que debería disparar nuestra acción.

Esto nos llevaría a reflexionar sobre cómo queremos que se comporte el sistema si ocurre un retraso. Si es preferible que se mantenga el tiempo entre acciones (millis() + interval) o el tiempo entre disparos (previousMillis + interval).

Multitarea en Arduino

¿Qué pasaría si tenemos que temporizar más de una acción con intervalos distintos? Pues que el código es muy similar, simplemente ahora tendremos dos fragmentos de código iguales, con su intervalo, su previousMillis, y su action() para cada tarea.

Aquí tenemos un ejemplo donde tenemos dos tareas temporizadas que simplemente muestran por puerto serie Action1 y Action2, respectivamente, en intervalos de 1000 y 800ms.

Aquí tenemos la salida del sistema.


Multitarea en una clase

En ejemplo anterior nos permite ver el código para temporizar una tarea tiene la misma estructura, y es susceptible de encapsularlo en una clase.

Aquí tenemos las librerías AsyncTask, MultiTask, StoryBoard, que permiten añadir tareas temporizadas de forma sencilla. Por su parte, tenemos AsyncServo y AsyncStepper para mover un servo y un motor paso a paso siguiendo la misma filosofía.

Por ejemplo, con AsyncTask el ejemplo de blink sin delay quedaría así.

Que estaremos de acuerdo es mucho más compacto y conveniente.

FreeRTOS

La última opción que vamos a ver para conseguir multitarea en un micro procesador es emplear un SO operativo para sistemas embebidos como FreeRTOS.

FreeRTOS es un micro sistema operativo en tiempo real diseñado para sistemas embebidos, de uso gratuito, escrito en C, y compatible con más de 30 procesadores.

La filosofía general de FreeRTOS es similar a la que hemos visto en esta entrada, en la que el bucle principal se convierte en un Schedule en el que hemos registrado tareas, y FreeRTOS se encarga de dispararlas en el momento oportuno.

FreeRTOS, por supuesto, añade muchas más funciones, como la priorización de tareas o el pase de parámetros a las acciones. Además, al controlar por completo los timings, permite aprovechar los estados de baja energía y conseguir una alta eficiencia.

Sin embargo, aunque es muy ligero, en una placa como Arduino Nano ocupa más del 20% de la memoria. Pero, por ejemplo, en un ESP32 encaja perfectamente, y de hecho es la base de muchas placas basadas en este SoC.

Su uso es más complejo que lo que hemos visto en esta entrada, y merece que en el futuro le dedicaremos su propia entrada.  

Si te ha gustado esta entrada y quieres leer más sobre Arduino puedes consultar la sección
tutoriales de Arduino

Previous Coche robot barato 2WD con Arduino: Presupuesto
Next Paint.NET un editor de imágenes gratuito e intuitivo

¡Deja un comentario!...

avatar
1000
Sort by:   newest | oldest | most voted
Amisadai P.
Guest

Muy interesante el tema, hace unos meses tuve este mismo dilema con la multitarea, asi que lo que hice “provisionalmente” fue exactamente lo que recomienda aqui, poner variables exclusivas para cada tarea, pero ahora que leo esto me siento mejor. Saludos!

PD; Espero el artículo sobre FreeRTOS.

Manu_Z
Guest

Muy Interesante me ha gustado mucho.
Tambien espero con ansias el artículo sobre FreeRTOS.