Detailed analysis of STM32 startup code

Publisher:bobojrtLatest update time:2019-04-01 Source: eefocusKeywords:STM32 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

1. Stack memory


The stack storage area is a data storage area opened up by the user in the SRAM (or RAM) of the on-chip memory, and the size of the stack area can be arbitrarily specified according to the user's needs (as long as it does not exceed the size of the SRAM or RAM), and the location of the stack area is allocated by the compiler.

 

The stack pointer SP of the Cortex-M3/M4 processor is "decrement when full and increment when empty", showing the characteristic of reverse growth downward. 


The storage characteristics of data in the stack area are "first in, last out, last in, first out".


This characteristic is determined by the way the stack pointer moves. The pointer value corresponding to the data pushed onto the stack first is relatively large, and the pointer value corresponding to the data pushed onto the stack later is relatively small. When popping the stack, the value of the stack pointer increases incrementally, so the data with a larger pointer value is popped last.


The role of the stack:


Storage of local variables, transfer of data between functions or subroutines when calling a function (saving of formal parameters, in fact, all local variable units defined in the called function will be released once the function call is completed), saving of field data when calling a function, and saving of field data when an interrupt occurs.


2. Function of startup code


The execution of a C program starts with the main function main().


But how does the microcontroller find the main() function after it is powered on?


Obviously, it is impossible for us to find the entry address of the main() function from the hardware.


In fact, the entry address of the main() function is assigned by the compiler during the compilation process.


In addition, the microcontroller has a startup process from the time it is powered on to the execution of the main() function. This startup process is exactly the process of executing the startup code, and it takes a very short time.


The role of the startup code: 


1) Initialize the stack pointer SP == _initial_sp; 


2) Initial PC pointer == Reset_Handler (reset handler); 


3) Initialize the interrupt vector table; 


4) Configure the system clock; 


5) Call the C library _main() function to initialize the user stack, and finally call the main() main function to go to the C world.


3. Write startup code


Generally, standard CMSIS startup codes are in assembly language. Before using C to write startup code, we need to first consider whether C can be used to write startup code?


a) Correctly place the exception & interrupt vector table in the storage space;


b) Scatter loading & C Library initialization (call __main function);


c) Stack initialization.


The function a) is implemented in the standard assembly startup code by declaring a data segment (that is, the assembly instruction DCD xxx). We can use an array to do this in C. The key is to place it in the specified position, which requires scattered loading. Therefore, a) should be achievable;


b) is the easiest to implement, just call __main directly;


c) Although you can also know how the stack is initialized by reading the standard CMSIS assembly startup code.


Taking LPC54608 as an example, early chips had different processing methods. For example, the STM32F103 series that everyone is familiar with requires the startup code and scattered loading to be completed.


3.1. Exception & interrupt vector table


We said before that this can be achieved with an array, but an ordinary array certainly cannot, because the essence of these vectors is the entry point of the interrupt service function, that is, the "function pointer", so this array must be a function pointer array:


So we first declare a function pointer type:


typedef void ( *__vector )( void );


Then define a function pointer array named __vector_table:


__vector __vector_table[] = {};

    This array is easy to make, but the most important thing is the placement. We must ensure that the vector table is placed at the 0x00000000 address of the Flash (the default vector table address in the LPC54608 chip). This is the key to the problem.


C and assembly cannot solve this problem. The linker in the compiler development environment is responsible for placing the program in the storage space, and the linker is guided by the scattered loading script file, which requires the intervention of scattered loading. 


For example, among the various development environments commonly used to develop MCUs (Keil, IAR, Eclipse, etc.), the one that really plays a compiling role is the compiling kernel. Keil's compiling kernel is ARMCC (sometimes written as CCARM), IAR's compiling kernel is ICCARM, and Eclipse is generally GCC. The compiling kernel is actually divided into four functional modules:


Preprocessor, Compiler, Assembler, Linker. The preprocessor is responsible for combining various header files with the corresponding C files, deleting comments, expanding macro definitions, etc.


