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.
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:
0xAA 0xBB) para que el DMA del STM32 sepa dónde empieza la información válida.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);
}
}
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).
0xAA 0xBB) y un ID de Secuencia (Chunk ID). Esto previene que el hardware queme el mismo paquete dos veces si ocurre un retardo en las respuestas UART o se invierte el orden lógico de los mensajes.ACK confirmando que el bloque fue exitosamente quemado y verificado en la memoria física no volátil.