Disclaimer: This guide was tested on an STM32G070CB, but with minor adjustments, it should work for most STM32 models.
To jump to the STM32 Bootloader from your running application, simply call the following function:
#define BOOTLOADER_ADDR 0x1FFF0000 // Bootloader start address (refer to AN2606). STM32 family-dependent.
struct bootloader_vectable__t {
uint32_t stack_pointer;
void (*reset_handler)(void);
};
#define BOOTLOADER_VECTOR_TABLE ((struct bootloader_vectable__t *)BOOTLOADER_ADDR)
void JumpToBootloader(void) {
// Deinit HAL and Clocks
HAL_DeInit();
HAL_RCC_DeInit();
// Disable all interrupts
__disable_irq();
// Disable Systick
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// Disable interrupts and clear pending ones
for (size_t i = 0; i < sizeof(NVIC->ICER)/sizeof(NVIC->ICER[0]); i++) {
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
// Re-enable interrupts
__enable_irq();
// Map Bootloader (system flash) memory to 0x00000000. This is STM32 family dependant.
__HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH();
// Set embedded bootloader vector table base offset
WRITE_REG(SCB->VTOR, SCB_VTOR_TBLOFF_Msk & 0x00000000);
// Switch to Main Stack Pointer (in case it was using the Process Stack Pointer)
__set_CONTROL(0);
// Instruction synchronization barrier
__ISB();
// Set Main Stack Pointer to the Bootloader defined value.
__set_MSP(BOOTLOADER_VECTOR_TABLE->stack_pointer);
__DSB(); // Data synchronization barrier
__ISB(); // Instruction synchronization barrier
// Jump to Bootloader Reset Handler
BOOTLOADER_VECTOR_TABLE->reset_handler();
// The next instructions will not be reached
while (1){}
}After executing this function, the Bootloader will be active and ready to receive your commands. For details on how to send commands to the Bootloader, please refer to the application notes listed in the references.
During my tests, when I attempted to return to the application by sending the GO COMMAND to the address of my application's vector table (0x08000000), the microcontroller would hang. It seemed as though the bootloader was passing control to the application in an unstable state, causing the application to freeze. The only solution was to perform a power cycle, which allowed the application to boot normally. However, for the project I was developing, this wasn’t an option. I needed a mechanism to return to the application without powering down the device.
Then I had a simple thought: if returning to the application required forcing a reboot of the microcontroller, why not make the bootloader jump to a function that triggers a reboot?
And that's exactly what I did, and what I’m presenting in this guide. We will create a new 'fake' vector table, where its 'reset handler' simply triggers a reboot of the microcontroller.
First, create a file named secondary_vtable.c with the following contents and add it to your project. Alternatively, you can simply paste this code wherever it fits within your project.
#include "stm32g0xx.h" // STM32 family-dependent.
void Force_Reset_Handler(void) {
while(1) {
NVIC_SystemReset(); // Force a reboot
}
}
extern uint32_t _estack;
void* __attribute__ ((section(".force_reset_vtable")))
force_reset_vtable[] = {
[0] = (void*)&_estack,
[1] = Force_Reset_Handler
};This is the new 'fake' vector table that the Bootloader will jump to. Please note that the extern variable _estack should already be defined in the Linker Script, as it represents the address of the stack pointer in the original vector table.
Next, we need to place this new vector table at a fixed address in Flash. To do this, we will need to modify the Linker Script. In this guide, we will place the new vector table at 0x08000200.
Edit your Linker Script and add this after the isr_vector (the original vector table). If your original vector table is larger than 0x200 bytes (512 bytes), you should place the new vector table at a later address, such as 0x08000300 or 0x08000400. The exact address will depend on the STM32 family.
.secondary_isr_vector 0x08000200 (READONLY):
{
. = ALIGN(4);
KEEP(*(.force_reset_vtable)) /* Startup code */
. = ALIGN(4);
} >FLASHThat's it! You're ready to go. And by 'go', I mean send the Bootloader GO COMMAND to the address of your new vector table (0x08000200 in this example). This will cause the bootloader to set the Main Stack Pointer to the value of _estack and then jump to Force_Reset_Handler(), which will trigger a reboot of the microcontroller, ultimately allowing the application to start.
If you found this guide useful and would like to show your appreciation, please consider making a donation. Your support helps me continue to create and share helpful resources.
References:
- AN2606: STM32 microcontroller system memory boot mode
- AN3154: CAN protocol used in the STM32 bootloader
- AN3155: USART protocol used in the STM32 bootloader
- AN3156: USB DFU protocol used in the STM32 bootloader
- AN4221: I2C protocol used in the STM32 bootloader
- AN4286: SPI protocol used in the STM32 bootloader
- AN5405: FDCAN protocol used in the STM32 bootloader
- AN5927: I3C protocol used in the STM32 bootloader

@gonzabrusco : Thanks a lot for the reply. I would like to ask you a short second question which is fundamental for my understanding. So the goal is to switch from user application to internal STM32 Bootloader. I am using an STM32G0B1CBT6 with a single memory page.
The thing I did not find an answer yet ist very simple: Is it possible (I suppose yes) to trigger this action of "updating firmware through ROM Bootloader by before jumping there from user application" really possible without a boot button oe other HW interaction by the user?
Case 1: The Device is empty (fresh, unprogrammed device) -> By default ROM Bootloader is started and USB DFU programming works fine
Case 2: There is already the User Application running and I would like to reprogram the user app by again using USB DFU
- The code you were so nice to provide can manage the jumping to ROM bootloader from user app
- What I am missing is - how does that work together with dfu-util?
For Case 2: I have included ST`s HAL Lib of USB DFU and the Device (besides running the user code now ALSO shows up as USB DFU on the host side). So dfu-util can access it. But what I am missing is the following: when I start programming with dfu-util, the tool immediately wants to "program" the device, but before that, I have to trigger the jump to ROM Bootloader. So I miss the portion between the first taslking from dfu-util to the DFU device and the actual program command from dfu-util. I have set breakpoints to see where I can add user code as soon as dfu-util is statrting to taslk with my DFU Device Code but I think If i there add your jumping code. dfu-util in the meantime already wants to start programming or with other words: I would need a dfu-util command that just triggers the jump on the device and THEN starts the programming. I have not seen such a "wait" command that could facilitate the necessary "2-step" procedure (let the device jump first and then do the programming). I experiemnted with the E command line optin of dfu-util, but I think that is something else.
Would you be so kind to shed some light because I am not sure what specifically I am missing and how others do what I try to accomplish =)
Thanks!