STM32 Flash Dual Bank Mode:
Enabling Bank Swap for Safe OTA Updates on G4, L4 and U5
Nothing worse than a firmware update that bricks the device mid-transfer. Power loss during flash erase, a corrupted CRC, or an incomplete download — and the board is dead until someone physically reconnects a programmer. STM32 dual-bank flash gives you a hardware-backed atomic swap: two complete flash banks, one active, one receiving the update, swapped with a single register write. This article walks through option-byte configuration, bank swapping, vector relocation, and the full architecture on STM32G4, L4, and U5.
The STM32 dual-bank flash feature splits the main flash memory into two independent banks. Each bank can hold a complete firmware image. When a new image is ready in the inactive bank, a single register write triggers an atomic bank swap on the next system reset — or immediately, depending on the part. There is no risk of bricking from a power loss during the swap: the hardware guarantees that if the swap was in progress and power failed, the device boots from whichever bank was valid before the operation.
This is fundamentally different from a software-only OTA approach, where you erase and rewrite the same flash region in place. In a single-bank scheme, a power loss during the erase phase leaves you with a blank device. Dual-bank eliminates that risk.
Which STM32 Families Support Dual Bank?
Dual-bank flash is available on several modern STM32 series, but the exact register map and swap mechanics differ:
| Series | Flash Size | Bank Size | Swap Mechanism |
|---|---|---|---|
| STM32G4 | Up to 512 KB | 2 × 256 KB | FB_MODE option bit; swap on reset |
| STM32L4/L4+ | Up to 2 MB | 2 × 1 MB | DBANK option bit; swap via FLASH_OPTCR |
| STM32U5 | Up to 2 MB | 2 × 1 MB | DBANK option bit; swap via FLASH_OPTCR |
| STM32F7 | Up to 2 MB | 2 × 1 MB | DBANK option bit on selected parts |
| STM32H7 | Up to 2 MB | 2 × 1 MB (some) | FLASH_OPTCR register |
This article focuses on STM32G4 (RM0440) and STM32L4 (RM0351) — the most common mid-range series where dual-bank is both available and practical for contractor projects. The U5 follows the same L4-style register model.
Memory Layout with Dual Bank Enabled
When dual-bank mode is inactive (default), the flash appears as a single contiguous block starting at 0x08000000. Enabling dual-bank splits this into two equally-sized banks:
Single bank (default):
0x0800 0000 ┌──────────────────────────┐
│ Single flash bank │
│ (e.g. 512 KB) │
0x0807 FFFF └──────────────────────────┘
Dual bank (FB_MODE/DBANK=1):
0x0800 0000 ┌──────────────────────────┐ ← Bank 1 (active, boot)
│ Firmware image A │
│ (256 KB on G4) │
0x0803 FFFF ├──────────────────────────┤
0x0804 0000 │ Firmware image B │ ← Bank 2 (inactive)
│ (256 KB on G4) │
0x0807 FFFF └──────────────────────────┘
Bank 1 starts at 0x08000000 — the reset vector address. Bank 2 starts at the midpoint: 0x08040000 for a 512 KB G4, 0x08080000 for a 1 MB L4, and so on. After a bank swap, Bank 2 is remapped to 0x08000000 and Bank 1 becomes the inactive bank at the upper address.
Configuring Dual Bank via Option Bytes
Dual-bank mode is selected through option bytes — a separate area of flash that stores configuration parameters like read protection level, hardware watchdog, and boot settings. Option bytes persist across firmware updates and are only modified through the flash controller's option-byte programming sequence.
STM32G4: FB_MODE Option Bit
On the G4, the FB_MODE bit in option byte FLASH_OPTR controls dual-bank mode. Programming it requires the standard option-byte sequence: unlock FLASH_CR, set the option-byte programming bit, write OPTR, then generate a system reset to load the new configuration:
/* Unlock the flash control register */ FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xCDEF89AB; /* Unlock option bytes */ FLASH->OPTKEYR = 0x08192A3B; FLASH->OPTKEYR = 0x4C5D6E7F; /* Set FB_MODE in option register */ FLASH->OPTR |= FLASH_OPTR_FB_MODE; /* Start option-byte programming */ FLASH->CR |= FLASH_CR_OPTSTRT; while (FLASH->SR & FLASH_SR_BSY); /* System reset to load new option bytes */ NVIC_SystemReset();
The new bank organization only takes effect after a system reset (or power-on reset). The option bytes are loaded by the boot ROM before the user code starts. You cannot enable dual-bank and immediately start using Bank 2 in the same execution session.
STM32L4/U5: DBANK Option Bit
The L4 and U5 use the DBANK bit in FLASH_OPTR. The programming sequence is identical:
FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xCDEF89AB; FLASH->OPTKEYR = 0x08192A3B; FLASH->OPTKEYR = 0x4C5D6E7F; FLASH->OPTR |= FLASH_OPTR_DBANK; /* enable dual bank */ FLASH->CR |= FLASH_CR_OPTSTRT; while (FLASH->SR & FLASH_SR_BSY); NVIC_SystemReset();
After reset, the flash is split into two banks. You can verify the active configuration by reading FLASH->OPTR and checking the FB_MODE or DBANK bit.
Bank Swap: The Core Mechanism
Once dual bank is enabled and both banks contain valid firmware images, the bank swap is triggered by writing to the FLASH_OPTCR register (or the relevant swap control register, depending on the family). The swap can be requested either for the next boot or immediately.
Swap on Next Reset (STM32G4)
On the G4, the swap is controlled by the BKSEL bits in FLASH_OPTCR. Setting BKSEL to 0b01 requests Bank 2 as the boot bank on the next reset. The actual swap happens during the reset sequence:
/* Request boot from Bank 2 on next reset */ FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_BKSEL) | (1 << FLASH_OPTCR_BKSEL_Pos); /* Request a system reset to apply */ NVIC_SystemReset();
On some G4 parts, the swap request register is write-once until the next reset. If you write it accidentally during normal execution (e.g. from a stray pointer), the swap won't trigger until the next reset anyway — but debug this carefully if your device boots from the wrong bank after a watchdog reset.
Immediate Swap via FLASH_KEYR + FLASH_OPTCR (STM32L4/U5)
The L4/U5 support a slightly different model: the swap is programmed through the option-byte key mechanism and takes effect on the next system reset. The SWAP_BANK bit in FLASH_OPTR toggles which bank is active:
/* Read current OPTR, toggle SWAP_BANK */
uint32_t optr = FLASH->OPTR;
if (optr & FLASH_OPTR_SWAP_BANK) {
optr &= ~FLASH_OPTR_SWAP_BANK; /* switch to Bank 1 */
} else {
optr |= FLASH_OPTR_SWAP_BANK; /* switch to Bank 2 */
}
/* Program the new OPTR value */
FLASH->OPTR = optr;
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);
NVIC_SystemReset(); /* apply on reset */
After reset, the processor boots from the previously inactive bank. The hardware reads the option bytes during the boot ROM phase and remaps the 0x08000000 window accordingly.
Vector Table Relocation
When you swap banks, the CPU always boots from 0x08000000. After the swap, Bank 2 is mapped there, so the vector table of the image in Bank 2 is used directly. This is the elegant part: you do not need to relocate the vector table at runtime for the boot flow. The hardware handles the remapping.
However, during an OTA update where the bootloader runs from a separate sector or from Bank 1 while writing to Bank 2, you do need the bootloader's vector table to remain accessible. The typical approach is:
- Bootloader lives in a fixed sector of Bank 1 (sectors 0–1, ~32 KB). Its vector table is at
0x08000000. - Application A lives in the rest of Bank 1. Its vector table is at
0x08008000(after the bootloader). The application setsSCB->VTOR = 0x08008000early in its startup. - Application B lives in Bank 2 entirely. Its vector table is at the start of Bank 2 (
0x08040000on a 512 KB G4). After the swap, this becomes0x08000000, so VTOR does not need to change — it just works.
/* Application A: relocate VTOR past the bootloader */ SCB->VTOR = 0x08008000; /* Application B (after swap to Bank 2): */ /* Bank 2 is now at 0x08000000, VTOR stays at reset default */ /* Only relocate if you need to jump to a sub-section */
Complete OTA Update Architecture
Here is the dual-bank OTA flow that I use on production medical and industrial projects:
- Bootloader stage — On every reset, the bootloader checks a "swap_pending" flag in a dedicated backup register (RTC backup domain or last 4 bytes of SRAM with battery backup). If set, it performs the bank swap and clears the flag. If not, it validates the current active bank's CRC and jumps to the application.
- Application stage — The running application receives the new firmware binary over UART/SPI/I2C/USB/CAN. It writes each page to the inactive bank using the standard flash programming sequence.
- Validation stage — After the complete image is written, the application computes and compares the CRC over the entire inactive bank. If it matches the CRC embedded in the firmware header, it sets the "swap_pending" flag in the backup register and triggers a system reset.
- Swap stage — The bootloader runs on the next reset, sees the "swap_pending" flag, performs the bank swap via
FLASH_OPTCR, clears the flag, and jumps to the newly active application. The old firmware remains intact in the now-inactive bank as a fallback.
Failsafe Fallback
If the new firmware fails to boot (watchdog timeout, hard fault, user-initiated rollback), the bootloader can detect consecutive failed boots by incrementing a counter in the backup register. After N consecutive failures, it swaps back to the previous bank, effectively restoring the last known-good firmware. This is the failsafe bootstrap pattern:
/* Bootloader: check boot attempt counter */
uint32_t attempts = RTC->BKP0R; /* backup register */
if (attempts > MAX_BOOT_ATTEMPTS) {
/* Rollback: swap back to the other bank */
bank_swap();
RTC->BKP0R = 0; /* reset counter */
NVIC_SystemReset();
}
RTC->BKP0R = attempts + 1; /* increment */
/* Jump to application... */
/* Application startup must clear the counter */
/* RTC->BKP0R = 0; */
RTC backup registers only retain their value when the device has a backup battery on VBAT, or when VDD is maintained. If neither is available, use the last 4 bytes of SRAM (with the RCC_CSR_RMVF flag trick) or a dedicated EEPROM sector. On STM32U5, the secure backup registers offer a more robust alternative.
Practical Example: Bank Swap on STM32G474
Here is a complete, self-contained bank swap function for the STM32G474 (512 KB flash, dual banks of 256 KB each). The function writes the new firmware image to Bank 2, validates it, and requests a boot from Bank 2 on the next reset:
#define BANK2_START 0x08040000UL
#define BANK2_END 0x0807FFFFUL
typedef struct {
uint32_t magic; /* validation constant */
uint32_t crc32; /* firmware CRC */
uint32_t size; /* firmware size in bytes */
uint32_t version; /* monotonic version number */
} 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. Unlock flash */
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
/* 2. Erase all pages in Bank 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. Program each word */
FLASH->CR = FLASH_CR_PG; /* enable programming */
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. Validate CRC */
uint32_t computed_crc = compute_crc32((void *)BANK2_START, hdr->size);
if (computed_crc != hdr->crc32) return -3;
/* 5. Request bank swap */
FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_BKSEL) | (1 << FLASH_OPTCR_BKSEL_Pos);
return 0; /* caller triggers NVIC_SystemReset() */
}
Practical checklist
| Step | Register/Check | What to verify |
|---|---|---|
| Dual bank enabled | FLASH->OPTR (FB_MODE/DBANK) | Bit set after option-byte programming and reset |
| Bank 2 address | 0x08040000 (G4 512K) / 0x08080000 (L4 1M) | Correct for your flash size |
| Option byte unlock | FLASH->OPTKEYR | Must write two keys in sequence; no other register access between writes |
| Bank swap request | FLASH->OPTCR (BKSEL) or SWAP_BANK | Written before reset; verify after reset that the correct bank is active |
| Vector table | SCB->VTOR | Match the boot bank offset; if bootloader present, application VTOR must be past bootloader |
| Fallback CRC | Inactive bank header | Compute CRC of inactive bank before swap; keep fallback available after swap |
| Boot counter | Backup register | Consecutive failures trigger automatic rollback |
| Flash latency | FLASH->ACR | Must match the new SYSCLK after reset; if dual bank changes timing, re-check the datasheet |
How I would approach this on a client project
On a real production system, I do not let the application directly manipulate option bytes. Instead, I partition the bootloader into two stages:
Stage 1 bootloader (first 4 KB, write-protected): handles swap requests, CRC validation, and fallback logic. It never writes to its own sector and occupies the minimum possible flash area. This stage is programmed once during production and never modified in the field.
Stage 2 bootloader (next 28 KB): handles the communication protocol for receiving new firmware (UART/CAN/SPI), drives the flash programming of the inactive bank, and manages version tracking. If a communication protocol update is needed in the field, only Stage 2 is updated — Stage 1 validates the new Stage 2 CRC before booting it.
I also include a versioned firmware header at the start of each bank with a monotonic version number. The bootloader refuses to swap to a version older than the currently running one. This prevents a race condition where an old update request, delayed by a network retry, overwrites a newer firmware with an older one.
For the CRC, I use hardware CRC (STM32's built-in CRC32 peripheral) clocked from HSI, not the software CRC used in the code example above. The hardware CRC computation completes in microseconds for a 256 KB bank, compared to tens of milliseconds for a software implementation.
Sources
- STM32G4 Reference Manual (RM0440), Section 4 — Flash memory (dual bank, option bytes)
- STM32L4 Reference Manual (RM0351), Section 3 — Flash program memory and option bytes
- STM32U5 Reference Manual (RM0456), Section 4 — Flash memory organization
- ST Application Note AN4767 — STM32L4 dual bank flash swap
- ST Application Note AN4654 — STM32G4 in-application programming (IAP) using the USART
- ST Application Note AN2606 — STM32 system memory boot mode
- Stack Overflow: "Use STM32L4xx dual bank mechanism for software upgrade" — discussion on swap mechanics
- Stack Overflow: "Setting dual bank mode on STM32F779II" — option byte sequence for F7 parts
- Stack Overflow: "Problem on executing firmware from Bank2 on STM32 Dual-Bank flash" — common pitfalls

Questions or corrections? Email me — I reply to every message.