Un Caso Límite (Edge Case o Corner Case) es una situación operativa extrema que ocurre fuera de los parámetros de uso normales. En el firmware, la forma más común en la que un edge case destruye tu sistema es provocando un Deadlock (quedarse colgado). Esto sucede cuando la Máquina de Estados (FSM) entra a un estado esperando una condición (ej. while(sensor_responde == false)), pero por un fallo físico, el sensor jamás responde.
HAL_GetTick() para sellar cada estado con un cronómetro de "Tiempo Máximo de Vida" (TTL). Si el tiempo expira, la FSM fuerza una transición a un estado de ERROR_FAILSAFE.Paso 1: Identificar Puntos Ciegos (Blind Spots).
En el código del Día 302, el estado STATE_DISPENSING espera que current_vol_mL >= TARGET_VOLUME_ML.
Edge Case 1: ¿Qué pasa si el tanque del cliente ya estaba lleno, la presión se iguala, y el gas deja de fluir, pero faltan 2 litros para la meta? La FSM se queda atascada.
Edge Case 2: ¿Qué pasa si el operario corta el cable del caudalímetro con unas pinzas por accidente? Los pulsos se detienen a medio despacho.
Paso 2: Implementar Contadores de Tiempo (Software).
Al entrar a cada estado crítico, capturamos el tiempo de ingreso usando HAL_GetTick(). En cada iteración del bucle while(1), restamos el tiempo actual menos el tiempo de ingreso. Si supera los X milisegundos permitidos (Ej. 10 segundos sin actividad de flujo), obligamos a la máquina a saltar a un estado de error que apaga los actuadores.
Paso 3: Armar la Bomba de Tiempo (Hardware IWDG).
Iniciaremos el Perro Guardián Independiente (IWDG). Si tu bucle while(1) toma más de 2,000 ms en completarse (porque un periférico I2C se bloqueó o hubo un pico de voltaje que colgó la CPU), el IWDG reiniciará el STM32. El arranque seguro (Safe Boot del Día 289) garantizará que al despertar, todas las válvulas amanezcan apagadas.
Reestructuraremos la Máquina de Estados del Día 302. Añadiremos el HAL_IWDG_Refresh() y la lógica de Timeouts. Si un sensor de gas no responde, la máquina lo interpretará como una crisis, cerrará el gas, y se negará a continuar.
/**
* @file e2e_fault_tolerant_fsm.c
* @brief Máquina de Estados Defensiva con IWDG y Timeouts de Software
*/
#include "stm32f4xx_hal.h"
#include <stdint.h>
#include <stdio.h>
// Dependencias de Hardware
extern IWDG_HandleTypeDef hiwdg; // Declarada en la inicialización
extern void Valve_Main_Open(void);
extern void Valve_Main_Close(void);
extern void Cloud_Telemetry_Log(const char* msg);
// Estados de la Máquina
typedef enum {
STATE_IDLE,
STATE_DISPENSING,
STATE_FAILSAFE_ERROR // El nuevo Calabozo
} Safe_E2E_State_t;
volatile uint32_t Flow_Pulses_Count = 0;
Safe_E2E_State_t current_state = STATE_IDLE;
// =========================================================================
// MÁQUINA DE ESTADOS DEFENSIVA
// =========================================================================
void FSM_Defensive_Run(void) {
// Variables para el control del Timeout de Software
uint32_t state_entry_time = 0;
uint32_t last_pulse_count = 0;
uint32_t last_flow_time = 0;
printf("\r\n[SYS] FSM A PRUEBA DE FALLOS INICIADA.\r\n");
while (1) {
// -----------------------------------------------------
// 1. PET THE DOG (El salvavidas de hardware)
// Si nos colgamos en cualquier punto inferior por más de 2 segs,
// el STM32 sufre un HARD RESET.
HAL_IWDG_Refresh(&hiwdg);
// -----------------------------------------------------
switch (current_state) {
case STATE_IDLE:
// ... Lógica de espera ...
// Transición al inicio del despacho:
Flow_Pulses_Count = 0;
last_pulse_count = 0;
last_flow_time = HAL_GetTick(); // Registrar momento de inicio
Valve_Main_Open();
current_state = STATE_DISPENSING;
break;
case STATE_DISPENSING:
// Edge Case: Cable cortado o flujo obstruido.
// Evaluamos si los pulsos están aumentando.
if (Flow_Pulses_Count != last_pulse_count) {
// Hay movimiento de fluido, reseteamos el timeout local
last_pulse_count = Flow_Pulses_Count;
last_flow_time = HAL_GetTick();
}
// TIMEOUT CHECK: ¿Han pasado 8 segundos sin detectar un solo pulso
// mientras la válvula de 24V está físicamente abierta?
if ((HAL_GetTick() - last_flow_time) > 8000) {
// [!] ESTO ES UN CASO LÍMITE MORTAL.
// No llegamos a la meta, el tanque no está lleno, pero el fluido no avanza.
// Puede ser cable roto, bomba dañada, o fraude.
printf("\r\n[CRÍTICO] TIMEOUT: Pérdida de pulsos detectada. Abortando.\r\n");
// Transición forzada (Bail-out)
current_state = STATE_FAILSAFE_ERROR;
break;
}
// ... Lógica normal (Si alcanza el volumen, pasar a Cierre normal) ...
break;
case STATE_FAILSAFE_ERROR:
// El calabozo de la máquina. Un estado seguro inamovible.
Valve_Main_Close(); // 1. Garantizar corte mecánico.
// Alertar a la oficina central (Telemetría MQTT)
Cloud_Telemetry_Log("ERROR_CODE_07: Timeout de Caudalímetro en Despacho.");
printf("\r\n[S_SAFE] Sistema bloqueado por seguridad. Requiere reset manual.\r\n");
// El sistema se queda atascado aquí deliberadamente.
// El IWDG sigue siendo refrescado abajo para que no reinicie,
// pero la máquina no volverá a STATE_IDLE hasta que un humano lo audite.
while(1) {
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(1000);
// Parpadeo de LED de Alarma Roja
}
break;
}
}
}
Objetivo del día: Tolerancia a Fallas (Fault Tolerance) y Prevención de Catástrofes Industriales.
Un estudiante asume que si escribe abrir_valvula() el gas fluye, y si escribe cerrar_valvula() el gas se detiene. Un Arquitecto sabe que un ratón pudo haber mordido el cable del caudalímetro durante la noche. En tu Despachador de Gas LP, si el microcontrolador entra al estado de llenado, enciende el relé de potencia y de repente los pulsos del sensor de flujo nunca llegan porque el cable está roto, la matemática de (volumen >= meta) jamás se cumplirá.
Sin la lógica de Timeouts que introdujimos hoy, la válvula quedaría abierta infinitamente presurizando el tanque del cliente hasta la explosión térmica. Al inyectar la función HAL_GetTick() para rastrear el "tiempo desde el último pulso" (Timeout de Software), dotaste a la máquina de intuición: "Llevo 8 segundos inyectando corriente a la válvula y el sensor no detecta movimiento. Algo físico se rompió, aborto misión". Al mismo tiempo, el Independent Watchdog Timer (IWDG) actúa como el supervisor de silicio máximo. Si es el propio STM32 el que enloquece debido a un pico EMI, la falta del refresco HAL_IWDG_Refresh() obligará al procesador a suicidarse y reiniciarse en frío, cortando la alimentación de la válvula al instante. Has blindado tu máquina contra el fallo humano, el fallo mecánico y el fallo lógico.
while() infinita, perdiendo el control del sistema y manteniendo las salidas (actuadores) en un estado inseguro.FAILSAFE que apague los peligros.