Skip to main content
Graduate
May 6, 2025
Solved

STM32F0x0 custom bootloader

  • May 6, 2025
  • 1 reply
  • 534 views

This is not a question, but a solution to a problem that took me forever to solve. This may be evident to many, but it was not to me, soI hope this will save others (and my future self) a lot of time...

I was trying to implement a custom bootloader, where I could not even get the minimal implementation working: a slowly blinking LED when doing nothing and staying in the bootloader, or a quickly blinking LED when jumping to a different application.

The issue is that this is tricky for the STM32F030/070 on which I am developing, because it does not have a VTOR register and the few code examples I found did not work, or did jump, but interrupts stopped working.

The bootloader only needs to jump to the new reset vector:

#define APP_ADDRESS 0x08004000U

void JumpToApplication(void) {
	__disable_irq();
	if (*(uint32_t*)(APP_ADDRESS + 4) == 0xFFFFFFFF) {
		return; // invalid reset handler
	}
	uint32_t jump_address = *(volatile uint32_t*)(APP_ADDRESS + 4);
	void (*app_reset_handler)(void) = (void (*)(void))jump_address;
	__set_MSP(*(volatile uint32_t*)APP_ADDRESS);
	app_reset_handler();
}

int main(void)
{
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	
	if (!HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin)) { // press button to go to app
		JumpToApplication();
	}
	
	while(1) { // no jump took place: blink slowly
		HAL_GPIO_TogglePin(GPIOA, LD2_Pin);
		HAL_Delay(1000);
	}
}

with something like this section in *FLASH.ld:

/* Memories definition */
MEMORY
{
 RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K
 FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
 FIRMWARE (rx)	 : ORIGIN = 0x08004000, LENGTH = 128K - 16K
}

 The application now needs to copy the vector table:

#define VECTOR_TABLE_SIZE 48 // Covers 0xC0 bytes (16 + IRQs)
#define APP_VECTOR_TABLE ((uint32_t*)0x08004000)
#define RAM_VECTOR_TABLE ((uint32_t*)0x20000000)

void relocate_vector_table_to_ram(void)
{
	for (uint32_t i = 0; i < VECTOR_TABLE_SIZE; i++) {
		RAM_VECTOR_TABLE[i] = APP_VECTOR_TABLE[i];
	}
	__HAL_SYSCFG_REMAPMEMORY_SRAM();
}

int main(void){
	__enable_irq();
	relocate_vector_table_to_ram();
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	while(1){ // quickly blink in the app
		HAL_GPIO_TogglePin(GPIOA, LD2_Pin); // Example: onboard LED
		HAL_Delay(500);
	}
}

Where __HAL_SYSCFG_REMAPMEMORY_SRAM(); is critical, this macro sets MEM_MODE to 0b11 in SYSCFG->CFGR1

The app also needs its linker script edited:

MEMORY
{
 RAM (xrw) : ORIGIN = 0x200000C0, LENGTH = 16K - 0xC0
 FLASH (rx)	 : ORIGIN = 0x08004000, LENGTH = 128K - 16K
}

