Lynn Linse


Discussion created by Lynn Linse on Feb 28, 2017
Latest reply on Mar 11, 2017 by Jack Peacock

Geez, as someone who's spent 8 years doing high-level Python programming on 64-bit machines with near "infinite" resources, getting back into embedded "C" coding has been challenging. So I've spent a lot of time struggling with understanding the terse STMicro docs, which often make sense only after you understand what they are trying to say!


My most recent issue was getting the Dual Bank (& nBFB2) to work on an L152RET6 (with 2 banks of 256K flash) ... and to be blunt, it does NOT work like any of the "Dual Bank" app notes at (such as AN2606 or AN4767). In a nutshell, while bit nBFB2 defines if BANK1 or BANK2 is run, the L152 does NOT support the virtual bank swapping described. Look in the L152 peripheral manual to find the terse details of the nBFB2 bit. Since the banks do NOT swap, you need to link/locate the BANK1 image to run at FLASH_BASE, which == 0x08000000U. You need to link/locate the BANK2 image to run at FLASH_BANK2_BASE, which == 0x08040000U.


(Note: some people get around this by using BANK2 as a scratch pad to load new code, such as over-the-air, then once the code is validated, a small bootloader copies it BANK1 & so they always run BANK1 (period). My goal is to always have a back-up image to run. So we'll be putting a factory image in BANK1 & write-protecting it, and any field-upgrades happen in BANK2. Erasing BANK2 always falls back to BANK1 and the "factory" image. There are pro's and con's to this. You can do as you wish.)


Some details (assuming STM32 HAL 'drivers' are being used):

  • To read the nBFB2, OB->USER is R/W and (OB->USER & 0x80) shows your desired nBFB2 - but it might NOT be applied yet. &OB->USER == 0x1FF80004
  • So the safest quick test is (FLASH->OBR & FLASH_OBR_nRST_BFB2), which is read-only and shows what the processor is acting upon. &FLASH->OBR == 0x40023C1C.


So you end up with something like this:

        // this is SET/1, so force running BANK 1
        return FALSE;
    } // else is RESET/0, so try running BANK 2 first
    return TRUE;


When you build the image to run at 0x08040000, for example putting "FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K" in your LinkerScript. You do get a valid image ... however, the stock STM startup code in "stm32l1xx.c" will ALWAYS set the vector table (SCB->VTOR) to FLASH_BASE. Oops! My solution to this was to place this code at the very beginning of my main.c. It leaves VTOR set to FLASH_BASE when the function "main" is obviously loaded into BANK1, but changes it to FLASH_BANK2_BASE when "main" is in BANK2.

    if((uint32_t)&main >= FLASH_BANK2_BASE) {
        // then relocate to FLASH BANK2!


The PSEUDO-CODE to set nBFB2 is something like this (I'm ignoring the error handling)

  • FLASH_AdvOBProgramInitTypeDef init;
       init.OptionType = OPTIONBYTE_BOOTCONFIG;
       init.BootConfig = {OB_BOOT_BANK1 or OB_BOOT_BANK2);
  • HAL_FLASH_OB_Unlock();
  • HAL_FLASHEx_AdvOBProgram(&init);
  • HAL_FLASH_OB_Launch();
  • HAL_FLASH_OB_Lock();
  • Maybe add some delay here, or debug output saying "We're now going to reboot into selected bank"
  • HAL_NVIC_SystemReset();


So now you can make a 'factory image' linked to 0x08000000, and all field upgrades have to be linked to 0x08040000. As mentioned, this is just one way to design a "dual-boot" product which can fully recover from errors during field upgrades.