FreeRTOS en Arduino y ESP32 es la integración del kernel de FreeRTOS en el entorno con el que programamos la placa.
Ya sabemos qué es un RTOS y por qué la multitarea apropiativa nos ayuda a superar el “Super Loop”. Ahora toca aterrizar esto en nuestra placa, porque no es lo mismo ejecutar FreeRTOS en un Arduino Uno que en un ESP32.
En esta entrada vamos a ver cómo se integra el sistema operativo en el entorno de Arduino y qué configuración previa necesitamos antes de escribir nuestra primera línea de código.
El caso del ESP32: nativo de fábrica
Si usas un ESP32 con el core oficial de Arduino, tengo buenas noticias: no tienes que instalar FreeRTOS por separado.
El entorno de desarrollo de Arduino para ESP32 está construido sobre el SDK oficial de Espressif (ESP-IDF). Y resulta que el corazón del ESP-IDF es FreeRTOS.
Esto significa que cuando escribes código para ESP32 en Arduino, ya estás usando FreeRTOS, aunque no te des cuenta.
El secreto del loop() en ESP32
Aunque escribamos el mismo void loop(), por debajo no funciona igual que en un Arduino Uno.
En el Core de Arduino para ESP32, existe un código oculto (en el main.cpp del core) que hace algo parecido a esto:
// Pseudocódigo simplificado del core de Arduino-ESP32
void app_main() {
initArduino();
xTaskCreateUniversal(loopTask, "loopTask", 8192, NULL, 1, NULL, ARDUINO_RUNNING_CORE);
}
void loopTask(void *pvParameters) {
setup();
for(;;) {
loop();
// En las variantes de un núcleo, el core cede periódicamente la CPU.
}
}Tu querido void loop() se ejecuta dentro de una tarea de FreeRTOS llamada loopTask, con prioridad 1. El núcleo depende del modelo y de la configuración del core de Arduino.
Por eso, en ESP32, podemos mezclar código “estilo Arduino” con llamadas a FreeRTOS (xTaskCreate, vTaskDelay, etc.) sin necesidad de #include extraños ni configuraciones complejas. Es nativo.
El caso de Arduino AVR (Uno, Nano, Mega)
Si usas un microcontrolador de 8 bits como el ATmega328P (Arduino Uno), la historia cambia. Aquí FreeRTOS no viene integrado. El microcontrolador es mucho más limitado en RAM y velocidad.
Para usarlo aquí, debemos instalarlo como una librería externa.
- Vamos al Gestor de Librerías del IDE.
- Buscamos “FreeRTOS”.
- Instalamos un port compatible, como
Arduino_FreeRTOS_Library.
Y en nuestro código debemos incluirla explícitamente:
#include <Arduino_FreeRTOS.h>
void setup() {
// Crear tareas aquí...
}En Arduino_FreeRTOS_Library, el Scheduler arranca automáticamente al terminar setup(). Otros ports de FreeRTOS sí pueden exigir una llamada explícita, así que conviene seguir la documentación de la plataforma concreta.
Un Arduino Uno solo tiene 2 KB de SRAM. El kernel y las pilas de las tareas consumen una parte importante, así que el margen es muy pequeño. Para aprender está bien, pero para proyectos con más carga suele convenir un microcontrolador de 32 bits como ESP32, STM32 o SAMD.
Configuración: FreeRTOSConfig.h
FreeRTOS es extremadamente modular. Se puede configurar para ser muy ligero o para tener muchas funcionalidades. Todo esto se controla desde un archivo de cabecera llamado FreeRTOSConfig.h.
En este archivo se definen constantes mediante #define que activan o desactivan partes del Kernel.
Si usas ESP32 desde el core de Arduino, la configuración forma parte de las librerías precompiladas y no se cambia como un archivo más del sketch. Para personalizarla necesitas recompilar esas librerías o integrar Arduino como componente de ESP-IDF.
Sin embargo, es importante conocer algunos parámetros clave que definen el comportamiento de nuestra placa:
configTICK_RATE_HZ
Define la frecuencia de la interrupción del Tick (el latido del sistema).
- En ESP32, suele estar configurado a 1000 Hz.
- Esto significa que el Scheduler se despierta cada 1 ms.
- Por tanto, la resolución mínima de tiempo para
vTaskDelayes de 1 ms.
configMAX_PRIORITIES
El número máximo de niveles de prioridad disponibles.
- En ESP32 suele ser 25.
- Recuerda: a mayor número, mayor prioridad de tarea. Las prioridades de interrupción usan otra escala.
configMINIMAL_STACK_SIZE
El tamaño mínimo de pila (Stack) recomendado para una tarea vacía.
- Es importante para no desperdiciar RAM.
configUSE_PREEMPTION
- Establecido a
1. Confirma que el sistema es apropiativo (preemptivo) y no cooperativo.
¿Core 0 o Core 1? (Intro al Dual Core)
El ESP32 clásico tiene dos núcleos: Protocol CPU (PRO_CPU / Core 0) y Application CPU (APP_CPU / Core 1). No toda la familia ESP32 es multinúcleo, así que comprueba el modelo que estás usando.
Por defecto en Arduino:
- Core 0: Ejecuta varias tareas de sistema; el driver WiFi suele estar fijado aquí.
- Core 1: Es el núcleo habitual de
loopTasken el ESP32 clásico con la configuración estándar de Arduino.
Cuando creemos nuestras propias tareas, podremos decidir si queremos que el Scheduler decida dónde ponerlas, o si queremos “anclarlas” (Pinned) a un núcleo específico. Pero eso lo veremos en la sección de “Multicore” al final del curso.