cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H745XI: Software Jump to System Bootloader Without BOOT0/Option Bytes – Has Anyone Succeeded?

Hello everyone,

MCU in use: STM32H745XI (dual-core, Bootloader v9.1, CubeIDE 1.16.1, HAL v1.11.3, CubeProgrammer v2.17.0)
Hardware status: BOOT0 HIGH works perfectly, I can access the system bootloader over USART1 (PA9/PA10), and program via UART. Hardware is validated and all connections are good. PB15/PB14 pins are floating, not connected anywhere.

My need:
I want to jump from user application (C code only, no BOOT0 pin) to the internal ROM system bootloader for firmware update purposes, so that I can upload new firmware over UART (using CubeProgrammer or similar), and then return to normal application on the next reset.


Here are the methods I have tried (none worked):

  • Set MSP to 0x1FF09800, jump to 0x1FF09804 (function pointer), disable all interrupts (NVIC->ICER), set PRIMASK/FAULTMASK, disable and invalidate caches (SCB), disable SysTick, deinit all peripherals (HAL_DeInit, RCC_DeInit, etc.).

  • Disable MPU/SAU regions (including secure/privileged regions).

  • Put all GPIOs (including used UART pins) in analog input (to avoid interference).

  • Set all memory barriers (DSB/ISB) and do multiple dummy memory accesses before jump.

  • Used both CM7 and CM4 cores for jump (with CM4 in low power/stop or held in reset).

  • Verified that all Option Bytes are in default state except BOOT_CM7_ADD1 = 0x1FF0 (0x1FF00000).

Result:
MCU freezes (hard fault or lockup) and system bootloader never responds on UART.
BUT, if I use BOOT0 HIGH and reset the MCU, bootloader always works (handshakes over UART as expected).


My questions:

  1. Has anyone successfully jumped to the system bootloader on STM32H745XI purely by C code (no BOOT0/OB change), and if so, how?

  2. Is there any undocumented, low-level trick (register hack, RAM/stack/clock/secure config, etc.) to force bootloader entry, or is it silicon-level blocked?

  3. Why exactly does the function pointer jump method (as used in STM32F4/F7/L4, and sometimes H747) fail on H745XI? Is there an internal hardware barrier at reset/boot?

  4. Are there any real-world solutions beyond changing Option Bytes (which I can do, but would like to avoid for safety reasons)?

Notes:

  • PB15/PB14 pins are floating and not connected, so no TX damage risk.

  • If I brick the MCU, I can recover via SWD/JTAG or CubeProgrammer, so I am open to any risky/experimental ideas.

  • Any direct practical experience from someone who actually achieved this on STM32H745XI (not just theory or F4/F7 tricks) would be extremely valuable.

References:

  • Reference Manual Table 123 and Table 124 (bootloader address 0x1FF00000, entry 0x1FF09800, V9.1 limitations).

  • Datasheet: "Jump issue" and "boot mode is only checked at reset."

Thank you in advance!
  

2.png

1.png

1 ACCEPTED SOLUTION

Accepted Solutions

SOLVED: STM32H745XI Jump to System ROM Bootloader from C Code (no BOOT0 pin or Option Byte change)

Hello again everyone,

I previously asked a detailed question about jumping from application code directly into the ROM system bootloader (at 0x1FF09800) on STM32H745XI without using BOOT0 or Option Bytes. After extensive debugging and experimentation, I finally solved the problem and want to share my verified solution clearly for others.

What was the real issue?

The critical problem turned out to be the PLL2 and PLL3 configurations set by the function PeriphCommonClock_Config().

When the application configures these PLLs and related peripheral clocks, the system bootloader's UART handshake (USART1) fails to initialize properly, causing it to become unresponsive.

