Cuando energizas un procesador ARM Cortex-M4, su hardware interno (el núcleo de silicio) ejecuta una secuencia inamovible:
0x08000000 y lee los primeros 4 bytes. Este valor lo carga en su registro MSP (Main Stack Pointer) para saber dónde está la RAM libre.0x08000004 y lee los siguientes 4 bytes. Este valor es la dirección del Reset Handler (La primera función de tu código).A esta lista de direcciones se le conoce como la Tabla de Vectores de Interrupción (Vector Table).
Si escribimos un programa Bootloader (que se guarda en 0x08000000) y nuestra verdadera Aplicación de Gasolinera la guardamos más adelante en 0x08004000, tenemos un problema. Cuando ocurra una interrupción del UART en la aplicación, el hardware ciegamente irá a buscar a la Tabla de Vectores del Bootloader en la dirección cero. ¡El sistema colapsará!
La magia del salto en Bare-Metal requiere el registro SCB->VTOR (Vector Table Offset Register). Justo antes de saltar a la aplicación, el Bootloader debe decirle al CPU: "A partir de este microsegundo, tu nueva tabla de vectores ya no está en el cero, está desplazada a 0x08004000". Luego, el Bootloader configura el Stack Pointer (MSP) de la aplicación, y finalmente realiza un salto ciego usando un puntero a función en C.
Ejercicio 1: Escribiremos el código fuente exclusivo para el archivo main.c de nuestro proyecto "Bootloader". Este proyecto se compila y se quema de forma independiente a la Aplicación. Su única misión es verificar si la memoria en `0x08004000` tiene un programa válido (leyendo si su stack pointer apunta a la RAM válida 0x20000000). Si todo está correcto, paralizará sus propios periféricos y saltará hacia lo desconocido.
/**
* @file bootloader_main.c
* @brief Gestor de Arranque y Ejecutor de Salto (Vector Relocation)
* @note Este código se compila SOLO y se aloja en 0x08000000
*/
#include "stm32f401xe.h"
#include <stdint.h>
// Definimos la dirección donde empieza nuestra aplicación real
#define APPLICATION_ADDRESS 0x08004000
// Definimos un tipo de dato "Puntero a Función" que no recibe nada y no retorna nada.
// Representa la función `Reset_Handler()` de la aplicación de destino.
typedef void (*pFunction)(void);
/**
* @brief Rutina crítica de transferencia de control.
*/
void Bootloader_JumpToApp(void) {
// 1. OBTENER VECTORES DE LA APLICACIÓN
// Leemos los primeros 4 bytes de la aplicación (El valor del Stack Pointer en RAM)
uint32_t app_stack_pointer = *(__IO uint32_t*)APPLICATION_ADDRESS;
// Leemos los siguientes 4 bytes (La dirección de memoria del Reset_Handler de la App)
uint32_t app_reset_handler_addr = *(__IO uint32_t*)(APPLICATION_ADDRESS + 4);
// 2. VALIDACIÓN DE INTEGRIDAD
// En la familia STM32F4, la SRAM empieza en 0x20000000.
// Usamos una máscara de bits para comprobar si el SP leído realmente apunta a la RAM.
// Si la memoria Flash estuviera vacía (borrada), leería 0xFFFFFFFF, y no saltará.
if ((app_stack_pointer & 0x2FFE0000) == 0x20000000) {
// 3. AISLAMIENTO DEL SISTEMA (Desarme absoluto)
// Deshabilitamos interrupciones globales. Si el Systick del Bootloader
// interrumpe a la aplicación mientras arranca, generará un HardFault.
__disable_irq();
// Apagamos explícitamente el SysTick Timer
SysTick->CTRL = 0;
// 4. REUBICACIÓN DE TABLA DE VECTORES
// Le indicamos al núcleo Cortex-M4 que las interrupciones ya no viven
// en 0x00000000, sino en la dirección de la Aplicación.
SCB->VTOR = APPLICATION_ADDRESS;
// 5. INICIALIZAR EL PUNTERO DE PILA (STACK POINTER)
// Forzamos al CPU a usar la memoria RAM que requiere la Aplicación
__set_MSP(app_stack_pointer);
// 6. EL SALTO DE FE
// Convertimos la dirección leída en un puntero a función ejecutable en C
pFunction app_entry = (pFunction)app_reset_handler_addr;
// Llamamos a la función. A partir de esta línea, el Bootloader "muere"
// y el microcontrolador le pertenece 100% a la Aplicación de Gasolinera.
app_entry();
}
else {
// La validación falló. No hay aplicación instalada o está corrupta.
// Opcional: Encender LED rojo permanente o esperar actualización serial.
while(1) {
// Error fatal.
}
}
}
int main(void) {
// 1. Inicialización ultra-rápida (Reloj básico)
// El Bootloader no necesita la FPU ni arreglos complejos.
// 2. ¿HAY UNA ACTUALIZACIÓN PENDIENTE (OTA)?
// Aquí leeríamos el RTC Backup Register (BKP). Si dice "UPDATE_READY",
// significa que la App en su ejecución normal descargó el archivo en el Sector 4.
// if (RTC->BKP0R == 0xUPDATE) {
// Flash_Unlock();
// Flash_Erase_Sector(1); Flash_Erase_Sector(2); Flash_Erase_Sector(3);
// Flash_Copy_From_Sector4_To_Sector1();
// RTC->BKP0R = 0; // Limpiar bandera
// }
// 3. Ejecutar salto ciego
Bootloader_JumpToApp();
// Si la función anterior tiene éxito, el procesador JAMÁS llegará a esta línea.
while(1) {}
return 0;
}
Objetivo del día: Inmortalidad Evolutiva (Zero-Touch Maintenance).
El mayor terror de un fabricante industrial es un "Brick". Si vendes 2,000 máquinas despachadoras en 2,000 gasolineras a nivel nacional, y descubres un bug en el cálculo del precio, necesitas actualizar el firmware. Si durante la actualización remota (OTA) hay un apagón eléctrico, la memoria queda borrada a la mitad. La máquina amanecería muerta (brickeada), obligándote a enviar 2,000 técnicos en camionetas por todo el país solo para reconectar el programador USB ST-LINK.
Al dotar a tu placa de un **Bootloader Bare-Metal Aislado**, eliminas el riesgo. El Bootloader vive en el inamovible Sector 0. Nunca se borra, nunca se sobreescribe. Si hay un apagón mientras actualizas la "App Activa", al regresar la luz el procesador entra al Bootloader, valida la integridad, descubre que la aplicación está corrupta, y se niega a saltar, entrando en modo de rescate seguro. Has convertido a tu placa de circuito en un servidor tolerante a fallas que puede evolucionar por décadas sin intervención humana física.
0x08000000).0x08004000), el registro Vector Table Offset Register (SCB->VTOR) debe actualizarse para redirigir los eventos de hardware hacia el código correcto.__disable_irq()), Reubicar la tabla de vectores y el Stack Pointer (__set_MSP()), y finalmente invocar la dirección de reinicio de la nueva aplicación casteada como un Puntero a Función.