I hope this helps :)

    This topic has been closed for replies.
    Best answer by Saket_Om

    Hello @EvO 

    Thanks for sharing this case and the solution you found. 

    To make this post more visible and easy to find by others who may face the same issue, I mark my reply as the solution. 

    But yes, the solution is on your original post below!


    @EvO wrote:

    This is not a question, but a solution to a problem that took me forever to solve. This may be evident to many, but it was not to me, soI hope this will save others (and my future self) a lot of time...

    I was trying to implement a custom bootloader, where I could not even get the minimal implementation working: a slowly blinking LED when doing nothing and staying in the bootloader, or a quickly blinking LED when jumping to a different application.

    The issue is that this is tricky for the STM32F030/070 on which I am developing, because it does not have a VTOR register and the few code examples I found did not work, or did jump, but interrupts stopped working.

    The bootloader only needs to jump to the new reset vector:

    #define APP_ADDRESS 0x08004000U
    
    void JumpToApplication(void) {
    	__disable_irq();
    	if (*(uint32_t*)(APP_ADDRESS + 4) == 0xFFFFFFFF) {
    		return; // invalid reset handler
    	}
    	uint32_t jump_address = *(volatile uint32_t*)(APP_ADDRESS + 4);
    	void (*app_reset_handler)(void) = (void (*)(void))jump_address;
    	__set_MSP(*(volatile uint32_t*)APP_ADDRESS);
    	app_reset_handler();
    }
    
    int main(void)
    {
    	HAL_Init();
    	SystemClock_Config();
    	MX_GPIO_Init();
    	
    	if (!HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin)) { // press button to go to app
    		JumpToApplication();
    	}
    	
    	while(1) { // no jump took place: blink slowly
    		HAL_GPIO_TogglePin(GPIOA, LD2_Pin);
    		HAL_Delay(1000);
    	}
    }

    with something like this section in *FLASH.ld:

    /* Memories definition */
    MEMORY
    {
     RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K
     FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
     FIRMWARE (rx)	 : ORIGIN = 0x08004000, LENGTH = 128K - 16K
    }

     The application now needs to copy the vector table:

    #define VECTOR_TABLE_SIZE 48 // Covers 0xC0 bytes (16 + IRQs)
    #define APP_VECTOR_TABLE ((uint32_t*)0x08004000)
    #define RAM_VECTOR_TABLE ((uint32_t*)0x20000000)
    
    void relocate_vector_table_to_ram(void)
    {
    	for (uint32_t i = 0; i < VECTOR_TABLE_SIZE; i++) {
    		RAM_VECTOR_TABLE[i] = APP_VECTOR_TABLE[i];
    	}
    	__HAL_SYSCFG_REMAPMEMORY_SRAM();
    }
    
    int main(void){
    	__enable_irq();
    	relocate_vector_table_to_ram();
    	HAL_Init();
    	SystemClock_Config();
    	MX_GPIO_Init();
    	while(1){ // quickly blink in the app
    		HAL_GPIO_TogglePin(GPIOA, LD2_Pin); // Example: onboard LED
    		HAL_Delay(500);
    	}
    }

    Where __HAL_SYSCFG_REMAPMEMORY_SRAM(); is critical, this macro sets MEM_MODE to 0b11 in SYSCFG->CFGR1

    The app also needs its linker script edited:

    MEMORY
    {
     RAM (xrw) : ORIGIN = 0x200000C0, LENGTH = 16K - 0xC0
     FLASH (rx)	 : ORIGIN = 0x08004000, LENGTH = 128K - 16K
    }

    I hope this helps :)


     

    1 reply

    Saket_OmAnswer
    Technical Moderator
    May 7, 2025

    Hello @EvO 

    Thanks for sharing this case and the solution you found. 

    To make this post more visible and easy to find by others who may face the same issue, I mark my reply as the solution. 

    But yes, the solution is on your original post below!


    @EvO wrote:

    This is not a question, but a solution to a problem that took me forever to solve. This may be evident to many, but it was not to me, soI hope this will save others (and my future self) a lot of time...

    I was trying to implement a custom bootloader, where I could not even get the minimal implementation working: a slowly blinking LED when doing nothing and staying in the bootloader, or a quickly blinking LED when jumping to a different application.

    The issue is that this is tricky for the STM32F030/070 on which I am developing, because it does not have a VTOR register and the few code examples I found did not work, or did jump, but interrupts stopped working.

    The bootloader only needs to jump to the new reset vector:

    #define APP_ADDRESS 0x08004000U
    
    void JumpToApplication(void) {
    	__disable_irq();
    	if (*(uint32_t*)(APP_ADDRESS + 4) == 0xFFFFFFFF) {
    		return; // invalid reset handler
    	}
    	uint32_t jump_address = *(volatile uint32_t*)(APP_ADDRESS + 4);
    	void (*app_reset_handler)(void) = (void (*)(void))jump_address;
    	__set_MSP(*(volatile uint32_t*)APP_ADDRESS);
    	app_reset_handler();
    }
    
    int main(void)
    {
    	HAL_Init();
    	SystemClock_Config();
    	MX_GPIO_Init();
    	
    	if (!HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin)) { // press button to go to app
    		JumpToApplication();
    	}
    	
    	while(1) { // no jump took place: blink slowly
    		HAL_GPIO_TogglePin(GPIOA, LD2_Pin);
    		HAL_Delay(1000);
    	}
    }

    with something like this section in *FLASH.ld:

    /* Memories definition */
    MEMORY
    {
     RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K
     FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
     FIRMWARE (rx)	 : ORIGIN = 0x08004000, LENGTH = 128K - 16K
    }

     The application now needs to copy the vector table:

    #define VECTOR_TABLE_SIZE 48 // Covers 0xC0 bytes (16 + IRQs)
    #define APP_VECTOR_TABLE ((uint32_t*)0x08004000)
    #define RAM_VECTOR_TABLE ((uint32_t*)0x20000000)
    
    void relocate_vector_table_to_ram(void)
    {
    	for (uint32_t i = 0; i < VECTOR_TABLE_SIZE; i++) {
    		RAM_VECTOR_TABLE[i] = APP_VECTOR_TABLE[i];
    	}
    	__HAL_SYSCFG_REMAPMEMORY_SRAM();
    }
    
    int main(void){
    	__enable_irq();
    	relocate_vector_table_to_ram();
    	HAL_Init();
    	SystemClock_Config();
    	MX_GPIO_Init();
    	while(1){ // quickly blink in the app
    		HAL_GPIO_TogglePin(GPIOA, LD2_Pin); // Example: onboard LED
    		HAL_Delay(500);
    	}
    }

    Where __HAL_SYSCFG_REMAPMEMORY_SRAM(); is critical, this macro sets MEM_MODE to 0b11 in SYSCFG->CFGR1

    The app also needs its linker script edited:

    MEMORY
    {
     RAM (xrw) : ORIGIN = 0x200000C0, LENGTH = 16K - 0xC0
     FLASH (rx)	 : ORIGIN = 0x08004000, LENGTH = 128K - 16K
    }

    I hope this helps :)