Cuando usas un cable ST-Link conectándolo a los pines SWDIO y SWCLK, estás haciendo ICP. El procesador es un esclavo apagado y el ST-Link manipula sus registros desde afuera. Pero cuando el procesador está encendido, lee un archivo y decide reescribirse a sí mismo, eso es IAP.
Para lograr IAP, el STM32 debe ejecutar código desde el Sector 0 (Bootloader) para poder borrar y escribir en los Sectores 4 y 5 (Aplicación). No puedes borrar el sector desde el cual estás ejecutando el código; es como serrar la rama del árbol sobre la que estás sentado.
Escribir en la memoria Flash no es como escribir en la RAM. Tiene reglas de hardware estrictas:
1 (hexadecimal 0xFF). Esta operación puede tardar hasta 2 segundos.1 (borrados) para cambiarlos a 0. El STM32F401 requiere que escribas en bloques alineados (Bytes, Half-Words o Words). Para 3.3V (Voltaje nominal), usamos paralelismo de 32 bits (Word).El Circuito SD (SPI): La tarjeta micro SD se conectará al STM32 mediante un bus SPI secundario (ej. SPI2). Necesitas 4 pines: CS, SCK, MISO, MOSI. Todas las señales lógicas son de 3.3V (¡No le metas 5V a la SD o se freirá!). Es crítico colocar resistencias Pull-Up de 10kΩ en las líneas de datos para estabilizar el bus durante el arranque.
Ejercicio 1: El Quemador de Silicio (Flash Driver). Escribiremos la función en C que toma un arreglo de RAM y lo inyecta a la fuerza en la memoria Flash no volátil, cumpliendo con la secuencia de desbloqueo, borrado y escritura exigida por STMicroelectronics.
/**
* @file iap_flash_driver.c
* @brief Driver In-Application Programming para escritura de Flash (MISRA-C)
*/
#include "stm32f401xe.h"
#include <stdint.h>
#include <stdbool.h>
// =========================================================================
// DEFINICIONES DEL TARGET
// =========================================================================
// Empezaremos a borrar/escribir desde el Sector 4
#define APP_BASE_ADDRESS (0x08010000U)
#define APP_SECTOR_START FLASH_SECTOR_4
// =========================================================================
// BORRADO DEL SECTOR (PREPARACIÓN DEL LIENZO)
// =========================================================================
/**
* @brief Borra la memoria Flash asignada a la aplicación principal.
* ADVERTENCIA: Destruye el código de la App actual. Bloquea el CPU.
* @retval bool: TRUE si el borrado fue exitoso.
*/
bool IAP_Borrar_Slot_Aplicacion(void) {
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t SectorError = 0U;
bool status = true;
// 1. Desbloquear el acceso al hardware Flash
HAL_FLASH_Unlock();
// 2. Limpiar banderas de errores pasados
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
// 3. Configurar la estructura de Borrado
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 2.7V a 3.6V
EraseInitStruct.Sector = APP_SECTOR_START;
// En nuestro mapa, la App ocupa el Sector 4 (64KB) y Sector 5 (128KB)
EraseInitStruct.NbSectors = 2U;
// 4. Ejecutar la aniquilación (Puede tardar hasta 2 segundos)
// El Watchdog debe alimentarse antes de llamar esto, o el chip se reiniciará.
if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) {
status = false; // El borrado falló (Flash desgastada o caída de voltaje)
}
// 5. Volver a bloquear el cerrojo por seguridad temporal
HAL_FLASH_Lock();
return status;
}
// =========================================================================
// ESCRITURA BLOQUE A BLOQUE (WORD x WORD)
// =========================================================================
/**
* @brief Escribe un bloque de datos (descargado de la SD) en la Flash.
* @param start_address: Dirección de Flash destino
* @param p_data: Puntero al buffer en RAM (ej. array de 512 bytes)
* @param length_bytes: Cantidad de bytes a escribir (debe ser múltiplo de 4)
* @retval bool: TRUE si la escritura y validación tuvieron éxito.
*/
bool IAP_Escribir_Bloque(uint32_t start_address, const uint8_t* p_data, uint32_t length_bytes) {
bool status = true;
uint32_t current_addr = start_address;
uint32_t words_to_write = length_bytes / 4U; // Escribimos en trozos de 32 bits (4 bytes)
// Convertimos el puntero de 8 bits a 32 bits para facilitar la lectura
// MISRA: Cast de punteros validado por diseño de hardware
const uint32_t* p_data_32 = (const uint32_t*)p_data;
HAL_FLASH_Unlock();
for (uint32_t i = 0U; i < words_to_write; i++) {
// Escribimos una "Palabra" (32 bits)
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, current_addr, p_data_32[i]) == HAL_OK) {
// VERIFICACIÓN INMEDIATA FÍSICA (Read-Back)
// Leemos el silicio directamente para ver si los electrones se atraparon
if (*(__IO uint32_t*)current_addr != p_data_32[i]) {
status = false;
break; // Corrupción física (Death sector)
}
current_addr += 4U; // Avanzamos 4 bytes físicos
} else {
status = false; // Falló el comando de programación
break;
}
}
HAL_FLASH_Lock();
return status;
}
// =========================================================================
// RUTINA DE ORQUESTACIÓN DEL BOOTLOADER (Pseudocódigo de flujo FATFS)
// =========================================================================
/*
void Bootloader_Update_From_SD(void) {
if (FatFs_FileExists("update.bin")) {
// 1. Borramos el lienzo
IAP_Borrar_Slot_Aplicacion();
// 2. Leemos la SD por trozos
uint32_t flash_ptr = APP_BASE_ADDRESS;
uint8_t buffer_sd[512];
uint32_t bytes_leidos = 0;
while (FatFs_Read("update.bin", buffer_sd, 512, &bytes_leidos) == OK && bytes_leidos > 0) {
// Si el último bloque no es múltiplo de 4, lo rellenamos con 0xFF
while (bytes_leidos % 4 != 0) {
buffer_sd[bytes_leidos++] = 0xFF;
}
// 3. Quemamos el bloque en el procesador
IAP_Escribir_Bloque(flash_ptr, buffer_sd, bytes_leidos);
flash_ptr += bytes_leidos;
}
FatFs_Rename("update.bin", "update_done.bin");
NVIC_SystemReset(); // Reinicio maestro para arrancar la nueva App
}
}
*/
Objetivo del día: Soporte de Mantenimiento Offline y Recuperación ante Bricking.
El diseño de un sistema industrial no termina cuando la máquina sale de la fábrica; comienza ahí. Si un Despachador instalado a 500 kilómetros de tu base pierde la conexión a internet de forma permanente (ej. quiebra el proveedor de SIMs M2M), tu estrategia Over-The-Air es inútil.
Al dotar a tu Bootloader con capacidades **IAP mediante Tarjeta SD**, has creado el protocolo de rescate definitivo ("Sneakernet OTA"). El operador de la gasolinera solo necesita recibir un archivo `.bin` por WhatsApp, pasarlo a una tarjeta micro SD con su celular, insertarla en la ranura interna del Despachador y reiniciar el equipo. El Bootloader detectará el archivo "update.bin", borrará el código de la aplicación de forma segura (verificando cada bloque escrito con Read-Back Verification) y reiniciará la máquina renovada. Has eliminado la necesidad de programadores especializados (ST-Link) en campo, reduciendo tus costos de soporte técnico (SLA) a cero. Este nivel de autonomía es el sello de un producto verdaderamente maduro.
0xFF.HAL_FLASH_Unlock) inyectando llaves numéricas en registros específicos, y debe bloquearse de nuevo inmediatamente después (HAL_FLASH_Lock) para prevenir escrituras accidentales por ruido.