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

gbm
Principal
June 19, 2023

The code shown above is full of errors and unnecessary actions.
- There is no need to dynamically create on the stack a variable vector of addresses while only one address, constant and known a priori, is used.
This causes unnecessary usage of Flash for code and RAM for stack data, which could easily lead to stack overflow.
- There is no reason to declare this unnecessary vector as volatile.
- No. of ICER registers differs between various MCUs and should not be hardcoded to 5.
- Copying of constant addresses to ad-hoc variables is unnecessary.
- The notation used for MSP and PC values stored in system ROM is very cryptic.

How about getting rid of most of the code above and replacing the last piece with the following, much shorter and easier to read:

 

 

/* USER CODE BEGIN 4 */
#define BOOT_ADDR	0x1FFFF000	// my MCU boot code base address
#define	MCU_IRQS	70u	// no. of NVIC IRQ inputs

struct boot_vectable_ {
 uint32_t Initial_SP;
 void (*Reset_Handler)(void);
};

#define BOOTVTAB	((struct boot_vectable_ *)BOOT_ADDR)

void JumpToBootloader(void)
{
	/* 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 (uint8_t i = 0; i < (MCU_IRQS + 31u) / 32; i++)
	{
		NVIC->ICER[i]=0xFFFFFFFF;
		NVIC->ICPR[i]=0xFFFFFFFF;
	}

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

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

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

/* USER CODE END 4 */

 

 

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

Hi @B.Montanari @gbm ,
Can we flash hex or bin file in hyperterminal like teraterm, without STMCube Programmer,
I was able to flash using STMcube programmer, by making BOOT pin high with inbuilt bootloader Via UART.
Same i tried with teraterm, Its not flashinh.



Thanks 

Tej

B.Montanari
ST Employee
August 22, 2023

Hi @gbm ! 

Thanks for your comment and code suggestion, I do agree with you and I'll make the proper adjustments. Also, for some series there is an additional step that is currently missing, for example, for the STM32F4 series we need to remap by hand before making the jump, so SYSCFG->MEMRMP = 0x01; should be added, the same is true for G0, SYSCFG->CFGR1 = 0x01;

Thanks again! I'll make the updates shortly

B.Montanari
RhSilicon
Lead
August 25, 2023

Hi, a while ago, I couldn't get the application to work using DFU via USB HOST. When USB Host was added to the application, it didn't work. I'll try again following these tips. Thanks.

Reference: STM32 USB training - 11.3 USB MSC DFU host labs

Stanford
Associate II
August 27, 2023

@gbm - I agree w/you.  Thanks for the response to this.  Another way of writing the interrupt clearing loop:

 

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

 

EThom.3
Senior II
September 28, 2023

I tried the code in this thread, but couldn't quite make it work on the STM32L4 controller that I'm using.

On my board, I use the USB port as a primary communication port. Using the instructions in the original post, and some modifications in the thread, I did get the microcontroller to enter the bootloader, but the STM32CubeProgrammer software couldn't find a USB DFU port.

I had a hunch that it had something to do with the USB port setup. All the other peripherals were disabled by HAL_xxx_DeInit() functions, but the USB port doesn't have HAL functions. Unable to find a USB DeInit() equivalent, I settled for this:

 

 // Reset USB
 USB->CNTR = 0x0003;

 

It made a huge difference.

Edit: Typo

SBaro.11
Associate III
September 29, 2023

Hi @B.Montanari ,

Thnaks for the post it's very usefull. I have a question, I'm working with Seed Lora E5 module (STM32WLE5JC) and I want to develop and use my owm firmware, but the problem is Seed doesn't expose BOOT0 pin so adfter firmware erase Ican only program my board with ST-LINK.

My question is do you think I can use your code to jump to bootloader ? Beceause I'd like to update my firmware from UART with UART bootloader for example.

The dev board is here https://wiki.seeedstudio.com/LoRa_E5_Dev_Board/ 

usefull explication coming from Seed :

"The "PB13/SPI_SCK/BOOT" pin on the Wio-E5 module is just a normal GPIO, not the "BOOT0" pin of the MCU. This "PB13/SPI_SCK/BOOT" pin is used in the bootloader of the Factory AT firmware, to decide to jump to APP or stay in bootloader(for DFU). The real "BOOT0" pin doesn't pinout to the module, so users need to be careful when developing the low-power applications."

 

Thanks for help

EThom.3
Senior II
September 29, 2023

@SBaro.11 

I would certainly think so. Your microcontroller is listed in Application Note 2606, so you should be able to upload through USART1 (pins PA9 and 10) or USART2 (pins PA2 and 3).

SBaro.11
Associate III
September 29, 2023

@EThom.3thanks for your answer. So for you if I've not access to BOOT0 pin it's not a problem for jump to bootloader from software ?

Because in AN2606 for my microcontroller, it's say The STM32WLE5xx/55xx bootloader is activated by applying Pattern 13, here is the pattern 13

SBaro11_0-1695989934243.png

On this pattern we can see BOOT0...

EThom.3
Senior II
September 29, 2023

@SBaro.11 

Yes, the main point of jumping to the bootloader from from software is to avoid the need to use BOOT0. I have both options in my latest board. For my customer, it is much more practical to activate the bootloader from software.

The microcontrollers I use are activated by other patterns in that table. But that doesn't mean that it's the only way to activate the bootloader.

In my version of AN2606 (rev. 56), your microcontroller is listed in chapter 72. The bootloader code starts from address 0x1FFF0000. So I guess that you can use the code example in this post the same way as I did. You just don't need to disable the USB port, as I needed to do.