Bootloader di Sistema STM32:
Programmare il Firmware Senza Debugger
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:
| BOOT0 | BOOT1 (nBOOT1 / PH2) | Area di Boot |
|---|---|---|
| 0 | X (indifferente) | Flash principale (applicazione utente) |
| 1 | 0 | System Memory (bootloader di fabbrica) |
| 1 | 1 | SRAM 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:
| Famiglia | USART | I2C | SPI | CAN/FDCAN | USB DFU |
|---|---|---|---|---|---|
| STM32F0 | USART1 (PA9/PA10) | I2C1 (PB6/PB7) | SPI1 (PA5/PA6/PA7) | — | — |
| STM32F1 | USART1 (PA9/PA10) | I2C1 (PB6/PB7) | SPI1 (PA5/PA6/PA7) | — | — |
| STM32F4 | USART1 (PA9/PA10) | I2C1 (PB6/PB7) | SPI1 (PA5/PA6/PA7) | CAN2 (PB5/PB6) | OTG_FS (DP/DM) |
| STM32G0 | USART1 (PA9/PA10 / PA2/PA3) | I2C1 (PB6/PB7) | SPI1 (PA5/PA6/PA7) | — | — |
| STM32G4 | USART1 (PA9/PA10) | I2C1 (PB6/PB7) | SPI1 (PA5/PA6/PA7) | FDCAN1 (PB8/PB9) | — |
| STM32L0 | USART1 (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) |
| STM32H7 | USART1 (PA9/PA10 / PB14/PB15) | I2C1 (PB6/PB7 / PB8/PB9) | SPI1 (PA5/PA6/PA7 / PB3/PB4/PB5) | FDCAN1 (PD0/PD1) | OTG_HS / OTG_FS |
| STM32U5 | USART1 (PA9/PA10 / PA2/PA3) | I2C1 (PB6/PB7) | SPI1 (PA5/PA6/PA7) | FDCAN1 | OTG_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:
- TX (adattatore) → PA10 (USART1_RX sull'STM32)
- RX (adattatore) → PA9 (USART1_TX sull'STM32)
- GND → GND (massa comune)
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
- Conferma i pin del bootloader per il tuo dispositivo esatto: Apri AN2606, trova il tuo part number, annota i pin USART/I2C/SPI. Non ci sono alternative — il bootloader usa pin fissi.
- Testa USART sia a 9600 che a 115200 baud: Alcuni dispositivi partono a 9600 per l'handshake iniziale e passano a 115200 dopo la sincronizzazione. Se STM32_Programmer_CLI non rileva il dispositivo, prova l'opzione
baudrate=9600. - Verifica il livello di tensione di BOOT0: BOOT0 deve essere campionato ALTO nel momento in cui RESET viene rilasciato. Una pull-up debole (10 kΩ a 3.3 V) funziona, ma un jumper su VDD è più affidabile durante lo sviluppo.
- Disabilita il watchdog prima di saltare al bootloader: Se IWDG è attivo, il bootloader non lo serve. Il chip si resetta prima che la sincronizzazione USART sia completata. Disabilita il watchdog (o configuralo con il timeout più lungo possibile) prima di chiamare
jump_to_bootloader(). - Controlla gli option byte su G0/G4/L5/U5: Questi componenti leggono
nBOOT0enBOOT_SELdagli option byte. Un valore errato può far sì che il chip si avvii in System Memory anche con BOOT0 = 0. - USB DFU su Windows necessita del driver corretto: I dispositivi STM32 DFU usano il driver STTub30 o libusb/WinUSB. Senza il driver, Windows enumera il dispositivo come periferica sconosciuta.
- Verifica il contenuto della Flash dopo la programmazione: Leggi sempre e verifica (flag
-vin STM32_Programmer_CLI). Una discrepanza di baud rate può produrre dati corrotti che comunque superano il controllo CRC del bootloader (raro ma possibile con timing marginale).
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:
- La fabbrica programma il firmware iniziale tramite il bootloader USART sull'attrezzatura di test di produzione (usando STM32_Programmer_CLI in uno script Python).
- L'applicazione implementa un meccanismo di aggiornamento in campo: un GPIO (o un messaggio CAN) attiva il salto software al bootloader di sistema.
- Un tecnico in campo collega un adattatore seriale robusto (basato su FT232, con morsetti a vite) e riprogramma usando un semplice script batch. Niente debugger, niente IDE, niente interfaccia grafica CubeProgrammer.
- Per prodotti con USB, uso DFU come percorso di aggiornamento primario e USART come fallback se il PHY USB è danneggiato.
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
- ST Application Note AN2606 — STM32 microcontroller system memory boot mode (il riferimento definitivo per tutti i comandi, pin e indirizzi del bootloader per dispositivo)
- ST Application Note AN3155 — USART protocol used in the STM32 bootloader
- ST Application Note AN3154 — I2C protocol used in the STM32 bootloader
- ST Application Note AN4286 — SPI protocol used in the STM32 bootloader
- ST Application Note AN5362 — USB DFU protocol used in the STM32 bootloader
- STM32CubeProgrammer Manuale Utente (UM2237)
- Documentazione dfu-util su dfu-util.sourceforge.net
- Cortex-M3/M4/M7 Generic User Guide — inizializzazione tabella vettori e stack pointer
💬 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.