STM32启动过程分析

硬件: STM32F1系列

软件环境:Keil 4.54

注:本文中提到的RTOS以RT-Thread为例,不涵盖所有RTOS的情况

 

在Keil MDK中新建工程时会根据所选的device自动生成启动代码文件startup.s,该文件的作用可根据其头部的注释看出

This module performs:
;* - Set the initial SP
;* - Set the initial PC == Reset_Handler
;* - Set the vector table entries with the exceptions ISR address
;* - Configure the clock system
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* After Reset the CortexM3 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.

 在startup.s中,完成了堆栈大小和中断向量表的设置。默认的栈大小为400字节,堆大小为200字节,可自行更改。这个栈在bare-metal系统中为全局所使用,在带RTOS的系统中被操作系统内核和中断所使用,如果无多层函数嵌套调用,通常是够用的。堆在使用malloc()的时候会被用到。堆栈的设置必须用汇编语言完成,因为C语言通常会用到函数,而函数调用是依赖于堆栈的。关于startup.s的详细分析请参考004:STM32启动文件详解及SystemInit函数分析一文

 

 系统上电后,默认从地址为0的地方开始执行。在STM32中,若根据boot引脚选择从主闪存存储器启动,则主闪存存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x0800 0000)访问它。0x08000000开始的一段区域存放的是中断向量表(即startup.s中__Vectors开始的部分)
; Vector Table Mapped to Address 0 at Reset  
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp                    ; Top of Stack
                DCD     Reset_Handler                   ; Reset Handler   
 先是执行__initial_sp设置主堆栈指针MSP(相关概念请参考宋岩翻译的《Cortex-M3权威指南》),而后执行复位操作Reset_Handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
     IMPORT  __main
     IMPORT  SystemInit
                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP
 Reset_Handler中首先执行SystemInit()函数(在system_stm32f10x.c文件中定义),该函数主要完成了RCC时钟的设置。接着执行__main()处的代码。在Keil IDE的工程窗口中,是无法搜索到__main()的定义的,但在汇编代码和工程map文件中可以找到它的身影
__main                  0x08000121   Thumb Code     0  entry.o(.ARM.Collect$$$$00000000)

 推测应该是在entry.c文件中,而entry.c文件应该是在Keil自带的library里。 

 参考MDK __main()代码执行过程分析一文,__main()中主要通过

1. __scatterload()把RW/RO输出段从装载域地址复制到运行域地址,并完成ZI运行域的初始化工作。

2. __rt_entry()初始化堆栈,完成库函数的初始化,最后自动跳转向main()函数。其中__user_initial_stackheap()是在startup.s中定义的

EXPORT  __user_initial_stackheap                 
__user_initial_stackheap
                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

根据AAPCS的规定,栈任何时候都得4字节对齐,在调用入口得8字节对齐。对于带RTOS的系统,该函数根据

AREA STACK, NOINIT, READWRITE, ALIGN=3
AREA    HEAP, NOINIT, READWRITE, ALIGN=3
PRESERVE8
 保证了主堆栈指针MSP是遵守规定的,而线程堆栈指针PSP全靠自己来保证每次进入C世界时是8字节对齐,通常的做法是在程序中使用__attribute__((aligned(8)))来告知编译器在分配空间时采用8字节对齐。在发生中断时,如果当前正在使用的栈指针不是8字节对齐,则先把SP-4,调整为8字节对齐,参考 cortex-m3 栈的8字节对齐一文

补充:startup.s定义了中断处理函数

NMI_Handler     PROC        ;过程的开始
                EXPORT  NMI_Handler  [WEAK]
                B    .      ;原地跳转(即无限循环),
                ENDP        ;过程的结束

 其中WEAK作为若定义,意思是如果在别处也定义该symbol,在链接时用别处的地址,而stm3210x_it.c这个文件通常会进行这些中断处理函数的重新定义

 

 

猜你喜欢

转载自lanxinyuchs.iteye.com/blog/2086357