como-usar-freertos-en-arduino

Cómo usar FreeRTOS en Arduino

Empezamos una nueva serie de entradas destinadas a ver cómo usar FreeRTOS en un procesador como Arduino, o en un ESP866 o ESP32.

En esta primera entrada haremos una primera introducción y veremos cómo usar FreeRTOS en un Arduino AVR. Sin embargo, el resto de la entradas de las serie las realizaremos usando un ESP32, dentro de la sección del ESP8266/ESP32.

En el blog hemos hablado frecuentemente de cómo conseguir un comportamiento “asíncrono” en un procesador como Arduino. Así hemos visto desde lo más básico Aproximación a la multitarea en Arduino. Blink sin delay, a librerías como Librería Arduino AsyncTask.

Si hablamos cómo conseguir tareas asíncronas, tarde o temprano íbamos a encontrarnos con FreeRTOS porque es una de las mejores formas de gestionar la ejecución de tareas concurrentes en un procesador.

Pero, antes de meternos en harina, vamos a empezar por el principio viendo porqué esta obsesión con la asincronía, y porqué FreeRTOS nos va a ser de gran ayuda para conseguirla.

Porqué usar FreeRTOS

Cuando realizas un proyecto de tamaño medio o alto tienes varias partes del programa que actúan de forma simultánea. Una de los aspectos más importantes del diseño del proyecto es ver cómo se relacionan e interactúan estas partes.

Por ejemplo, un robot va a necesitar controlar la velocidad de los motores, medir distancias, tomar decisiones, mostrar datos en una pantalla o transmitirlos de forma remota.

Un aspecto importante es que no todas estas tareas necesitan el mismo timing. Por ejemplo, el control de la velocidad de los motores tendrá que realizarse más frecuentemente que, por ejemplo, la transmisión de datos.

El otro punto importante es que no todas esas tareas tienen la misma prioridad. Por ejemplo, atender a una alerta de colisión o de calentamiento de un motor tendrá una prioridad más alta a, por ejemplo, mostrar los datos en la pantalla.

Gestionar todo esto puede ser complicado. Aquí es donde entra en juego FreeRTOS proporcionando un framework para que la coordinación y gestión de este tipo de tareas.

Qué es FreeRTOS

Como su nombre indica FreeRTOS (Free Real-time operating system) es un sistema operativo de tiempo real para dispositivos embebidos. Está distribuido bajo la licencia MIT, y disponible en la página web del proyecto https://www.freertos.org/

Veamos que significa esto. Por un lado, como cualquier sistema operativo, estamos ante un programa que controla el acceso al hardware y sobre el cuál se ejecutan en el resto de tareas.

Por su parte “en tiempo real” significa que, a diferencia de los SO a los que estamos acostumbrados (Windows, Linux, Android, etc), este está centrado en tener un control muy fino y preciso del tiempo. Algo que es prescindible en el caso de un ordenador, pero muy importante en un sistema embebido.

FreeRTOS proporciona diversas funcionalidades y herramientas tales como tareas, semáforos, pilas o un bus de notificaciones. Veremos todas estas funciones en su momento en próximas entradas.

Es un proyecto robusto y consolidado, cuyas primeras versiones datan de 2003, y que ha sido portado a más de 35 arquitecturas. Está principalmente desarrollado en C, con unas pocas funciones en ensamblador. Además, fue diseñado para tener un bajo consumo de recursos y una ejecución rápida.

Cómo funciona FreeRTOS

Como era de esperar, dentro del “Core” mismo del funcionamiento de FreeRTOS encontramos el concepto de “Tarea”. De forma resumida, una tarea es un envoltorio en torno a una función, que pasamos a FreeRTOS para que la gestione.

Íntimamente relacionado tenemos el concepto de Scheduler que es la parte de FreeRTOS que se encarga de gestionar y ejecutar las distintas tareas que tengamos definidas.

Para ello cada tarea tiene un estado que puede ser disponible, en ejecución, suspendida o bloqueada. El cambio de estados lo realiza el Scheduler, según el siguiente flujo.

arduino-freertos-task-state

Estos estados son,

  • Ejecutando: La tarea está usando el procesador
  • Disponible: La tarea está disponible para pasar a ejecución, pero aún no lo ha hecho porque hay otra tarea ejecutándose
  • Bloqueada: Básicamente la tarea esta “esperando”. Por ejemplo, porque ha hecho un vTaskDelay, está esperando un semáforo, etc. La tarea pasará a disponible cuando ocurra un evento (temporal, timeout, evento externo)
  • Suspendida: La tarea está “parada”, para lo cuál hay que llamar explícitamente a vTaskSuspend(). Se reactivará usando xTaskResume()

Cronograma de ejecución

Pese a que digamos que en FreeRTOS pueden ejecutarse varias tareas “en paralelo” esto no es realmente cierto. Un procesador sólo puede ejecutar una tarea simultánea por cada núcleo / hilo disponible.

Para conseguir que las tareas se ejecuten, mejor dicho, de forma “no bloqueante” lo que hace un SO es repartir el tiempo que dedica el procesador a cada una de las tareas que lo demandan. O lo que es lo mismo, entre las que están “disponibles” para ejecución.

Para ponerlo sencillo, imaginemos que tenemos dos funciones (tareas) ejecutadas sin FreeRTOS. En este caso cada una de las tareas se ejecutaría secuencialmente, sin más gracia ni misterio.

arduino-freertos-task-sequential

