MCU startup code is the low-level code that executes during the early stages of MCU initialization after a reset. On a Cortex-M device, the processor begins the reset sequence by loading the initial Main Stack Pointer (MSP) and the reset handler address from the vector table. Depending on the MCU’s boot architecture, a boot ROM or bootloader may execute first before control is transferred to the application startup code.
The main purpose of startup code is to prepare the execution environment before the application begins. It sets up the initial stack configuration, initializes memory sections such as .data and .bss, performs early system initialization, and then transfers control to the C/C++ runtime and eventually to main().
Startup code is commonly implemented using a combination of assembly and C. Assembly is often used for processor-specific operations that need to be performed at the lowest level, such as implementing the reset entry point, accessing processor registers, and preparing the processor state before entering the C/C++ runtime. C can then be used for initialization tasks that are easier to maintain and understand.
The exact contents of an MCU startup file depend on the processor architecture, compiler/toolchain, and MCU vendor. In a typical Cortex-M project, the startup file also contains the interrupt vector table and the reset handler, along with weak default handlers for interrupts that the application does not explicitly implement.
In short, MCU startup code bridges the gap between the processor’s reset state and the application’s runtime environment, performing the initialization required before normal application execution begins.
Let’s first understand the start-up code. This post explains the start-up code for STM32H5 using IAR, but the concepts are similar for most microcontrollers.
What is MCU Startup Code and Why Does it Matter?
The startup code is the first set of instructions executed after an MCU reset or power-up. It performs the essential low-level initialization required before the application can run. This typically includes setting up the stack, initializing memory, configuring the runtime environment, and eventually transferring control to main().
Without the startup code, the application would not be able to start properly.
In the IAR environment, the startup code is typically provided as an assembly-language (.s) file, although some initialization functionality may be implemented in C. It executes immediately after a reset and setup the basic execution environment before control is transferred to the application’s main() function.
The startup code works closely with several other components:
IAR linker configuration file (.icf): This IAR-specific linker configuration file defines the MCU’s memory layout, including Flash and RAM regions, stack and heap placement, and the placement of program sections.
System initialization code: This code performs low-level system initialization, including configuration of the system clock, PLL, Flash latency, and other system-level settings. The startup code typically calls SystemInit() as part of the early startup sequence.
Interrupt handler file:This file contains the application’s interrupt service routines (ISRs) for peripherals and exceptions that require application-specific handling. The startup file normally provides the interrupt vector table and default or weak handler implementations. Application-specific handlers can override these default handlers when required.
CMSIS: CMSIS (Cortex Microcontroller Software Interface Standard) provides standardized definitions and APIs for the Cortex-M core, including access to the NVIC, system control registers, exception types, and device-specific definitions used by the startup and system initialization code
Key Responsibilities of the MCU Startup File:
The startup file typically handles the following responsibilities:
Reset Handling: Defines the Reset Handler, which serves as the processor’s entry point after a reset. On a Cortex-M device, the processor obtains the initial stack pointer value and the address of the Reset Handler from the interrupt vector table.
Stack Initialization: On a Cortex-M device, the processor initializes the Main Stack Pointer (MSP) by loading its initial value from the first entry of the interrupt vector table during reset. The startup code then executes using this stack.
Vector Table Setup:Defines and places the interrupt vector table, which contains the initial stack pointer value followed by the addresses of the Reset Handler, exception handlers, and peripheral interrupt handlers.
System Initialization: Performs or invokes early system initialization, typically through SystemInit(). This may configure system-level settings such as the clock source, PLL, Flash latency, and other MCU-specific parameters.
C Runtime and Memory Initialization: Ensures that the C runtime environment is properly initialized before the application starts. This may include copying the initial values of the .data section from non-volatile memory to RAM and clearing the .bss section so that uninitialized global and static variables are initialized to zero. The exact implementation is typically provided by the compiler and runtime library.
Application Startup:After the required low-level and C runtime initialization is complete, control eventually reaches the application’s main() function, where normal application execution begins.
🧑💻 Step-by-Step: What Happens During Startup?
When a microcontroller powers up or is reset, the processor does not jump directly to the application’s main() function. Instead, it follows a well-defined startup sequence that establishes the initial processor state and prepares the hardware, memory, and runtime environment before application code begins executing.
Immediately after reset, the Cortex-M processor performs a defined sequence of hardware-level operations, beginning with loading the initial Main Stack Pointer (MSP) and the address of the Reset Handler from the interrupt vector table. The startup code then performs or invokes the necessary low-level initialization and hands control to the compiler’s runtime initialization code. After the required system and C/C++ runtime initialization is complete, execution eventually reaches the application’s main() function.
In this article, we will follow the startup sequence step by step, starting from the processor’s initial state after reset and ending when the first line of application code begins executing.
The explanation below is based on an actual IAR startup implementation for an STM32 device. The exact details can vary depending on the STM32 device, compiler, linker, and runtime library. However, the underlying startup flow is broadly similar across most ARM Cortex-M microcontrollers.
Let’s break down the startup sequence step by step:
Stage 1: Hard Reset & Vector Table Fetch:
The startup sequence begins when the MCU undergoes a reset, such as:
- Power-on reset.
- External reset.
- Software reset.
After reset, the Cortex-M processor obtains its initial execution state from the interrupt vector table.
For an STM32 device, the vector table is initially available through the memory region mapped at the device’s boot address. Depending on the device and boot configuration, Flash, system memory, or SRAM may be mapped to 0x0000_0000.
The vector table contains the initial stack pointer value followed by the addresses of the reset, exception, and interrupt handlers.
The hardware automatically loads the first two entries of the vector table:
Entry 0 (0x00000000): Initial MSP Value
The first entry of the vector table contains the initial value of the Main Stack Pointer (MSP).
Conceptually, the processor performs:
MSP ← *(VectorTable + 0)
If the vector table is currently mapped at 0x0000_0000, this becomes:
MSP ← *(0x00000000).
The value stored at this location is not an instruction address. It is a memory address that defines the initial top of the stack.
Entry 1 (Address 0x0000 0004): Reset Handler Address
The second entry of the vector table contains the address of the Reset Handler.
Conceptually:
PC ← *(VectorTable + 4)
If the vector table is located at 0x0000_0000:
PC ← *(0x00000004)
With these two values loaded, the processor jumps to Reset_Handler and begins executing it.
The processor loads this value into the Program Counter (PC) and begins executing instructions from that address. In a typical STM32 application, this address points to the Reset_Handler defined in the startup file.
At this point, the Cortex-M hardware has established the minimum processor state required to begin executing the startup code. The processor has not yet performed the application’s C/C++ runtime initialization.
The subsequent startup stages are responsible for tasks such as system initialization, C runtime initialization, .data initialization, .bss clearing, and eventually reaching main().

