2
\$\begingroup\$

So, I am trying to use this USB mass storage bootloader for the STM32F103C8 (64kB flash): https://github.com/sfyip/STM32F103_MSD_BOOTLOADER

To compile .hex files for it, I've already set the flash origin to 0x8004000 and the vector table offset to the same value. I've verified that, indeed, the generated hex file starts at 0x8004000 and through debug I've verified that SCB->VTOR is the right value. However, I still could run a simple blink app only from the debug from STM32CubeIDE - when I uploaded the firmware to the 0x8004000 address either via ST-link or via the bootloader, the blink did not work.

However, I then took a look at the example hex file of a blink project the bootloader creator provides. Here I saw that the first value of the hex file is 0x20000408 - the initial stack pointer value (in RAM space), if I understand everything correctly. In the bootloader hex itself, the initial stack pointer value is 0x20001250 (compiled with keil uVision 5). However, in all applications I compile from Stm32CubeIDE that I use, the value is 0x20005000. That is when I started to edit my hex files and play with it.

What I discovered is that any values lower than 0x20005000 - I've tried 0x20000408, 0x20002000, 0x20003000, 0x20004000 and even 0x20004999 - do work, they successfully upload to the microcontroller both with ST-link and the bootloader. The bootloader flawlessly jumps to the main application and the blinking starts. But with the value 0x20005000, the application does not work (larger values obviously don't work since they are beneath the RAM space). During debug I discovered that with 0x20005000 value the microcontroller jumps into the hard fault handler, with 0x20004999 it does not.

The jump to main application in the bootloader is performed with this code, where APP_ADDR is 0x8004000:

  uint32_t jump_addr = *((__IO uint32_t*)(APP_ADDR+4u));
  HAL_DeInit();
  /* Change the main stack pointer. */
  __set_MSP(*(__IO uint32_t*)APP_ADDR);
  SCB->VTOR = APP_ADDR;
  
  ((void (*) (void)) (jump_addr)) (); 

Everything seems fine, the MSP is set according to the hex file (0x20005000), Vector offset is set to 0x8004000 (which is set again in my app in SystemInit()) and the jump is performed.

  1. What is going on here, why can't I set initial stack pointer to 0x20005000?
  2. Am I breaking something by changing it? Will apps more complex than blink() work?
  3. Could the problem be related to the fact that the bootloader is compiled with Keil uVision, while I use Stm32CubeIDE? Perhaps, different linker settings? If so, what do I need to change in the keil uVision linker?
  4. If I do need to set the initial stack pointer to another value, how do I do it in Stm32CubeIDE, not by modifying the .hex file?
  5. Could this issue be common for all bootloaders?
  6. Am I even on the right track, or the issue is deeper than I think?
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Please mention which exact STM32F103 model you have, C8, CB or something else? \$\endgroup\$
    – Justme
    Dec 16, 2021 at 5:20
  • \$\begingroup\$ Does your part have physical RAM at that address? I'm assuming the very first thing you did when picking this MCU was to view this page: st.com/en/microcontrollers-microprocessors/stm32f103.html. Do you have 6k of RAM or 96k? It kind of matters... obviously you cannot place the stack where no physical memory exists. \$\endgroup\$
    – Lundin
    Dec 16, 2021 at 9:43
  • \$\begingroup\$ @Justme thank you, fixed. C8, 64kb flash. \$\endgroup\$
    – sx107
    Dec 16, 2021 at 10:14

1 Answer 1

3
\$\begingroup\$

Normally in ARM systems, setting the stack pointer is done by hardware, but apparently not in this bootloader scenario(?).

So we have to treat it just like any old-fashioned microcontroller program where we set the SP manually. Generally speaking:

  • C programming is enabled at the point where you leave the function setting the stack pointer. You shouldn't normally write C code in that same function, or in case you do, don't declare any variables.
  • The C compiler doesn't understand what inline asm and similar code used for setting the SP does.
  • Therefore, in the function where you set the stack pointer, you cannot declare any local variables because those risk getting allocated by the stack. And if you allocate a variable on the stack before you have told the program where the stack is... well, that's obviously going to to end up badly.

In your code you have uint32_t jump_addr and this is a local variable with automatic storage. If you are lucky, the compiler places it in a CPU register but it is by no means required to do so. We have to ensure that it doesn't end up on the stack. Ideally we should be able to declare it as register but this is just a recommendation to the compiler, not a direct order.

A more reliable way might be to declare the variable as static const jump_addr = .... This makes the assumption that such a variable ends up in flash. Otherwise, if it ends up in .data, that might not improve the situation since .data is probably not initialized this early on in the CRT code. If you use this declaration you must verify in the .map file where the variable ended up.

The best solution ends up as not declaring the variable at all:

( (void(*)(void))(APP_ADDR+4u) ) (); 

This should result in the function code getting baked into the machine code and not placed in a memory area which is unreliable at this point.

Alternatively, you could write this whole function in inline assembler and ensure to use a register for the address.

A brief guide for how to implement the whole CRT startup code yourself can be found here: https://stackoverflow.com/a/47940277/584518

\$\endgroup\$
9
  • \$\begingroup\$ Not declaring the variable at all did not help. I did solver the problem, though, by placing the jumping code in the int main() function instead of another function that is called from int main(). No idea why it helped though, but thank you very much for your help. Any idea why moving from another function to int main() helped? \$\endgroup\$
    – sx107
    Dec 16, 2021 at 12:36
  • \$\begingroup\$ @sx107 This code cannot be placed in any function that declares local variables, simple as that. And you cannot execute any code containing local variables before you have set the SP. \$\endgroup\$
    – Lundin
    Dec 16, 2021 at 12:43
  • \$\begingroup\$ It works fine even if the variable is declared in int main(). The problem was solved by moving the code from a function that is called from int main() to int main() directly. What is going on here? Perhaps the return address is written there and code does not like that? \$\endgroup\$
    – sx107
    Dec 16, 2021 at 12:51
  • \$\begingroup\$ @sx107 "The problem was solved by moving the code from a function that is called from int main() to int main() directly." So you are calling a function from C code which sets up the stack. Before that function is called, the return address of that function will be stacked... where? Just read what I'm telling you: C programming is enabled at the point where you leave the function setting the stack pointer. If you are doing anything else, you are doing it wrong. \$\endgroup\$
    – Lundin
    Dec 16, 2021 at 12:56
  • \$\begingroup\$ Exactly my thoughts that the return address was the issue. I'll modify the github code of the bootloader to make the jump and stack pointer changes in int main(), not in any another function. \$\endgroup\$
    – sx107
    Dec 16, 2021 at 13:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.