If your project doesn’t reach a breakpoint set in main()
, that means it can actually only fail in a handful of places before. Very good for debugging.
The very first instruction that the processor executes should be the Reset_Handler
. You can start the debugger at that very first instruction by setting a breakpoint there:
debug_init_break = break Reset_Handler
(docs) in the platformio.ini
.
Which means execution should start in the startup_stm32f427xx.S
startup file (I hope you have a startup assembly file in your project or your framework = ...
provides it!), by standard
The startup file will initialize the stack pointer (sp), then immediately calls into SystemInit()
to get the CPU clock pumped up to the maximum (usually via the PLL and a quartz oscillator), before continuing to copy the initial values for variables from flash to RAM, zeroing-out the uninitialized variables and buffers (.bss
segment), initializing the C runtime library (call to __libc_init_array()
and calling into the main()
function.
See also the explanation of the startup file in:
As such, it can only fail in a handful of places, for example:
- if the initial stack pointer (sp) is initialized wrongly, for example, to a address that can’t actually be written to, then the first
push
instruction will crash (as it attempts to push data onto a stack, aka at the stack pointer)
- the initial SP is usually the highest address of RAM and the stack grows downwards (towards lower addresses)
- clock initialization might fail, for example if the code is expecting a HSE / crystal oscillator to be connected, when there is none, or some macro definition is missing that causes the code to select the wrong clock source
- while calling
__libc_init_array()
, it will also call all functions marked with attribute((constructor))
and C++ object constructors of globally allocated objects. If one of the constructor functions crashes, execution won’t get to main()
By stepping through the Reset_Handler
’s assembly code line by line (and skipping over long copy or zeroing loops by setting a breakpoint and continuing), we could see exactly where it’s failing. We can also just let the program run (play button), then press the pause button to halt the processor. Then, one could see if the code is stuck in a loop it never exits, or if it has crashed and landed in the Hardfault_Handler()
function. The “Call Stack” will then show the path that execution took to get to the crash, pin-pointing the problem.
But, to make a long story short, I already have a pretty good idea: There is actually a problem in the linker script numbers that I’ve posted. Looking back we settled on:
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 262144
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1048576
}
These RAM sizes are not correct here. By looking at the memory map of the STM32F427VG, we can see how much memory mapped is mapped to which addresses:
https://www.st.com/resource/en/datasheet/stm32f427vg.pdf, page 86
You can see the “256 KB” of RAM of the device are actually sitting in different blocks:
- core-coupled-memory (CCM) is at 0x10000000 with a size of 64 kilobytes (good, exactly as we have it in the linker script)
- the rest of the SRAM can be seen as one big continuous block starting at address 0x20000000 and of length 112 + 16 + 64 = 192 KB = 196608 bytes. Our linker script however says that there is 256 KB of RAM there. Wrong!!.
- This will cause the linker script to set the stack pointer to “the end of RAM memory”, which is 0x20000000 + 256KB, at which there is no validly mapped memory, causing a hard crash (
Hardfault_Handler()
) when trying to access it.
Thus, we should instead write the MEMORY
section as
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
}
This also has the impliciation that, in order to use the 64K core-coupled-memory, we have to annotate variables that should live in that memory explicitly with a gcc attribute((section(".."))
directive (see here), but I don’t think that’s relevant for now.