All LPCOpen applications, regardless of the platform, device, or tool chain they run on, use a similar application startup sequence. Understanding how this sequence works and it's requirements can help prevent problems when developing new applications, developing a new board layer, or customizing early initialization of a board or device prior to entering main().
The common LPCOpen application startiup sequence is shown below:
The board is powered on or reset. The initial device stack is setup and reset vector is fetched and the control of the device is transferred to the reset handler in the startup code.
The startup code first calls SystemInit(), which performs low level early initialization of the device and board. SystemInit(), as implemented in LPCOpen, performs 3 functions: initial board level pin muxing, system clock setup, and external memory setup (if applicable). SystemInit() usually calls Board_SystemInit(), which is part of the board layer code.
Most board level pin muxing is perform as the first function of SystemInit(). Doing the pin muxing in one location allows the pin mux data to be organized in a common table and use less code space. The LPCOpen board layer handles pin muxing and sets up most, but not necessarily all, pins to the functions as they are connected on the board.
System clocking is setup after pin muxing. This step usually performs oscillator enable, PLL setup, main clock selection, etc. Clock setup for the same device may vary per board based in external clock input rate, oscillator rate, etc.
This step only applies to boards that have external memory such as SDRAM, NOR FLASH, external SRAM, etc. If this setup step is required, it is setup here by setting up the correct timing, initializing SDRAM, etc. If special pin muxing is required to use the external memory, it is usually done as part of the board level pin muxing functions.
External memory is setup in the application early and prior to initializing the data segments.
After SystemInit(), the code and data segments are initialized. This means that code segments not in the initial boot/run medium (such as internal FLASH) are relocated by at run-time. Pre-initialized data segments are copied to volatile memory regions and the zero-initialized data is cleared. This happens before the call to main().
For IAR and Keil startup code, this is done after SystemInit(). For LPCXpresso, this is done *prior* to SystemInit().
Finally, the main() function is called and the application is started.
All LPCopen applications that are routed via SystemInit() typically call Board_SystemInit() via the sysinit.c file. If a board layer isn't available, you can call Chip_SystemInit() instead. The chip layer system initialization file attempts to set the system up without making assumptions about the board. If you are using the SystemInit() function in sysinit.c, you can use the chip layer's startup code instead of the board layer's version by defining NO_BOARD_LIB somewhere in your project. Using Chip_SystemInit() may vary per device and may still have assumed requirements (ie, crystal rate) that may prevent it from always working correctly on every platform. See the API documentation for the Chip_SystemInit() function for any limitations on using the function.
/* Set up and initialize hardware prior to call to main */ void SystemInit(void) { #if defined(NO_BOARD_LIB) /* Chip specific SystemInit */ Chip_SystemInit(); #else /* Board specific SystemInit */ Board_SystemInit(); #endif }
If you don't want to use the standard SystemInit() that calls Chip_SystemInit() or Board_SystemInit() in sysinit.c, you can remove the file sysinit.c from the project and create a custom version of SystemInit() to use in your applicaiton.
The example below, located in the main.c file, uses the board layer's pin mux setup function, but the chip layer's PLL clock set function using the IRC.
/** * @brief Override of SystemInit() to use IRC clocking * @return Nothing */ void SystemInit(void) { /* Setup system clocking and muxing */ Board_SetupMuxing(); Chip_SetupIrcClocking(); }
Because of this approach, there are several requirements for LPCOpen software that must be met to work correctly.
Any data used in SystemInit() and functions that SystemInit() use must not be assumed to be correctly pre-initialized. This includes both 0 and non-0 values. SystemInit() is called prior to calling the data segment initialization functions, so data that is located in a data segment (non-stack data) is not yet initialized to it's starting values. All LPCOpen code used in early system bring up avoids the use on non-stack data for this reason.
This also applies to the use of any data that is located in external memory. External memory may not be correctly setup until the end of SystemInit(), so LPCopen functions used for SystemInit() should avoid the use of any memory except internal IRAM.
Because code and data relocation occurs after SystemInit(), care must be used when relocating code to IRAM. If the code used for SystemInit() functions expects to be in IRAM (and is linked for IRAM addresses), SystemInit() will fail.
The code samples below are from IAR, Keil, LPCXPresso startup code and provides a reference only to early board startup. Startup code in LPCopen can be found in the applications folder in the <chip>/startup area.
The IAR startup code first calls SystemInit() and then calls __iar_program_start(). The _iar_program_start() functions performs code and data segment relocation and calls main().
Reset_Handler LDR R0, =SystemInit BLX R0 LDR R0, =__iar_program_start BX R0
The Keil startup code first calls SystemInit() and then calls __main(). The _main() functions performs code and data segment relocation and calls main().
Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0
The LPCXpresso startup code works differently from the Keil and IAR startup code in that data segment initialization is performed first. If you need to change this behaviour, you can move the data segment initialization further down in the startup code sequence. After data segment initialization, SystemInit() is called and __main is called.
ResetISR(void) { // // Copy the data sections from flash to SRAM. // unsigned int LoadAddr, ExeAddr, SectionLen; unsigned int *SectionTableAddr; // Load base address of Global Section Table SectionTableAddr = &__data_section_table; // Copy the data sections from flash to SRAM. while (SectionTableAddr < &__data_section_table_end) { LoadAddr = *SectionTableAddr++; ExeAddr = *SectionTableAddr++; SectionLen = *SectionTableAddr++; data_init(LoadAddr, ExeAddr, SectionLen); } // At this point, SectionTableAddr = &__bss_section_table; // Zero fill the bss segment while (SectionTableAddr < &__bss_section_table_end) { ExeAddr = *SectionTableAddr++; SectionLen = *SectionTableAddr++; bss_init(ExeAddr, SectionLen); } #if defined (__USE_CMSIS) || defined (__USE_LPCOPEN) SystemInit(); #endif #if defined (__cplusplus) // // Call C++ library initialisation // __libc_init_array(); #endif #if defined (__REDLIB__) // Call the Redlib library, which in turn calls main() __main() ; #else main(); #endif