Hasta hoy, al presionar "Upload" en tu IDE, el código se compilaba en un único archivo binario que se alojaba en la dirección base de la memoria Flash del STM32: 0x08000000. Cuando el microcontrolador recibe energía, su hardware interno (Cortex-M4) busca ciegamente en esa dirección dos cosas innegociables:
Esta es la Tabla de Vectores (Vector Table). El problema es que, si tu aplicación está descargando una actualización de sí misma desde internet y ocurre un apagón mientras sobreescribe el sector `0x08000000`, la Tabla de Vectores se corromperá. Al reiniciar, el procesador leerá basura, saltará al vacío y la máquina quedará convertida en un ladrillo irrecuperable (Bricked).
Para lograr OTA seguro, dividiremos la memoria Flash del STM32 (ej. 256 KB) en dos programas totalmente independientes:
Cuando el Bootloader salta a la Aplicación en `0x08010000`, hay un problema. Si la Aplicación genera una interrupción (ej. el pin del Caudalímetro), el hardware del procesador Cortex-M4 irá a buscar la función de interrupción a la tabla original en `0x08000000` (que ahora le pertenece al Bootloader). Para arreglar esta esquizofrenia de memoria, la Aplicación debe reprogramar el hardware usando el registro VTOR, diciéndole al CPU: "Mi nueva tabla de vectores ahora vive en `0x08010000`, ignora la original".
Desviación Obligatoria de MISRA-C: La regla 11.1 de MISRA-C prohíbe explícitamente castear un número entero a un Puntero de Función. Sin embargo, para escribir un Bootloader, es físicamente obligatorio decirle al procesador: *"Toma el número hexadecimal 0x08010004 y conviértelo en una función ejecutable"*. Documentaremos esta desviación en el código fuente para apaciguar a los auditores estáticos.
Ejercicio 1: El Salto de Fe. Escribiremos la función pura del Bootloader que desactiva todo el hardware, redirige el Stack Pointer de la RAM y dispara la ejecución hacia el nuevo sector.
/**
* @file bootloader_core.c
* @brief Bootloader Nativo de Arranque y Redirección (Desviación MISRA 11.1 Aprobada)
*/
#include "stm32f401xe.h"
#include <stdint.h>
#include <stdbool.h>
// =========================================================================
// MAPA DE MEMORIA (Offset de Aplicación)
// =========================================================================
// El Sector 0, 1, 2 y 3 (Primeros 64KB) están reservados para este Bootloader.
// La aplicación final empieza en el Sector 4.
#define APPLICATION_ADDRESS (0x08010000U)
// La RAM del STM32 empieza en 0x20000000
#define RAM_BASE_ADDRESS (0x20000000U)
// =========================================================================
// DEFINICIÓN DE PUNTEROS MÁGICOS
// =========================================================================
// Definimos un tipo que representa una función vacía (El Reset Handler)
typedef void (*pFunction)(void);
// =========================================================================
// EL MOTOR DEL BOOTLOADER
// =========================================================================
/**
* @brief Verifica la existencia de código válido y salta a la aplicación principal.
*/
void Bootloader_Jump_To_Application(void) {
// 1. EXTRAER EL STACK POINTER DE LA APLICACIÓN
// El primer 'Word' (4 bytes) de la aplicación contiene el Main Stack Pointer (MSP).
// Usamos punteros a memoria volátil para forzar la lectura del silicio.
uint32_t app_msp_value = *(__IO uint32_t*)APPLICATION_ADDRESS;
// 2. VALIDACIÓN DE CÓDIGO (Sanity Check)
// Para que un código sea válido, su Stack Pointer debe apuntar a un área de la RAM.
// Verificamos si el valor leído empieza con 0x2000XXXX.
if ((app_msp_value & 0x2FFE0000U) == RAM_BASE_ADDRESS) {
// --- INICIA PREPARACIÓN PARA EL SALTO ---
// Deshabilitar todas las interrupciones. No queremos que un Timer del
// Bootloader salte mientras la Aplicación está despertando.
__disable_irq();
// Extraer la dirección del Reset Handler de la Aplicación.
// Se encuentra en la dirección Base + 4 bytes.
uint32_t app_reset_handler_addr = *(__IO uint32_t*)(APPLICATION_ADDRESS + 4U);
// (Deviación MISRA 11.1 Justificada: Requerimiento de Arquitectura Cortex-M)
// Casteamos el número extraído a una función ejecutable.
pFunction App_Reset_Handler = (pFunction)app_reset_handler_addr;
// Configuramos el Main Stack Pointer del CPU para que apunte a la RAM
// solicitada por la Aplicación.
__set_MSP(app_msp_value);
// --- EL SALTO DE FE ---
// Ejecutamos la función. A partir de esta línea de código,
// el Bootloader "muere" temporalmente y la Aplicación toma el control total.
App_Reset_Handler();
} else {
// La memoria está vacía, corrupta o hubo un fallo en una actualización FOTA.
// Nos quedamos en el Bootloader.
// Acción: Encender LED Rojo Fijo o habilitar Modo Recovery por UART.
// Pantalla_Mostrar_Error("NO FIRMWARE FOUND");
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
// El bootloader hace sus cosas rápidas aquí (ej. Leer un botón
// por si el operador quiere forzar un downgrade).
// Si no hay botón presionado, intenta saltar.
Bootloader_Jump_To_Application();
// Si el código llega aquí, la aplicación falló.
while(1) {
// Modo pánico del Bootloader
}
}
El lado de la Aplicación (El Relevo): Para que la Aplicación sepa que ya no vive en la dirección cero, debemos modificar el archivo system_stm32f4xx.c (o usar la función del CMSIS SCB->VTOR) y ajustar su Linker Script (archivo .ld) para que el compilador desplace todas las direcciones hacia `0x08010000`.
// En el archivo de la Aplicación (main.c de tu código de los días pasados)
int main(void) {
// REUBICACIÓN OBLIGATORIA
// Le indicamos al hardware del CPU dónde está su nueva tabla de interrupciones
SCB->VTOR = 0x08010000;
HAL_Init();
// ... Todo el código RTOS normal
}
Objetivo del día: Ciclo de Vida del Producto (Lifecycle Management) y Preparación para OTA.
Diseñar un Despachador que funciona el Día 1 es el trabajo de un ingeniero Junior. Diseñar un Despachador que puede repararse y evolucionar a sí mismo el Día 1000 es el trabajo de un Arquitecto de Sistemas. Cuando lances tu producto al mercado, inevitablemente querrás agregarle nuevas funciones (por ejemplo, pagos con tarjeta de crédito directo en la pantalla LCD).
Al implementar el **Bootloader Nativo y el SCB->VTOR**, acabas de dividir el "alma" de tu hardware en dos. Creaste una partición inmutable, un salvavidas de silicio que jamás se corromperá. Si mañana el corporativo te pide actualizar el firmware de 500 gasolineras, tu Módem LTE descargará el nuevo código en el sector secundario de la Flash. El Bootloader simplemente despertará, detectará el nuevo bloque, modificará los punteros y arrancará la nueva máquina. Acabas de ahorrar millones de dólares en logística de mantenimiento y abriste la puerta a los ingresos recurrentes (SaaS) mediante actualizaciones de software over-the-air.
0x08000000). Allí busca el Stack Pointer (MSP) y el Reset Handler.0x08010000), desactiva sus propias interrupciones, ajusta el MSP de la CPU y ejecuta un Salto Absoluto (Function Pointer Jump) hacia la Aplicación.