cancel
Showing results for 
Search instead for 
Did you mean: 

How to jump to system bootloader from application code on STM32 microcontrollers

B.Montanari
ST Employee

Introduction

There are many possible ways to access the System Bootloader in STM32 devices and, in this tutorial, we will cover how to easily perform this jump directly from application code for all our families and product series, except for the STM32F0 and some STM32L0 that have an empty check mechanism in place.
To achieve this goal, we highly recommend following two main sources of documentation to ensure an easy and successful jump. Both the microcontroller’s Reference Manual and the Application Note AN2606 contain bootloader details, such as important addresses, supported peripherals and specific requirements to keep in mind when using the STM32 devices Custom or System Bootloader. In the app note it is possible to see the note explaining the effect of the empty check for the mentioned series:
496.png
This article will not cover the workaround of erasing the first page, but the code will have all STM32s addresses in it to facilitate the implementation, in case the reader wants to use it. We do suggest reading this article that explains how to execute code from SRAM.
Assuming you are using a series that allow you to make the jump from the application into system memory, we need to be aware of some important and necessary steps to be performed and some notes to keep in mind:
 

1. How to find System Memory / Bootloader Start Address value:

Refer to AN2606 “Configuration in System Memory Boot Mode” tables. Each device will have a specific System Memory/Bootloader address, and this value must be known to jump to bootloader correctly.
For example, for the STM32H723ZG microcontroller (a quick article showing the code for this series is available here), the Bootloader doesn’t start from the same address as the System Memory, but this is clearly defined in Table 111.
497.png

1.2. Disable, deinit and clear all peripherals your application has configured:

Including Clock Structure, Systick timer, ISR, Peripheral initializations and GPIO, every peripheral settings must be set to their default states to avoid interruptions when system is in boot mode. That’s why it is of the most importance to deinit all these functions and prevent an interruption to happen without a proper handler.

1.3. Re-enable Interrupts:

With the registers cleared, the interrupts can be re-enabled without compromising the application while in Bootloader.
 

1.4. Set the Bootloader Reset Handler address:

Bootloader Reset Handler address = Bootloader address + 4 bytes offset.

1.5. Set the Main Stack Pointer (MSP) to the values stored at the Bootloader stack.

1.6. Call a function pointing to the system bootloader to start execution.


Obs.: If your project uses watchdogs (IWDG and or WWDG), set the time base to the higher value possible to avoid a reset from it while in Boot Mode.

2. Development


Considering all the topics mentioned above, there is defined below a general code where you can simply set the MCU used in your application according to the list in the “enum” structure and the function is ready to be used in your project.
This code works by lighting one of the available User LEDs in the NUCLEO-H723ZG board, and then entering in system bootloader mode by calling the JumpToBootloader function.
Please be aware that all the different parts of code are specifically written between /*USER CODE BEGIN*/ regions so the code will not be erased when regenerating the project *.ioc file.
 

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* Set the enumeration of the STM32 Microcontrollers Series*/
enum{C0, F030x8, F030xC, F03xx, F05, F07, F09, F10xx, F105, F107, F10XL, F2, F3, F4, F7, G0, G4, H503, H563, H573, H7x, H7A, H7B, L0, L1, L4, L5, WBA, WBX, WL, U5};

