En FreeRTOS, las tareas operan en contenedores aislados de tiempo. Si una tarea llama a osMutexAcquire(candado, osWaitForever) y el candado jamás se libera, esa tarea queda muerta. El Kernel simplemente la ignora y ejecuta el resto del programa. Si tenías un perro guardián (IWDG) configurado, y lo estás refrescando desde otra tarea que sigue viva, el hardware jamás se enterará de que tu máquina se volvió un "zombie" parcial.
Para solucionar esto, aplicamos un patrón arquitectónico militar: El Pase de Lista. Creamos una Tarea Centinela, la cual tendrá la máxima prioridad (o una muy alta). Esta tarea será la ÚNICA autorizada en todo el código para reiniciar el perro guardián de hardware (IWDG).
El mecanismo opera utilizando las Banderas de Eventos (Event Flags) que aprendimos en el Día 195:
osEventFlagsWait) con un límite de tiempo (Timeout). Exige que TODOS los bits estén encendidos mediante una lógica AND.while(1) infinito. El IWDG de hardware expirará en unos milisegundos, salvando al sistema mediante un Reset de fábrica.Ejercicio 1: Vamos a integrar el silicio con el RTOS. Crearemos las Banderas de Eventos (Event Flags) para nuestros hilos. En lugar de usar `osWaitForever` (como hicimos en el arranque del Día 195), usaremos un Timeout Estricto de 1,000 milisegundos. Si la función retorna el código de error por tiempo expirado (osErrorTimeout), ejecutaremos nuestra rutina destructiva.
/**
* @file sentinel_watchdog_rtos.c
* @brief Implementación Híbrida de Watchdog Software-Hardware en CMSIS-OS v2
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
// =======================================================
// MÁSCARAS DE HEARTBEAT (Latidos de Vida)
// =======================================================
#define ALIVE_METRO (1U << 0) // Bit 0
#define ALIVE_PANTALLA (1U << 1) // Bit 1
#define ALIVE_WIFI (1U << 2) // Bit 2
// La suma de todas las máscaras que la Centinela exige
#define ALIVE_TODOS (ALIVE_METRO | ALIVE_PANTALLA | ALIVE_WIFI)
osEventFlagsId_t wdg_flags_id;
IWDG_HandleTypeDef hiwdg;
// =======================================================
// TAREAS ESCLAVAS (Trabajadoras)
// =======================================================
void Thread_Metrologia(void *argument) {
for(;;) {
// ... Trabaja despachando gas ...
// Reporta que sigue viva en cada vuelta
osEventFlagsSet(wdg_flags_id, ALIVE_METRO);
osDelay(10);
}
}
void Thread_WiFi(void *argument) {
for(;;) {
// ... Tarea propensa a colgarse enviando JSONs ...
// Reporta que sigue viva (Al menos una vez por segundo)
osEventFlagsSet(wdg_flags_id, ALIVE_WIFI);
osDelay(500);
}
}
// =======================================================
// TAREA CENTINELA (Vigilante Supremo)
// =======================================================
void Thread_Centinela(void *argument) {
// 1. Iniciamos el hardware IWDG a 2000 ms (2 segundos)
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_32;
hiwdg.Init.Reload = 2000;
HAL_IWDG_Init(&hiwdg);
uint32_t flags_capturadas;
for (;;) {
// 2. MAGIA: Esperamos los latidos de todos por máximo 1 segundo.
// La opción osFlagsWaitAll hace que al cumplirse, los bits se pongan a 0
// automáticamente (auto-clear), preparándolos para la siguiente vuelta.
flags_capturadas = osEventFlagsWait(wdg_flags_id, ALIVE_TODOS,
osFlagsWaitAll, 1000U);
// 3. EVALUACIÓN DE VIDA O MUERTE
if (flags_capturadas == (uint32_t)osErrorTimeout) {
// ¡ALERTA ROJA! El segundo pasó y no recibimos todos los bits.
// Una tarea ha entrado en Inanición, Deadlock o Bucle infinito.
// NO pateamos el perro guardián.
UART_Enviar_String("FATAL: TAREA ZOMBIE. EJECUTANDO RESET HARDWARE...\r\n");
// Para asegurar el reset, encerramos al procesador en la muerte:
__disable_irq(); // Apagamos interrupciones
while(1) {
// En 1 segundo restante, el IWDG de silicio reiniciará el chip.
}
}
else {
// Todos vivos. Recompensamos al hardware pateando al perro.
HAL_IWDG_Refresh(&hiwdg);
}
}
}
// =======================================================
// INICIALIZACIÓN
// =======================================================
int main(void) {
SystemClock_Config();
osKernelInitialize();
// El objeto bandera
wdg_flags_id = osEventFlagsNew(NULL);
// Crear Hilos
osThreadNew(Thread_Metrologia, NULL, &attr_alta);
osThreadNew(Thread_WiFi, NULL, &attr_baja);
// CRÍTICO: La Centinela debe tener prioridad ABSOLUTA (Realtime)
// Nada debe impedirle evaluar la salud del sistema.
const osThreadAttr_t attr_centinela = {
.name = "Sentinel",
.priority = osPriorityRealtime
};
osThreadNew(Thread_Centinela, NULL, &attr_centinela);
osKernelStart();
while (1);
}
Objetivo del día: Tolerancia a Fallos de Software (Software Fault Tolerance) y Normativa SIL.
El estándar internacional IEC 61508 (Seguridad Funcional de Sistemas Electrónicos) no te pregunta "¿Tu programa tiene errores?", asume que los tiene. La pregunta que te hace el auditor es: "¿Qué hace tu máquina cuando el software falla?".
Si la tarea encargada de leer el sensor de presión (I2C) se queda atrapada en un bucle infinito (Ej. un ciclo while mal diseñado esperando un bit físico de un cable que se rompió), tu sistema de control de gas está legalmente "ciego". Al integrar una **Tarea Centinela Híbrida**, has diseñado un auditor insobornable. La Centinela exige que la Tarea de Presión se reporte cada segundo; si el cable se rompe y el bucle la atrapa, el latido `ALIVE_SENSOR` faltará. La Centinela detectará la anomalía, suspenderá las interrupciones cerrando las válvulas por defecto, e invocará un Reset Físico Limpio mediante el IWDG. Esta es la diferencia entre un microcontrolador de aficionado y una computadora industrial certificada.
osEventFlagsWait permite a la Centinela despertar e identificar un fallo sistémico. Si falla la recolección de latidos, la Centinela asume la muerte del sistema y detiene los "pateos" al IWDG, forzando un reinicio seguro para restaurar el estado inicial conocido.