Stage 2: Hardware pre-initialization:
Once the processor enters Reset_Handler, the IAR startup code typically calls the vendor-supplied SystemInit() function before transferring control to your main application code:
LDR R0, =SystemInit BLX R0
SystemInit() performs the device-specific hardware initialization required to establish a suitable environment for the application. Depending on the MCU family and vendor implementation, SystemInit() may perform tasks such as
What SystemInit() usually handles:
- SystemInit performs all the essential low-level configuration, including:
- Setting up the system clock (PLL, prescalers, clock sources)
- Configuring Flash wait states for higher clock speeds
- Enabling the FPU (if present)
- Initializing caches, MPU, and bus-related settings
- Updating the VTOR register if the vector table has been moved from the default address
These steps ensure that the core is running on stable hardware foundations.
Stage 3: Jump to IAR Runtime Entry Point
Once the basic hardware setup is complete, the startup code transfers control to the IAR C/C++ runtime. In the startup file, this is done with:
LDR R0, =__iar_program_start BX R0
These instructions transfer control from the low-level assembly code to the IAR Runtime Library, which is responsible for initializing the entire C/C++ runtime environment.
System state at this point:
- Hardware is now stable
- System clocks are configured
- The FPU (if available) is enabled
- Memory sections (.data, .bss, static objects) are not yet initialized
In other words, assembly-level setup is done, and this is the moment when IAR’s runtime takes over and prepares the memory and environment needed for your application.
Stage 4: C/C++ Runtime Initialization
Once execution enters __iar_program_start, the IAR runtime begins setting up the full C/C++ environment. This stage prepares memory, global variables, and C++ objects before main() can run.
1. Floating-Point Unit Initialization:
The runtime first calls:
__iar_init_vfp()
This configures the Floating-Point Unit (FPU), including its registers, stacking mode, and lazy context-saving behavior.
2. Optional Low-Level Initialization:
Before the standard C/C++ runtime performs memory setup, the IAR startup sequence calls an optional user function:
// Called by the IAR runtime: int __low_level_init(void);
The Role of __low_level_init():
- Custom Control: This function is a critical hook for developers who need to perform early configuration that might influence the memory setup.
- The Gatekeeper: Its primary function is to act as a gatekeeper for the time-consuming memory initialization steps (copying .data and clearing .bss.
- If this function returns 0, all data initialization is skipped.
- This is especially useful for bootloaders, RAM-only execution, or scenarios where you want full control over memory setup.
3. Data Initialization:
Memory initialization is performed through:
__iar_data_init3()
It handles two crucial tasks:
1. Zero-initializing the .bss section
All uninitialized global and static variables (in .bss) are set to zero.
2. Copying .data from Flash → RAM
Variables with initial values stored in Flash are copied into their RAM locations so the program can modify them.
4. C++ Runtime Initialization:
Finally, IAR calls the C++ initialization wrapper:
__cmain()
This function:
- Invokes all global and static C++ constructors
- Prepares the runtime state required before entering main()
This ensures every global object is fully constructed and ready for use.
Stage 5: Entering main() — Your Application Finally Starts:
After the hardware is set up and the memory is prepared, the last step in the startup process is to call your program’s starting point:
main();
This is where your actual application code finally begins to run.
At this moment:
- System clock = configured
- FPU = enabled
- RAM = initialized (.data/.bss ready)
- Global/static constructors = executed
- Vector table = active
- Interrupts = safe to use
You now have complete control.
Structure of the ARM Cortex-M33 Startup File:
Let’s walk through this Startup file’s structure.
1. Vector Table Declaration:
This array of function pointers defines where the MCU should jump for each interrupt. The first entry is the initial stack pointer, and the second is the Reset_Handler.
EXPORT __vector_table
EXPORT Reset_Handler
AREA RESET, DATA, READONLY
ALIGN
__vector_table
DCD __initial_sp
DCD Reset_Handler
DCD NMI_Handler
DCD HardFault_Handler
; ... (rest of the interrupt vectors)
Note: IAR places the vector table in flash at address 0x08000000 unless changed via linker.
2. Stack and Heap Configuration:
The actual stack size is defined in the IAR linker configuration file (.icf), not directly in the startup assembly file. The startup file declares symbols for the initial stack pointer, often like this:
EXTERN __initial_sp
3. Default Exception and Interrupt Handlers:
All exception and IRQ handlers are declared as weak by default. If you do not provide your own implementations, they will default to a dummy handler that runs an infinite loop. You can override any of these handlers simply by defining a function with the same name in your C code.
NMI_Handler
B .
HardFault_Handler
B .
4. The Reset Handler:
This is the first function called after a reset. In IAR, the Reset Handler typically looks like:
Reset_Handler:
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
Here, __iar_program_start is an internal C library function provided by provided by the IAR runtime library. It’s called by the Reset_Handler in the startup file.
SystemInit()is a function defined in system_stm32h5xx.c. It typically sets up:- Clock configuration
- PLL setup
- FPU configuration
- Possibly watchdog disabling, etc.
__iar_program_start()is the IAR-specific C runtime entry point:- Zero-initialization of .bss
- Copying initialized .data from flash to RAM
- Calling global/static C++ constructors
- Calling main()
📝 Note: If you don’t see SystemInit being called explicitly, then you should add it yourself just before __iar_program_start.
⚙️ Customizing the Startup File in IAR:
You code stuck before main()? Here’s what to check:
- Verify the vector table.
Make sure SCB->VTOR points to the correct base address and the table is properly aligned. - Check the stack pointer.
The first word in the vector table must contain a valid initial SP value—usually the top of SRAM. - Review SystemInit().
Look for any incorrect clock or peripheral configurations that could cause a lock-up. - Initialize .data and .bss sections properly.
If the runtime doesn’t zero or copy them correctly, uninitialized variables can cause undefined behavior.
✅ Use IAR C-SPY to debug early startup code.
Place breakpoints in Reset_Handler and step through line-by-line to find where the system fails.
FAQ Related to MCU Startup Code:
1. What is MCU startup code?
MCU startup code is the first set of instructions that run immediately after a microcontroller resets. It initializes the stack pointer, system clock, memory, and runtime environment before calling the main() function.
2. Why is startup code important in microcontrollers?
Startup code prepares the hardware and memory for safe execution. Without it, global variables, system clocks, and essential CPU states would not be initialized correctly.
3. What happens before main() runs on an ARM Cortex-M MCU?
Steps include vector table fetch, SystemInit execution, C/C++ runtime initialization, copying .data variables, zeroing .bss, and finally calling main().
4. Is the startup code different for each compiler?
Yes. IAR, Keil, and GCC all generate slightly different startup files, especially in how they initialize memory and reset handlers.
5. Where is the startup code stored in an MCU?
The startup code is typically stored in Flash memory at the start of the program image, where the vector table resides.
6. Can I modify the startup code in STM32 or ARM Cortex-M?
Yes. You can customize clock setup, memory initialization, or boot behavior by editing the startup assembly file or SystemInit().
7. What is the vector table in MCU startup?
The vector table is a list of addresses that tell the CPU where to find interrupt handlers and the reset handler.
📘 Related Articles:
- Understand the IAR Linker Script for STM32.
- STM32 RCC Reset Domains: System, Power & Backup Explained.
- STM32 Clock Configuration Guide: Understanding the Clock System Step-by-Step.
- How to Calculate Memory Regions in Embedded Systems (Start, End, Size)
- Why Flash Memory is Divided into Banks: A Deep Dive.