When an ARM-based microcontroller powers on or resets, the processor begins executing instructions from a specific memory address. The code located at this address is known as the startup code or boot code. The primary job of the startup code is to initialize the processor, memory, and any peripherals so that the main application code can run properly.
After the startup code completes its initialization tasks, it must transfer control to the main application code. This is typically accomplished by calling the reset handler. The reset handler is a function or code label defined by the application developer which represents the entry point of the main application code.
In summary:
- The startup code runs first after reset and sets up the hardware environment.
- The reset handler is called by the startup code to start executing the main application.
Startup Code Details
When an ARM processor comes out of reset, it loads the value 0x00000000 into the program counter register. This causes execution to begin at the memory address 0x00000000, which is where the startup code is located.
The key tasks performed by the typical startup code include:
- Initializing processor registers – The startup code sets up important registers like the stack pointer, frame pointer, and control registers.
- Configuring clock speeds – The system clock and bus clocks are initialized to required speeds.
- Setting up memory – This includes the flash and RAM memory controllers to allow proper access.
- Initializing device peripherals – Any required device drivers and peripherals like GPIO, timers, I2C, etc are initialized.
- Zeroing out RAM contents – RAM contents are cleared or initialized to known values.
- Copying data sections – Constants and initialized variables are copied from flash to RAM.
- Calling runtime initialization – Any C/C++ runtime setup code provided by the compiler is called.
The startup code is tightly coupled to the hardware design. It must contain device drivers and setup code for all the specific peripherals used in the microcontroller system. The exact sequence of initialization and configuration tasks may vary across different ARM chips and boards.
Reset Handler Details
After the startup code has performed all its necessary hardware and runtime environment setup, it must transfer control to the main application code. This is accomplished by calling the reset handler function.
The reset handler is a regular C/C++ function defined by the application developer. It has the following key characteristics:
- Defined with
__attribute__((noreturn))
to prevent compiler optimizations. - Labelled with
__Vectors
to locate it in the vector table. - Represented by the
Reset_Handler
symbol in the vector table. - Called by the startup code by name or by vector table offset.
A simple reset handler definition in C code looks like: void __attribute__((noreturn)) __Vectors Reset_Handler() { // Application code entry point while(1); // main loop }
When the reset handler function is called, it represents the starting point for the main application program. All application specific initialization, the main loop, and application tasks are written inside the reset handler and called functions.
The reset handler is customized by the application developer and provides the transitions from the generic startup code to the application code. It is responsible for:
- Calling C++ constructors for global objects
- Initializing the application’s stack pointer
- Setting up any custom handlers or interrupt service routines
- Calling the
main()
function in C programs - Starting the main supervision loop or main task handler
In summary, the reset handler represents the beginning of the application code after the processor and hardware environment has been set up by the fixed startup code.
Code Execution Sequence
To understand the difference between the startup code and reset handler, it is useful to visualize the full code execution sequence on reset:
- Microcontroller comes out of reset state
- Startup code executes
- Sets up stack pointer, registers, clocks
- Initializes memory and peripherals
- Calls C++ constructors and C runtime init
- Reset handler called by startup code
- Optional application specific initialization
- Sets up interrupt handlers and application tasks
- Calls main() function in C program
- Starts main supervision loop
- Main application executes
This clearly shows the separation between the generic startup code provided by the SDK/HAL vendor and the application specific reset handler and main code.
Startup Code Configuration
The startup code is closely coupled to the ARM processor variant and the board or SoC design. Therefore, it must be provided by the silicon vendor as part of a Hardware Abstraction Layer (HAL) or Software Development Kit (SDK).
For example, ARM Cortex-M processors utilize a HAL or CMSIS that contains the startup code and vector table templates. Similarly, microcontroller vendors like STM32, NXP LPC, Microchip SAMD provide startup code and linker scripts tailored for their SoCs.
The application developer must configure the startup code and linker scripts to match their hardware configuration. Key configuration settings include:
- Processor core variant (Cortex-M3, M4, etc)
- Clock speeds and oscillators used
- Memory size and mapping
- Enabled peripherals and I/O pins used
- Interrupt priority levels
These settings ensure that the startup code initializes the SoC in the proper hardware state required by the application. SDKs provide configuration tools or #define macros to easily configure the startup code.
Reset Handler Implementation
In contrast to the startup code, the reset handler is fully implemented by the application developer. The compiler combines it with the configured startup code to produce a complete program image.
For simple programs, the reset handler can directly contain the entire application code. For more complex projects, it is structured to call other modules and functions: void Reset_Handler() { // Optional application initialization ConfigurePorts(); ConfigureTimers(); // Start main application tasks InitializeTasks(); StartScheduler(); }
This allows the application code to be cleanly separated from the fixed startup sequence. The use of high-level frameworks can further abstract the reset handler into a configuration based initialization call.
Example: STM32 Startup vs Reset Handler
As a practical example, we can look at an STM32 ARM Cortex-M microcontroller:
- The STM32F103xx startup code is provided by ST in system_stm32f1xx.c
- It configures clocks, memory, peripherals based on configuration header files
- At end, it calls SystemInit() then jumps to reset handler
int main(void) { /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART_UART_Init(); /* Infinite loop */ while (1) { // Application code here } }
- The developer implements main.c with application specific code
- Main calls HAL/SDK functions to initialize hardware
- Main contains infinite loop with application tasks
This demonstrates how the STM32 startup code acts as the fixed bootstrap sequence, while the user main.c behaves as a portable reset handler + application.
Startup Code vs Reset Handler Comparison
Here is a summary comparing the startup code and reset handler:
Startup Code | Reset Handler |
---|---|
Executes first after processor reset | Called by startup code to begin main program |
Sets up memory and clocks | Starts application tasks and functions |
Initializes fixed hardware peripherals | Optionally initializes application variables |
Provided by vendor SDK/HAL | Implemented by application developer |
Fixed functionality for each MCU | Portable code across devices |
Boots up the system hardware | Entry point of firmware application |
In summary, the startup code acts as a hardware-specific bootloader to configure the system, while the reset handler provides a bridge to portable application code across different microcontrollers.
Typical Reset Sources
The reset handler is versatile to support multiple reset sources and entry conditions in an embedded application. Some common reset causes include:
- Power-on reset – When power is first applied to the microcontroller.
- Brownout reset – If the power supply voltage dips below a threshold.
- Watchdog reset – From a watchdog timer peripheral timeout event.
- Software reset – Resets intentionally triggered by software.
- External reset – Using a reset pin to reset from an external circuit or supervisor.
- Debug reset – When the processor is reset via the debug port.
To handle these different scenarios, the reset handler code can check processor flags or reset status registers to identify the reset source. Special handling for problematic reset sources like brownout or watchdog events can be implemented.
The application may also define a dedicated interrupt service routine (ISR) for the low power reset vector. This allows the main reset handler to remain portable across reset conditions.
Guidelines for Reset Handler Code
Here are some guidelines for effectively implementing the reset handler function:
- Keep it short and focused on initializing the application only.
- Avoid complex code since debugging capability may be limited.
- Initialize the stack pointer first before any stack variables are used.
- Use static variables with initialization instead of global variables.
- Check for and handle problematic reset sources like brownout.
- Jump to main() or the main loop function quickly.
- Minimize application initialization tasks and instead move them to main().
By following these practices, the reset handler can serve as a lean bridge from the startup sequence into the portable application code in a variety of reset scenarios.
Summary
The startup code and reset handler play complementary roles in an embedded ARM application:
- The fixed startup code acts as a hardware-specific system bootloader.
- The flexible reset handler starts execution of the portable application.
Keeping these two stages separate allows reusing application code across different microcontroller systems easily. The reset handler implements a hardware abstraction layer for transition into the common application.
Understanding the distinction between the startup and reset handler code leads to robust firmware design across various ARM platforms.