Skip to main content
Visitor II
October 28, 2021
Solved

Jump issue with custom bootloader (STM32H7)

  • October 28, 2021
  • 6 replies
  • 4968 views

Hello,

I've made several bootloader in the past without so much problem in the past with F2, F4, F7, but this time, I have some serious troubles with a STM32H743BI :)

The bootloader (using the first sector, 128kb) reads a bin file from the SD card and program the rest of the flash sectors.

Everything works well if : I have flashed the main application one time minimum, thanks to my j-link debugger. Entering the bootloader erase the main app sector, then flash the .bin code, then jump. No issue at all.

But if the main application has never been flashed with "manually" with j-link, the jump fail (no error/hardfault, the uC is just lost after jump).

Also, when everything works (first case), if I flash "manually" the main application starting on the first sector (VECT_TAB_OFFSET = 0, instead of VECT_TAB_OFFSET = 0x020000) : after a bootloader erase/flashing, same issue = jump fails.

It looks like flashing "manually" does configure some hidden registers, and after that the bootloader is able to successfully jump?

This is the code I use for the jump :

void JumpToApplication()
{
 uint32_t JumpAddress = *(__IO uint32_t*)(app_address);
 pFunction Jump = (pFunction)JumpAddress;
 
 // __disable_irq(); // tested, do not change anything
 SysTick->CTRL = 0;
 SysTick->LOAD = 0;
 SysTick->VAL = 0;
 SCB->VTOR = app_address;
 
 HAL_RCC_DeInit();
 // HAL_DeInit(); // tested, do not change anything
 
 // tested, do not change anything
 // for(uint8_t i = 0; i < 5; i++) 
 // {
 // NVIC->ICER[i] = 0xFFFFFFFF;
 // NVIC->ICPR[i] = 0xFFFFFFFF;
 // }
 
 // __enable_irq(); //tested, do not change anything
 __set_MSP(*(__IO uint32_t*)app_address);
 Jump();
}

On the side of the main application :

#define VECT_TAB_OFFSET 0x020000UL

Do you have idea on what is going wrong?

Thanks!