#define MCU			H7x		//Define here the MCU being used
/* USER CODE END PD */
/* USER CODE BEGIN PFP */
void JumpToBootloader(void);
/* USER CODE END PFP */
/* Infinite loop */
	/* USER CODE BEGIN WHILE */
	while (1)
	{
		HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
		HAL_Delay(1000);

		JumpToBootloader();

		/* USER CODE END WHILE */
		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
/* USER CODE BEGIN 4 */

void JumpToBootloader (void)
{
uint32_t i=0;
void (*SysMemBootJump)(void);


/* Set a vector addressed with STM32 Microcontrollers names */
/* Each vector position contains an address to the boot loader entry point */

	volatile uint32_t BootAddr[33];

	BootAddr[C0]     = 0x1FFF0000;
	BootAddr[F030x8] = 0x1FFFEC00;
	BootAddr[F030xC] = 0x1FFFD800;
	BootAddr[F03xx]  = 0x1FFFEC00;
	BootAddr[F05]    = 0x1FFFEC00;
	BootAddr[F07]    = 0x1FFFC800;
	BootAddr[F09]    = 0x1FFFD800;
	BootAddr[F10xx]  = 0x1FFFF000;
	BootAddr[F105]   = 0x1FFFB000;
	BootAddr[F107]   = 0x1FFFB000;
	BootAddr[F10XL]  = 0x1FFFE000;
	BootAddr[F2]     = 0x1FFF0000;
	BootAddr[F3]     = 0x1FFFD800;
	BootAddr[F4]     = 0x1FFF0000;
	BootAddr[F7]     = 0x1FF00000;
	BootAddr[G0]     = 0x1FFF0000;
	BootAddr[G4]     = 0x1FFF0000;
	BootAddr[H503]   = 0x0BF87000;
	BootAddr[H563]   = 0x0BF97000;
	BootAddr[H573]   = 0x0BF97000;
	BootAddr[H7x]    = 0x1FF09800;
	BootAddr[H7A]    = 0x1FF0A800;
	BootAddr[H7B]    = 0x1FF0A000;
	BootAddr[L0]     = 0x1FF00000;
	BootAddr[L1]     = 0x1FF00000;
	BootAddr[L4]     = 0x1FFF0000;
	BootAddr[L5]     = 0x0BF90000;
	BootAddr[WBA]    = 0x0BF88000;
	BootAddr[WBX]    = 0x1FFF0000;
	BootAddr[WL]     = 0x1FFF0000;
	BootAddr[U5]     = 0x0BF90000;

	/* 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 */
	for (i=0;i<5;i++)
	{
		NVIC->ICER[i]=0xFFFFFFFF;
		NVIC->ICPR[i]=0xFFFFFFFF;
	}

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

	/* Set up the jump to boot loader address + 4 */
	SysMemBootJump = (void (*)(void)) (*((uint32_t *) ((BootAddr[MCU] + 4))));

	/* Set the main stack pointer to the boot loader stack */
	__set_MSP(*(uint32_t *)BootAddr[MCU]);

	/* Call the function to jump to boot loader location */
	SysMemBootJump();

	/* Jump is done successfully */
	while (1)
	{
		/* Code should never reach this loop */
	}
}

/* USER CODE END 4 */


There are different useful ways to test if the code worked successfully, two of them are explained below.
 

2.1. STM32CubeIDE debugging tab.

You can check whether the PC is set to the Flash or Boot address in the debug tab on the top left corner of the debug perspective. At first, the PC starts in Flash address: 0x08000614 (for this particular demo).
498.png
After entering the Bootloader, the PC is now set to an address inside the Boot region: 0x1FF127BC. You can see the tab below after hitting “pause” button.
499.png
 

2.2. STM32CubeProgrammer Tool


Once within the Bootloader region, we can use the STM32CubeProg to guarantee that the system is working in system boot mode.
For this tutorial, we will use the USB entry point to perform this, but other interfaces available for the given series could be used as well. Just keep in mind that the USB has higher priority than the others, so if the cable is connected it will be the one selected. The AN2606 covers all of this peripherals and needed settings for all series.
Obs.: Please pay attention to the pins specified in the AN2606, because even if the STM32CubeIDE allows the pin reallocation, to enter in bootloader the pins must be the same as in the Application Note.
To execute this over USB, connect the USB cable on the User USB connector. After that:


- In the right corner, select USB instead of ST-LINK
- Refresh the Port to recognize the USB
- Hit “Connect”


501.png
After connecting,  search for the Bootloader start address and you will see something similar to the following image.
505.png
You have now successfully jumped to bootloader from the application. Well done!
Hope this article was helpful.
 

Comments
Konami
Senior II

We have this working with a G0. What are the issues you're facing?

ATempleyPEL
Associate II

Hi @Konami ,

That's good to hear! We've implemented the code as set out by @gbm earlier in this post, and have also added the memory re-map, see below:

 

#define BOOTLOADER_ADDR 0x1FFF0000
struct boot_vectable {
	uint32_t Initial_SP;
	void (*Reset_Handler)(void);
};
#define BOOTVTAB	((struct boot_vectable*)BOOTLOADER_ADDR)

/**
 * @brief Trigger stm bootloader mode
 * 
 * @param BootloaderStatus 
 */
void BootloaderInit(bool BootloaderStatus) {
	if (BootloaderStatus == true) {
		__disable_irq();

		//osKernelSuspend();

		SysTick->CTRL = 0;

		// Reset the clock to its default settings
		HAL_RCC_DeInit();

		// clear the interrupt enable and pending registers
		NVIC->ICER[0] = 0xFFFFFFFF;
		NVIC->ICPR[0] = 0xFFFFFFFF;

		// Set the MSP
		__set_PSP(*(uint32_t*)BOOTLOADER_ADDR);
		__set_MSP(*(uint32_t*)BOOTLOADER_ADDR);

		// Re-enable all interrupts
		__enable_irq();

		SYSCFG->CFGR1 = 0x01;

		// Jump to app firmware
		BOOTVTAB->Reset_Handler();

		while (1) {
			// should never reach this point
		}
	}
}

 

We can see the PC is in the system memory area, however when we try to connect with STM32CubeProgrammer over UART it doesn't work. MSP/PSP don't seem to change at all.

We have used this method on a previous project with an STM32U535 with no issues.

We've also used the option bytes to set nBOOT0 = 0 and enter the bootloader that way with no issues

gbm
Lead III

Look at the code generated by the compiler - under CubeIDE you may see it in <projectname>.list file. Find the place with __set_MSP() and check if the instructions after MSP setting use sp register. If so, the jump will be incorrect. Current GCC emits the code using sp with optimization levels below -O2. Just set -O2 optimization and this particular problem disappears.

ATempleyPEL
Associate II

@gbm 

We're using Keil for this project - do you know how to check the instructions here? In any case, we've tried setting -O2 optimization and it has had no effect.

If it helps, this is the state of the registers after the jump:

ATempleyPEL_0-1729778944289.png

When we entered the bootloader via the option bytes, I noticed a couple of pins obviously change state. I do not see the same changes when we try and enter via our application, so I'm assuming we're not fully entering the bootloader for some reason?

Thanks in advance

gbm
Lead III

The safest way to start the application, custom bootloader or ST bootloader is to go through hardware reset. In all my bootloader-based projects, I use the backup RAM/register to store a value which is checked at the very start of main(), before configuring anything (also before clock configuration). This way the selected piece of firmware is started in the cleanest possible environment. After the check and before starting the selected firmware, the magic value is reset.

With custom bootloader/app combination this is done in the bootloader code. With app invoking ST bootloader (no custom bootloader), this is done in the app code.

Well, maybe I should publish the minimal version of this on github...

Konami
Senior II

+1 to @gbm 

 

In main

 

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{
    /* Check if bootloader entry was requested before reset */
    if (bootloader_is_pending())
    {
        enter_bootloader();
    }

 

 

Bootloader entry check. Note: put your bootloader blob in an __attribute__((section(".noinit")))

 

bool bootloader_is_pending(void)
{
    return (*(uint32_t*)bootloader_blob == BOOT_FROM_BOOTLOADER);
}

 

 

To get into bootloader

 

ATempleyPEL
Associate II

@gbm , @Konami 

Thanks very much for this - we can now enter the bootloader.

Last question, when programming with STM32CubeProgrammer, with the 'Run after programming' option set, would you expect the program to start automatically after completion?

JHöfl.1
Associate III

@ATempleyPEL 

How do you have the BOOT0 configured?

ATempleyPEL
Associate II

@JHöfl.1 

We have the option bytes set to prefer the nBOOT0 bit over the legacy BOOT0 pin. Our device shares the BOOT0 pin with the SWCLK pin, would you recommend a pull-down on this?

EThom.3
Associate III

Did anyone successfully jump to the bootloader in a G4? I have a stubborn G473 where I can't get the USB port to work in the bootloader, when jumping from the application. If I use the hardware BOOT pin it works fine.

With some help from this thread, I got it working in an L4 more than a year ago, and thought that I could use much the same code in the G4. Except that I would need to add a memory remap line. But the USB port fails to connect properly with the computer, as is evident in this message:

image.png

My code, heavily inspired by several people on this site:

  #define conBootloadAddress 0x1FFF0000

  void (*SysMemBootJump)(void);
  uint8_t i;

  // Disable interrupts
  __disable_irq();

  // Power down USB
  USB->CNTR |= 0x0002;

  //De-init all peripherals
  HAL_ADC_DeInit(&hadc1);
  HAL_ADC_DeInit(&hadc2);
  HAL_ADC_DeInit(&hadc3);
  HAL_ADC_DeInit(&hadc4);
  HAL_ADC_DeInit(&hadc5);
  HAL_SPI_DeInit(&hspi1);
  HAL_TIM_Base_DeInit(&htim2);
  HAL_TIM_Base_DeInit(&htim3);
  HAL_TIM_Base_DeInit(&htim15);
  HAL_DMA_DeInit(&hdma_usart2_tx);
  HAL_UART_DeInit(&huart2);

  // Reset clock to default
  HAL_RCC_DeInit();
  HAL_DeInit();

  // Disable Systick
  SysTick->CTRL = 0;
  SysTick->LOAD = 0;
  SysTick->VAL = 0;

  // Clear all interrupt bits
  for (i = 0; i < sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]); i++)
  {
    NVIC->ICER[i] = 0xFFFFFFFF;
    NVIC->ICPR[i] = 0xFFFFFFFF;
  }

  // Enable interrupts
  __enable_irq();

  // Set jump address
  SysMemBootJump = (void (*)(void)) (*((uint32_t *) (conBootloadAddress + 4)));
  // Set stack pointer to bootloader stack
  __set_MSP(*(uint32_t *)conBootloadAddress);

  // Remap memory
  SYSCFG->MEMRMP = 1;
  // Jump
  SysMemBootJump();

  while (1); // Just in case...

