Skip to main content

STM32F0x0 custom bootloader

  • October 31, 2025
  • 3 replies
  • 24 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:

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

3 replies

  • Author
  • Community Manager
  • October 31, 2025

Hello

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!

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:

 

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


  • Author
  • Community Manager
  • October 31, 2025

Hello

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!

 

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:

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

 


Forum|alt.badge.img+2

Test reply for accepted solution