Retour à l'accueil

MBR 324 bytes STM32F407VE : chargeur d'amorçage compact

Développé un chargeur d'amorçage MBR minimal pour STM32F407VE de 324 bytes. Vérifie la table des vecteurs avant de passer le contrôle au chargeur d'amorçage secondaire à l'adresse 0x08060000. En cas d'erreur, clignote la LED sur PE13. Code complet, Makefile et étapes d'optimisation.

Chargeur d'amorçage MBR 324 bytes STM32F407VE avec validation
Advertisement 728x90

Chargeur d'amorçage MBR compact pour STM32F407VE : Seulement 324 octets

Ce chargeur d'amorçage principal (MBR) pour le STM32F407VE occupe le premier secteur Flash à l'adresse 0x08000000 et transmet le contrôle au chargeur secondaire à 0x08060000. Avant le saut, il valide la table des vecteurs d'interruption : pointeur de pile dans la plage SRAM (0x20000000–0x2001FFFF), adresse de ResetHandler en Flash (0x08000000–0x080FFFFF) avec le bit de mode Thumb activé.

En cas d'échec de validation, il signale l'erreur en clignotant la LED sur la broche PE13 à 10 Hz (période de 100 ms, rapport cyclique de 50 %). Le code est réduit à 324 octets en supprimant la bibliothèque standard, les interruptions externes, et en utilisant le temporisateur DWT du cœur pour les délais.

Schéma de la Flash NOR :

Google AdInline article slot

| Secteur | Adresse de début | Taille, Ko | Contenu |

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

| 0 | 0x08000000 | 16 | Chargeur principal |

Google AdInline article slot

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

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

| 7 | 0x08060000 | 128 | Chargeur secondaire |

Google AdInline article slot

Chaque octet économisé sur le MBR libère de l'espace pour la NVRAM.

Étapes d'optimisation

L'optimisation a été itérative avec ARM-GCC et GNU Make :

  • Implémentation HAL naïve : 25 192 octets.
  • -O0 sans debug : 12 052 octets.
  • -Os -flto : 9 684 octets.
  • DeepSeek + -Os -flto : 2 124 octets.
  • -nostdlib, sans libc_init : 652 octets.
  • Table de vecteurs minimale : 324 octets.

Principaux drapeaux du compilateur :

  • -nostdlib -nostartfiles -ffreestanding
  • -ffunction-sections -fdata-sections -Wl,--gc-sections
  • -Os pour la taille, -mcpu=cortex-m4 -mthumb

Implémentation en C

Le code principal dans un seul fichier main.c accède directement aux registres, sans HAL :

// main.c - Chargeur d'amorçage pour STM32F407VE
#include <stdint.h>

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

// Périphériques
#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));
}

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

// Délai ~ms à 168 MHz
static void delay_ms(uint32_t ms) {
    uint32_t start = DWT_CYCCNT;
    uint32_t cycles = ms * 16000;
    while ((DWT_CYCCNT - start) < cycles);
}

// Clignotement LED PE13 à 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();
}

Construction du projet

Makefile avec support debug et emballage :

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

Dans startup_stm32f407xx.S, nous avons supprimé bl __libc_init_array et réduit la table de vecteurs aux essentiels (Pile, Reset, NMI, HardFault).

Points clés

  • Taille de 324 octets libère plus de 15 Ko pour la NVRAM dans le premier secteur.
  • Validation de la table de vecteurs évite les blocages sur code corrompu.
  • Temporisateur DWT pour des délais précis sans SysTick ni PLL.
  • Accès direct aux registres minimise les surcoûts.
  • Build complet : ARM-GCC + script de lien personnalisé, sans bibliothèque standard.

— Editorial Team

Advertisement 728x90

Lire ensuite