How did I find the solution?

  1. Debug step-by-step:

    • I called my jump-to-bootloader function at various points in main(), from immediately after startup (no peripherals initialized), gradually adding peripheral initialization functions until I pinpointed exactly which configuration broke the bootloader jump.

  2. Identified problematic configuration:

    • The bootloader jump worked consistently until I executed PeriphCommonClock_Config(), which sets PLL2 and PLL3 for peripherals (USART, SPI, I2C, ETH, etc.). After this function call, the jump failed every time.

  3. Verified memory and registers during debugging:

    • After jumping, I paused execution and checked via debugger that the program counter (PC) was indeed at the ROM bootloader entry (0x1FF09800). Despite being at the correct entry point, UART handshakes never initiated.

 

 

void HandleBootloaderCommand(void)
{
 const uint32_t BootAddr = 0x1FF09800;
 DisableInterruptsAndClearPending();
 ResetEXTILines();
 DisableCachesAndMPU();
 DeInitPeripherals();
 DeInitGPIO();
 AdvancedRCCReset();
 ResetClocksToHSI();
 JumpToSystemBootloader(BootAddr);
while (1);
}

 

Why does this solution work?

The STM32H745XI ROM bootloader (v9.1) relies on PLL2 and PLL3 being completely disabled, caches and interrupts properly cleared, and RCC restored to default states before initialization. If these steps are performed out of order or skipped, the bootloader UART handshake (USART1) will fail to initiate, causing the bootloader to become unresponsive.

Adhering strictly to the above sequence guarantees that the bootloader consistently initiates and functions correctly every single time.

Conclusion & Recommendations

  • Always fully disable PLL2 and PLL3 explicitly before jumping to the ROM bootloader.

  • Ensure RCC and all peripherals (especially GPIO & UART) are fully reset to their default states.

  • This issue appears specific to STM32H7 dual-core series due to their complex clock tree.

My Test Environment (for reference):

  • MCU: STM32H745XI (dual-core)

  • Bootloader version: v9.1

  • IDE: STM32CubeIDE 1.16.1

  • HAL: STM32Cube HAL v1.11.3

  • Programmer: STM32CubeProgrammer v2.17.0

  • USART pins: USART1 (PA9/PA10)

This solution is thoroughly tested, verified, and repeatable. I hope this information saves others significant debugging time.

Thanks again for everyone’s input and help!

 

View solution in original post

10 REPLIES 10
TDK
Super User

Yes, people have done this.

Here is some code which does this:

How to jump to system bootloader from application ... - STMicroelectronics Community

If you feel a post has answered your question, please click "Accept as Solution".

I have tested the solutions suggested in the ST Community forum, including the recommended code, but on the STM32H745XI, the attempt to jump to the system bootloader did not work. The classic method of setting MSP and PC to 0x1FF09800 for a direct jump (like with F4/F7) did not work at all.

SOLVED: STM32H745XI Jump to System ROM Bootloader from C Code (no BOOT0 pin or Option Byte change)

Hello again everyone,

I previously asked a detailed question about jumping from application code directly into the ROM system bootloader (at 0x1FF09800) on STM32H745XI without using BOOT0 or Option Bytes. After extensive debugging and experimentation, I finally solved the problem and want to share my verified solution clearly for others.

What was the real issue?

The critical problem turned out to be the PLL2 and PLL3 configurations set by the function PeriphCommonClock_Config().

When the application configures these PLLs and related peripheral clocks, the system bootloader's UART handshake (USART1) fails to initialize properly, causing it to become unresponsive.

How did I find the solution?

  1. Debug step-by-step:

    • I called my jump-to-bootloader function at various points in main(), from immediately after startup (no peripherals initialized), gradually adding peripheral initialization functions until I pinpointed exactly which configuration broke the bootloader jump.

  2. Identified problematic configuration:

    • The bootloader jump worked consistently until I executed PeriphCommonClock_Config(), which sets PLL2 and PLL3 for peripherals (USART, SPI, I2C, ETH, etc.). After this function call, the jump failed every time.

  3. Verified memory and registers during debugging:

    • After jumping, I paused execution and checked via debugger that the program counter (PC) was indeed at the ROM bootloader entry (0x1FF09800). Despite being at the correct entry point, UART handshakes never initiated.

 

 

