Dual Bank Flash on STM32L476
Preface
We want to ensure that the program running before firmware update will not stop, so we need to implement the Dual Bank Flash feature. While downloading the updated firmware on one bank, the program will continue to run on the other bank. After completion, the firmware version will be compared, and the latest version will be selected to boot from the corresponding bank.
Flash
Flash memory is a type of EEPROM that can be erased and rewritten multiple times during operation. This technology is primarily used for general data storage and for exchanging data between computers and other digital devices, such as memory cards and USB drives. Flash memory is a special type of EEPROM that is erased in large blocks. Early flash memory had to be completely erased with each write cycle, which erased all data on the chip.
Flash memory is much cheaper than EEPROM that can be written on a per-byte basis, which has made it the most important and widely adopted non-volatile solid-state storage technology. Flash memory is used in PDAs, laptops, digital audio players, digital cameras, and mobile phones. In addition, the use of flash memory in game consoles has become increasingly popular, replacing EEPROM or battery-backed SRAM used to store game data.
Dual Bank IAP Principle
Some models of STM32 microcontrollers offer dual-bank flash functionality, which essentially divides the flash memory into two sections: BANK1 and BANK2. When an app is running in BANK1, the updated firmware can be written to BANK2. Once the write is complete, the system switches to running the new app from BANK2. If the app is currently running from BANK2, the new firmware can be written to BANK1, and once the write is complete, the system switches to running the new app from BANK1
How to identify and enable Dual Bank Flash
First, check the first page of the datasheet to see if there are 2 banks in Memories (taking STM32L476/STM32L073 as examples). If not, it will indicate a single bank Flash.
To enable Dual Bank Flash, first open STM32CubeProgrammer and check the BFB2 option (using L476 as an example, for L073 it is already dual bank and does not need to be enabled).
For STM32F4/G4/G0/L4/L0, the Vector Table is not reset in SystemInit() when using Dual Bank Flash. If an interrupt occurs after switching to another bank, it may cause system anomalies. It is recommended to enable “USER_VECT_TAB_ADDRESS” (which is disabled by default) so that the Vector Table can be reset in SystemInit() after switching to another bank.
what is UFB?
This is a bit located in the SYSCFG register with two possible values: 0 or 1, which determines the following:
- 0: FLASH bank 1 is mapped to address 0x8000000.
- 1: FLASH bank 2 is mapped to address 0x8000000.
This is the main determinant for switching between banks. The following two figures illustrate the different startup processes for the two banks:
Bank diagram
BANK1 start flow
BANK2 start flow
As shown in the figure above, Bank 2 is mapped to the starting address of 0x8000000. Therefore, if Flash is written to address 0x08090000 in Bank 2, it will actually appear at address 0x08010000 when viewed with CubePrg. On the other hand, whether Bank 1 is mapped or not, reading and writing Flash remains the same as in normal mode. With this principle in mind, Bank 2 can read values stored in Bank 1 and vice versa.
Reference code
The following is the initialization part of the example program provided by L476
void toggleBankAndReset() {
FLASH_OBProgramInitTypeDef OBInit;
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
HAL_FLASH_OB_Unlock();
<mark style="background-color:rgba(0, 0, 0, 0);color:#09ed1b" class="has-inline-color"> /* Get the Dual boot configuration status */</mark>
<mark style="background-color:rgba(0, 0, 0, 0);color:#eb0c0c" class="has-inline-color"> HAL_FLASHEx_OBGetConfig(&OBInit);
</mark><mark style="background-color:rgba(0, 0, 0, 0);color:#0ceb16" class="has-inline-color"> /* Enable/Disable dual boot feature */</mark><mark style="background-color:rgba(0, 0, 0, 0);color:#eb0c0c" class="has-inline-color">
OBInit.OptionType = OPTIONBYTE_USER;
OBInit.USERType = OB_USER_BFB2;</mark>
if (((OBInit.USERConfig) & (OB_BFB2_ENABLE)) == OB_BFB2_ENABLE) {
OBInit.USERConfig = OB_BFB2_DISABLE;
} else {
OBInit.USERConfig = OB_BFB2_ENABLE;
}
if (HAL_FLASHEx_OBProgram(&OBInit) != HAL_OK) {
// uint32_t errorCode = HAL_FLASH_GetError();
while (1) {
BSP_LED_On(LED2);
HAL_Delay(500);
BSP_LED_Off(LED2);
HAL_Delay(500);
}
}
if (HAL_FLASH_OB_Launch() != HAL_OK) {
//uint32_t errorCode = HAL_FLASH_GetError();
while (1) {
BSP_LED_On(LED2);
HAL_Delay(500);
BSP_LED_Off(LED2);
HAL_Delay(500);
}
}
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
}
Below is the code to verify which bank is currently being used.
uint8_t getActiveBank() {
volatile uint32_t remap = READ_BIT(SYSCFG->MEMRMP, 0x1 << 8);
return remap == 0 ? 1 : 2;
}
Burning method
The burning method here needs to be noted because CubeIDE currently cannot burn directly from 0x08080000, so bin file burning will be used with the help of CubeProgrammer, as follows:
- First, clear the entire flash memory.
- The red box indicates the programming button to be clicked.
- The blue box is to select the bin file to be programmed (here, we need to program Bank2 first, so we start programming from 0x08080000).
- The green box is the programming start position.
- Check if 0x08080000 has been successfully programmed.
- Check if 0x08000000 is still FF.
- Correct the programming start position and repeat steps 2-4 to program Bank1.
- Check if the programming is successful for the entire memory.