Cuando ocurre un apagón, el voltaje no cae de 3.3V a 0V instantáneamente. Los capacitores de la fuente de poder se descargan creando una curva que dura unos 20 milisegundos. Si el microcontrolador sigue trabajando mientras el voltaje cae (ej. a 2.1V), el procesador entrará en locura lógica: ejecutará instrucciones mal, saltará a direcciones aleatorias y, peor aún, corromperá la memoria EEPROM escribiendo basura.
Los sistemas industriales utilizan un circuito integrado supervisor o el hardware interno del STM32 llamado BOD (Brown-Out Reset). Se configura para que, si el voltaje cruza el umbral de 2.7V hacia abajo, el procesador se bloquee instantáneamente (Reset forzado), congelando los pines y deteniendo cualquier escritura antes de que se vuelva "loco".
Escribir en una EEPROM I2C tarda unos 5 milisegundos. ¿Qué pasa si el BOD corta la energía justo en el milisegundo 3? El dato a la mitad estará corrupto.
La técnica de *Journaling* obliga a usar Banderas de Estado (Flags) y Sumas de Verificación (Checksums):
Si la máquina arranca y encuentra una bandera `VENTA_EN_CURSO`, significa que sufrió una muerte violenta (apagón). Debe recuperar ese volumen, cerrarlo forzosamente y facturarlo a la nube antes de permitir un nuevo cliente.
Ubicación de la Rutina (El Bloqueo Pre-RTOS): Este análisis no se debe programar dentro de una Tarea de FreeRTOS. Debe programarse en la función main(), inmediatamente después de inicializar los periféricos físicos (I2C) pero antes de llamar a osKernelStart(). Si arrancas el RTOS primero, la pantalla y el Módem podrían reaccionar antes de saber que hay una emergencia pendiente.
Ejercicio 1: El Analizador Forense. Programaremos la estructura de la Transacción utilizando la macro __attribute__((packed)) o `#pragma pack` para evitar relleno (Padding) del compilador. Aplicaremos la regla de oro de MISRA-C sobre validación de datos recuperados (Checksum CRC8).
/**
* @file disaster_recovery.c
* @brief Rutina Pre-Arranque: Análisis de EEPROM y Journaling (MISRA-C)
*/
#include "stm32f401xe.h"
#include <stdint.h>
#include <stdbool.h>
// =========================================================================
// ESTRUCTURA DE JOURNALING (Diario de EEPROM)
// =========================================================================
#define ESTADO_VENTA_CERRADA (0xAAU)
#define ESTADO_VENTA_EN_CURSO (0x55U)
// Forzamos alineación de 1 byte para escritura contigua en EEPROM
#pragma pack(push, 1)
typedef struct {
uint32_t folio_id; // Identificador único de transacción
uint32_t id_rfid_cliente; // ¿A quién le estábamos vendiendo?
float litros_respaldo; // El último volumen guardado antes de morir
uint8_t estado_bandera; // 0xAA (OK) o 0x55 (Murió a la mitad)
uint8_t checksum_crc8; // Protección contra corrupción física (BOD)
} RegistroVentaEEPROM_t;
#pragma pack(pop)
// API de Hardware I2C (Implementada en otros archivos)
extern bool EEPROM_I2C_LeerBloque(uint16_t direccion, uint8_t* buffer, uint16_t longitud);
extern void EEPROM_I2C_EscribirBloque(uint16_t direccion, const uint8_t* buffer, uint16_t longitud);
extern uint8_t Calcular_CRC8(const uint8_t* data, uint16_t length);
// =========================================================================
// RUTINA FORENSE (Ejecutada UNA SOLA VEZ en el main.c antes de osKernelStart)
// =========================================================================
void Recuperacion_Desastre_Init(void) {
RegistroVentaEEPROM_t bloque_memoria;
// 1. Extraer el bloque de la "Última Transacción" (Supongamos dirección 0x0100)
if (EEPROM_I2C_LeerBloque(0x0100U, (uint8_t*)&bloque_memoria, sizeof(RegistroVentaEEPROM_t))) {
// 2. Comprobar la Corrupción Física (Regla MISRA 10.4)
// Calculamos el CRC8 de toda la estructura EXCEPTO el último byte (el propio CRC)
uint8_t crc_calculado = Calcular_CRC8((uint8_t*)&bloque_memoria, sizeof(RegistroVentaEEPROM_t) - 1U);
if (crc_calculado != bloque_memoria.checksum_crc8) {
// FATAL: El apagón ocurrió exactamente mientras el STM32 escribía en el I2C.
// Los datos son basura. No podemos confiar en ellos.
// Log_Error("EEPROM CORRUPTA. Imposible recuperar desastre.");
return;
}
// 3. Análisis Lógico del Desastre
if (bloque_memoria.estado_bandera == ESTADO_VENTA_EN_CURSO) {
// ¡EL PACIENTE MURIÓ EN LA CAMILLA!
// La máquina se apagó a mitad de una venta.
// A) Notificar al Operador (Pantalla pre-RTOS o Baliza Ámbar Fija)
// LCD_Mostrar_Pantalla_Recuperando(bloque_memoria.litros_respaldo);
// B) Preparar la trama para inyección al Módem
// Aunque el RTOS no ha arrancado, dejamos la bandera lista para que
// la tarea de Telemetría (Día 251) la despache a AWS inmediatamente al bootear.
// Sistema_Encolar_Venta_Recuperada(bloque_memoria);
// C) Sellar la tumba: Marcar como CERRADA en EEPROM para no re-facturar
// si hay otro apagón un segundo después.
bloque_memoria.estado_bandera = ESTADO_VENTA_CERRADA;
bloque_memoria.checksum_crc8 = Calcular_CRC8((uint8_t*)&bloque_memoria, sizeof(RegistroVentaEEPROM_t) - 1U);
EEPROM_I2C_EscribirBloque(0x0100U, (uint8_t*)&bloque_memoria, sizeof(RegistroVentaEEPROM_t));
// D) Enviar log al sysadmin "Recuperación Exitosa de X Litros".
}
else if (bloque_memoria.estado_bandera == ESTADO_VENTA_CERRADA) {
// El paciente murió de viejo en su cama. Todo está en orden.
// La máquina fue apagada intencionalmente por el botón general.
// Continuar arranque normal.
}
else {
// Estado alienígena (Memoria virgen de fábrica o Bit-Flip)
// Formatear bloque...
}
}
}
Objetivo del día: Tolerancia a Fallos (Fault Tolerance), Integridad Transaccional y Cierre Contable.
Si alguna vez has visto la leyenda "No apague la consola mientras se guarda" en un videojuego, conoces el problema. Pero en una gasolinera, no puedes pedirle al rayo que no caiga mientras despachas. La Norma Oficial de Metrología Legal exige que todo dispensador preserve el registro de venta frente a pérdidas totales de suministro (Caída Cero). Si vendiste 100 litros, y hay un apagón, al encender debe facturar esos 100 litros.
Al programar el **EEPROM Transaction Journaling**, has implementado el mismo principio de bases de datos ACID (Atomicidad, Consistencia, Aislamiento, Durabilidad) que usa PostgreSQL, pero incrustado en 15 bytes de memoria. Guardando el estado progresivamente (`EN_CURSO`) y blindándolo con un CRC8 de hardware, fuerzas al Despachador a realizar una autopsia matemática en cada arranque (Pre-RTOS). Si descubre un desastre, "resucita" los litros perdidos y le asegura al dueño del negocio que ni un centavo se ha evaporado por culpa de la red eléctrica. Esta función convierte un simple "proyecto Maker" en un Activo Financiero Auditable (Industrial Grade Asset).
0x55 para "Iniciado", 0xAA para "Terminado") grabadas en una memoria no volátil como la EEPROM o Flash I2C/SPI.main(). Se ejecuta en un entorno Pre-RTOS (antes de habilitar el Kernel y las interrupciones concurrentes) para garantizar acceso exclusivo a la memoria y a los periféricos de inicialización.