void HandleBootloaderCommand(void)
{
 const uint32_t BootAddr = 0x1FF09800;
 DisableInterruptsAndClearPending();
 ResetEXTILines();
 DisableCachesAndMPU();
 DeInitPeripherals();
 DeInitGPIO();
 AdvancedRCCReset();
 ResetClocksToHSI();
 JumpToSystemBootloader(BootAddr);
while (1);
}

 

Why does this solution work?

The STM32H745XI ROM bootloader (v9.1) relies on PLL2 and PLL3 being completely disabled, caches and interrupts properly cleared, and RCC restored to default states before initialization. If these steps are performed out of order or skipped, the bootloader UART handshake (USART1) will fail to initiate, causing the bootloader to become unresponsive.

Adhering strictly to the above sequence guarantees that the bootloader consistently initiates and functions correctly every single time.

Conclusion & Recommendations

  • Always fully disable PLL2 and PLL3 explicitly before jumping to the ROM bootloader.

  • Ensure RCC and all peripherals (especially GPIO & UART) are fully reset to their default states.

  • This issue appears specific to STM32H7 dual-core series due to their complex clock tree.

My Test Environment (for reference):

  • MCU: STM32H745XI (dual-core)

  • Bootloader version: v9.1

  • IDE: STM32CubeIDE 1.16.1

  • HAL: STM32Cube HAL v1.11.3

  • Programmer: STM32CubeProgrammer v2.17.0

  • USART pins: USART1 (PA9/PA10)

This solution is thoroughly tested, verified, and repeatable. I hope this information saves others significant debugging time.

Thanks again for everyone’s input and help!

 

The easy way to jump to the bootloader is to set a flag somewhere in RAM that survives a reset and check that first thing in your main(), before any initialization is done.  From there, you can jump to the bootloader without having to worry about de-initializing the peripherals. 

So, rather than figuring out how to get the peripherals configured just right for the bootloader before jumping, just let a system reset handle that and jump to the bootloader after a reset. 

This is a/o described here:

https://stm32world.com/wiki/STM32_Jump_to_System_Memory_Bootloader 

My implementation for the STM32F, inspired by this article:

#include "stm32f4xx_hal.h"
#include "stdbool.h"
#include "protocol_processor.h"


#define BOOTLOADER_ADDRESS 		0x1FFF0000  // STM32F40xxx. See https://bit.ly/AN2606
#define BOOTLOADER_FLAG_OFFSET 	100			// 4 * 100 = 400 bytes below top of stack
#define BOOTLOADER_FLAG_MAGIC	0xFEEFFEEF
#define RELAYMODE_FLAG_MAGIC	0xEFFEEFFE

typedef void 		(*pFunction)(void);

static pFunction 	JumpToBootLoader;
static uint32_t 	JumpAddress;

extern int 			_estack;
static uint32_t* 	bootloader_flag = (uint32_t*) (&_estack - BOOTLOADER_FLAG_OFFSET);


void resetToBootLoader()
{
	*bootloader_flag = BOOTLOADER_FLAG_MAGIC;

	HAL_NVIC_SystemReset();
}

void resetToRelayMode()
{
	*bootloader_flag = RELAYMODE_FLAG_MAGIC;

	HAL_NVIC_SystemReset();
}

void handleBootLoader()
{
	if( *bootloader_flag == BOOTLOADER_FLAG_MAGIC )
	{
		*bootloader_flag = 0; // so next boot won't be affected

		/* Jump to system memory bootloader */
		JumpAddress = *(__IO uint32_t*) (BOOTLOADER_ADDRESS + 4);

		JumpToBootLoader = (pFunction) JumpAddress;
		JumpToBootLoader();
	}
	else if( *bootloader_flag == RELAYMODE_FLAG_MAGIC )
	{
		*bootloader_flag = 0; // so next boot won't be affected
		daisyChainRelayMode = true;
	}
	//*bootloader_flag = 0;  -> don't mess if flag not set
}

 

