RangeAyer vimos cómo MQTT es genial para telemetría (JSON cortos). Sin embargo, para descargar un archivo binario de 150 KB, el protocolo rey sigue siendo HTTP (específicamente desde servicios de almacenamiento como Amazon S3).
El estándar HTTP/1.1 introdujo una característica salva-vidas para los sistemas embebidos: Los Partial Requests (Peticiones Parciales). Si le agregas a tu paquete HTTP GET la cabecera Range: bytes=0-1023, el servidor en la nube NO te enviará el archivo completo. Te enviará única y estrictamente los primeros 1024 bytes (1 KB). Luego puedes pedir del 1024-2047, y así sucesivamente.
Para escribir ese fragmento de 1 KB en tu STM32, debes respetar las reglas de la memoria Flash:
0 a 1, debes borrar un Sector entero masivamente. Por lo tanto, antes de iniciar el ciclo FOTA, el STM32 debe ejecutar el comando de "Erase" sobre los Sectores 6 y 7 (Nuestro Bank B).1), puedes escribir tu código binario fragmento por fragmento (Chunk por Chunk), generalmente en bloques de 32 bits (Palabras/Words) o 64 bits.Circuito: Este es un proceso híbrido (Red y Hardware). Exige abrir un socket TCP AT+CIPSTART hacia el bucket de AWS S3 que aloja tu firmware, y manipular la memoria ROM interna del STM32 (Silicio) utilizando las llaves de desbloqueo de la API HAL.
Ejercicio 1: Programaremos la Máquina de Estados FOTA. Consta de 3 fases: Desbloqueo y Borrado (Erase), el Bucle de Descarga Paginada (Chunk Loop), y el Sellado/Reinicio.
/**
* @file fota_downloader.c
* @brief Algoritmo de Paginación FOTA y Programación de Flash (Dual-Bank)
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include "fota_map.h" // Creado en el Día 218
#include <stdio.h>
#include <string.h>
// Tamaño del Chunk adaptado a nuestra RAM (1024 bytes)
#define FOTA_CHUNK_SIZE 1024
// Endpoint del Firmware
#define FOTA_SERVER "s3.amazonaws.com"
#define FOTA_FILE_URL "/mi-bucket-gas/firmware_v2.bin"
// Buffer de RAM dedicado a atrapar el binario
uint8_t buffer_descarga[FOTA_CHUNK_SIZE];
// =======================================================
// FASE 1: PREPARACIÓN DEL SILICIO (ERASE)
// =======================================================
bool FOTA_Borrar_Bank_B(void) {
UART_Enviar_String("[FOTA] Borrando Sectores del Bank B (6 y 7). Por favor espere...\r\n");
// 1. Desbloquear la Flash (Requiere las llaves maestras de hardware de ST)
HAL_FLASH_Unlock();
// 2. Configurar el borrado masivo
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t SectorError = 0;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
EraseInitStruct.Sector = FLASH_SECTOR_6; // Bank B empieza aquí
EraseInitStruct.NbSectors = 2; // Borramos 6 y 7
// 3. Ejecutar el Borrado (Esta función bloquea la CPU unos segundos)
if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) {
HAL_FLASH_Lock();
return false;
}
// No bloqueamos la Flash aún, la dejaremos abierta para el Bucle
return true;
}
// =======================================================
// FASE 2: EL BUCLE DE CHUNKING (GET + WRITE)
// =======================================================
void FOTA_Iniciar_Descarga(void) {
uint32_t offset = 0;
bool descarga_completa = false;
char request_http[256];
if (!FOTA_Borrar_Bank_B()) {
UART_Enviar_String("[FOTA] Error crítico al borrar Flash.\r\n");
return;
}
UART_Enviar_String("[FOTA] Bank B limpio. Iniciando Paginación...\r\n");
// Abrimos Túnel TCP al servidor S3
// AT_Enviar_Comando("AT+CIPSTART=\"TCP\",\"s3.amazonaws.com\",80\r\n", 5000);
while (!descarga_completa) {
// 1. Armar el GET con el Header RANGE (La Magia de la Paginación)
// Pedimos del offset actual hasta offset + 1023 (1 KB exacto)
int len = snprintf(request_http, sizeof(request_http),
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Range: bytes=%lu-%lu\r\n\r\n",
FOTA_FILE_URL, FOTA_SERVER, offset, offset + FOTA_CHUNK_SIZE - 1);
// 2. Enviar por ESP32 (AT+CIPSEND) y recibir el binario
// Aquí asumimos una función que envía la petición y llena el 'buffer_descarga'
// Extrae exactamente los bytes crudos del cuerpo de la respuesta (ignorando Headers).
uint16_t bytes_recibidos = Descargar_Fragmento(request_http, buffer_descarga);
if (bytes_recibidos == 0) {
// Si el servidor retorna HTTP 416 (Range Not Satisfiable), el archivo terminó.
descarga_completa = true;
break;
}
// 3. Escribir el Chunk en la Memoria Flash del Bank B
uint32_t dir_destino = FOTA_BANK_B_START_ADDR + offset;
for (uint16_t i = 0; i < bytes_recibidos; i += 4) {
// Escribimos de 4 en 4 bytes (1 Word a la vez)
uint32_t word_data;
memcpy(&word_data, &buffer_descarga[i], 4);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, dir_destino + i, word_data);
}
// 4. Mover el apuntador para el siguiente ciclo
offset += bytes_recibidos;
// Yield para que el RTOS no colapse y la Tarea de Metrología siga vendiendo gas
osDelay(50);
}
// =======================================================
// FASE 3: EL SELLADO Y REINICIO
// =======================================================
UART_Enviar_String("[FOTA] Descarga 100%. Sellando Partición...\r\n");
// Escribimos el Sello Mágico (0xDEADBEEF) al final del Bank B (Día 218)
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FOTA_FLAG_ADDRESS, FOTA_MAGIC_WORD_PENDING);
// Cerramos con llave la Flash
HAL_FLASH_Lock();
UART_Enviar_String("[FOTA] Reiniciando la máquina para aplicar actualización...\r\n");
osDelay(1000);
// Reset Físico por Software (Hardware Reset)
NVIC_SystemReset();
}
Objetivo del día: Mantenimiento Continuo Desacoplado y Evitación de Fragmentación de RAM.
Descargar 150 KB de internet suena fácil en un celular moderno, pero en un entorno Bare-Metal/RTOS con apenas 96 KB de RAM, intentar alojar el archivo completo antes de grabarlo es imposible. Peor aún, detener las operaciones de tu Despachador de Gas durante los 2 o 3 minutos que tarda la descarga causaría pérdidas de ventas.
Al implementar el **Bucle de Paginación FOTA mediante Cabeceras Range**, has vencido las limitaciones de hardware. El STM32 solo necesita 1 KB de RAM (un pequeño vaso de agua) para trasladar el archivo completo de 150 KB (el océano) desde el módulo ESP32 hasta la memoria Flash del Bank B. Al incluir un `osDelay(50)` al final del ciclo de descarga, el Scheduler de FreeRTOS suspende la Tarea WiFi y le cede la CPU a la Tarea de Metrología. El resultado es mágico: El Despachador de Gas sigue midiendo combustible, controlando válvulas y emitiendo tickets sin retraso visible, mientras en las sombras (Background), su propio ADN se está descargando y reconstruyendo byte a byte. Al finalizar, la máquina ejecuta el comando suicida `NVIC_SystemReset()`, el Bootloader despierta, reconoce la actualización, la instala y arranca la nueva versión. Has dominado el ciclo de vida continuo de la Industria 4.0.
Range: bytes=X-Y.osDelay() o taskYIELD()) dentro del pesado bucle while de descarga HTTP/Flash es obligatorio en un RTOS. Esto evita la *Inanición* (Starvation) de las tareas críticas, logrando verdaderamente el concepto de **Zero-Downtime Updates** (Actualizaciones sin interrumpir la operación principal de la máquina).NVIC_SystemReset() desencadena la fase final donde el Bootloader toma el control antes de que el RTOS siquiera arranque.