Skip to main content
B.Montanari
ST Employee
May 22, 2023

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

  • May 22, 2023
  • 84 replies
  • 132226 views

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.

I'd also like to appreciate the fantastic contribution from @Ahmet Yasin CİVAN in the post STM32H745XI: Software Jump to System Bootloader Without BOOT0/Option Bytes – Has Anyone Succeeded? , as this address a particular problem related to the STM32H745 series. 
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.
 

84 replies

Associate
November 22, 2023

@APrim.2 

Refer RM-0390 section 8.2.1. Using SYSCFG_MEMRMP register you can boot from desired memory location. 

Screenshot_20231122-075300.png

EThom.3
Senior II
November 22, 2023

@Lavanya1 

Yeah, that's exactly what @APrim.2 is trying to do.

 // REMAP
 SYSCFG->MEMRMP = 0x01;
 
// Re-enable all interrupts
__enable_irq();
 
// Set the MSP
__set_MSP(BOOTVTAB->Initial_SP);
 
// Jump to app firmware
BOOTVTAB->Reset_Handler();
}

 Could it be a problem with the order of events? The manual is more than a little vague on this.

gbm
Principal
December 25, 2023

For the compiler, there is only one way to interpret this statement. What's your doubt?

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
Associate II
January 1, 2024

Was trying this on a G0 MCU and ran into problems.  Tracked it down to my calling of JumpToBootloader() from within a FreeRTOS task context.  This has the result of going into the bootloader while SP is using PSP not MSP and the bootloader ends up corrupting the stack in it's initialization.

If you do not exit task mode so MSP is used you must also set PSP prior to jumping to the bootloader.

Adding a __set_PSP(); along with the __set_MSP(); will allow you to call this from any context.

Without setting PSP, the bootloader will corrupt it's stack and cause an exception resulting in the appearance of just executing your normal flashed code.

TDJ
Senior III
January 2, 2024

@B.Montanari I need to build a bootloader which would allow end user to update STM32U5A9 firmware from pendrive, preferably using encrypted and signed SFI package conveniently created with CubeProgrammer.
How do I go about it? Where to start? Are there any examples available?
Is there any open source software for decoding/decrypting SFI packages available? Probably I can figure it out and write from scratch based on AN4992 and AN5054, but maybe some solution is already available.
I would appreciate any hints/pointers. After all, this task seems to be common.

Reading file from pendrive is out of scope. This part I know how to handle.

Update: This question has been answered here.

Visitor II
January 30, 2024

Hi @B.Montanari 

This is a great thread for a use case that I think is needed for many many deployment scenarios.

Is the current state of the code example available somewhere on the ST site ?

It would be great if this feature is an option to enable/configure in STM32CubeIDE or any other ST IDE.

/Per

 

 

 

Visitor II
March 6, 2024

Posting in case others have run into this. Also, code provided by ST above seems incorrect based on findings below. @B.Montanari would be good to have clarification here.

We just ran into an issue where some of our devices (~10%) were locking up before jumping to the application. This was root caused to the location of disabling systick. If disabling systick was moved AFTER HAL_RCC_DeInit(), the problem devices no longer locked up and were able to jump to the main application.

Inside of HAL_RCC_DeInit(), there are multiple locations where a while loop is protected by Systick timeout. If Systick is disabled before this function, this obviously has no effect.

The source code provided by ST above disables Systick before calling HAL_RCC_DeInit, which seems incorrect based on our findings.

 

Original:

 // Continue from JumpToBootloader() 

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

 // Reset clock to default
 HAL_RCC_DeInit();

 // 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_irq();

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

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

 

Updated:

 // Continue from JumpToBootloader() 

 // Reset clock to default
 HAL_RCC_DeInit();

 // IMPORTANT! Disable Systick AFTER HAL_RCC_Deinit
 // Some devices were locking up inside of HAL_RCC_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_irq();

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

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

 

HAL_RCC_DeInit while loop line 15

HAL_StatusTypeDef HAL_RCC_DeInit(void)
{
 uint32_t tickstart;

 /* Get Start Tick*/
 tickstart = HAL_GetTick();

 /* MSI PLL OFF */
 LL_RCC_MSI_DisablePLLMode();

 /* Set MSION bit */
 LL_RCC_MSI_Enable();

 /* Wait till MSI is ready */
 while (LL_RCC_MSI_IsReady() == 0U)
 {
 // Systick timeout detect timeout
 if ((HAL_GetTick() - tickstart) > MSI_TIMEOUT_VALUE)
 {
 return HAL_TIMEOUT;
 }
 }

 

 

Associate
April 6, 2024

Hello.

I have an issue to access STM32G4 (G431, G474) boot-loader from my app.

The simplest program exhibit this issue is

 

 

 

Vectors: /* to be compiled at 0x0800 0000 */
.word (0x20000000 + 0x1000) /* 0x0000 top stack @ RAM_Base + 0x1000 */
Thumb_Vector Hard_Reset_Handler /* 0x0004 reset vector */

Hard_Reset_Handler:
/* part 1 : to count number of reset */
ldr r4,=0x20008000 /* a RAM address not acceded by bootloader */
ldr r0,[r4]
adds r0,#1
str r0,[r4]

/* part 2 : to jump to bootloader */
ldr r4,=0x1fff0000 /* from AN2606 doc, checked: this address contains SP and BOOT */ 
ldr r1,[r4,#0]
mov r13,r1 /* init sp */
ldr r1,[r4,#4]
mov pc,r1

 

When I run this simple test, RAM location 0x20008000 is well incremented at each reset (value checked with debugger) but the boot-loader is never called and reset occurs permanently ... maybe 1000 times per seconds.
I don't reset anything in RCC etc. because nothing is initiated ;)

An explanation ?

 

Uwe Bonnes
Chief
April 6, 2024

Francois, I miss SYSCFG->CFGR1 = 1! And is  "mov r13,r1" good for setting MSP? What about using MSR, reg, MSP?

Associate
April 6, 2024

Thanks for your answer, Uwe.

I think you mean SYSCFG->MEMRMP = 1 (because it is no usable bit 0 in CFGR1).

I see bit 0 of MEMRMP is set with BOOT0 pin (and BOOT1 option bit) at reset.

after adding the following code

 ldr r4,=0x40010000 /* SYSCFG */
 mov r1,#1
 str r1,[r4,#0] /* SYSCFG_MEMRMP */

All is working like a charm :)

Thanks a lot for the solution.