DÍA 261 DE 365

Fase 5: Diseño Industrial y Proyecto Final

📖 Teoría a Estudiar

Zero-Downtime FOTA (Background OTA)

Tradicionalmente, para actualizar un equipo, lo apagas, lo pones en "Modo DFU/Bootloader", le pasas el archivo, y se reinicia. En un Despachador de Gas LP que atiende clientes 24/7, detener la máquina durante 5 minutos para descargar un archivo es inaceptable.

Con nuestra arquitectura, el STM32 (la Aplicación actual) sigue despachando gas y ejecutando el RTOS. En segundo plano, una tarea de baja prioridad recibe el firmware bloque a bloque desde el ESP32 y lo guarda en el Slot OTA (0x08040000) (Día 258). La máquina nunca se detiene. Solo cuando el archivo completo de 192KB ha sido recibido y su Firma SHA-256 es validada, el STM32 se reinicia. El Bootloader (Día 257) despierta, ve un archivo válido en el Slot OTA, lo copia a la Aplicación Principal y arranca. El tiempo de inactividad de la máquina se reduce de minutos a 5 segundos.

El Protocolo de Tramas (Chunking & Handshake)

Para enviar datos por el UART sin que se corrompan, usaremos una topología de "Comando y Respuesta" parecida al viejo pero confiable protocolo YMODEM:

  1. Sincronización (Header): Cada paquete inicia con bytes mágicos (ej. 0xAA 0xBB) para que el DMA del STM32 sepa dónde empieza la información válida.
  2. Secuencia (Chunk ID): Si el ESP32 envía el paquete #4 y luego el #6, el STM32 sabrá que el paquete #5 se perdió por ruido.
  3. Datos (Payload): Un bloque fijo de 256 Bytes de código binario.
  4. Checksum (CRC16): Una huella digital matemática del paquete.
  5. Respuesta (ACK / NACK): El STM32 responde `ACK` si quemó el bloque en la Flash con éxito, o `NACK` si el CRC falló, obligando al ESP32 a reenviar ese bloque específico.
DIAGRAMA DE SECUENCIA: FOTA UART HANDSHAKE (ZERO-DOWNTIME) ESP32 (Coprocesador) STM32 (Main App) SYNC ID:01 PAYLOAD (256 Bytes de Firmware) CRC16 CMD: FOTA_START (Size: 192KB) ACK: READY (Borrando Slot 0x08040000...) CHUNK ID: 00 | Payload | CRC: 0xA4F1 Valida CRC -> OK HAL_FLASH_Program ACK: CHUNK 00 OK CHUNK ID: 01 | Payload | CRC: 0x9B2C RUIDO EMI Valida CRC -> ERROR Drop Packet NACK: CHUNK 01 (Retransmitir) RETRY CHUNK ID: 01 | Payload | CRC: 0x9B2C ACK: CHUNK 01 OK Repetir hasta EOF...

⚙️ Ejercicios Prácticos

Tamaño del Chunk (Payload Size): Si el Chunk es de 4,000 bytes, el overhead del CRC será casi nulo, pero si hay ruido, el ESP32 tendrá que reenviar 4,000 bytes, perdiendo mucho tiempo. Si el Chunk es de 16 bytes, el sistema será inmune al ruido, pero pasarás el 80% del tiempo enviando "Headers" y "ACKs". El punto dulce (Sweet-spot) en UART industrial es de 256 Bytes, que además coincide matemáticamente con los bloques de borrado lógico de memoria de muchos chips Flash SPI.

Ejercicio 1: El Parser FOTA MISRA-C. Programaremos la estructura de la trama y la función de la tarea de la aplicación principal (Task_Telemetria_Cloud del Día 251) que recibe los fragmentos mediante UART DMA, valida las matemáticas y responde la confirmación.


/**
 * @file fota_uart_protocol.c
 * @brief Parser de Recepción FOTA por UART y Handshake (MISRA-C)
 */

#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include <stdint.h>
#include <stdbool.h>

// =========================================================================
// DEFINICIONES DEL PROTOCOLO FOTA
// =========================================================================
#define FOTA_SYNC_WORD_1    (0xAAU)
#define FOTA_SYNC_WORD_2    (0xBBU)
#define FOTA_PAYLOAD_SIZE   (256U)

#define FOTA_ACK            (0x06U) // ASCII Acknowledge
#define FOTA_NACK           (0x15U) // ASCII Negative Acknowledge

// Estructura de paquete rígidamente empaquetada para evitar Padding
#pragma pack(push, 1)
typedef struct {
    uint8_t  sync_1;
    uint8_t  sync_2;
    uint16_t chunk_id;
    uint16_t payload_length;
    uint8_t  payload_data[FOTA_PAYLOAD_SIZE];
    uint16_t crc16;
} FotaPacket_t;
#pragma pack(pop)

