En FreeRTOS, en cualquier microsegundo dado, una Tarea (Thread) se encuentra obligatoriamente en uno de estos 4 estados:
osThreadResume). Es útil para apagar módulos temporalmente.Cuando el reloj del sistema (SysTick) interrumpe la CPU cada 1 milisegundo, evalúa las listas de tareas READY. Si encuentra una con mayor prioridad que la tarea RUNNING actual, dispara una interrupción de hardware profunda llamada PendSV.
El PendSV congela la CPU. Toma los 16 registros internos del procesador ARM (R0-R15), el Program Counter (dónde se quedó leyendo la línea de código C) y las banderas matemáticas, y los "apila" (Pushes) brutalmente en el espacio de memoria RAM privado de esa tarea, llamado Task Stack. El RTOS anota la dirección de esa memoria en el Task Control Block (TCB). Luego, toma el TCB de la tarea ganadora, "desapila" (Pops) sus registros hacia la CPU, y reanuda el tiempo.
Ejercicio 1: Vamos a crear tres hilos con CMSIS-OS v2 para gobernar tu Despachador de Gas LP. Veremos claramente cómo forzar a los hilos a viajar a través de los diferentes estados (RUNNING, READY, BLOCKED, SUSPENDED) usando las APIs osDelay, osThreadSuspend y osThreadResume para optimizar la CPU al máximo.
/**
* @file task_states.c
* @brief Demostración de Transición de Estados de Hilos en CMSIS-OS v2
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
// Variables Globales de los Hilos (IDs) para poder controlarlos desde afuera
osThreadId_t id_hilo_metrologia;
osThreadId_t id_hilo_pantalla;
osThreadId_t id_hilo_mantenimiento;
// =======================================================
// HILO 1: METROLOGÍA (Prioridad ALTA)
// =======================================================
void Thread_Metrologia(void *argument) {
for (;;) {
// [ESTADO: RUNNING]
// Lee el caudalímetro y opera el actuador (Válvula PID)
Ejecutar_PID_Valvula();
// [TRANSICIÓN: RUNNING -> BLOCKED]
// Rinde la CPU estrictamente por 10 milisegundos.
// Durante este tiempo, la Metrología no consume NINGÚN ciclo de CPU.
osDelay(10);
// Al pasar los 10ms: [TRANSICIÓN: BLOCKED -> READY -> RUNNING]
// Si hay una tarea de menor prioridad ejecutándose, el Kernel le
// hace un Context Switch forzado y se la da a este hilo de nuevo.
}
}
// =======================================================
// HILO 2: PANTALLA LCD (Prioridad NORMAL)
// =======================================================
void Thread_Pantalla(void *argument) {
for (;;) {
// [ESTADO: RUNNING (Cuando Metrología está Blocked)]
Actualizar_Pantalla_SPI();
// Simular un botón que activa el Mantenimiento del equipo.
if (Boton_Mantenimiento_Presionado()) {
// Reanimamos a la tarea suspendida, pasa a [READY]
osThreadResume(id_hilo_mantenimiento);
// Congelamos esta misma tarea (Pantalla). Pasa a [SUSPENDED].
// A diferencia del osDelay, no despertará jamás hasta que
// alguien llame a osThreadResume(id_hilo_pantalla).
osThreadSuspend(id_hilo_pantalla);
}
osDelay(50); // Refresca a 20 FPS
}
}
// =======================================================
// HILO 3: MANTENIMIENTO OTA (Prioridad BAJA)
// =======================================================
// Esta tarea consume mucha RAM, pero solo se usa 1 vez al mes para
// actualizar el firmware por WiFi.
void Thread_Mantenimiento(void *argument) {
for (;;) {
// [ESTADO: RUNNING]
// Descarga el firmware de AWS y reescribe la Flash
Ejecutar_Actualizacion_OTA();
// Al terminar, despertamos a la Pantalla de regreso
osThreadResume(id_hilo_pantalla);
// Y auto-suspendemos esta tarea de Mantenimiento. Pasa a [SUSPENDED].
// Ya no estorba al planificador en absoluto.
osThreadSuspend(id_hilo_mantenimiento);
}
}
// =======================================================
// INICIALIZACIÓN
// =======================================================
int main(void) {
SystemClock_Config();
osKernelInitialize();
// Configuración de atributos (omitidos para brevedad)
// ...
// Crear los Hilos
id_hilo_metrologia = osThreadNew(Thread_Metrologia, NULL, &attr_metro);
id_hilo_pantalla = osThreadNew(Thread_Pantalla, NULL, &attr_pantalla);
id_hilo_mantenimiento = osThreadNew(Thread_Mantenimiento, NULL, &attr_mante);
// Forzar a la tarea de Mantenimiento a iniciar Suspendida (En coma).
// Si no hacemos esto, correría el OTA al arrancar la máquina.
osThreadSuspend(id_hilo_mantenimiento);
osKernelStart();
while (1);
}
Objetivo del día: Optimización de Ciclos de CPU y Partición de Estados.
Tu Despachador es una máquina que debe ser extremadamente ágil, pero también incluye código monstruosamente pesado: las actualizaciones de firmware por la red (OTA). En la Fase 3, incluir el código OTA en tu Súper Bucle era un riesgo porque llenaba la memoria de variables globales innecesarias que ralentizaban la Metrología de las válvulas.
Al dominar los **Estados de Tareas** hoy, le has otorgado a tu sistema *inteligencia dinámica*. Has creado la `Thread_Mantenimiento`, pero inmediatamente la pusiste en estado `SUSPENDED`. Para el procesador ARM Cortex-M4, esta tarea es ahora un fantasma; el *Scheduler* ni siquiera pierde 1 microsegundo evaluándola. El 100% de la CPU se reparte pacíficamente entre la válvula y la pantalla gráfica. Solo el día en que un técnico presiona el botón físico oculto en el gabinete, invocas `osThreadResume`. La tarea despierta, bloquea la pantalla, hace la actualización del sistema, y vuelve a su coma inducido. Esto es arquitectura de alta eficiencia industrial.