Search code examples
embeddedstm32bootloadercan-bus

STM32G0B1CE Can the Boot Option bits be used to jump to system bootloader?


I have seen that there are quite a few questions about jumping from an app to the ST system bootloader, for example this one. These use the method of setting the MSP and PC then doing the jump with a function pointer.

This seems to cause an issue with the system bootloader dual-bank management whereby the first jump fails and a second jump needs to be done.

My question is - would it be possible/better to use the user option bytes to jump to the bootloader instead?

Since the OB register is read during boot in the OBL phase, if we set both the "nBOOT1 bit" and "nBOOT_SEL bit" and clear the "nBOOT0 bit" then do a soft reset would this avoid the empty check weirdness and let us jump to the bootloader in one go?

2

(Just for context - this would be the first step of doing updates via CAN as the MCU in question has a CAN bootloader built in)

Thanks in advance!


Solution

  • After some time tinkering with a dev board and with some help from Tilen Majerle I found that this is indeed possible and does work well.

    I added the following in my main() while(1) loop so that when the blue button is pressed, the user option bits are modified and a reset is performed.

    I found that we don't have to do the soft reset ourselves as the HAL_FLASH_OB_Launch() function triggers the reset for us, after which we should boot into system memory according to the reference manual page 67.

    Also I found that the flash and option bytes must be unlocked before setting the option bytes, but not locked afterwards or the reset won't occur.

    Here is the code to do it:

    if(HAL_GPIO_ReadPin(BUTTON_GPIO_Port, BUTTON_Pin) == GPIO_PIN_RESET)
    {
       // Basic de-bounce for testing
       HAL_Delay(100);
       while(HAL_GPIO_ReadPin(BUTTON_GPIO_Port, BUTTON_Pin) == GPIO_PIN_RESET)
       {
          __NOP();
       }
    
       // Read, modify & write user option bits
       // nBOOT1 = 1, nBOOT_SEL = 1, nBOOT0 = 0; will select system memory as boot area
       uint32_t optBits = FLASH->OPTR;
       optBits = (optBits | FLASH_OPTR_nBOOT1 | FLASH_OPTR_nBOOT_SEL);
       optBits &= ~(FLASH_OPTR_nBOOT0);
    
       // Unlock flash
       HAL_FLASH_Unlock();
    
       // Clear OPTLOCK
       HAL_FLASH_OB_Unlock();
    
       // Set up struct with desired bits
       FLASH_OBProgramInitTypeDef optionBytesSetting = {0};
       optionBytesSetting.OptionType = OPTIONBYTE_USER;
       optionBytesSetting.USERConfig = optBits;
       optionBytesSetting.USERType = OB_USER_nBOOT0;
    
       // Write Option Bytes
       HAL_FLASHEx_OBProgram(&optionBytesSetting);
    
       HAL_Delay(10);
    
       // Soft reset
       HAL_FLASH_OB_Launch();
       NVIC_SystemReset();    // is not reached
    }
    

    I verified that the flash OPTR register is modified correctly (it goes from 0xFFFFFEAA to 0xFBFFFEAA, essentially just the nBOOT0 bit is cleared as the other two bits were already set). The MCU does reset at HAL_FLASH_OB_Launch() as expected and pausing the program reveals that after reset it is running the system bootloader based on the PC address.

    I also verified it using STM32CubeProgrammer which allows me to view the PC and option bytes, plus lets me set nBOOT0 back to 1 and boot the board to my app.

    As for reverting the OB settings programmatically, you could either use the Write Memory command before jumping to the app, or you could use the Go command to jump to the app then modify the option bytes first thing in your app.