Skip to main content
Explorer
June 2, 2025
Solved

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

  • June 2, 2025
  • 5 replies
  • 2472 views

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

    This topic has been closed for replies.
    Best answer by Ahmet Yasin CİVAN

    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!

     

    5 replies

    Super User
    June 2, 2025

    Yes, people have done this.

    Here is some code which does this:

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

    Explorer
    June 2, 2025

    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.

    Ahmet Yasin CİVANAuthorAnswer
    Explorer
    June 3, 2025

    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!

     

    Graduate
    June 3, 2025

    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()

     

    Explorer
    June 3, 2025

    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!

    Super User
    June 3, 2025

    > 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)

     

    Explorer
    June 3, 2025

    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.

    Technical Moderator
    June 8, 2025

    Dear @Ahmet Yasin CİVAN ,

    Thank you very much for the detailed post and also valuable contribution to our STCommunity on how to jump in safe mode to system memory.

    Thank you again

    STOne-32

    Explorer
    June 10, 2025

    Thank you so much for the kind words! I'm glad I could share some insights and help out in the STCommunity. If there’s ever any other tricky STM32H7 issue you’d like to chat about or if you’d like more details on my approach, just let me know — always happy to discuss!

    Visitor II
    September 11, 2025

    This thread has been great to follow. Has anyone been able to use these methods on the STM32C011 MCUs to connect via UART? I've attempted to implement the software-only jump to the bootloader and it locks-up in a similar manor. I've pulled in the relevant suggestions from the thread but the issue persists. Looking at the PC in the debugger, it jumps to the bootloader address but the chip doesn't respond to the UART connection from STM32CubeProgramer. The error being:

    Error: Activating device: KO. Please, verify the boot mode configuration and check the serial port configuration. Reset your device then try again... 

    This is the simplified version of the code which, I believe, pulls in all the suggested fixes:

    volatile uint32_t BootAddr_STM32C0 = 0x1FFF0000;
    
    _Pragma("GCC push_options") \
    _Pragma("GCC optimize (\"O0\")")
    void JumpToBootloader(void)
    {
    	uint32_t i=0;
    	void (*SysMemBootJump)(void);
    
    	/* Disable all interrupts */
    	__disable_irq();
    
    	//De-init all peripherals
    	HAL_DMA_DeInit(&hdma_tim3_ch4);
    	HAL_DMA_DeInit(&hdma_tim17_ch1);
    	HAL_TIM_PWM_DeInit(&htim3);
    	HAL_TIM_Base_DeInit(&htim3);
    	HAL_TIM_PWM_DeInit(&htim14);
    	HAL_TIM_Base_DeInit(&htim14);
    	HAL_TIM_PWM_DeInit(&htim17);
    	HAL_TIM_Base_DeInit(&htim17);
    	HAL_UART_DeInit(&huart1);
    	HAL_UART_DeInit(&huart2);
    
    	/* Set the clock to the default state */
    	HAL_RCC_DeInit();
    
    	/* Disable Systick timer */
    	SysTick->CTRL = 0;
    	SysTick->LOAD = 0;
    	SysTick->VAL = 0;
    
    	/* Clear Interrupt Enable Register & Interrupt Pending Register */
    	for (i=0;i<(sizeof(NVIC->ICER)/sizeof(NVIC->ICER[0]));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_STM32C0 + 4))));
    
    	/* Set the main stack pointer to the boot loader stack */
    	__set_MSP(*(uint32_t *)BootAddr_STM32C0);
    
    	// Set the Memory Mode
    	SYSCFG->CFGR1 = 0x01;
    
    	/* Call the function to jump to boot loader location */
    	SysMemBootJump();
    
    	/* Jump is done successfully */
    	/* Code should never reach this loop */
    	while (1){}
    }
    _Pragma("GCC pop_options")

    The only lead I'm still checking into is the possibility of some USART interrupts needing to be reenabled after clearing all the ICER and ICPR registers. 

    If anyone has been able to get this working on the STM32CO11, I'd love to hear about it. 
    Thanks in advance. 
    .

     

    Graduate II
    September 12, 2025

    Does it work with the BOOT0 pin method?

    How about very early in Reset_Handler code in startup.s? This might be the simplest as the MCU is close to reset conditions without all your peripherals, clocks and interrupts.

    Check the SYSCFG clock is enabled, and that you're actually mapping the ROM at zero. Inspect the vector table and confirm the entry point