Bootloader di Sistema STM32:
Programmare il Firmware Senza Debugger

2026-06-17 · Davide Carrese
STM32 · Bootloader · UART · DFU · Produzione · Field-Upgrade

Ogni STM32 è fornito con un bootloader pre-programmato in fabbrica in un'area ROM dedicata chiamata System Memory. Non puoi cancellarlo, non puoi corromperlo, ed è sempre lì — anche su un chip vergine appena uscito dalla bobina. Questo bootloader può programmare la Flash interna via USART, I2C, SPI, CAN (su famiglie selezionate) e USB DFU, usando niente più di un adattatore seriale o un cavo USB.

In oltre un decennio di consulenza embedded, ho visto team passare giorni a debuggare un prototipo solo per scoprire che il debugger aveva un pin rotto, o sprecare ore a sostituire microcontrollori perché un'unità in campo non si avviava dopo un aggiornamento fallito. Il bootloader di sistema è il fallback universale: funziona quando nient'altro funziona. In questo articolo, coprirò tutto ciò che devi sapere per usarlo con sicurezza in produzione, aggiornamenti in campo e avviamento di schede.

Come Entrare nel Bootloader di Sistema

Ci sono due modi per forzare un STM32 ad eseguire il bootloader di sistema dopo il reset:

1. Configurazione dei Pin BOOT

Questo è il metodo classico. Sulla maggior parte delle famiglie STM32, i pin di boot vengono campionati sul fronte di salita del RESET per determinare da dove la CPU inizia a eseguire codice:

BOOT0BOOT1 (nBOOT1 / PH2)Area di Boot
0X (indifferente)Flash principale (applicazione utente)
10System Memory (bootloader di fabbrica)
11SRAM interna (debug)

Per entrare nel bootloader: porta BOOT0 a LIVELLO ALTO, imposta BOOT1 a LIVELLO BASSO (o lascia il pin flottante se è internamente pull-down), commuta il pin RESET, e il MCU inizia a eseguire il bootloader ROM. Molte schede Nucleo e Discovery espongono BOOT0 su un header dedicato proprio per questo scopo.

2. Option Byte: nBOOT0 e nBOOT_SEL

Sulle famiglie STM32 più recenti (STM32G0, G4, L4+, L5, U5, H5, H7), puoi evitare il pin BOOT esterno configurando gli option byte. Impostando nBOOT0 = 0 e nBOOT_SEL = 0 il MCU viene forzato a eseguire il boot dal System Memory senza alcun hardware esterno. Questo è particolarmente utile su PCB con vincoli di spazio dove non puoi permetterti un header BOOT0:

/* Esempio STM32G4 — imposta option byte per boot da System Memory */
HAL_FLASH_OB_Unlock();

/* nBOOT0 = 0 (valore BOOT0 da option byte, non dal pin) */
/* nBOOT_SEL = 0 (usa nBOOT0 option byte) */
OB->USER &= ~(OB_USER_nBOOT0 | OB_USER_nBOOT_SEL);

HAL_FLASH_OB_Launch();  /* causa un reset di sistema — MCU entra nel bootloader */

Dopo la programmazione, ripristina gli option byte per eseguire il boot dalla Flash. Se te ne dimentichi, il chip entra nel bootloader ogni volta — che in realtà è una strategia di produzione valida se usi il bootloader per gli aggiornamenti dell'applicazione (ne parliamo più avanti).

Periferiche Supportate per Famiglia

La disponibilità delle periferiche del bootloader varia significativamente tra le famiglie STM32. Ecco una tabella di riferimento rapido per le famiglie più comuni che incontro sul campo:

