Los procesadores de la familia ARM Cortex-M (como tu STM32F401) no entienden el antiguo conjunto de instrucciones ARM de 32 bits. Fueron diseñados para ser ultra eficientes, por lo que utilizan exclusivamente el conjunto de instrucciones comprimidas llamado Thumb-2.
Cuando el procesador realiza un salto (Jump/Branch) a una dirección de memoria, examina el Bit Menos Significativo (LSB - Bit 0) de esa dirección:
0, el procesador intenta ejecutar código ARM tradicional y lanza una excepción letal (HardFault).1, el procesador activa el decodificador Thumb y ejecuta el código correctamente.Afortunadamente, el Linker de C (el programa que compila tu código) es inteligente. Cuando compila el archivo principal de tu Despachador (la Aplicación), busca la dirección real del Reset_Handler() y, antes de guardarla en la tabla de vectores en 0x08010004, le suma 1 automáticamente.
Por lo tanto, tu Bootloader no debe modificar ese valor. Simplemente debe leer los 4 bytes almacenados en 0x08010004, castearlos ciegamente a un puntero de función y ejecutarlos.
Antes de ceder el control, el Bootloader debe restaurar el hardware a su estado virgen de fábrica:
Desarmar el Sistema: La biblioteca HAL de ST proporciona la función HAL_DeInit(), la cual intenta devolver todos los registros de los periféricos a su valor de reset. Además, debemos apagar el SysTick usando la API interna del Cortex-M.
Ejercicio 1: El Algoritmo C del Salto (Jump). Uniremos la validación de memoria del día anterior con la rutina de limpieza total. Este código debe residir exclusivamente en el proyecto del Bootloader.
/**
* @file bootloader_jump.c
* @brief Algoritmo definitivo de Teardown y Salto a la Aplicación (MISRA 11.1 Dev)
*/
#include "stm32f401xe.h"
#include <stdint.h>
#include <stdbool.h>
// =========================================================================
// DEFINICIONES DE DIRECCIONES DE MEMORIA
// =========================================================================
#define APP_BASE_ADDRESS (0x08010000U)
#define RAM_START_ADDRESS (0x20000000U)
// SRAM1 del STM32F401RE tiene 96KB (0x18000)
#define RAM_END_ADDRESS (0x20018000U)
// =========================================================================
// PUNTERO DE FUNCIÓN (TIPADO STRICTO)
// =========================================================================
typedef void (*pFunction_JumpApp)(void);
// =========================================================================
// FUNCIÓN PRINCIPAL DE SALTO (BOOTLOADER KERNEL)
// =========================================================================
void Ejecutar_Salto_A_Aplicacion(void) {
// 1. Extraer el valor almacenado en la primera palabra (MSP)
// Usamos el modificador __IO (volatile) para asegurar lectura directa de Flash
uint32_t msp_app_value = *(__IO uint32_t*)APP_BASE_ADDRESS;
// 2. VALIDACIÓN ESTRICTA DE MEMORIA (Sanity Check)
// El puntero de pila DEBE estar dentro del rango físico de la RAM del microcontrolador.
// Si no lo está, la Flash está vacía (0xFFFFFFFF) o corrupta.
if ((msp_app_value >= RAM_START_ADDRESS) && (msp_app_value <= RAM_END_ADDRESS)) {
// --- 3. EXTRACCIÓN DEL VECTOR DE RESET ---
// El Reset Handler está en Base_Address + 4 bytes
uint32_t jump_address = *(__IO uint32_t*)(APP_BASE_ADDRESS + 4U);
// (DESVIACIÓN MISRA-C Regla 11.1)
// Transformamos el valor hexadecimal a un puntero ejecutable.
// El compilador sabrá que el bit 0 es 1 (Thumb) pero ejecutará en direcciones pares.
pFunction_JumpApp App_Salto_Handler = (pFunction_JumpApp)jump_address;
// --- 4. TEARDOWN: DESTRUIR EL ESTADO DEL BOOTLOADER ---
// A) Deshabilitar todas las interrupciones en el Core del Cortex-M4
__disable_irq();
// B) Detener el latido del sistema (SysTick Timer)
// Si el SysTick queda encendido, lanzará un HardFault en la App.
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// C) De-inicializar periféricos (HAL)
HAL_DeInit();
// D) Limpiar posibles interrupciones pendientes en el NVIC
for (uint8_t i = 0U; i < 8U; i++) {
NVIC->ICER[i] = 0xFFFFFFFFU;
NVIC->ICPR[i] = 0xFFFFFFFFU;
}
// --- 5. ASIGNACIÓN DEL NUEVO PUNTERO DE PILA ---
// Le decimos al procesador que olvide la RAM usada por el Bootloader
// y asuma la RAM que la Aplicación necesita.
__set_MSP(msp_app_value);
// --- 6. EL SALTO AL VACÍO ---
// Esta función NO tiene "return". El procesador entra en el Reset_Handler
// de la nueva aplicación y la vida comienza de cero.
App_Salto_Handler();
} else {
// ERROR CRÍTICO: No hay aplicación válida.
// Modo Emergencia: Encender LED Rojo y esperar comandos por UART.
// Entrar_Modo_Recuperacion_Bootloader();
}
}
Objetivo del día: Infraestructura FOTA (Firmware Over-The-Air) y Control de Ciclo de Vida.
En un entorno industrial y corporativo, el costo de hardware es secundario; el verdadero costo oculto de un proyecto es el OPEX (Gastos de Operación Mantenimiento). Cada vez que detectas un "bug" crítico o necesitas modificar la Tabla Termodinámica del Gas LP por regulaciones estatales, actualizar físicamente cientos de estaciones a nivel nacional es logísticamente inviable.
Al dominar la escritura de un **Bootloader Bare-Metal** y el enrutamiento de la Tabla de Vectores, has independizado al hardware de la fragilidad del software. El procesador de tu Despachador de Gas LP ya no es un esclavo de un único programa grabado de fábrica. Ahora posee un mecanismo de supervivencia primitivo (El Bootloader en `0x08000000`) que es capaz de auditar, limpiar la memoria y ceder el trono al código operativo real. Has creado una máquina que puede mutar, evolucionar y recibir cirugías cerebrales a control remoto sin jamás arriesgarse a quedar inoperativa ("Bricked"). Tu diseño es ahora, oficialmente, IoT de Grado Industrial.
Reset_Handler) debe tener su Bit 0 (LSB) en 1 para notificarle al hardware que active el decodificador Thumb.0x08010004). El Linker ya ha configurado este valor con el LSB en 1.__set_MSP(). Reasigna el Puntero de Pila Principal de la CPU al valor dictado por la primera palabra de la memoria Flash de la nueva aplicación, garantizando que el nuevo RTOS cuente con la memoria RAM limpia y configurada a su medida.