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
April 7, 2024

@B.Montanari 

in our new STM32G474 product we're planning of using a single Flash bank configuration. At the same time we'd like to maintain the possibility of flashing/erasing internal Flash pages runtime. Without the dual bank configuration, and considering we must carefully manage flash drivers execution timing, Option Bytes and interrupts, we initially considered patching the stm32g4xx_hal_flash library and moving the sensitive parts to ram (ramCode). With "sensitive" I obviously mean those moments flash writing or erasing processes become active, and fetching code from the same (single bank) memory is forbidden. A quite usual approach.

 

But recently I reminded that several years ago with a different MCU I asked the bootloader source code, and its memory map. That bootloader was stored in a separate (OTP) memory  mapped and visible in the linear memory map. Knowing the low-level entry points and signature of a few key Flash functions we managed to write and erase all flash pages from the application without any extra code.

 

So, and I get to the point, in your interesting Blog you consider running the entire bootloader, but in our case, if the bootloader structure allows it, we only need entry point and signature of a few functions (Write(), Erase()). Probably it's not even relevant if the "empty check" mechanism is in place, because I guess this will be only implemented once, at the moment the entire bootloader is started.

 

1. Do you think it may work? In case, since the AN2606 does not cover the topic, is ST available disclosing the  necessary internal bootloader details? Should I ask the support of our distributors?

2. Apart the ramCode option you also discussed in another interesting blog, do you see another solution?

3. Is ST considering of releasing soon a CUBE stm32_flash library also able to support single bank configurations?

 

 

Visitor II
May 16, 2024

--Removed--

Associate II
June 5, 2024

I need to make a bootloader on the stm32l072 micro and I would need it to work as follows, downloading a new firmware from the cloud and then making the jump, but it seems to me that my micro has a dual boot mechanism. Can I make the bootloader without needing to use a USB or interface? doing everything by code?

JHöfl.1
Associate III
June 17, 2024

I'm trying to get this to work on a G031. I can successfully jump into the bootloader, but I'm unable to communicate with the G0. There's no response from the device when using CubeProgrammer, and it also doesn't reply when I send commands manually via Python. I know the connection is working because I need to send a sequence via UART to initiate the jump.

I've read through all the comments, and some suggest that the BOOT0 pin needs to be pulled up. My G0 shares the BOOT0 pin with SWCLK, and when I use legacy mode in the option bits for BOOT0_SEL, my MCU gets bricked if I also reconfigure the BOOT0/SWCLK pin to OUTPUT_PP with a pull-up resistor.

Has anyone managed to get this working on a G0?

Visitor II
August 28, 2024

I solved my issue editing th following code (using STM32F439VGT6):

 

void JumpToBootloader (void)

{

uint8_t i=0;

 

/* Disable all interrupts */

__disable_irq();

// Reset USB

USBx_DEVICE->DCTL|= 0x02;

/* Disable Systick timer */

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 Interrupt Enable Register & Interrupt Pending Register */

for (i=0;i<8;i++) /* Depend on the micro used, for the STM32F439xx is 8 */

{

NVIC->ICER[i]=0xFFFFFFFF;

NVIC->ICPR[i]=0xFFFFFFFF;

}

/* Re-enable all interrupts */

__enable_irq();

/* Set up the jump to boot loader address + 4 */

uint32_t jump_address = *(__IO uint32_t *)(0x1FFF0000 + 4);

/* Set the main stack pointer to the boot loader stack */

__set_MSP(*(uint32_t *)0x1FFF0000);

/* Call the function to jump to boot loader location */

void (*boot_load)(void) = (void (*)(void))(jump_address);

//remap memory

SYSCFG->MEMRMP = 0x01;

__enable_irq();

// Now jump to the boot loader

boot_load();

/* Jump is done successfully */

while (1)

{

/* Code should never reach this loop */

}

}

hroze.1
Associate II
September 7, 2024

I applied the program on stm32F4 and it works great I get DFU without problem
But on STM32H7A3 it doesn't work, you used the H7A code, which means address 0x1FF0A800

USB in VPC mode and communicates with the computer properly
BOOT0 line with 510 ohm resistor to ground

What I was able to understand is that after jumping to the address of the BOOTLOADER, the software reaches HARD FAULT
I would be happy to help

thanks

 

haim 

Visitor II
September 9, 2024

Hi,

I'm trying this on STM32U5A5 based Nucleo board via USB DFU but cannot get the DFU recognized in STM32CubeProgrammer. Shows up as "Unknown USB Device" on device manager after I load this. Any help is appreciated. 

Thanks,

RC

Associate III
October 10, 2024

For stm32h562, i did it before, but in other project it can not jump, (HARD FALULT).

should i any configuration in MPU or somewhere else?

 

Associate II
October 23, 2024

@JHöfl.1 Did you manage to find a solution for doing this on a G0? We're having similar issues with an STM32G070. 

We have tried the PSP solution proposed by @dave4444 with no success.

It appears to enter the bootloader area from reading the SP, but it is not accessible with STM32CubeProgrammer or other tools.

Thanks!

JHöfl.1
Associate III
October 23, 2024

@ATempleyPEL 

Hi, unfortunately I didn’t manage to get it working either. I’m now proceeding with third-party bootloaders. Good luck with your project!