DÍA 187 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

La Metáfora del Microondas

Imagina que pones comida en un horno de microondas por 3 minutos. Un HAL_Delay(3000) es como si te quedaras parado frente al microondas, mirando fijamente el plato girar durante 3 minutos, sin hacer absolutamente nada más, negándote a contestar el teléfono o atender a un cliente. Esto es un Busy-Wait (Espera Activa).

Por otro lado, osDelay(3000) es como poner los 3 minutos, irte a responder correos, hacer una llamada telefónica y dejar que la alarma del microondas te avise cuando terminó para regresar a la cocina. Estás cediendo (Yielding) el procesador.

¿Qué hace realmente osDelay()?

Cuando llamas a osDelay(10), le estás diciendo al Scheduler de FreeRTOS: "Sácame del estado RUNNING, colócame en la lista de tareas BLOCKED, anota que me debes despertar dentro de 10 Ticks (milisegundos) y entrégale el 100% del procesador a la siguiente tarea en la lista de READY". Durante esos 10ms, tu Tarea consume exactamente cero ciclos de CPU.

IMPACTO EN LA CPU: HAL_Delay() VS osDelay() Escenario 1: Uso de HAL_Delay() o while() bloqueante 0 ms 20 ms Task Alta Procesa HAL_Delay(20) [BUSY-WAIT] LA CPU ESTÁ SECUESTRADA Task Normal READY (Muriendo de inanición, sin acceso a la CPU) Escenario 2: Uso de osDelay() / vTaskDelay() 0 ms 20 ms Task Alta Procesa osDelay(20) [BLOCKED] Cede el control al Scheduler Run... Task Normal Ejecuta libremente (RUNNING) Preempt

⚙️ Ejercicios Prácticos

Ejercicio 1: Refactorizaremos el código. Eliminaremos un HAL_Delay() destructivo. Además, introduciremos su hermano avanzado: osDelayUntil(). Mientras osDelay(10) duerme la tarea 10ms *a partir de ahora* (creando una leve deriva en el tiempo por el retraso del cálculo), osDelayUntil garantiza una periodicidad matemática absoluta, vital para algoritmos de control como el PID de la válvula.


/**
 * @file delay_test.c
 * @brief Transición de Retardos Bloqueantes a Tiempos RTOS Absolutos
 */

#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include "valvula_pid.h"

// =======================================================
// EL ANTIPATRÓN: Delay Bloqueante (Prohibido en RTOS)
// =======================================================
void Thread_Peligroso_Viejo(void *argument) {
    for (;;) {
        // Enciende una bomba de llenado
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
        
        // ¡CATASTROFE! HAL_Delay usa un while() interno.
        // Si este hilo tiene prioridad Alta o Normal, la pantalla
        // y el WiFi se congelarán completamente durante 2 segundos.
        HAL_Delay(2000); 
        
        // Apaga la bomba
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
        
        HAL_Delay(2000);
    }
}

// =======================================================
// LA SOLUCIÓN: Yielding con osDelay
// =======================================================
void Thread_Seguro_Nuevo(void *argument) {
    for (;;) {
        // Enciende la bomba
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
        
        // La tarea se duerme. El Kernel salta a ejecutar la Pantalla y el WiFi.
        osDelay(2000); // 2000 Ticks del OS (2 segundos)
        
        // El Kernel nos despierta exactamente 2000ms después.
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
        
        osDelay(2000);
    }
}

// =======================================================
// NIVEL DIOS: Periodicidad Absoluta con osDelayUntil
// =======================================================
void Thread_Metrologia_PID(void *argument) {
    
    // Obtenemos el Tick exacto actual (La marca de tiempo del reloj del sistema)
    uint32_t tick_absoluto = osKernelGetTickCount();
    
    // Queremos que el PID se calcule cada 10ms exactos, sin importar 
    // cuánto tarden las funciones internas matemáticas.
    const uint32_t PERIODO_PID_MS = 10; 

    for (;;) {
        // 1. Ejecutamos la matemática pesada (Tarda ej. 2ms en calcularse)
        float correccion = Calcular_PID_Valvula();
        Aplicar_Correccion(correccion);
        
        // 2. Sumamos el periodo deseado a nuestra marca de tiempo base
        tick_absoluto += PERIODO_PID_MS;
        
        // 3. Dormimos HASTA QUE el reloj maestro alcance ese número exacto.
        // Si el cálculo tardó 2ms, el OS nos dormirá solo 8ms. 
        // ¡La frecuencia del PID jamás derrapará!
        osDelayUntil(tick_absoluto);
    }
}

int main(void) {
    // ... Configuración CMSIS-OS (Omitida por brevedad) ...
    // osThreadNew(Thread_Seguro_Nuevo, ...);
    // osThreadNew(Thread_Metrologia_PID, ...);
    osKernelStart();
    while(1);
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Sincronización Estricta del Lazo de Control.

En el corazón de la Metrología (Fase 2), programamos un Algoritmo PID para que la válvula cierre suavemente, evitando que la tubería estalle por "Golpe de Ariete" al terminar la carga de Gas LP. El secreto de un algoritmo PID (Proporcional, Integral, Derivativo) es la "D" y la "I": ambas operaciones dependen de que la variable del tiempo ($\Delta t$) sea perfectamente constante.

Si usas un osDelay(10) común y corriente, el ciclo no dura 10ms. Dura: [Tiempo del cálculo PID] + 10ms. Si el cálculo fluctúa por el tráfico de interrupciones del WiFi, el $\Delta t$ fluctúa, y el PID de la válvula se vuelve inestable, oscilando la presión del gas. Al reescribir tu tarea de control utilizando **`osDelayUntil()`**, has amarrado la ejecución a la rejilla de Ticks absolutos del procesador. El Kernel absorberá las variaciones, despertando tu hilo de Metrología exactamente en los milisegundos 10, 20, 30, 40... logrando un control de fluidos con precisión quirúrgica, digno de la certificación legal.

📝 Resumen del Día