Jean

    This topic has been closed for replies.
    Best answer by jean

    Hi everybody,

    Thanks a lot for your ideas!

    Unfortunately any of them did work...

    But I found the problem : for the erase, I was using FLASH_VOLTAGE_RANGE_4 instead of FLASH_VOLTAGE_RANGE_3

    The final code is :

    FLASH_EraseInitTypeDef pEraseInit { 
     .TypeErase { FLASH_TYPEERASE_MASSERASE },
     .Banks { FLASH_BANK_2 },
     .Sector { FLASH_SECTOR_0 },
     .NbSectors { 8 },
     .VoltageRange { FLASH_VOLTAGE_RANGE_3 },
    };

    Now, everything works well in any condition!

    6 replies

    Graduate II
    October 28, 2021

    Check the BOOT0 pin state

    Turn off all your random interrupts, this isn't "__disable_irq()", this is you turning off your peripheral interrupt sources, AT THE SOURCE.

    Print out the Option Bytes content.

    Print out the addresses in the vector table.

    I would avoid changing SCB->VTOR here, your app side SystemInit() code should set this properly. Make sure it is. Ideally use linker symbols, and not ST's #define nonsense.

    App side code can also explicitly set the Stack Point when it arrives in Reset_Handler, this would work more safely as the auto-variables are in the stack frame.

    Make sure the Segger isn't erasing the first sector by accident, otherwise the processor will enter the System Loader, and not yours. Inspect the memory in the failing cases.

    Visitor II
    October 29, 2021

    Hi,

    I had many troubles on that topic too, the jump function I ended up with is the following:

    void vSetupAndJumpToAddr(uint32_t flashStartAddr)
    {
     __disable_irq();
     
     HAL_MPU_Disable();
     
     // Deactivate the used peripherals
     HAL_DeInit();
     HAL_RCC_DeInit();
     
     //Disable ALL enabled peripherals
     MX_GPIO_DeInit();
     MX_DMA_DeInit();
     
     HAL_I2C_MspDeInit(&hi2c5);
     HAL_OSPI_MspDeInit(&hospi1);
     HAL_TIM_Base_MspDeInit(&htim12);
     HAL_TIM_Base_MspDeInit(&htim15);
     HAL_TIM_Base_MspDeInit(&htim23);
     HAL_UART_MspDeInit(&huart4);
     HAL_UART_MspDeInit(&huart5);
     HAL_UART_MspDeInit(&huart9);
     HAL_USART_MspDeInit(&husart10);
     HAL_TIM_Base_MspDeInit(&htim6);
     HAL_CRC_MspDeInit(&hcrc);
     HAL_RTC_MspDeInit(&hrtc);
     
     __HAL_DBGMCU_FREEZE_IWDG1();
     __HAL_DBGMCU_FREEZE_TIM1 ();
     __HAL_DBGMCU_FREEZE_TIM4 ();
     __HAL_DBGMCU_FREEZE_TIM6 ();
     __HAL_DBGMCU_FREEZE_TIM12 ();
     __HAL_DBGMCU_FREEZE_TIM15 ();
     __HAL_DBGMCU_FREEZE_TIM16 ();
     __HAL_DBGMCU_FREEZE_TIM23 ();
     __HAL_DBGMCU_FREEZE_TIM3 ();
     __HAL_DBGMCU_FREEZE_I2C5 ();
     __HAL_DBGMCU_FREEZE_WWDG1 ();
     __HAL_DBGMCU_FREEZE_IWDG1 ();
     
     // Disable I-Cache
     SCB_DisableICache();
     // Disable D-Cache
     SCB_DisableDCache();
     
     // Make sure, the CPU is in privileged mode.
     if( CONTROL_nPRIV_Msk & __get_CONTROL( ) )
     { /* not in privileged mode */
     EnablePrivilegedMode( ) ;
     }
     
     for (int i = 0; i < 8; i++)
     {
     NVIC->ICER[i] = 0xFFFFFFFF; //Disable all enabled interrupts in NVIC.
     NVIC->ICPR[i] = 0xFFFFFFFF; //Clear all pending interrupt requests in NVIC.
     }
     
     // Disable SysTick and clear its exception pending bit, if it is used in the bootloader, e. g. by the RTX.
     SysTick->CTRL = 0 ;
     SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk ;
     
     // Disable individual fault handlers if the bootloader used them.
     SCB->SHCSR &= ~( SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk | SCB_SHCSR_MEMFAULTENA_Msk ) ;
     
     
     //Activate the MSP, if the core is found to currently run with the PSP.
     // As the compiler might still use the stack, the PSP needs to be copied to the MSP before this.
     if( CONTROL_SPSEL_Msk & __get_CONTROL( ) )
     { /* MSP is not active */
     __set_MSP( __get_PSP( ) ) ;
     __set_CONTROL( __get_CONTROL( ) & ~CONTROL_SPSEL_Msk ) ;
     }
     
     // SCB->VTOR is setup in the appl startup
     // "VECT_TAB_OFFSET = 0x40400" setup into keil > option for target > C/C++ > Preprocessor symbols
     
     // !!! Attention !!!
     // re-enable IRQ with _enable_irq() in the application
     // AFTER the initialization of all the peripherals!
     
     // Jump to user application
     JumpAddress = *(__IO uint32_t*)(flashStartAddr+4);
     Jump_To_Adress = (pFunction)JumpAddress;
     
     // re-init stack pointer (first entry of the vector table)
     __set_MSP(*(__IO uint32_t*) flashStartAddr);
     Jump_To_Adress();
     
     // *** codeline never reached ***
     Error_Handler();
    }

    Many of these lines may or may not be useful, but now it works fine for me. The VECT_TAB_OFFSET of the VTOR as written in the comments is configured inside keil in my case.

    With this procedure I'm able to directly jump to the application after programming Application sectors.

    If this is not enough, compare the binary and the flash area when it might be flashed in, and then also the flashing procedure.

    Visitor II
    November 4, 2021

    Good information, but order of de-initializing seems wrong. Shouldn't it be something like that:

    HAL_UART_MspDeInit(&huart4); // de-init peripherals first
    HAL_RCC_DeInit();
    HAL_DeInit(); // everything is in reverse order compared to initialization stage

    In my opinion, best approach is to place bootloader in sector 0 and keep it simple, with only one UART initialized. In this case de-iniatilization is very simple:

    HAL_UART_DMAStop(&huart1);
    HAL_UART_DeInit(&huart1);
    HAL_DeInit();
     
    JumpAddress=0x24000000;
    JumpAddress = *(__IO uint32_t*) (JumpAddress + 4);
    Jump_To_Application = (pFunction) JumpAddress;
     
    __set_MSP(*(__IO uint32_t*) JumpAddress);
    Jump_To_Application();

    I always keep sector 0 write protected, where simple bootloader is located. It jumps to main application after power-up if no special sequence of bytes is received. Bootloader is started from main application by calling HAL_NVIC_SystemReset(); and then sending specific bytes to stay in bootloader. This way it is nearly impossible to brick the device, and you always can get to bootloader through power cycling even if something is messed up within main application.

    Visitor II
    October 29, 2021

    Be aware of asm code produced by C compiler​, you can single step asm to identify what happen.

    Another trap: @jean​  consider allocating "jump" as a global Var and not a local var. Because just before jumping the stack pointer is forced with set MSP!

    So depending of generated code​ the stack relative address of local Var is no more guaranted!

    Prefetching and out of order execution​ (due to compiler optim and/or Cortex M7) can also change what we are usually expecting with previous STM32 generations...

    jeanAuthorAnswer
    Visitor II
    November 3, 2021

    Hi everybody,

    Thanks a lot for your ideas!

    Unfortunately any of them did work...

    But I found the problem : for the erase, I was using FLASH_VOLTAGE_RANGE_4 instead of FLASH_VOLTAGE_RANGE_3

    The final code is :

    FLASH_EraseInitTypeDef pEraseInit { 
     .TypeErase { FLASH_TYPEERASE_MASSERASE },
     .Banks { FLASH_BANK_2 },
     .Sector { FLASH_SECTOR_0 },
     .NbSectors { 8 },
     .VoltageRange { FLASH_VOLTAGE_RANGE_3 },
    };

    Now, everything works well in any condition!

    Graduate II
    November 7, 2021

    Your copy-paste procedure has bug... ;) The initial code lacks the 4 byte offset for reading the Reset_Handler:

     uint32_t JumpAddress = *(__IO uint32_t*)(app_address);
     pFunction Jump = (pFunction)JumpAddress;

    Also it amazes me how people just copy any code and don't think for themselves even for a second. There is no need for an intermediate useless JumpAddress variable:

     pFunction Jump = (pFunction)(*(__IO uint32_t*)(app_address + 4));

    And ARM clearly says that changing the VTOR requires a DSB after it and changing the stack pointer requires an ISB after it...

    jeanAuthor
    Visitor II
    November 8, 2021

    Hi @Piranha​ 

    Thanks for your comment and sorry for the typo, indeed it was a copy paste error from my side!

    Never heard of DSB / ISB, do you have an example of use in this case?