FamigliaUSARTI2CSPICAN/FDCANUSB DFU
STM32F0USART1 (PA9/PA10)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)
STM32F1USART1 (PA9/PA10)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)
STM32F4USART1 (PA9/PA10)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)CAN2 (PB5/PB6)OTG_FS (DP/DM)
STM32G0USART1 (PA9/PA10 / PA2/PA3)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)
STM32G4USART1 (PA9/PA10)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)FDCAN1 (PB8/PB9)
STM32L0USART1 (PA9/PA10 / PA2/PA3)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)
STM32L4/L4+USART1 (PA9/PA10)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)OTG_FS (DP/DM)
STM32H7USART1 (PA9/PA10 / PB14/PB15)I2C1 (PB6/PB7 / PB8/PB9)SPI1 (PA5/PA6/PA7 / PB3/PB4/PB5)FDCAN1 (PD0/PD1)OTG_HS / OTG_FS
STM32U5USART1 (PA9/PA10 / PA2/PA3)I2C1 (PB6/PB7)SPI1 (PA5/PA6/PA7)FDCAN1OTG_FS

L'assegnazione esatta dei pin dipende dal package. Consulta sempre l'Application Note AN2606 (il riferimento definitivo) per il tuo dispositivo specifico. Il bootloader usa pin fissi — non possono essere rimappati.

Esempio Pratico: Programmazione via USART con STM32CubeProgrammer CLI

USART è l'interfaccia del bootloader più universalmente disponibile — ogni famiglia STM32 la supporta. Ecco il flusso di lavoro completo:

Configurazione Hardware

Collega un adattatore USB-to-UART (es. FT232, CP2102) ai pin USART1 dell'STM32:

Imposta BOOT0 = ALTO (3.3 V), BOOT1 = BASSO (GND). Applica alimentazione e commuta RESET.

Rileva e Programma

# 1. Rileva il dispositivo bootloader su /dev/ttyUSB0 (Linux)
STM32_Programmer_CLI -c port=USART1 baudrate=115200

# 2. Cancella la Flash principale (opzionale ma raccomandato)
STM32_Programmer_CLI -c port=USART1 -e all

# 3. Scrivi il firmware binario
STM32_Programmer_CLI -c port=USART1 -w firmware.bin 0x08000000

# 4. Verifica e avvia l'applicazione
STM32_Programmer_CLI -c port=USART1 -v firmware.bin 0x08000000 -g 0x08000000

Il bootloader è preimpostato a 115200 baud sulla maggior parte delle famiglie, ma puoi cambiarlo inviando un comando speciale dopo l'handshake di sincronizzazione. Se il dispositivo non risponde, prova a 9600 baud — alcune revisioni più vecchie del bootloader partono a 9600 e passano a 115200 dopo la sincronizzazione.

Handshake Manuale (per Strumenti Host Personalizzati)

Se stai scrivendo il tuo programmatore (per attrezzature di produzione, per esempio), il bootloader usa un semplice handshake bidirezionale via USART:

/* Host invia 0x7F (il byte di sincronizzazione) */
/* Bootloader risponde con 0x79 (ACK) o 0x1F (NACK) */
/* Poi l'host invia il byte comando + byte invertito (checksum) */
/* Snippet C lato host minimale (POSIX) */
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>

int sync_bootloader(int fd) {
    uint8_t sync = 0x7F;
    write(fd, &sync, 1);
    usleep(100000);  /* 100 ms — bootloader ha bisogno di tempo */
    uint8_t resp;
    if (read(fd, &resp, 1) != 1 || resp != 0x79) {
        return -1;  /* NACK o timeout */
    }
    return 0;
}

/* Invia comando GET (0x00) per leggere la versione del bootloader */
int get_version(int fd) {
    uint8_t cmd[] = {0x00, 0xFF};  /* cmd + cmd invertito */
    write(fd, cmd, 2);
    uint8_t resp, ver;
    if (read(fd, &resp, 1) != 1 || resp != 0x79) return -1;
    read(fd, &ver, 1);
    printf("Versione bootloader: 0x%02X\n", ver);
    return 0;
}

USB DFU — Programmazione Senza Hardware Aggiuntivo

Sugli STM32F4, L4, U5, H7 e altre famiglie con USB OTG, il bootloader supporta anche USB DFU (Device Firmware Update). Quando il MCU si avvia nel bootloader di sistema e le linee USB DP/DM sono collegate a un host, si enumera come dispositivo DFU. Su Linux, lo vedi con lsusb come "STMicroelectronics STM Device in DFU Mode".

