Flash Dual Bank su STM32:
Attivazione e Bank Swap per Aggiornamenti OTA Sicuri su G4, L4 e U5
Un aggiornamento firmware che brickka il dispositivo a metà della trasmissione è uno degli incubi peggiori per chi sviluppa embedded. Perdita di alimentazione durante la cancellazione della flash, CRC corrotto o download incompleto — e la scheda è morta finché qualcuno non riconnette fisicamente un programmatore. La flash dual-bank degli STM32 offre uno scambio atomico basato su hardware: due banchi di flash completi, uno attivo e uno che riceve l'aggiornamento, scambiati con una singola scrittura di registro. Questo articolo spiega la configurazione degli option byte, il bank swap, la rilocazione del vettore di interrupt e l'architettura completa su STM32G4, L4 e U5.
La modalità dual-bank divide la memoria flash principale in due banchi indipendenti. Ogni banco può contenere un'immagine firmware completa. Quando la nuova immagine è pronta nel banco inattivo, una singola scrittura di registro attiva uno scambio atomico al prossimo reset — o immediatamente, a seconda del dispositivo. Non c'è rischio di brick in caso di perdita di alimentazione durante lo scambio: l'hardware garantisce che se l'operazione era in corso e l'alimentazione è mancata, il dispositivo si avvia dal banco che era valido prima dell'operazione.
Questo è fondamentalmente diverso da un approccio OTA puramente software, dove cancelli e riscrivi la stessa regione di flash in place. In uno schema a banco singolo, una perdita di alimentazione durante la cancellazione lascia il dispositivo completamente vuoto. Il dual-bank elimina questo rischio.
Quali Famiglie STM32 Supportano il Dual Bank?
La flash dual-bank è disponibile su diverse famiglie STM32 moderne, ma la mappa dei registri e i meccanismi di scambio differiscono:
| Serie | Dimensione Flash | Dimensione Banchi | Meccanismo di Scambio |
|---|---|---|---|
| STM32G4 | Fino a 512 KB | 2 × 256 KB | Bit FB_MODE negli option byte; swap al reset |
| STM32L4/L4+ | Fino a 2 MB | 2 × 1 MB | Bit DBANK; swap tramite FLASH_OPTCR |
| STM32U5 | Fino a 2 MB | 2 × 1 MB | Bit DBANK; swap tramite FLASH_OPTCR |
| STM32F7 | Fino a 2 MB | 2 × 1 MB | Bit DBANK su alcuni modelli |
| STM32H7 | Fino a 2 MB | 2 × 1 MB (alcuni) | Registro FLASH_OPTCR |
Questo articolo si concentra su STM32G4 (RM0440) e STM32L4 (RM0351) — le serie mid-range più comuni dove il dual-bank è disponibile e pratico per progetti da contractor. L'U5 segue lo stesso modello di registri dell'L4.
Layout di Memoria con Dual Bank Attivo
Quando la modalità dual-bank è inattiva (default), la flash appare come un unico blocco contiguo che parte da 0x08000000. Attivando il dual-bank, la flash viene divisa in due banchi di uguale dimensione:
Banco singolo (default):
0x0800 0000 ┌──────────────────────────┐
│ Singolo banco flash │
│ (es. 512 KB) │
0x0807 FFFF └──────────────────────────┘
Dual bank (FB_MODE/DBANK=1):
0x0800 0000 ┌──────────────────────────┐ ← Banco 1 (attivo, boot)
│ Immagine firmware A │
│ (256 KB su G4) │
0x0803 FFFF ├──────────────────────────┤
0x0804 0000 │ Immagine firmware B │ ← Banco 2 (inattivo)
│ (256 KB su G4) │
0x0807 FFFF └──────────────────────────┘
Il Banco 1 parte da 0x08000000 — l'indirizzo del vettore di reset. Il Banco 2 parte dal punto medio: 0x08040000 per un G4 da 512 KB, 0x08080000 per un L4 da 1 MB, e così via. Dopo un bank swap, il Banco 2 viene rimappato a 0x08000000 e il Banco 1 diventa il banco inattivo all'indirizzo superiore.
Configurazione del Dual Bank tramite Option Byte
La modalità dual-bank viene selezionata tramite gli option byte — un'area separata della flash che memorizza parametri di configurazione come il livello di protezione in lettura, il watchdog hardware e le impostazioni di boot. Gli option byte persistono tra aggiornamenti firmware e vengono modificati solo attraverso la sequenza di programmazione specifica del controller flash.
STM32G4: Bit FB_MODE
Sul G4, il bit FB_MODE nell'option byte FLASH_OPTR controlla la modalità dual-bank. La programmazione richiede la sequenza standard: sblocca FLASH_CR, imposta il bit di programmazione option byte, scrive OPTR, poi genera un reset per caricare la nuova configurazione:
/* Sblocco del registro di controllo flash */ FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xCDEF89AB; /* Sblocco degli option byte */ FLASH->OPTKEYR = 0x08192A3B; FLASH->OPTKEYR = 0x4C5D6E7F; /* Imposta FB_MODE nel registro option */ FLASH->OPTR |= FLASH_OPTR_FB_MODE; /* Avvia la programmazione degli option byte */ FLASH->CR |= FLASH_CR_OPTSTRT; while (FLASH->SR & FLASH_SR_BSY); /* Reset per caricare i nuovi option byte */ NVIC_SystemReset();
La nuova organizzazione dei banchi diventa effettiva solo dopo un reset (o un power-on reset). Gli option byte vengono caricati dalla boot ROM prima che il codice utente parta. Non puoi attivare il dual-bank e usare subito il Banco 2 nella stessa sessione di esecuzione.
STM32L4/U5: Bit DBANK
L'L4 e l'U5 usano il bit DBANK in FLASH_OPTR. La sequenza di programmazione è identica:
FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xCDEF89AB; FLASH->OPTKEYR = 0x08192A3B; FLASH->OPTKEYR = 0x4C5D6E7F; FLASH->OPTR |= FLASH_OPTR_DBANK; /* abilita dual bank */ FLASH->CR |= FLASH_CR_OPTSTRT; while (FLASH->SR & FLASH_SR_BSY); NVIC_SystemReset();
Dopo il reset, la flash è suddivisa in due banchi. Puoi verificare la configurazione attiva leggendo FLASH->OPTR e controllando il bit FB_MODE o DBANK.
Bank Swap: Il Meccanismo Centrale
Una volta che il dual-bank è attivo ed entrambi i banchi contengono immagini firmware valide, lo scambio viene attivato scrivendo nel registro FLASH_OPTCR (o nel registro di controllo dello scambio specifico della famiglia). Lo scambio può essere richiesto per il prossimo boot o immediatamente.
Swap al Prossimo Reset (STM32G4)
Sul G4, lo scambio è controllato dai bit BKSEL in FLASH_OPTCR. Impostando BKSEL a 0b01 si richiede l'avvio dal Banco 2 al prossimo reset. Lo scambio effettivo avviene durante la sequenza di reset:
/* Richiedi boot dal Banco 2 al prossimo reset */ FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_BKSEL) | (1 << FLASH_OPTCR_BKSEL_Pos); /* Attiva un reset per applicare */ NVIC_SystemReset();
Su alcuni G4, il registro di richiesta swap è write-once fino al prossimo reset. Se lo scrivi accidentalmente durante l'esecuzione normale (es. da un puntatore errante), lo swap non scatterà comunque fino al prossimo reset — ma fai attenzione durante il debug se il dispositivo si avvia dal banco sbagliato dopo un watchdog reset.
Swap Immediato tramite FLASH_KEYR + FLASH_OPTCR (STM32L4/U5)
L'L4/U5 supportano un modello leggermente diverso: lo scambio viene programmato tramite il meccanismo delle chiavi degli option byte e prende effetto al prossimo reset di sistema. Il bit SWAP_BANK in FLASH_OPTR determina quale banco è attivo:
/* Leggi OPTR corrente, commuta SWAP_BANK */
uint32_t optr = FLASH->OPTR;
if (optr & FLASH_OPTR_SWAP_BANK) {
optr &= ~FLASH_OPTR_SWAP_BANK; /* passa al Banco 1 */
} else {
optr |= FLASH_OPTR_SWAP_BANK; /* passa al Banco 2 */
}
/* Programma il nuovo valore OPTR */
FLASH->OPTR = optr;
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);
NVIC_SystemReset(); /* applica al reset */
Dopo il reset, il processore si avvia dal banco precedentemente inattivo. L'hardware legge gli option byte durante la fase di boot ROM e rimappa la finestra 0x08000000 di conseguenza.
Rilocazione del Vettore di Interrupt
Quando si scambiano i banchi, la CPU si avvia sempre da 0x08000000. Dopo lo scambio, il Banco 2 viene mappato lì, quindi la tabella dei vettori dell'immagine nel Banco 2 viene usata direttamente. Questa è la parte elegante: non è necessario rilocare il vettore a runtime per il flusso di boot. L'hardware gestisce il rimappaggio.
Tuttavia, durante un aggiornamento OTA in cui il bootloader è in esecuzione da un settore separato o dal Banco 1 mentre si scrive sul Banco 2, è necessario che la tabella dei vettori del bootloader rimanga accessibile. L'approccio tipico è:
- Bootloader — vive in un settore fisso del Banco 1 (settori 0–1, ~32 KB). La sua tabella dei vettori è a
0x08000000. - Applicazione A — vive nel resto del Banco 1. La sua tabella dei vettori è a
0x08008000(dopo il bootloader). L'applicazione impostaSCB->VTOR = 0x08008000all'inizio del suo startup. - Applicazione B — vive interamente nel Banco 2. La sua tabella dei vettori è all'inizio del Banco 2 (
0x08040000su un G4 da 512 KB). Dopo lo scambio, questo diventa0x08000000, quindi VTOR non deve cambiare — funziona e basta.
/* Applicazione A: riloca VTOR dopo il bootloader */ SCB->VTOR = 0x08008000; /* Applicazione B (dopo lo scambio al Banco 2): */ /* Il Banco 2 è ora a 0x08000000, VTOR rimane al default di reset */ /* Riloca solo se devi saltare a una sotto-sezione */
Architettura OTA Completa
Ecco il flusso OTA dual-bank che uso su progetti industriali e medicali in produzione:
- Fase bootloader — A ogni reset, il bootloader controlla un flag "swap_pending" in un registro di backup dedicato (dominio di backup RTC o ultimi 4 byte di SRAM). Se impostato, esegue il bank swap e cancella il flag. Altrimenti, convalida il CRC del banco attivo corrente e salta all'applicazione.
- Fase applicazione — L'applicazione in esecuzione riceve il nuovo binario firmware tramite UART/SPI/I2C/USB/CAN. Scrive ogni pagina nel banco inattivo usando la sequenza standard di programmazione flash.
- Fase di validazione — Dopo aver scritto l'immagine completa, l'applicazione calcola e confronta il CRC sull'intero banco inattivo. Se corrisponde al CRC inserito nell'header del firmware, imposta il flag "swap_pending" nel registro di backup e attiva un reset di sistema.
- Fase di swap — Il bootloader viene eseguito al prossimo reset, vede il flag "swap_pending", esegue il bank swap tramite
FLASH_OPTCR, cancella il flag e salta all'applicazione appena attivata. Il vecchio firmware rimane intatto nel banco ora inattivo come fallback.
Fallback Failsafe
Se il nuovo firmware non si avvia (watchdog timeout, hard fault, rollback richiesto dall'utente), il bootloader può rilevare avvii falliti consecutivi incrementando un contatore nel registro di backup. Dopo N fallimenti consecutivi, torna al banco precedente, ripristinando di fatto l'ultimo firmware funzionante. Questo è il pattern di bootstrap failsafe:
/* Bootloader: controlla il contatore di tentativi */
uint32_t attempts = RTC->BKP0R; /* registro di backup */
if (attempts > MAX_BOOT_ATTEMPTS) {
/* Rollback: torna all'altro banco */
bank_swap();
RTC->BKP0R = 0; /* reset contatore */
NVIC_SystemReset();
}
RTC->BKP0R = attempts + 1; /* incrementa */
/* Salta all'applicazione... */
/* L'avvio dell'applicazione deve azzerare il contatore */
/* RTC->BKP0R = 0; */
I registri di backup RTC mantengono il loro valore solo quando il dispositivo ha una batteria di backup su VBAT, o quando VDD è mantenuto. Se nessuno dei due è disponibile, usa gli ultimi 4 byte di SRAM (con il trucco del flag RCC_CSR_RMVF) o un settore EEPROM dedicato. Su STM32U5, i registri di backup sicuri offrono un'alternativa più robusta.
Esempio Pratico: Bank Swap su STM32G474
Ecco una funzione di bank swap completa e autonoma per STM32G474 (512 KB flash, banchi duali da 256 KB ciascuno). La funzione scrive la nuova immagine firmware nel Banco 2, la valida e richiede l'avvio dal Banco 2 al prossimo reset:
#define BANK2_START 0x08040000UL
#define BANK2_END 0x0807FFFFUL
typedef struct {
uint32_t magic; /* costante di validazione */
uint32_t crc32; /* CRC del firmware */
uint32_t size; /* dimensione firmware in byte */
uint32_t version; /* numero versione monotonico */
} firmware_header_t;
int ota_program_bank2(const uint8_t *data, uint32_t len,
const firmware_header_t *hdr)
{
uint32_t addr = BANK2_START;
/* 1. Sblocca flash */
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
/* 2. Cancella tutte le pagine del Banco 2 */
for (uint32_t page = 0; page < 64; page++) {
FLASH->CR = (page << FLASH_CR_PNB_Pos) | FLASH_CR_PER | FLASH_CR_STRT;
while (FLASH->SR & FLASH_SR_BSY);
if (FLASH->SR & FLASH_SR_PGSERR) return -1;
}
/* 3. Programma ogni parola */
FLASH->CR = FLASH_CR_PG; /* abilita programmazione */
for (uint32_t i = 0; i < len; i += 4) {
*(volatile uint32_t *)(addr + i) = *(const uint32_t *)(data + i);
while (FLASH->SR & FLASH_SR_BSY);
if (FLASH->SR & FLASH_SR_PGPERR) { FLASH->CR &= ~FLASH_CR_PG; return -2; }
}
FLASH->CR &= ~FLASH_CR_PG;
/* 4. Convalida CRC */
uint32_t computed_crc = compute_crc32((void *)BANK2_START, hdr->size);
if (computed_crc != hdr->crc32) return -3;
/* 5. Richiedi bank swap */
FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_BKSEL) | (1 << FLASH_OPTCR_BKSEL_Pos);
return 0; /* il chiamante attiva NVIC_SystemReset() */
}
Checklist pratica
| Passo | Registro/Verifica | Cosa controllare |
|---|---|---|
| Dual bank attivo | FLASH->OPTR (FB_MODE/DBANK) | Bit impostato dopo programmazione option byte e reset |
| Indirizzo Banco 2 | 0x08040000 (G4 512K) / 0x08080000 (L4 1M) | Corretto per la tua dimensione flash |
| Sblocco option byte | FLASH->OPTKEYR | Scrivere due chiavi in sequenza; nessun altro accesso tra le scritture |
| Richiesta swap | FLASH->OPTCR (BKSEL) o SWAP_BANK | Scritta prima del reset; verifica dopo il reset che il banco corretto sia attivo |
| Tabella vettori | SCB->VTOR | Corrispondere all'offset del banco di boot; se c'è un bootloader, VTOR deve essere dopo di esso |
| CRC fallback | Header banco inattivo | Calcola CRC del banco inattivo prima dello swap; mantieni fallback disponibile dopo lo swap |
| Contatore boot | Registro backup | Fallimenti consecutivi attivano rollback automatico |
| Latenza flash | FLASH->ACR | Deve corrispondere al nuovo SYSCLK dopo il reset; se il dual bank cambia i tempi, ricontrolla il datasheet |
Come lo affronterei su un progetto cliente
Su un sistema di produzione reale, non permetto all'applicazione di manipolare direttamente gli option byte. Invece, partiziono il bootloader in due stadi:
Bootloader Stage 1 (primi 4 KB, write-protetto): gestisce le richieste di swap, la convalida CRC e la logica di fallback. Non scrive mai nel proprio settore e occupa l'area flash minima possibile. Questo stadio viene programmato una volta in produzione e non viene mai modificato in campo.
Bootloader Stage 2 (successivi 28 KB): gestisce il protocollo di comunicazione per ricevere nuovi firmware (UART/CAN/SPI), guida la programmazione flash del banco inattivo e gestisce il monitoraggio delle versioni. Se è necessario un aggiornamento del protocollo di comunicazione in campo, viene aggiornato solo lo Stage 2 — lo Stage 1 convalida il CRC del nuovo Stage 2 prima di avviarlo.
Includo anche un header firmware versionato all'inizio di ogni banco con un numero di versione monotonico. Il bootloader rifiuta di scambiare a una versione più vecchia di quella attualmente in esecuzione. Questo previene una race condition in cui una richiesta di aggiornamento vecchia, ritardata da un ritardo di rete, sovrascrive un firmware più recente con uno più vecchio.
Per il CRC, uso il CRC hardware (il periferico CRC32 integrato dell'STM32) clockdato da HSI, non il CRC software usato nell'esempio di codice sopra. Il calcolo hardware del CRC si completa in microsecondi per un banco da 256 KB, rispetto ai decine di millisecondi di un'implementazione software.
Fonti
- Manuale di Riferimento STM32G4 (RM0440), Sezione 4 — Memoria flash (dual bank, option byte)
- Manuale di Riferimento STM32L4 (RM0351), Sezione 3 — Memoria flash e option byte
- Manuale di Riferimento STM32U5 (RM0456), Sezione 4 — Organizzazione memoria flash
- Nota Applicativa ST AN4767 — STM32L4 dual bank flash swap
- Nota Applicativa ST AN4654 — Programmazione in applicazione (IAP) STM32G4 tramite USART
- Nota Applicativa ST AN2606 — Modalità boot system memory STM32
- Stack Overflow: "Use STM32L4xx dual bank mechanism for software upgrade" — discussione sui meccanismi di swap
- Stack Overflow: "Setting dual bank mode on STM32F779II" — sequenza option byte per F7
- Stack Overflow: "Problem on executing firmware from Bank2 on STM32 Dual-Bank flash" — problemi comuni

Domande o correzioni? Scrivimi — rispondo a ogni messaggio.