The startup code of an ARM processor written in C is responsible for configuring the processor and setting up the environment before the main() function is called. This involves tasks like initializing registers, enabling interrupts, copying data sections to RAM, and jumping to the main() function. The exact startup code varies depending on the specific ARM processor being used, the toolchain, and the application requirements. However, the general structure and key tasks are similar across most ARM C startup codes.
Purpose of the Startup Code
The main purposes of the ARM C startup code are:
- Initialize hardware peripherals like clocks, memory controllers, GPIO pins, etc.
- Configure stack pointers and heap memory
- Copy initialized data from flash to RAM
- Clear uninitialized data sections (BSS)
- Configure interrupt vectors
- Enable interrupts
- Call static constructors
- Jump to main() function
Handling these essential setup tasks allows the startup code to prepare the environment for the application code. Without proper configuration by the startup code, the processor and memory would be in an unknown state, interrupts would not function, and calling the main() function could result in a crash.
Typical Sections of an ARM C Startup Code
A typical ARM C startup code contains the following high-level sections:
1. Reset Handler
This label indicates the entry point that the processor will start executing from after a reset. It must be placed at the very start of the startup code.
2. Processor Initialization
This section sets up core registers like the stack pointer, initializes clocks, and configures memory controllers.Low-level processor initialization takes place here.
3. .data Initialization
The .data section contains global and static variables that have been initialized. This startup code routine copies the .data section from flash to RAM.
4. .bss Initialization
The .bss section contains global and static variables that have not been initialized and just reserve space. This startup code routine clears the .bss section to zero.
5. Interrupt Vector Table Setup
The interrupt vector table contains the handlers for exceptions and peripheral interrupts. This section sets the location of the vector table and populates it with handler functions.
6. Enable Interrupts
Interrupts are globally disabled on reset. This startup code section enables interrupts so that exception and peripheral interrupts can occur.
7. Call Static Constructors
Any global C++ objects with constructors need to have their constructors called at startup before main(). This section calls all the registered static constructors.
8. Jump to main()
After all initialization is complete, the startup code jumps to the main() function to begin application execution.
A Simple ARM C Startup Code Example
Here is a basic example ARM C startup code for an ARM Cortex-M4 processor using GCC: /* Reset Handler */ void Reset_Handler(void) { /* Initialize core registers */ SystemInit(); /* Copy .data sections from flash to RAM */ copy_data_sections(); /* Clear .bss sections */ clear_bss_sections(); /* Setup interrupt vector table */ setup_interrupt_vectors(); /* Enable interrupts */ __enable_irq(); /* Call C++ constructors */ call_cpp_constructors(); /* Jump to main */ main(); } /* Interrupt vectors */ void (*const vectors[])() __attribute__((section(“.vectors”))) = { (void (*)()) Reset_Handler, (void (*)()) other_interrupt_handlers }; /* .data section */ int initialized_var = 5; /* .bss section */ int bss_var; int main() { /* Application code here */ }
This shows the overall structure with the key startup code tasks needed to initialize the system and call main(). The exact implementation of each startup task would be specific to the particular ARM processor and development environment.
Key Startup Code Implementation Details
Some important aspects to note when implementing an ARM Cortex C startup code:
- The Reset_Handler label must be placed at the very first address at the beginning of the startup code.
- Low-level processor initialization is done first to setup core registers and clocks.
- The interrupt vector table must be properly aligned on a suitable memory boundary.
- Defined interrupt handlers can be weak symbols, allowing them to be overridden by the application.
- The .data and .bss sections should be linked to appropriate linker script memory regions.
- Static constructors are called before main(), invoked via the .init_array section.
- Stack pointers and heap must be setup before calling main().
Toolchain Specific Startup Code
While the overall startup sequence remains similar across toolchains, the exact syntax and implementation do vary when using different compilers and libraries. For example:
GCC Startup Code
The GNU ARM Embedded Toolchain uses startup files like startup_ARMCMx.S, which implement common routines for Cortex-M processors. The SystemInit() call handles low-level processor initialization. Interrupt vectors are defined in handlers.c.
ARM Compiler Startup Code
The ARM Compiler provides the arm_startup_xxx.s files for Cortex-M processors, containing Reset_Handler and vector table.scatter files define memory regions. Low-level init can be done via SystemInit() or in asm code inside Reset_Handler.
IAR Startup Code
IAR Embedded Workbench utilizes files like startup_xxx.s specific to each Cortex-M processor. Reset_Handler performs low-level init. Interrupt vectors are defined in vector.s. Memory regions are in linker scatter files.
So while the overall startup sequence is standardized, check your toolchain documentation for exact syntax, file naming, and initialization details when creating a startup code.
Tips for ARM C Startup Code
Here are some useful tips when working with ARM Cortex C startup code:
- Use stack protection and initialization to help catch issues early.
- Debug and step through startup to verify configuration is as expected.
- Add early initialization code before calling main() when needed.
- Place startup code in own .c and .s files separate from main application.
- Check compiler documentation for specific syntax and features.
- Enable semihosting print debugging initially, then disable in production code.
- Set up a non-maskable interrupt handler to help catch hard faults.
Conclusion
The C startup code performs the essential steps of initializing an ARM Cortex processor, configuring memory, enabling interrupts, and starting the application code. While the exact implementation varies across compilers and ARM chips, understanding the common patterns and sequences involved in a typical ARM C startup process allows developers to design robust embedded systems.
Key tasks like establishing the stack pointers, copying initialized data to RAM, clearing BSS sections, setting up the interrupt vectors, calling constructors, and jumping to main() provide the foundation for an ARM application. Following best practices around organization, low-level init, debugability, and overall startup structure helps ensure a successful program initialization sequence.