Does anyone have a good guess at why the USB port doesn't connect properly with the computer? Is there something I need to set up in the USB port before making the jump?

Any help will be greatly appreciated.

gbm
Lead III
EThom.3
Associate III

@gbm

Thanks. Optimization is set to -O2.

I also believe that the relevant part of the .list file looks fine:

  \details Assigns the given value to the Main Stack Pointer (MSP).
  \param [in]    topOfMainStack  Main Stack Pointer value to set
 */
__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack)
{
  __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : );
 80025f0:	e9d3 3200 	ldrd	r3, r2, [r3]
 80025f4:	f383 8808 	msr	MSP, r3
  SYSCFG->MEMRMP = 1;
 80025f8:	4b0f      	ldr	r3, [pc, #60]	; (8002638 <JumpToBootloader+0xd4>)
 80025fa:	2101      	movs	r1, #1
 80025fc:	6019      	str	r1, [r3, #0]
  SysMemBootJump();
 80025fe:	4790      	blx	r2
  while (1); // Just in case...
 8002600:	e7fe      	b.n	8002600 <JumpToBootloader+0x9c>
 8002602:	bf00      	nop

However, I am no expert at STM32 assembly language.

Will be shutting down in a moment. I am so tired that I can hardly see straight.

m4l490n
Associate III

I'm working on the STM32WB5MM module with FreeRTOS and none of these suggestions worked out for me.

I will leave here what worked for me in case someone else also ends up at the end of this thread with no success.

Since I had a million unknown issues trying to configure or reset everything that needed to be configured or reset in preparation for jumping from a FreeRTOS task to the bootloader, I just implemented a simpler mechanism to achieve this.

The justification for this approach is that immediately after the MCU boots, everything is reset to defaults so there is no better place to jump to the bootloader than right after main is called by the init code before doing anything else. To achieve this, you just have to do the following:

  • Define a section of RAM in the linker file that is not initialized in the startup code.
  • Create a variable in this section.
  • When you want to jump to the bootloader, just put a known value in the variable you created in the non-initializable section and immediately call NVIC_SystemReset().
  • Add code to check the contents of this non-initializable variable as the very first thing you do in main. If the value is the one you put there before rebooting, then call JumpToBootloader (or whatever your function is called).

The changes will then be as follows:

Add this to the _FLASH.ld file just after the .bss section:

 

  .uninit_data (NOLOAD) :
  {
    . = ALIGN(4);
    *(.uninit_data)
    . = ALIGN(4);
  } >RAM

 

Create a variable like this in the file where you handle the jump to bootloader:

 

uint32_t boot_reason __attribute__((section(".uninit_data")));

 

Define a macro with a known value. It can be whatever you want:

 

#define JUMP_TO_BOOTLOADER   0x01234567

 

Do this when it is time to jump to the bootloader:

 

if (jump_to_bootloader == true)
{
    boot_reason = JUMP_TO_BOOTLOADER;
    NVIC_SystemReset();
}

 

Add this to main:

 

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
  // Let this be the very first thing that happens in main
  if (uninit_boot == JUMP_TO_BOOTLOADER)
  {
    uninit_boot = 0;
    JumpToBootloader();
  }
  /* USER CODE END 1 */

  // rest of main ...
}

 

And finally, this is what JumptoBootloader() looks like:

 

void JumpToBootloader(void)
{
  #define conBootloadAddress 0x1FFF0000

  void (*SysMemBootJump)(void);

  SysMemBootJump = (void (*)(void)) (*((uint32_t *) (conBootloadAddress + 4)));
  __set_MSP(*(uint32_t *)conBootloadAddress);

  SYSCFG->MEMRMP = 0x01;

  SysMemBootJump();

  while (1); // Just in case...
}

 

And that's it!

As you can see, the contents of JumpToBootloader are extremely simple because there is nothing to do but just jump to the bootloader. This greatly simplifies the process and it will work for any processor regardless of how many FreeRTOS tasks are running, the number of peripherals used, etc.

I hope it helps.

AWack
Associate III

Hi @m4l490n 

your code works great! I´ve implemented it in a STM32G0B1CE with FreeRTOS running :)

I can flash via CANFD successfully!

The only problem is, that when I want to jump afterwards with the bootloader GO command to the application, the application doesn´t start :(

I send with CANFD 0x08000000 and ID 0x21. Any idea why this doesn´t work?

Best regards,

Achim

m1ku3q
Associate

@m4l490n This is easy to understand and works well on my G071 board with FreeRTOS project, thanks!

MM..1
Chief III

If your app dont use WWDG or IWDG then reset do with it and memory marker isnt required

you swap 

  if (uninit_boot == JUMP_TO_BOOTLOADER)
  {
    uninit_boot = 0;

with

if ... __HAL_RCC_GET_FLAG()

/** @brief  Check RCC flag is set or not.  
  * @PAram  __FLAG__ specifies the flag to check.  
  *         This parameter can be one of the following values:  
  *            @arg RCC_FLAG_IWDGRST: Independent Watchdog reset.  
  *            @arg RCC_FLAG_WWDGRST: Window Watchdog reset.  
  * @retval The new state of __FLAG__ (TRUE or FALSE).  
  */  
Version history
Last update:
‎2024-06-10 02:04 AM
Updated by:
Contributors