STM32F20x custom bootloader
Hi Everyone!
I am writing a custom bootloader for STM32F207ZGT6. I divided the 1 Mb flash into 2 sections. The first one is 128 kbytes, my bootloader is loaded to this section. The second one is 896 kbytes, my main application is loaded here. I use the following code to the jump:
typedef void (*pFunction)(void); pFunction Jump_To_Application; uint32_t JumpAddress; . . . JumpAddress = 0x08020000 + 4; Jump_To_Application = (pFunction) JumpAddress; __set_MSP(0x20000000); Jump_To_Application();
I pasted the "+ 4" to the code because of endianness, but I may be wrong. My main application starts from 0x08020000. I set this in uVision. I ran to the following problem: When the Jump_To_Application() function is called, a HardFault is generated and its handler is called.
Anyone has a tip?
I am all ears.
Try with:
__set_MSP(0x08020000);
Look at the IAP examples that ST provide...
I pasted the "+ 4" to the code because of endianness, but I may be wrong.
I think +4 refers to the second entry in the vector table. A reminder: in the Cortex-M processors, the first entry in the vector table is the initial value of SP, the second entry is the starting address. So you should fetch the starting address and jump to it:
JumpAddress = *(pFunction*)0x08020004;
By the way, prior to that you should fetch the initial SP value and write it to the SP.
Most likely, the hard fault occurs because you jump to an even address. With Cortex-M processors, all jump destinations are odd (thumb mode bit is set.)
By the way, this is how I do it:
static const uint16_t launch_fw_code[] = { 0xF850, 0xDB04, /* LDR.W SP, [R0], #4 */ 0x6800, /* LDR.W R0, [R0] */ 0x4700, /* BX R0 */ }; ... ((void (*)(uint32_t))(1+(int)launch_fw_code))(FW_START);
Some comments in the code would be nice, but I hope you see the general idea. Basically, it's a small bit of assembly code to initialize the SP and jump to the app. The code is written in binary, so there is no need to resort to assembler. This should be fairly portable between compilers. The assumption is that an argument to a function is passed in R0.
Sorry for my very limited technical ability and English ability.
#include <stdint.h> #define FW_START 0x80000000 static const uint16_t launch_fw_code[] = { 0xF850, 0xDB04, /* LDR.W SP, [R0], #4 */ 0x6800, /* LDR.W R0, [R0] */ 0x4700, /* BX R0 */ }; int main(void) { ((void (*)(uint32_t))(1+ (int)launch_fw_code))(FW_START); // type_A ((void (*)(uint32_t))(1+(unsigned int)launch_fw_code))(FW_START); // type_B ((void (*)(uint32_t))(1+ launch_fw_code))(FW_START); // type_C return 0; }
Why the (int) is needed? What is the difference between type_A, type_B, and type_C?
launch_fw_code is an uint16_t array, so it is also a constant address. I guess it must be an even address, and the 1 is to make it odd.
(int) is needed because we want to add 1 to the address. We could have used (uint8_t*) instead. If you use the address of launch_fw_code without a type cast, 2 will be added instead of 1, that is sizeof(uint16_t). There is no difference between type_A and type_B: they yield the same result.
I guess it must be an even address, and the 1 is to make it odd.
Exactly.
I completely forgot one more thing: of course, the calculated address of the entry point should be compatible with the type of the function argument. I should have used int everywhere: it's shorter and it does the job:
((void (*)(int))(1+(int)launch_fw_code))(FW_START);
It's kind of misleading to pass a pointer to code as an int. But making it a pointer to a function would not clear things up much: we still would have to do some type casting in order to add 1 to the address. It's still a mess.
Hi Mike,
Many thanks for your explanation.
(I regularly discover that, I am not a good C programmer.)
(1+launch_fw_code) == &launch_fw_code[1]