Sin embargo, si pasamos a usar FreeRTOS, las cosas funcionan “en paralelo”. Así, si tenemos dos tareas de la misma prioridad 1, llamadas A y B, la ejecución sería la siguiente.

arduino-freertos-task-parallel

Como ambas tareas están disponibles y tienen la misma prioridad FreeRTOS alterna entre ambas para que puedan ejecutarse. Los pequeños fragmentos amarillos que han aparecido es el tiempo necesario por el Scheduler para realizar su trabajo, de forma periódica.

Por el contrario, si tenemos dos tareas con prioridad diferente 1 y 2, el resultado de este cronograma de ejecución sería el siguiente.

arduino-freertos-task-priority

Como vemos, en el momento que la tarea de prioridad 2 está disponible pasa a ser ejecutada frente a la tarea de menor prioridad. Esta tendrá que esperar hasta que las tareas de mayor prioridad pasen a estar bloqueadas o suspendidas.

Notación y sintaxis en FreeRTOS

Una de las cosas que más “tira para atrás” de FreeRTOS para usuarios noveles es el uso de la notación húngara para el nombre de variables y funciones. De esta forma, los nombres de variables y funciones de FreeRTOS se anteceden con su tipo, según,

  • Void se preceden con una v
  • Char se preceden con c
  • Short se preceden con s
  • Long se preceden con una l
  • Float se preceden con una f
  • Double se preceden con una d
  • Estructuras y tipos de variables tipos de variables se preceden por una x

Adicionalmente a esto,

  • Punteros se preceden con una p adicional
  • Unsigned se preceden con una u adicional

Así, por ejemplo, veréis ‘xTaskCreate’, en lugar de simplemente ‘TaskCreate’. La ‘x’ inicial indica que esta función devuelve una estructura.

La notación húngara es una práctica cuya utilidad era discutible en su día y actualmente está totalmente desaconsejada. Pero FreeRTOS es un desarrollo de casi 20 años y, en su momento, tomaron la decisión de usarla.

Actualmente sería inasumible cambiar FreeRTOS para eliminar este anacronismo, porque rompería la compatibilidad con miles y miles de proyectos realizados.

Así que, mala suerte, nos lo tenemos que tragar. Simplemente no os dejéis asustar por el nombre de estas funciones y acostumbras el ojo a ignorar las primeras letras.

Más información en la página FreeRTOS - Free RTOS Coding Standard and Style Guide

FreeRTOS en Arduino

FreeRTOS está pensado para ejecutarse en procesadores algo más potentes que un Arduino. Así, como veremos en las futuras entradas, es el SO de serie en nuestros queridos ESP8266 y ESP32.

Sin embargo, es posible usar FreeRTOS en Arduino bajo arquitecturas AVR (Uno, Nano, Leonardo, Mega) gracias a la librería Arduino FreeRTOS Library desarrollada por Richard Barry y mantenida por Phillip Stevens junto a otros colaboradores.

Esta librería, disponible en este enlace, usa de forma muy original el Watchdog de Arduino para ejecutar el Scheduler a intervalos configurables desde 15ms a 500ms.

No vamos a negar que, pese al mérito que tiene está librería para procesadores AVR, FreeRTOS entra “con calzador” en un Arduino. Encontraremos mucho más útil en el ESP8266 y ESP32, donde se incluye de forma completa y nativa.

Ejemplo de FreeRTOS

Vamos a ver un ejemplo básico de “Hola Mundo” con FreeRTOS. Que, en el mundo de los procesadores el equivalente es un “Blink”, es decir, hacer parpadear un LED.

 #include <Arduino_FreeRTOS.h>

void TaskBlink(void *pvParameters)
{
  (void) pvParameters;

  pinMode(LED_BUILTIN, OUTPUT);

  while(true)
  {
    digitalWrite(LED_BUILTIN, HIGH);
    vTaskDelay( 500 / portTICK_PERIOD_MS );
    digitalWrite(LED_BUILTIN, LOW);    
    vTaskDelay( 500 / portTICK_PERIOD_MS );
  }
}

void setup()
{
  Serial.begin(9600);

  xTaskCreate(
    TaskBlink
    ,  "Blink"    // Nombre de la tarea
    ,  128      // Tamaño de la pila de la tarea
    ,  NULL
    ,  2        // Prioridad, siendo 0 la de menor prioridad, y 3 la de mayor
    ,  NULL );
}

void loop()
{
  Serial.println(millis());
  delay(1000);
}

Si subimos esto a una placa Arduino veremos, por un lado, que el LED de la placa se enciende y apaga cada 250ms y, a la vez, estamos enviando el valor “millis” por puerto serial.

En el código, simplemente hemos creado y asociado una Tarea TaskBlink encargada de hacer parpadear el LED de la placa. Por otro lado, tenemos el loop() que, al usar FreeRTOS, se transforma internamente en una tarea.

De momento nos vamos a quedar aquí, a modo de introducción. Por hoy no vamos a entrar en más detalle (oooh, que pena). Aunque, por supuesto, queda todo por explicar sobre Tasks, Queues, Semáforos.

Pero, todo esto lo veremos en las siguientes entradas dedicadas a FreeRTOS donde nos basaremos en el ESP32 para su desarrollo. En estos es donde se encuentra en su ambiente natural y podremos sacar todo su potencial.

No obstante, muchas de las cosas que veamos en el caso del ESP32 son aplicables a Arduino gracias a la genial librería Arduino FreeRTOS Library. ¡Hasta la próxima!