cancel
Showing results for 
Search instead for 
Did you mean: 

Jumping to system bootloader from FreeRTOS application code - STM32U5A5

DenzilDexter
Associate III

Hello Forum,
I'm trying to get a simple application, running on a NUCLEO-U5A5ZJ-Q dev board, to jump to the system bootloader from FreeRTOS. I've also got AN2606 to hand (application note for the STM bootloader) and RM0456 (STM32 U5 reference manual). I have been following this guide.  How to jump to system bootloader from application ... - STMicroelectronics Community

According to the guide and reference manuals the bootloader base address on my MCU (STM32U5A5) is at 0x0BF90000.  The 32 bit word at that address is the stack location, and the 32bit work at 0x0BF90004 is the entry point for the bootloader. In my setup values are read as:

  1. 0x200034E8 - this is valid memory (stack)
  2. 0x0BF98C39 - this is unexpected as lots of the internet says this should be 0x0BF99EFE but I'm guessing this is actually OK.

I currently have this code:

#define BOOTLOADER_BASE 0x0BF90000

typedef struct boot_vectable
{
  uint32_t Initial_SP;
  void (*Reset_Handler)(void);
}
boot_vectable_t;

void JumpToBootloader()
{
  boot_vectable_t* pBootVec = (boot_vectable_t*)(BOOTLOADER_BASE);

  // Stop FreeRTOS
  vTaskSuspendAll();
  taskDISABLE_INTERRUPTS();

  /* Disable all interrupts */
  __disable_irq();

  /* Disable Systick timer */
  SysTick->CTRL = 0;

  /* Set the clock to the default state */
  HAL_RCC_DeInit();

  /* Clear Interrupt Enable Register & Interrupt Pending Register */
  const uint8_t NVIC_count = sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]);
  for (uint8_t i = 0; i < NVIC_count; i++)
  {
    NVIC->ICER[i] = 0xFFFFFFFF;
    NVIC->ICPR[i] = 0xFFFFFFFF;
  }

  /* Re-enable all interrupts */
  __enable_irq();

  // Set the MSP
  __set_MSP(pBootVec->Initial_SP);

  // Jump to bootloader
  pBootVec->Reset_Handler();
}

I am calling `JumpToBootloader()` this from this simple task.  

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN DefaultTask */
  for(;;)
  {
    if (BootloaderRequested)
    {
      BootloaderRequested = false;
      JumpToBootloader();
    }
    else
    {
      BSP_LED_Toggle(LED_GREEN);
    }
    osDelay(500);
  }
  /* USER CODE END DefaultTask */
}

Where `BootloaderRequested` is set to true in an ISR when the BLUE button on the board is pressed. Unfortunately it doesn't work and I can't figure out what's missing/wrong. The code gets to line 42 (pBootVec->Reset_Handler()) but then something goes wrong and the BL doesn't run.

I'd appreciate any help/thoughts anyone can offer.
DD

4 REPLIES 4
TDK
Super User

You have to jump from within the main thread, not a task. Might be easier to set a flag in RAM, reset, and check for that flag on startup before the RTOS starts.

Which bootloader device are you trying to use?

If you feel a post has answered your question, please click "Accept as Solution".

I will try that although I'm not sure how that would work in the main thread (by which I assume main()) as it calls osKernelStart() and never exits.  I'm using the ST pre-loaded bootloader that's available in ROM at 0x0BF90000 on the STM32U5.

 

gbm
Principal

Thread code is not allowed to set MSP (nor to do many other things). The safe way to call a bootloader is to go through reset and divert to the bootloader at early stage of main function based on flag stored in safe place (RTC/backup RAM or isolated RAM location excluded from memory map in linker script). It should be done as the firs action in main, before anything is initialized.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
DenzilDexter
Associate III

@gbm , thank you, that's a much simpler approach that does appear to work.

For those coming to this thread in future here's how I implemented it:

1. In the linker script add a no-init region

  /* Reserved memory to store magic number for jumping to the bootloader */
  .noinit (NOLOAD) :
  {
    . = ALIGN(4);
    KEEP(*(.noinit))
    . = ALIGN(4);
  } >RAM

 2. In my application create a variable in the above region

/* This flag is stored in a reserved area of RAM that persists on soft reset */
__attribute__((section(".noinit"))) __attribute__((used)) volatile uint32_t g_boot_req_flag;

3. Implement some calls to set set the flag, reset the system and check the flag.

#define BOOTLOADER_BASE (0x0BF90000UL)
#define BOOTLOADER_MAGIC_FLAG (0xB00710ADUL)

void RequestBootloader(void)
{
  g_boot_req_flag = BOOTLOADER_MAGIC_FLAG;
  __DSB();
  __ISB();
  NVIC_SystemReset();
}

uint8_t IsBootloaderRequested()
{
  uint8_t result = 0U;
  if (g_boot_req_flag == BOOTLOADER_MAGIC_FLAG)
  {
    result = 1U;
  }
  return result;
}

 4. In main() do the check to see if the bootloader has been requested, if so jump to it

int main(void)
{
  /* USER CODE BEGIN 1 */
  if (IsBootloaderRequested())
  {
    RunBootloader();
  }
  /* USER CODE END 1 */

  ....
}

5. The code to actually enter the system bootloader looks like this:

typedef struct BootVector_t
{
  uint32_t Initial_SP;
  void (*Reset_Handler)(void);
} BootVector;

void RunBootloader()
{
  BootVector* pBootVec = (BootVector*)(BOOTLOADER_BASE);

  /* Do only once */
  g_boot_req_flag = 0U;

  /* Disable all interrupts */
  __disable_irq();

  /* Disable Systick timer */
  SysTick->CTRL = 0;

  /* Set the clock to the default state */
  HAL_RCC_DeInit();

  /* Clear Interrupt Enable Register & Interrupt Pending Register */
  const uint8_t NVIC_count = sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]);
  for (uint8_t i = 0; i < NVIC_count; i++)
  {
    NVIC->ICER[i] = 0xFFFFFFFF;
    NVIC->ICPR[i] = 0xFFFFFFFF;
  }

  /* Re-enable all interrupts */
  __enable_irq();

  /* Set the MSP */
  __set_MSP(pBootVec->Initial_SP);

  /* Jump to bootloader */
  pBootVec->Reset_Handler();
}

This appears to work reliably so I hope it helps someone else. I can call it from within FreeRTOS and my board ends up in the system bootloader.

Regards,

DD