La depuración en FreeRTOS es el conjunto de técnicas para observar tareas, memoria, tiempos y bloqueos en un sistema concurrente.
Cuando un RTOS falla, suele hacerlo de formas poco evidentes: el sistema se reinicia, una tarea deja de responder mientras las demás siguen vivas o la conexión WiFi se vuelve inestable.
Depurar un sistema concurrente es difícil porque no podemos detenerlo fácilmente (si pones un breakpoint, los timers siguen contando, el Watchdog salta y el WiFi se desconecta).
Por suerte, FreeRTOS incluye herramientas de introspección que nos permiten hacer una “radiografía” al sistema en tiempo real. Hoy vamos a aprender a usar vTaskList y las estadísticas de ejecución.
El administrador de tareas: vTaskList
Imagina que pudieras abrir el “Administrador de Tareas” de Windows o el monitor de actividad de Mac, pero dentro de tu ESP32. Eso es exactamente lo que hace la función vTaskList().
Esta función toma una instantánea del sistema y genera una tabla de texto con información importante sobre cada tarea.
Configuración previa
Para que esto funcione, FreeRTOS debe estar compilado con ciertas opciones activadas en FreeRTOSConfig.h.
- En Arduino-ESP32, comprueba la configuración de la versión instalada, porque las librerías precompiladas pueden habilitar o deshabilitar estas funciones.
- En un port configurable, necesitas:
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1Implementación
La función necesita un buffer donde escribir el informe, pero su API no recibe el tamaño. Debemos reservar margen suficiente para todas las tareas o usar uxTaskGetSystemState() y formatear nosotros la salida si necesitamos control estricto.
void TareaMonitor(void *pvParameters) {
char buffer[1024]; // Ajustar según el número de tareas y sus nombres.
for(;;) {
// 1. Tomamos una instantánea del sistema
// FreeRTOS rellenará el buffer con la tabla
vTaskList(buffer);
// 2. Imprimimos el resultado
Serial.println("--------------------------------------------------");
Serial.println("Nombre Estado Prio Stack Num");
Serial.println("--------------------------------------------------");
Serial.print(buffer);
Serial.println("--------------------------------------------------");
// Repetimos cada 5 segundos
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
void setup() {
Serial.begin(115200);
// Creamos algunas tareas de ejemplo para ver algo interesante
xTaskCreate(TareaBlink, "Blink", 2048, NULL, 1, NULL);
xTaskCreate(TareaWiFi, "WiFi", 4096, NULL, 2, NULL);
// Creamos la tarea monitor
xTaskCreate(TareaMonitor, "Monitor", 2048, NULL, 1, NULL);
}Interpretando la Salida
Si ejecutas el código, verás algo parecido a esto en el monitor serie:
Nombre Estado Prio Stack Num
--------------------------------------------------
WiFi R 2 1500 2
Blink B 1 1800 3
Monitor X 1 1024 4
IDLE R 0 1010 1
--------------------------------------------------Vamos a descifrar las columnas:
Nombre: El nombre que le dimos en xTaskCreate.
Estado: Una letra que indica cómo estaba la tarea en ese instante:
X(Running): Ejecutándose en el instante de la captura.R(Ready): Lista para ejecutarse.B(Blocked): Esperando (delay, cola, semáforo).S(Suspended): Suspendida explícitamente.D(Deleted): Marcada para borrar (esperando a la IDLE).
Prio: La prioridad actual.
Stack (High Water Mark): ¡Esta es la columna más importante!
- No indica la memoria usada, sino la mínima memoria libre histórica.
- En ESP-IDF se expresa en bytes; en FreeRTOS estándar se expresa en unidades de
StackType_t. - Un valor pequeño requiere atención, pero el margen correcto depende de los peores caminos de ejecución y de la carga futura.
Num: Un ID único autoincremental que asigna el sistema.
Medir la carga de CPU con vTaskGetRunTimeStats
vTaskList nos dice cómo están las tareas, pero no cuánto trabajan. Si el sistema va lento, necesitamos saber qué tarea está acaparando la CPU.
Para esto existe vTaskGetRunTimeStats. Esta función nos devuelve el tiempo absoluto y relativo (%) que cada tarea ha pasado ejecutándose.
Para medir esto, FreeRTOS necesita una fuente de tiempo de ejecución bastante más rápida que el tick y las opciones configGENERATE_RUN_TIME_STATS y configUSE_STATS_FORMATTING_FUNCTIONS. No des por hecho que las librerías precompiladas las incluyen.
Ejemplo de uso
El funcionamiento es idéntico a vTaskList: le pasamos un buffer.
void TareaStats(void *pvParameters) {
char bufferStats[512];
for(;;) {
vTaskGetRunTimeStats(bufferStats);
Serial.println("------ USO DE CPU ------");
Serial.println("Tarea Absoluto %");
Serial.print(bufferStats);
vTaskDelay(pdMS_TO_TICKS(10000));
}
}Salida típica:
Tarea Absoluto %
IDLE 9800500 98%
WiFi 100000 1%
Blink 50000 <1%
MathTask 50000 <1%Un porcentaje alto en Idle indica que ese núcleo ha tenido bastante tiempo sin trabajo de mayor prioridad. Un porcentaje bajo no es malo por sí mismo: compáralo con los plazos, las colas pendientes y la latencia que necesita la aplicación.
vTaskList() y vTaskGetRunTimeStats() formatean bastante texto y suspenden la planificación durante parte de la captura. Son herramientas de diagnóstico, no funciones para ejecutar continuamente en producción.
Errores comunes
Para cerrar el curso, recopilamos los “pantalazos azules” más comunes que encontraréis en el monitor serie del ESP32 y qué significan.
Guru Meditation Error: Stack Canary Watchpoint Triggered
- Causa: Una tarea ha escrito fuera de su memoria asignada. Stack Overflow.
- Solución: Mira cuál fue la última tarea activa (el propio error suele decirlo) y auméntale el
StackDepthenxTaskCreate.
Task Watchdog Got Triggered (TWDT)
- Causa: Una tarea lleva demasiado tiempo ejecutándose sin ceder el control (Starvation). La tarea IDLE no ha podido ejecutarse para “alimentar al perro”.
- Solución: Busca bucles o secciones largas y haz que la tarea se bloquee mediante una cola, semáforo, notificación o
vTaskDelay.taskYIELD()solo cede ante tareas de la misma prioridad y puede seguir dejando sin CPU a Idle.
LoadProhibited / StoreProhibited
- Causa: El programa intenta acceder a una dirección inválida por un puntero nulo, sin inicializar, ya liberado o corrupto.
- Solución: Revisa la traza y la vida de los punteros. Comprobar
NULLayuda, pero no detecta un puntero colgante.
Visualización de trazas
Lo que hemos visto es depuración por puerto serie. Es útil, pero intrusiva (el propio Serial.print afecta al tiempo de ejecución).
Herramientas como SEGGER SystemView permiten registrar eventos del kernel y visualizarlos en una línea temporal. La integración y el método de captura dependen del chip, del framework y de la sonda de depuración disponible.
Una traza permite ver cambios de contexto, ISR y operaciones de sincronización sin depender únicamente de mensajes serie. Su configuración es más avanzada, pero resulta muy útil para estudiar jitter, bloqueos y orden de ejecución.