And then call handleBootLoader first thing from main:

 

int main(void)
{

  /* USER CODE BEGIN 1 */

  handleBootLoader(); // MUST be first function to call from main()

 

I’ve actually seen this technique before and it’s a very helpful approach for many use cases. If I hadn’t been able to find a direct software solution for my STM32H7 system, I’d likely have resorted to this RAM flag + reset method as well. However, I find continuously writing and reading persistent data in RAM or flash risky — especially considering that flash/EEPROM has write/erase cycle limits (often 10k to 100k times). That’s why I didn’t choose this method, despite its simplicity.
Still, your post is super clear and helpful — thank you!

There's a big difference between RAM and ROM. RAM is continuously being read and written all the time, that's where all live data including your stack resides and AFAIK has no write/erase cycle limits, although it won't live forever either. 

So, writing a flag to RAM is no problem in that respect. 

You’re absolutely right — RAM is always being read and written to and doesn’t have a hardware cycle limit like flash/EEPROM. My main concern was that if you store the flag in flash or EEPROM (e.g., to survive power-cycles), then their 10k–100k cycle write/erase limit could become a problem in the long term.
Of course, if the flag is only stored in RAM (SRAM or DTCM-RAM), there’s no real write cycle worry. However, there’s another risk: if the RAM flag is accidentally corrupted (e.g., due to stack overflow, random memory corruption, or DMA overwrites), this could potentially cause the system to enter an infinite reset loop.
That’s why it’s usually safest to store the flag in RAM, check it immediately after reset, and then immediately clear it to avoid any unwanted bootloader loops. Just wanted to clarify this extra detail — thanks for raising it!

TDK
Super User

> The critical problem turned out to be the PLL2 and PLL3 configurations set by the function PeriphCommonClock_Config().

> When the application configures these PLLs and related peripheral clocks, the system bootloader's UART handshake (USART1) fails to initialize properly, causing it to become unresponsive.

HAL_RCC_DeInit resets the clocks to default and is in the example code. In particular, it turns off PLL2 and PLL3. Are you saying it doesn't work as expected?

/**
  * @brief  Resets the RCC clock configuration to the default reset state.
  * @note   The default reset state of the clock configuration is given below:
  *            - HSI ON and used as system clock source
  *            - HSE, PLL1, PLL2 and PLL3 OFF
  *            - AHB, APB Bus pre-scaler set to 1.
  *            - CSS, MCO1 and MCO2 OFF
  *            - All interrupts disabled
  * @note   This function doesn't modify the configuration of the
  *            - Peripheral clocks
  *            - LSI, LSE and RTC clocks
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_RCC_DeInit(void)

 

If you feel a post has answered your question, please click "Accept as Solution".

According to the docs, HAL_RCC_DeInit() should turn off HSE, PLL1, PLL2, and PLL3, and it usually does the job in most STM32 projects. But on the H7 series — especially after using PeriphCommonClock_Config() — I’ve noticed that some of the peripheral clock mux registers (like D1CCIPR, D2CCIP1R, D2CCIP2R) don’t actually get reset by HAL_RCC_DeInit(). So even after calling it, those mux settings can keep using PLL2/PLL3 as clock sources, and that seems to interfere with the bootloader’s UART handshake (which expects HSI).

In my case, I had to manually clear those mux registers (for example, by writing 0 to RCC->D1CCIPR) along with calling HAL_RCC_DeInit(). That finally sorted out the bootloader issue for me. So while HAL_RCC_DeInit() is great for a basic reset, on the H7’s more complicated clock architecture you usually need those extra manual tweaks to really bring it back to default.