2025-01-13 02:16 AM
Hi everyone,
I’m working on a custom bootloader for an STM32H750 and need some help. My goal is to be able to update the unit from USB and for doing so I use an external eeprom to store the MCU FW and load it into RAM at startup. The main reason to use the RAM to execute the code is that the h750 flash is 128KB and it can only be erased by 128KB sector.
I can succesfully write the code into the eeprom and load into ram at startup (address 0x20000000). However, when I try to jump to it, the debugger shows a 0x2xxxxxxx address but it get stuck.
For the application code, I took the original flash based code, modified the scatter file for the linker as follows:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x20000000 0x00020000 { ; load region size_region
ER_IROM1 0x20000000 0x00020000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM2 0x24020000 0x00080000 { ; RW data
.ANY (+RW +ZI)
}
}
I also tried setting the VTOR to 0x20000000 in the application, but it didn’t help. Looking at the .bin file, the stack pointer (SP) and program counter (PC) appear to point to valid addresses within the 0x20000000 range.
Here is the part of the code which is supposed to jump to the app.
#define APP_BASE_RAM (0x20000000)
uint32_t *p;
void (*jmp)();
----------
.....
__disable_irq();
p = (uint32_t *)(APP_BASE_RAM);
SCB->VTOR = (uint32_t)p; // Set Vector Table Offset Register (VTOR) to RAM base
__set_MSP(*p);
__DSB(); // Data Synchronization Barrier
__ISB(); // Instruction Synchronization Barrier
jmp = (void (*)())p[1];
__enable_irq();
while (true) ;
I’m not sure if there’s something I’m missing in the application code or bootloader setup. Has anyone encountered a similar issue or have suggestions on what I could check next?
Thanks in advance!
2025-01-15 10:14 AM
Hello, I tried to change the memory allocation for both bootloader and application to make sure there is no ram memory overlap but no improvements. Any advise?
Bootloader:
LR_IROM1 0x08000000 0x00020000 { ; load region size_region
ER_IROM1 0x08000000 0x00020000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM2 0x24040000 0x00040000 { ; RW data
.ANY (+RW +ZI)
}
}
Application:
LR_IROM1 0x20000000 0x00020000 { ; load region size_region
ER_IROM1 0x20000000 0x00020000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM2 0x24020000 0x00040000 { ; RW data
.ANY (+RW +ZI)
}
}
2025-01-15 12:58 PM
Branching to one's self is typical of while(1) from HardFault_Handler() or Error_Handler() or Default_Handler().
The code at Reset_Handler() is not going to expect interrupts to be disabled on the MCU. The mantra is to disable the SOURCE of the interrupt you have enabled in your loader.
I also would leave the setting of the SP and SCB->VTOR to code on the app side, SystemInit() typically sets SCB->VTOR. Frankly should be easy enough to do in startup_stm32h7xx.s
Debugging, get your handlers to output actionable diagnostics, step from the loader into the application and walk through until you understand what's happening.
2025-01-15 04:51 PM
In your code after __enable_irq() the "jmp" variable remains unused. You do not actually jump.
jmp = (void (*)())p[1];
__enable_irq();
while (true) ;
2025-01-16 01:18 AM - edited 2025-01-16 01:19 AM
Hi @Pavel A. , sorry, that is a copy paste mistake. In the code, right after the
jmp = (void (*)())p[1];
there is jmp();
I can see the Reset Handler address being correctly loaded into PC.
Hi @Tesla DeLorean Thanks for your comment, I must admit that the community seems divided when it comes to hadling bootloaders and applications.
I have found some interesting comments from you in this post, where you recommend not to set the SP in the botloader but in the Reset Handler of the application. Additionally you recommend not to set VTOR as well as disabling the interrupts. The user here had a clear PC corruption after using __set_MSP and this is not my case. But now I wonder if the issue is related to the MSP.
https://community.st.com/t5/stm32-mcus-products/after-calling-set-msp-the-local-variables-get-corrupted/td-p/90447
I´ve found this ARM article where instead they say to use __set_MSP, set VTOR, disable peripherals, disable interrupts etc ... I think I am really getting lost here :D
https://developer.arm.com/documentation/ka001423/latest/
2025-01-16 02:37 AM
The trick is not to have a lot of noise carry over as none of the handlers will be using the same RAM allocation or peripheral contexts.
Auto/local variables often live on the stack, some mapping the stack some place else mid-execution / mid-function is prone to fail.
The apps Reset_Handler is better placed to change the SP as it doesn't have any scope issues and isn't returning.
2025-01-16 07:55 AM
Implemented SP init in Reset handler and removed __MSP instruction from boot code. No improvements.
I have configured Keil to program the RAM (fetching SP and PC from first two location in memory) and the same code works without any issue.
2025-01-16 07:04 PM
When your code jumps to RAM address 0x20000000 check the stack pointer (MSP). Sometimes the debugger (or ST-LINK?) act up. Power down everything for a minute. Check grounding and cables. If no interrupts are pending it just should work.
> I´ve found this ARM article where instead they say to use __set_MSP, set VTOR
There are two conventions: GNU startup code (provided by ST with CubeIDE libraries) loads the initial SP itself as the first action, then it sets VTOR. Keil and IAR startup code expects that the caller (or hardware) sets the SP. Both assume that interrupts are enabled. Of course you can modify the startup and do whatever makes sense for you.
2025-01-22 11:32 PM - edited 2025-01-22 11:34 PM
Dear all, the issue was solved by replacing
__set_MSP(*p);
with:
__set_MSP(*(volatile uint32_t*)p);
Please note that p was defined as:
volatile uint32_t *p.