// Buffer de recepción mapeado a DMA
static uint8_t uart_rx_buffer[sizeof(FotaPacket_t) + 10U];

// =========================================================================
// DEPENDENCIAS EXTERNAS
// =========================================================================
extern uint16_t Calcular_CRC16_CCITT(const uint8_t *data, uint16_t length);
extern bool IAP_Escribir_Bloque(uint32_t address, const uint8_t* p_data, uint32_t length);
extern void UART_Transmitir_Byte(uint8_t byte); // Función envolvente de HAL_UART_Tx

// Puntero físico en Flash para la descarga en segundo plano
// (OTA Download Slot del Linker Script Día 258)
#define OTA_SLOT_ADDRESS    (0x08040000U)
static uint32_t current_flash_ptr = OTA_SLOT_ADDRESS;
static uint16_t expected_chunk_id = 0U;

// =========================================================================
// PROCESADOR DE TRAMAS FOTA (Llamado desde la Tarea de Red)
// =========================================================================
/**
 * @brief Analiza un bloque binario recibido por UART DMA.
 *        Si es válido, lo inyecta a la Flash (Background OTA).
 * @param packet_buffer: Puntero al buffer crudo recibido del ESP32.
 */
void FOTA_Procesar_Paquete(const uint8_t* packet_buffer) {
    
    // 1. Casting Seguro al struct (Asumiendo alineación asegurada por UART DMA)
    const FotaPacket_t* packet = (const FotaPacket_t*)packet_buffer;
    
    // 2. Validación del Header (Sincronización)
    if ((packet->sync_1 != FOTA_SYNC_WORD_1) || (packet->sync_2 != FOTA_SYNC_WORD_2)) {
        // Basura de red (Out-of-sync). No respondemos o mandamos NACK de formato.
        UART_Transmitir_Byte(FOTA_NACK);
        return;
    }
    
    // 3. Validación Matemática de Integridad (CRC-16)
    // Calculamos el CRC de todo el paquete EXCEPTO los últimos 2 bytes (el propio CRC)
    uint16_t calc_len = sizeof(FotaPacket_t) - 2U;
    uint16_t crc_calculado = Calcular_CRC16_CCITT((const uint8_t*)packet, calc_len);
    
    if (crc_calculado != packet->crc16) {
        // RUIDO ELECTROMAGNÉTICO DETECTADO EN LA PISTA DE COBRE
        // Rechazamos el paquete. La memoria Flash está a salvo.
        UART_Transmitir_Byte(FOTA_NACK);
        return;
    }
    
    // 4. Validación de Secuencia Lógica (Evitar duplicados o saltos)
    if (packet->chunk_id != expected_chunk_id) {
        // Si el ESP32 retransmitió un paquete que ya teníamos por error de latencia
        if (packet->chunk_id < expected_chunk_id) {
            UART_Transmitir_Byte(FOTA_ACK); // Le decimos "Ya lo tengo, sigue"
        } else {
            UART_Transmitir_Byte(FOTA_NACK); // Perdimos un paquete en medio, aborta
        }
        return;
    }
    
    // 5. INYECCIÓN A ESTADO SÓLIDO (FLASH)
    // Usamos el driver IAP (Día 260) modificado para apuntar al OTA Slot.
    if (IAP_Escribir_Bloque(current_flash_ptr, packet->payload_data, packet->payload_length)) {
        
        current_flash_ptr += packet->payload_length; // Avanzar el puntero lógico
        expected_chunk_id++;                         // Esperar el siguiente bloque
        
        // Handshake Exitoso. Le decimos al ESP32 que mande el siguiente.
        UART_Transmitir_Byte(FOTA_ACK);
        
    } else {
        // Fallo de voltaje o Flash desgastada al quemar
        UART_Transmitir_Byte(FOTA_NACK);
    }
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Zero-Downtime Flashing y Resiliencia en Redes Inestables.

El internet en las fábricas o gasolineras rurales no es como el internet de tu casa. La conexión celular (LTE/M) se cae constantemente. Si tu máquina intentara descargar el archivo binario completo en una sola conexión continua de TCP/IP, fallaría en el 90% de los intentos y el equipo jamás se actualizaría.

Al implementar este **Protocolo de Handshake FOTA (Chunking + CRC)**, has independizado tu arquitectura. El ESP32 es el "mulo de carga": él se pelea con los cortes de internet de Telcel/AT&T, resume las descargas HTTP y almacena el archivo pacientemente. Cuando por fin lo tiene completo, se voltea al STM32 y se lo inyecta limpiamente a través del cable UART a 1 Megabaudio. Y lo más impresionante: Como esto ocurre en el Sector OTA (`0x08040000`), el cliente que está despachando gas en ese mismo segundo jamás notará que la placa madre de la máquina acaba de asimilar un nuevo sistema operativo en segundo plano. Esto es verdadera Alta Disponibilidad (High Availability).

📝 Resumen del Día