The compiler is responsible for converting C language into the corresponding assembly language;


The assembler is responsible for converting the assembly language into a relocatable target file *.o, which is already a binary file;


The linker is responsible for linking all .o files into a whole and allocating real physical addresses (which is the scattered loading mentioned repeatedly above).


How to arrange the exception & interrupt vector table?


Here we take Keil MDK as an example (IAR and GCC have different syntax in this part, but the principle is the same). There is a special description script file to guide how the linker works. In Keil, this file has the extension .sct/.scf. Here you can find the scatter loading description file:


Here is a direct list of the scatter-loading files I wrote for my own startup code:


load_rom 0x00000000 0x00080000

{

    vector_rom 0x00000000 0x400

    {

        *( vector_table, +first)        

    }

    

    execute_rom 0x400 FIXED 0x0007FC00

    {

        *( InRoot$$Sections )

        .any( +ro )

    }

    

    execute_data 0x20000000 0x00010000   

    {

        .any ( +rw +zi )

    }

    

    ARM_LIB_HEAP +0 empty 0x400 {}

    ARM_LIB_STACK 0x20020000 empty -400 {}

}

The +first in *( vector_table, +first) is used to ensure that the necklace table is placed at the front of the Flash address. It should be noted here that the language in the scattered loading file is a script language, which is neither C nor assembly. This file cannot be compiled or debugged. If it is written incorrectly, it can only be modified through experience and cannot be debugged.


If we want to place an array, we need to declare a data segment Symbol, and then use scatter loading to specify the placement of this data segment Symbol, which is +first. So we need to first let this array generate a segment:


const __vector __vector_table[] __attribute__( ( section ( "vector_table" ), used ) ) = {};


So we use __attribute__ to achieve it.


We complete the stack specification in scatter loading, namely:


ARM_LIB_HEAP +0 empty 0x400 {}

ARM_LIB_STACK 0x20020000 empty -400 {}

These two sentences mean declaring a heap of size 0x400 and a stack of size 0x400. Note that the heap and the stack are different. Simply put, function calls will occupy stack space, and using functions such as malloc will generate heap allocation. For specific details, please go to Baidu.


*( InRoot$$Sections )


This part specifies the placement of the C Library and the scattered loading code;


In summary, this scatter-loading file ultimately places the code in memory space in a form similar to the following:

 

The __attribute__ keyword is used to specify the attributes of symbols such as functions and variables. It is a C language keyword that can be recognized by the compiler. Of course, this depends on what compiler is used. Different C compilers have slightly different syntax. Some C compilers do not even support the __attribute__ keyword. The following is the functions that can be achieved by the __attribute__ keyword in Keil's help document:


__attribute__( ( section ( "vector_table" ), used ) ), where "section("vector_table")" means placing a section named vector_table in the elf (relocatable object file) file. The used keyword specifies to the compiler that the variable should be kept as a static type in the OBJ file.


Combined with the +first attribute of *( vector_table, +first) of the scattered loading, we have successfully defined a vector data segment named vector_table that is placed at the front address 0x00000000 of the Flash and will not be optimized away by the compiler, and the value will not be changed.


This is a special stack label recognized by the compiler, which can be used in scatter loading, assembly or C language. The main stack pointer MSP is placed at address 0x00000000 (the first address of the vector table), and the PC pointer is placed at address 0x00000004, so we first import this label:


0x00000004 places the PC pointer, we need to put the reset vector here:


3.2, __reset_handler reset function


This is relatively simple. The most important thing is to call __main. Some initializations should be placed before __main. The FPU of LPC54608 and some SRAM switches should be placed before __main.


First import the __main function:


extern void __main( void );


Then comes the function body of __reset_handle:


void __reset_handler ( void )