La programmazione è semplice con dfu-util:

# Elenca i dispositivi DFU
dfu-util -l

# Scrivi firmware.bin nella Flash principale (indirizzo 0x08000000)
dfu-util -d 0483:df11 -a 0 -s 0x08000000:leave -D firmware.bin

Il suffisso :leave dice al bootloader di uscire dalla modalità DFU e saltare all'applicazione utente dopo il trasferimento. Senza di esso, il chip rimane nel bootloader e devi commutare RESET manualmente.

USB DFU è il mio metodo preferito per aggiornamenti in campo durante la prototipazione: nessun debugger, nessun adattatore seriale, solo un cavo USB. Su schede di produzione senza connettore USB, ripiego su USART.

Strategia di Produzione: Aggiornamenti Tramite Bootloader Senza Pin BOOT

Su prodotti a scheda singola dove aggiungere un header BOOT0 non è accettabile, puoi implementare un salto software al bootloader di sistema. L'applicazione verifica la presenza di una richiesta di aggiornamento (un livello GPIO, un messaggio CAN, un pulsante all'avvio) e, se rilevata, riconfigura il clock di sistema su HSI, disabilita tutte le periferiche e salta all'indirizzo base della System Memory:

/* Salto al bootloader di sistema dall'applicazione utente */
/* Funziona su STM32F4, G4, L4, H7 e la maggior parte dei Cortex-M4/M7 */
void jump_to_bootloader(void) {
    /* 1. Disabilita gli interrupt globali */
    __disable_irq();

    /* 2. Disabilita tutti i clock delle periferiche */
    RCC->CIR = 0;
    RCC->AHB1ENR = 0;
    RCC->APB1ENR = 0;
    RCC->APB2ENR = 0;

    /* 3. Resetta il timer SysTick per prevenire interrupt prematuri */
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;

    /* 4. Rimappa System Memory all'indirizzo 0x00000000 */
    SYSCFG->MEMRMP = 0x01;  /* rimappa System Flash a 0x0000 */

    /* 5. Imposta lo stack pointer principale dalla tabella vettori del bootloader */
    uint32_t boot_addr = 0x1FFF0000;  /* System Memory base (F4) */
    /* ^^ Questo indirizzo varia per famiglia — controlla AN2606! */
    uint32_t msp = *(volatile uint32_t *)boot_addr;
    __set_MSP(msp);

    /* 6. Salta al vettore di reset del bootloader */
    void (*bootloader)(void) = (void (*)(void))
        *(volatile uint32_t *)(boot_addr + 4);
    bootloader();

    /* Non ritorna mai */
}

Critico: l'indirizzo base della System Memory (0x1FFF0000 nell'esempio) è diverso per ogni famiglia STM32. Su STM32G0 è 0x1FFF0000, su STM32F1 è 0x1FFFB800, su STM32H7 è 0x1FF09800. Cerca l'indirizzo corretto in AN2606 Tabella 5 — usare l'indirizzo sbagliato causa un HardFault ogni volta.

Checklist Pratica

Come Lo Affronterei Su un Progetto Cliente

Su ogni progetto di produzione per cui faccio consulenza, insisto per includere almeno un'interfaccia USART o CAN compatibile con il bootloader accessibile sul connettore del prodotto finale, anche se il budget è stretto. Il costo di tre piste PCB e un header a 3 pin è trascurabile rispetto al costo di un singolo reso in campo che richiede l'apertura dell'involucro e il collegamento di un debugger.

La mia architettura di produzione standard per il bootloader:

In ambienti automotive e industriali, instrado CAN invece di USART al connettore in campo — CAN è più robusto contro ESD e cavi lunghi, e il bootloader su STM32G4/F4/H7 supporta FDCAN nativamente. Un protocollo in meno da gestire.

Fonti e Riferimenti

💬 Commenta via email

Se hai domande, correzioni, o vuoi condividere la tua esperienza con il bootloader di sistema su STM32, scrivimi a blog-comments@carrese.eu. Includi lo slug dell'articolo nell'oggetto.