DÍA 185 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

Los 4 Estados Fundamentales de un Hilo

En FreeRTOS, en cualquier microsegundo dado, una Tarea (Thread) se encuentra obligatoriamente en uno de estos 4 estados:

  1. RUNNING (En Ejecución): Es el rey actual. Tiene el control físico de la CPU. En un procesador Single-Core, solo puede haber una sola tarea en este estado a la vez.
  2. READY (Lista para Ejecutar): La tarea no está pausada y quiere ejecutarse, pero no puede porque una tarea de igual o mayor prioridad está ocupando la CPU (RUNNING). El RTOS las forma en una fila.
  3. BLOCKED (Bloqueada): El estado más importante del RTOS. La tarea le dice al Kernel: "Avisame cuando pasen 50ms, o cuando llegue un paquete de red". Mientras tanto, cede el 100% de la CPU. Una tarea bloqueada no consume un solo ciclo de reloj.
  4. SUSPENDED (Suspendida): Un coma inducido. La tarea no existe para el planificador hasta que otro hilo llame a la función de reanimación (osThreadResume). Es útil para apagar módulos temporalmente.

La Violencia del Context Switch (PendSV)

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.

DINÁMICA DEL PLANIFICADOR: ESTADOS Y CAMBIO DE CONTEXTO Ciclo de Vida de una Tarea RUNNING (1 Tarea) READY (Cola) BLOCKED (Esperando) SUSPENDED Despacho Expropiación (Preempt) osDelay() Fin de Delay o Evento osThreadSuspend Context Switch a nivel Silicio (RAM) CPU ARM Cortex-M4 R0-R12, LR, PC, xPSR Ejecutando "UI_Task" UI_Task Stack TCB Pointer Espacio para Registros Metro_Task Stack TCB Pointer Registros Congelados 1. PUSH (Guardar) 2. POP (Restaurar) Interrupción PendSV

⚙️ Ejercicios Prácticos

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);
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

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.

📝 Resumen del Día