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

1 ACCEPTED SOLUTION

Accepted Solutions
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

View solution in original post

5 REPLIES 5
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

1. Your routine 

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

may be reduced to

static inline bool IsBootloaderRequested(void)
{
    return g_boot_req_flag == BOOTLOADER_MAGIC_FLAG;
}

For interoperability between the bootloader and your application (so that the app could invoke the bootloader), the flag location should be fixed at the start or end of RAM. .noinit is (almost) a standard section and it may contain many objects. To put the flag at the start of RAM, define yet another section only for the flag and put it before other RAM sections in the linker script.

It usually requires less characters to be typed (and no dependency on the linker script) to put the flag in backup registers, assuming these are not all used for other purposes. To make the main bootloader code portable between different series, I define three routines in the project-specific header (example for F4):

static inline void enable_bootflag_access(void)
{
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
	PWR->CR |= PWR_CR_DBP;
	RCC->BDCR |= RCC_BDCR_RTCEN;
}

static inline uint32_t get_boot_flag(void)
{
	return RTC->BKP0R;
}

static inline void set_boot_flag(uint32_t v)
{
	RTC->BKP0R = v;
}

 

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