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.
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.
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);
}
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.
delay() y HAL_Delay() son Bloqueantes (Busy-Waits). Queman intencionalmente ciclos del reloj atrapando a la CPU en un bucle vacío e impidiendo el progreso general del sistema. En un RTOS, su uso está prohibido.osDelay(ms) (o `vTaskDelay` en sintaxis nativa de FreeRTOS) cambia el estado de la tarea a BLOCKED. Permite el milagro de la multitarea cediendo inmediatamente la CPU a la siguiente tarea lista para trabajar.osDelay() produce una temporización relativa. El retraso comienza a contar desde el momento en que se invoca la instrucción, lo que puede causar "deriva de fase" si el código previo a la llamada tiene un tiempo de ejecución variable.osDelayUntil() produce una temporización absoluta periódica. Mantiene una cadencia inquebrantable compensando internamente el tiempo consumido por el procesamiento de la propia tarea, haciéndola indispensable para sistemas de muestreo de sensores, modulación PWM por software y lazos de control cerrados (PID).