“ This article provides a detailed explanation of the STM32F4xx startup file startup_stm32f407xx.s, covering the stages from chip power-up to entering main(), analyzing key codes and their functions such as stack and heap definitions, vector tables, reset programs, interrupt service routines, and user stack initialization.”

01
—
Introduction
STM32 microcontroller startup files are used to complete hardware initialization at the moment of chip power-up, establish a C language runtime environment, and ultimately hand over control safely to the user program. Taking STM32F4xx (Cortex-M4 core) as an example, this article details the complete startup process from chip power-up to entering the user main() function, in conjunction with the assembly code of the startup file (startup_stm32f407xx.s).
02
—
Startup Process
For better understanding, the process is divided into the following 5 stages in chronological order:
• Initialize stack pointer SP = _initial_sp;
• Initialize PC pointer = Reset_Handler;
• Initialize interrupt vector table;
• Configure system clock;
• Call C library function __main to initialize user stack, ultimately calling the main function to enter the C world.
03
—
Detailed Explanation of Startup Code
The startup file can be found in the stm32xx project files, as shown below:

1. Stack
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
The stack size is allocated as 0x00000400 (1KB), named STACK, NOINIT means it is not initialized, readable and writable, and aligned to 8 (2^3) bytes.
The stack is used for local variables, function calls, and function parameters. The stack size must not exceed the internal SRAM size. If the program is large and many local variables are defined, the stack size needs to be modified. If one day your program encounters strange errors and enters a hard fault, you should consider whether the stack is large enough and has overflowed.
2. Heap
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
The heap size is allocated as 0x00000200 (512 bytes), named HEAP, NOINIT means it is not initialized, readable and writable, and aligned to 8 (2^3) bytes. __heap_base indicates the starting address of the heap, and __heap_limit indicates the ending address of the heap. The heap grows from low to high, opposite to the stack’s growth direction.
The heap is mainly used for dynamic memory allocation, such as memory allocated by the malloc() function. This is used less frequently in STM32.
3. Vector Table
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
A data segment named RESET is defined, which is readable. It declares that __Vectors, __Vectors_End, and __Vectors_Size have global attributes and can be called by external files.
EXPORT: Declares that a label can be used by external files, giving it global attributes. If using the IAR compiler, the GLOBAL directive is used instead.
__Vectors DCD __initial_sp ; Stack top address
DCD Reset_Handler ; Reset program address
DCD NMI_Handler
DCD HardFault_Handler
DCD MemManage_Handler
DCD BusFault_Handler
DCD UsageFault_Handler
DCD 0 ; 0 indicates reserved
DCD 0
DCD 0
DCD 0
DCD SVC_Handler
DCD DebugMon_Handler
DCD 0
DCD PendSV_Handler
DCD SysTick_Handler
; External interrupts start
DCD WWDG_IRQHandler
DCD PVD_IRQHandler
DCD TAMPER_IRQHandler
; Due to space limitations, intermediate code is omitted
DCD DMA2_Channel2_IRQHandler
DCD DMA2_Channel3_IRQHandler
DCD DMA2_Channel4_5_IRQHandler
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
__Vectors is the starting address of the vector table, __Vectors_End is the ending address of the vector table, and the difference between the two gives the size of the vector table. The vector table is placed starting from address 0 in FLASH, with each entry being 4 bytes. Address 0 stores the stack top address, 0x04 stores the reset program address, and so on. From the code, we can see that the vector table contains the names of interrupt service functions, which we know are addresses in C language.
DCD: Allocates one or more words of memory, aligned to four bytes, and initializes this memory. In the vector table, DCD allocates a block of memory and initializes them with the entry addresses of the ISR.
4. Reset Program
AREA |.text|, CODE, READONLY
; Define a code segment named .text, readable.
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
The reset subroutine is the first program executed after the system powers up, calling the SystemInit function to initialize the system clock, and then calling the C library function __main, ultimately calling the main function to enter the C world.
WEAK: Indicates a weak definition. If an external file defines this label first, it will reference that label; if the external file does not declare it, there will be no error. This indicates that the reset subroutine can be re-implemented by the user in other files, and it is not unique.
IMPORT: Indicates that this label comes from an external file, similar to the EXTERN keyword in C language. This indicates that both SystemInit and __main functions come from external files.
SystemInit() is a standard library function defined in the system_stm32f103xe.c library file. Its main function is to configure the system clock. After calling this function, the microcontroller’s system clock is configured to 72M.
__main is a standard C library function, mainly used to initialize the user stack, ultimately calling the main function to enter the C world. This is why we always have a main function in our programs. If we do not call __main here, the program will not eventually call the main function in our C file. If a mischievous user changes the name of the main function, they can import the name of their main function here.
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT user_main
LDR R0, =SystemInit
BLX R0
LDR R0, =user_main
BX R0
ENDP
At this point, the name of the main function you wrote in the C file is no longer main but user_main.
5. Interrupt Service Routines
All interrupt service functions are already written for us in the startup file. Unlike the interrupt service functions we usually write, these functions are empty. The actual interrupt service routines need to be re-implemented in external C files; here they just occupy a position in advance.
If we enable an interrupt while using a peripheral but forget to write the corresponding interrupt service routine or misname the function, when the interrupt occurs, the program will jump to the empty interrupt service routine pre-written in the startup file and will loop infinitely in this empty function, causing the program to hang.
```cpp
NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP
HardFault_Handler PROC EXPORT HardFault_Handler [WEAK] B . ENDP
MemManage_Handler PROC EXPORT MemManage_Handler [WEAK] B . ENDP
BusFault_Handler PROC EXPORT BusFault_Handler [WEAK] B . ENDP
UsageFault_Handler PROC EXPORT UsageFault_Handler [WEAK] B . ENDP
SVC_Handler PROC EXPORT SVC_Handler [WEAK] B . ENDP
DebugMon_Handler PROC EXPORT DebugMon_Handler [WEAK] B . ENDP
PendSV_Handler PROC EXPORT PendSV_Handler [WEAK] B . ENDP
SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B . ENDP
Default_Handler PROC; Due to space limitations, intermediate code is omitted
B .
ENDP
B: Jumps to a label. Here it jumps to a ‘.’, indicating an infinite loop.
6. User Stack Initialization
ALIGN: Aligns the address where instructions or data are stored, followed by an immediate number. By default, it indicates 4-byte alignment.
IF : DEF :__MICROLIB ; This macro is enabled in KEIL
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory ; This function is implemented by the user
EXPORT __user_initial_stack
__user_initial_stack
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
First, it checks whether __MICROLIB is defined. If this macro is defined, it grants global attributes to the labels __initial_sp (stack top address), __heap_base (heap starting address), and __heap_limit (heap ending address) for external file calls. This macro is configured in KEIL, as shown in the figure below. Then, the stack initialization is completed by the C library function __main.

This article references the following document:
“[Wildfire] Practical Guide to STM32 HAL Library Development – Based on Wildfire F4 Series Development Board”