The ARM Cortex-M processor family is designed for embedded applications requiring high performance and low power consumption. Cortex-M processors are based on the ARMv7-M architecture and include features like Thumb-2 instruction set, NVIC for interrupt handling, and SysTick timer for OS task scheduling. When power is first applied to a Cortex-M based microcontroller, it goes through a well-defined boot sequence to initialize the core registers and peripherals.
Reset Sequence
After power on reset or external reset signal, the Cortex-M processor loads the main stack pointer (MSP) with the value from address 0x00000000. This is where the processor expects the initial stack pointer value to be located. The reset value of MSP is configurable and provided by the microcontroller manufacturer. ARM recommends setting MSP to point to the start of the RAM region. After MSP initialization, the processor loads the reset value into the vector table offset register. This specifies the location of the vector table which holds the reset handler address and interrupt handler addresses. The standard location for the vector table is address 0x00000000. The processor then fetches the reset handler address from the first word in the vector table (0x00000000) and jumps to the reset handler code.
Reset Handler
The reset handler is responsible for initializations that must be done before calling main(). This includes:
- Setup core peripherals like SysTick timer and NVIC
- Initialize device specific peripherals
- Copy .data section initial values from flash to RAM
- Clear the .bss section in RAM
- Initialize the heap and stack
- Call library initializers for C runtime and middleware
- Call the main() function
The reset handler code can be written in assembly or C. Assembly allows precise control over the initialization sequence but C is more portable. A typical C implementation would look like: void Reset_Handler() { // Initialize core peripherals SystemInit(); // Initialize device peripherals // E.g. GPIO, UART, I2C, etc // Initialize .data and .bss sections __init_data(); // Call C library initializers __libc_init_array(); // Call main main(); }
System Initialization
The SystemInit() function is used to setup core peripherals like the SysTick timer and NVIC. This typically involves:
- Configure SysTick for OS task scheduling
- Set NVIC priorities for interrupts
- Enable desired interrupts in the NVIC
- Configure optional MPU regions for memory protection
- Enable FPU if present
The configuration values are vendor specific and configured through CMSIS headers provided by the manufacturer. This abstracts the core initialization from the underlying MCU hardware.
.data and .bss Initialization
The .data section contains initialized global and static variables that need to be copied from flash to RAM on startup. The __init_data() function loops through the .data section and copies values from the flash to the RAM address for each variable. After copying, the .data variables will contain their initialized values.
The .bss section contains uninitialized global and static variables which need to be zero initialized in RAM. The __init_data() function will clear the .bss section, typically by just memsetting it to zero before the main() call.
C Runtime Initialization
The C runtime contains startup code that must be executed before entering main(). This includes:
- Initialize heap region for dynamic memory allocation
- Initialize global C++ objects
- Initialize stdin, stdout, stderr
- Initialize C library state
The __libc_init_array() function will invoke the C runtime startup routines. The ARM compiler tools provide the implementation for this based on the compiler settings.
Calling main()
After C runtime initialization, the reset handler calls the main() function to start application execution. The arguments argc and argv are setup according to the application. With no OS, main() will never return and the program executes forever in the application code.
With an RTOS, main() will initialize the OS kernel and start the scheduler. It will then return after creating the OS tasks. The scheduler will take over execution from main() by running the created tasks in a multithreaded environment.
Startup Optimization
To optimize the startup time, various techniques can be used:
- Initialize only required peripherals in the reset handler
- Configure flash wait states to match CPU clock speed
- Optimize .data and .bss copy by using DMA
- Execute startup code from RAM instead of flash
Toolchain options can also impact startup time. For example, the –split_sections option generates faster code by splitting linker sections into separately loadable segments.
Startup time can be measured from reset release to the entry of main(). By optimizing the reset sequence and peripherals initialization, it can be reduced from milliseconds to hundreds of microseconds.
Summary
The ARM Cortex-M boot sequence follows a well-defined startup procedure to transition the device from reset to application execution. Configuring the MSP stack pointer, vector table offset, and reset handler are the critical first steps. The reset handler then initializes the system, C runtime environment, and application variables before calling main(). Optimization of the startup code improves embedded responsiveness and real-time performance.