Volver al inicio

MBR 324 bytes STM32F407VE: bootloader compacto

Desarrollé un bootloader MBR mínimo para STM32F407VE de 324 bytes. Verifica tabla de vectores antes de pasar el control al bootloader secundario en la dirección 0x08060000. En error, parpadea LED en PE13. Código completo, Makefile y pasos de optimización.

Bootloader MBR de 324 bytes STM32F407VE con validación
Advertisement 728x90

# Bootloader MBR Compacto para STM32F407VE: Solo 324 Bytes

Este bootloader principal (MBR) para el STM32F407VE se ubica en el primer sector de Flash en la dirección 0x08000000 y transfiere el control al bootloader secundario en 0x08060000. Antes de saltar, valida la tabla de vectores de interrupción: puntero de pila en el rango de SRAM (0x20000000–0x2001FFFF), dirección de ResetHandler en Flash (0x08000000–0x080FFFFF) con el bit de modo Thumb activado.

Si la validación falla, indica un error parpadeando el LED en el pin PE13 a 10 Hz (período de 100 ms, ciclo de trabajo del 50%). El código se ha reducido a 324 bytes eliminando la biblioteca estándar, interrupciones externas y usando el temporizador DWT del núcleo para retardos.

Distribución de la memoria NOR Flash:

Google AdInline article slot

| Sector | Dirección inicial | Tamaño, KB | Contenido |

|--------|-------------------|------------|-----------|

| 0 | 0x08000000 | 16 | Bootloader principal |

Google AdInline article slot

| 1–3 | 0x08004000–0x0800C000 | 16×3 | NVRAM |

| 4–6 | 0x08010000–0x08040000 | 64+128+128 | Aplicación |

| 7 | 0x08060000 | 128 | Bootloader secundario |

Google AdInline article slot

Cada byte ahorrado en el MBR amplía el espacio de NVRAM.

Etapas de Optimización

La optimización se realizó de forma iterativa con ARM-GCC y GNU Make:

  • Implementación HAL ingenua: 25.192 bytes.
  • -O0 sin depuración: 12.052 bytes.
  • -Os -flto: 9.684 bytes.
  • DeepSeek + -Os -flto: 2.124 bytes.
  • -nostdlib, sin libc_init: 652 bytes.
  • Tabla de vectores mínima: 324 bytes.

Banderas clave del compilador:

  • -nostdlib -nostartfiles -ffreestanding
  • -ffunction-sections -fdata-sections -Wl,--gc-sections
  • -Os para tamaño, -mcpu=cortex-m4 -mthumb

Implementación en C

El código principal en un único archivo main.c accede directamente a los registros, sin HAL:

// main.c - Bootloader para STM32F407VE
#include <stdint.h>

#define APP_BASE        0x08060000UL
#define SRAM_BASE       0x20000000UL
#define SRAM_END        0x2001FFFFUL
#define FLASH_BASE      0x08000000UL
#define FLASH_END       0x080FFFFFUL

// Periféricos
#define RCC_BASE        0x40023800UL
#define GPIOE_BASE      0x40021000UL
#define DWT_BASE        0xE0001000UL

#define RCC_AHB1ENR     (*((volatile uint32_t*)(RCC_BASE + 0x30)))
#define GPIOx_MODER     (*((volatile uint32_t*)(GPIOE_BASE + 0x00)))
#define GPIOx_ODR       (*((volatile uint32_t*)(GPIOE_BASE + 0x14)))
#define DWT_CYCCNT      (*((volatile uint32_t*)(DWT_BASE + 0x04)))
#define DWT_CTRL        (*((volatile uint32_t*)(DWT_BASE + 0x00)))

typedef struct {
    uint32_t stack_ptr;
    uint32_t reset_handler;
} VectorTable_t;

static inline int is_valid_sram(uint32_t addr) {
    return ((SRAM_BASE <= addr) && (addr <= SRAM_END));
}

static inline int is_valid_flash(uint32_t addr) {
    return ((FLASH_BASE <= addr) && (addr <= FLASH_END));
}

// Inicializar DWT
static void dwt_init(void) {
    *(volatile uint32_t*)0xE000EDF0 |= (1UL << 24);
    DWT_CTRL |= 1;
}

// Retardo ~ms a 168 MHz
static void delay_ms(uint32_t ms) {
    uint32_t start = DWT_CYCCNT;
    uint32_t cycles = ms * 16000;
    while ((DWT_CYCCNT - start) < cycles);
}

// Parpadear LED PE13 a 10 Hz
static void blink_led(void) __attribute__((noreturn));
static void blink_led(void) {
    RCC_AHB1ENR |= (1 << 4);
    GPIOx_MODER &= ~(3 << 26);
    GPIOx_MODER |= (1 << 26);
    dwt_init();
    while(1) {
        GPIOx_ODR |= (1 << 13);
        delay_ms(50);
        GPIOx_ODR &= ~(1 << 13);
        delay_ms(50);
    }
}

static int is_valid_vector_table(const VectorTable_t *app_vec) {
    if (!is_valid_sram(app_vec->stack_ptr)) return 0;
    uint32_t reset_addr = app_vec->reset_handler;
    if (!is_valid_flash(reset_addr & ~1)) return 0;
    if ((reset_addr & 1) == 0) return 0;
    return 1;
}

typedef void (*pFunction)(void);
int main(void) {
    const VectorTable_t *app_vec = (const VectorTable_t*)APP_BASE;
    if (is_valid_vector_table(app_vec)) {
        pFunction jump = (pFunction)app_vec->reset_handler;
        jump();
    }
    blink_led();
}

Compilación del Proyecto

Makefile con soporte para depuración y empaquetado:

CFLAGS += -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=softfp
CFLAGS += -Os -ffunction-sections -fdata-sections -nostdlib
CFLAGS += -nostartfiles -fno-builtin -ffreestanding
LDFLAGS += -T gcc_arm_mbr.ld -Wl,--gc-sections

SOURCES = main.c system_stm32f4xx.c
OBJECTS = $(SOURCES:.c=.o) startup_stm32f407xx.o

all: $(TARGET).bin

En startup_stm32f407xx.S, eliminamos bl __libc_init_array y recortamos la tabla de vectores a lo esencial (Pila, Reset, NMI, HardFault).

Lecciones Clave

  • Tamaño de 324 bytes libera más de 15 KB para NVRAM en el primer sector.
  • La validación de la tabla de vectores evita bloqueos por código corrupto.
  • El temporizador DWT ofrece retardos precisos sin SysTick ni PLL.
  • Acceso directo a registros minimiza el overhead al máximo.
  • Compilación completa: ARM-GCC + script de enlace personalizado, sin biblioteca estándar.

— Editorial Team

Advertisement 728x90

Leer después