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:
| Sektor | Startadresse | Größe, KB | Inhalt |
|--------|--------------|-----------|--------|
| 0 | 0x08000000 | 16 | Primärer Bootloader |
| 1–3 | 0x08004000–0x0800C000 | 16×3 | NVRAM |
| 4–6 | 0x08010000–0x08040000 | 64+128+128 | Anwendung |
| 7 | 0x08060000 | 128 | Sekundärer Bootloader |
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-Osfü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
Noch keine Kommentare.