Zurück zur Startseite

MBR 324 Bytes STM32F407VE: kompakter Bootloader

Entwickelt einen minimalen MBR-Bootloader für STM32F407VE mit 324 Bytes. Prüft Vektortabelle vor Übergabe der Steuerung an sekundären Bootloader unter Adresse 0x08060000. Bei Fehler blinkt LED auf PE13. Vollständiger Code, Makefile und Optimierungsschritte.

324-Byte MBR-Bootloader STM32F407VE mit Validierung
Advertisement 728x90

Kompakter MBR-Bootloader für STM32F407VE: Nur 324 Bytes

Dieser primäre Bootloader (MBR) für den STM32F407VE sitzt im ersten Flash-Sektor bei 0x08000000 und übergibt die Kontrolle an den sekundären Bootloader bei 0x08060000. Vor dem Sprung validiert er die Interrupt-Vektortabelle: Stackzeiger im SRAM-Bereich (0x20000000–0x2001FFFF), ResetHandler-Adresse im Flash (0x08000000–0x080FFFFF) mit gesetztem Thumb-Modus-Bit.

Bei Fehlvalidierung signalisiert er einen Fehler durch Blinken der LED an Pin PE13 mit 10 Hz (100 ms Periode, 50 % Duty Cycle). Der Code ist auf 324 Bytes getrimmt, indem Standardbibliothek, externe Interrupts und DWT-Core-Timer für Verzögerungen weggelassen wurden.

NOR-Flash-Layout:

Google AdInline article slot

| Sektor | Startadresse | Größe, KB | Inhalt |

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

| 0 | 0x08000000 | 16 | Primärer Bootloader |

Google AdInline article slot

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

| 4–6 | 0x08010000–0x08040000 | 64+128+128 | Anwendung |

| 7 | 0x08060000 | 128 | Sekundärer Bootloader |

Google AdInline article slot

Jeder eingesparte Byte im MBR erweitert den NVRAM-Platz.

Optimierungsphasen

Die Optimierung erfolgte iterativ mit ARM-GCC und GNU Make:

  • Naive HAL-Implementierung: 25.192 Bytes.
  • -O0 ohne Debug: 12.052 Bytes.
  • -Os -flto: 9.684 Bytes.
  • DeepSeek + -Os -flto: 2.124 Bytes.
  • -nostdlib, kein libc_init: 652 Bytes.
  • Minimale Vektortabelle: 324 Bytes.

Wichtige Compiler-Flags:

  • -nostdlib -nostartfiles -ffreestanding
  • -ffunction-sections -fdata-sections -Wl,--gc-sections
  • -Os für Größe, -mcpu=cortex-m4 -mthumb

C-Implementierung

Der Kerncode in einer einzigen main.c greift direkt auf Register zu, ohne HAL:

// main.c - Bootloader für STM32F407VE
#include <stdint.h>

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

// Peripherals
#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));
}

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

// Verzögerung ~ms bei 168 MHz
static void delay_ms(uint32_t ms) {
    uint32_t start = DWT_CYCCNT;
    uint32_t cycles = ms * 16000;
    while ((DWT_CYCCNT - start) < cycles);
}

// LED PE13 bei 10 Hz blinken
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();
}

Projekt-Build

Makefile mit Debug- und Paketierungsunterstützung:

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

In startup_stm32f407xx.S wurde bl __libc_init_array entfernt und die Vektortabelle auf das Wesentliche beschränkt (Stack, Reset, NMI, HardFault).

Wichtige Erkenntnisse

  • 324-Byte-Größe gibt über 15 KB NVRAM im ersten Sektor frei.
  • Validierung der Vektortabelle verhindert Hänger bei korruptem Code.
  • DWT-Timer liefert präzise Verzögerungen ohne SysTick oder PLL.
  • Direkter Registerzugriff reduziert Overhead auf das Nötigste.
  • Vollständiger Build: ARM-GCC + custom Linkerscript, keine Standardbibliothek.

— Editorial Team

Advertisement 728x90

Weiterlesen