cancel
Showing results for 
Search instead for 
Did you mean: 

Why might setting BFB2 cause printf to stop working?

Brian H
Senior

I've written a fairly simple test case for using the system bootloader to boot from flash bank 2 on a STM32F429ZIT6U (developing on a Nucleo F429ZI board). With stdio redirected to a UART, I'm able to direct the code to:

  1. Mass-Erase Bank 2
  2. Copy the entirety of Bank 1 to Bank 2
  3. Set the BFB2 bit

The function that sets BFB2 looks like this:

void boot_bank_select(uint8_t bank_bit) {
   FLASH_AdvOBProgramInitTypeDef optionbits = {
      .OptionType = OPTIONBYTE_BOOTCONFIG,
      .BootConfig = (bank_bit) ? OB_DUAL_BOOT_ENABLE : OB_DUAL_BOOT_DISABLE
   };
   HAL_FLASH_OB_Unlock();
   HAL_FLASHEx_AdvOBProgram(&optionbits);
   printf("Waiting for option bit write...\r\n");
   HAL_StatusTypeDef rc = HAL_FLASH_OB_Launch();
   if(rc == HAL_OK)
      printf("Bank select completed.\r\n");
   else
      printf("Bank select failed.\r\n");
}

By placing a few strategic breakpoints, I can see that once the option bit write takes place, printf starts causing an endless reboot cycle. Code in main() executes up to the first place printf() is called, then it reboots.

Looking at disassembly, I can see a point where a "bx lr" jumps into the middle of the boilerplate startup code, so I don't think an actual fault or reset is occuring; it's more likely a case of stack corruption. However, the situation persists through hard resets; I can only regain control of the chip by using STProg to clear the BFB2 option bit.

I'm at a loss. Bank 2 is identical to Bank 1; I know this because the code itself copies it directly, and I can download and compare the two banks via STProg and see that they're identical. So why would having the bank switched cause the code to jump to strange places?

I can provide all of the code if necessary.

14 REPLIES 14

Oh sorry, I should've included the code that far back. That's just the top of the loop that increments the timer variable. I'll edit the post to include it.

Screenshot: Stack while the code is flowing through that loop.

0693W00000WHuKJQA1.pngThe only activity on the stack is the storage for the 32-bit counter variable; the stack pointer doesn't move in this section of the code.

When it stops on ResetHandler, check the PWR reset reason register. Can it be watchdog?

Hi Pavel,

Thanks for your continued engagement.

I set a breakpoint on the first instruction in ResetHandler (which sets the stack pointer). Both the PWR_CR and PWR_CSR registers are all 0x0. I thought about watchdog, and added code to write the proper value to the watchdog register periodically and it made no difference (which isn't that surprising, the watchdog was not enabled).

What is still most puzzling to me is that this issue persists through complete power cycles once the BFB2 bit is set. If bank remapping is working properly and the image in bank 2 is bit-for-bit identical to the image in bank 1, what could possibly be causing this? The core shouldn't know the difference. I can literally use ST Cube Programmer to do nothing but flip the BFB2 bit; when it is 0, the code runs normally; when it is 1, the chip resets endlessly as described. Is there some side-effect of BFB2 that the code needs to handle? The example I've seen doesn't seem to indicate it, but the example is also much more complicated than my needs (I don't care if there's a reset at the time the bank is switched, and the example is showing how to do it without a reset).

Brian H
Senior

OH MY $DEITY I SOLVED IT.

Based on this one short paragraph in AN4767 that I managed to gloss over before:

"Beware that the VTOR reset value is zero, in case of BFB2 option active, it will by default point to system memory."

So yes, of course, if BFB2 is set and VTOR isn't re-pointed appropriately, the very first interrupt that comes along after the bank switch will send execution back into system memory. That's why I first saw it in the context of printf, which led to UART interrupts.

If the first instruction in main (or, really, any that happen before an interrupt) properly point the VTOR back at 0x0800 0000, the core stays happy in application code.

So I had this same problem but an older version build from the Atollic version did not have this problem.  Your solution had me look at SystemInit()  so in Atollic it had following so VTOR was set 
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
But newer has 
#if defined(USER_VECT_TAB_ADDRESS)
/* Configure the Vector Table location -------------------------------------*/
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
#endif

By default USER_VECT_TAB_ADDRESS is not enabled so this call is not helping.

I thought I would provide this nugget of information. The BFB2 can not be changed when you have configured RBP at level 2.  So you must instead keep BFB2 set and use Bank2 as your temporary code holder.
Step 1.  Running in Bank 1 you erase Bank 2. and then copy the new firmware into Bank2 but DO NOT write the 1st 32 or 64-bit value.
Step 2.  Once the last block is received and all your internal CRC checking show all data is received then do that 1st write last and soft reset.  Reason: BFB2 check Bank2 1st address for a RAM value so code will run from Bank1 if a problem happens during writing.
Step 3. After reset, the new code runs from Bank2.  At start-up, the code checks to see if running in Bank 2.  If so then it erases Bank 1 and copy Bank 2 into Bank 1.  This can be a straight copy because Bank 2 remains primary and Bank 1 is not checked.
Step 4. So now Bank 1 and Bank 2 have the same code.  But code is still running from Bank 2. Now you must  have a RAM function, that simply erases Bank2 and does a software reset.
Step 5. Microcontroller is in normal condition where Bank 1 contains the code and Bank 2 is erased and BFB2 is still set.  Some reference document say code in Bank2 will run when BFB2 is set (STM32L4x6) but actually it just checks to see if Bank 2 has valid initial stack pointer by looking at the 1st word.  If not valid then it will run the code in Bank 1. 
Extra: If you must save parameters in Flash that must be retained (i.e. defaults will not work),  Code and parameters need to fix into one bank and erase of Bank2 only needs to be the 1st block where the initial stack pointer is located.