{

#if ( ( __FPU_PRESENT == 1 ) && ( __FPU_USED == 1 ) )

    SCB->CPACR |= ( ( 3UL << 10 * 2 ) | ( 3UL << 11 * 2 ) ); // set CP10, CP11 Full Access 

#endif  

    SCB->VTOR = ( uint32_t ) 0x00000000;

    SYSCON->ARMTRACECLKDIV = 0;

    SYSCON->AHBCLKCTRLSET[ 0 ] = SYSCON_AHBCLKCTRL_SRAM1_MASK | SYSCON_AHBCLKCTRL_SRAM2_MASK | SYSCON_AHBCLKCTRL_SRAM3_MASK;

    

    __main();

}

Because it is in C language, it will be easier to add the function body you need before __main.


3.3. Default exception & interrupt handling function


Take one of the vectors as an example: (USB0 interrupt)


Function body:


void usb0_irqhandler ( void )                 

{

    while( 1 );

}   

Then declare it as a "weak function" and still use __attribute__ to specify its attribute as "weak":


extern void usb0_irqhandler ( void ) __attribute__( ( weak ) );    

    If you do not write an interrupt service function, but the program enters this interrupt, the program will enter the default interrupt service function. If you write one yourself, your own interrupt service function will overwrite the function declared as "weak", of course, the premise is that the function name is the same.


Keywords:STM32 Reference address:Detailed analysis of STM32 startup code

Previous article:STM32 byte alignment #pragma pack
Next article:Detailed explanation of STM32 startup code assembly instructions

Recommended ReadingLatest update time:2024-11-23 04:46

Question about using printf function in stm32
If you want to use printf in mdk, you need to redefine the fputc function and avoid using semihosting (semihosting mode). The default output device of the standard library function is the display. To output to the serial port or LCD, you must redefine the functions related to the output device called in the standard
[Microcontroller]
Make the external SRAM of STM32 operate the same as the internal SRAM
A few days ago, I saw someone asking this question on the Open Source Electronics Forum (openedv.com), so I deliberately did this experiment. In this way, using external SRAM is the same as using internal SRAM. There is no need to apply for memory yourself, nor do you need to consider memory addresses. Let the compiler
[Microcontroller]
Make the external SRAM of STM32 operate the same as the internal SRAM
About the problem of STM32 serial port inexplicably entering interruption
The stm32 serial port printf output always enters the interrupt entry address when executed at full speed. If the interrupt function is blocked, it will display execution B when executed at full speed , indicating that the interrupt entry address cannot be found, and then the interrupt function is turned on, and it di
[Microcontroller]
PWM asynchronous drive buzzer based on stm32 processor
In the past two days, I have studied the PWM (pulse width modulation) of the M3 processor to achieve asynchronous control of the buzzer due to work requirements. In view of the fact that the blocking control of the buzzer is time-consuming and affects the user experience, the original blocking control scheme has been
[Microcontroller]
PWM asynchronous drive buzzer based on stm32 processor
STM32 independent watchdog IWDG time limit
Determination of STM32 independent watchdog IWDG time limit    Watchdog timing limit = value of IWDG_SetReload() / watchdog clock frequency    Watchdog clock frequency = frequency of LSI (internal low-speed clock) (40KHz) / frequency division number 1. Example of STM32 watchdog The IW
[Microcontroller]
STM32 Notes External Interrupt GPIO
b) Initialization function definition: void EXTI_Configuration(void); //Define IO interrupt initialization function c) Initialization function call: EXTI_Configuration(); //IO interrupt initialization function call Simple application: d) Initialization function: void EXTI_Configuration(void) {   EXTI_InitTypeDef
[Microcontroller]
STM32 flash learning summary
(1) Introduction to Flash          Different models of STM32 have different FLASH capacities, the smallest is only 16K bytes, and the largest is 1024K bytes.  The FLASH capacity of STM32F103ZET6 is 512K bytes, which is a large-capacity product.      The flash memory module of STM32 consists of three parts: main memory
[Microcontroller]
STM32 memory management implements malloc, free, remalloc and other functions
I wrote a memory management function on STM32 these days, which implements malloc, free and remalloc functions. It also implements a function for querying memory usage. Experimental environment: ALIENTEK STM32 Mini development board The idea is as follows: manage the memory in blocks. The memory pool is divided i
[Microcontroller]
Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号