本帖最后由 15373042435 于 2021-3-28 21:24 编辑
1.启动流程
- 芯片在上电的时候,在内部时序逻辑的控制下,将0x0000 0000地址的内容读取到SP;将0x0000 0004地址的内容读取到PC。此时SP中存放的是栈顶地址,PC中存放的是Reset_Handler复位处理函数地址。在链接阶段,链接器就决定了程序中的各种数据和代码在FLASH中的存储地址。中断向量表存储在0x0000 0000空间上。
- 芯片执行Reset_Handler函数,进行时钟设置,最后跳入用户程序执行main函数。
2.启动文件分析
2.1汇编语言
2.1.1伪指令
- 伪指令是一条指令。它在程序中不是可有可无的,使用时受到严格的规范,与标准指令一样,在程序中占有固定的位置,有固定的书写格式。每条伪指令都与标准指令一样可实现特定的功能,伪指令是不能用标准指令替代的。
- 伪指令不是一条真正的指令,没有指令代码。在程序编译过程中,伪指令的功能会被实现,但伪指令会被删除,在编译后的目标文件中,不会有伪指令的编码。
- 也可以这样理解:指令是对计算机发出的命令,而伪指令则是对编译器发出的命令。在编译程序结束时,伪指令的使命就完成了。
- 伪指令是为程序开发工程师提供辅助的程序表达,让编译器实现一些标准指令所不能表达的内容。
2.1.2启动文件中用到的伪指令
- EQU:给数字常量取一个符号名,相当于C语言中的define
- AREA:指示汇编程序汇编新的代码或数据段。
- SPACE:分配内存空间
- PRESERVE8:当前文件堆栈需按照8字节对齐
- EXPORT:声明一个标号具有全局属性,可被外部的文件使用
- DCD:以字为单位分配内存,要求4字节对齐,并要求初始化这些内存
- PROC:定义子程序,与ENDP成对使用,表示子程序结束
- MSR:Move to Special register from Register. 恢复到特殊寄存器
- WEAK:弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不出错。要注意的是:这个不是ARM的指令,是编译器的,这里放在一起只是为了方便。
- IMPORT:声明标号来自外部文件,跟C语言中的EXTERN关键字类似
- B:跳转到一个标号ALIGN编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4字节对齐。要注意的是:这个不是ARM的指令,是编译器的,这里放在一起只是为了方便。
- END:到达文件的末尾,文件结束
- IF,ELSE,ENDIF:汇编条件分支语句,跟C语言的if else类似
以V3.5.0库 startup_stm32f10x_hd.s 为例。版本不同,启动文件不同(hd.s or md.s),启动代码略有差异。
2.2建立栈
栈用于存放局部变量、函数形参和返回值,为函数运行的必要条件。
Stack_Size EQU 0x00001400 AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp ; stack used for SystemInit_ExtMemCtl
__initial_spTop EQU 0x20000400 ; always internal RAM used
- STACK_SIZE EQU 0x00001400:给0x00001400起了个别名STACK_SIZE,类似于C语言的define
- AREA STACK,NOINIT,READWRITE,ALIGN=3:开辟一个数据段,名字是STACK,不初始化、可读写,2*2*2=8字节对齐。开辟的数据段空间为0,需要后面的内容把数据段空间撑起来。
- Stack_Mem SPACE Stack_Size__initial_sp :为STACK数据段分配一段以STACK_SIZE为长度的内存空间,栈顶地址是__initial_sp。栈的生长是由高地址向低地址生长的。
- __initial_spTop EQU 0x20000400:给0x20000400起了个别名__initial_spTop 这部分代码建立了2个栈。
第一个栈
栈顶地址是__initial_spTop,用于启动代码文件中的SystemInit_ExtMemCtl函数。对于startup_stm32f10x_md.s,是没有这个函数的,所以也就没有建立这个栈。
SystemInit_ExtMemCtl:用来初始化外部ram。
下面的代码来自启动文件。
SystemInit_ExtMemCtl PROC
EXPORT SystemInit_ExtMemCtl [WEAK]
BX LR
ENDP
- SystemInit_ExtMemCtl:函数名
- PROC:函数开始;ENDP:函数结束
- EXPORT:此函数外部可引用,类似于extern
- WEAK:弱定义;如果外部文件定义了SystemInit_ExtMemCtl,则使用外部函数。
- BX:函数指针跳转
- LR:函数调用的返回地址
#ifdef DATA_IN_ExtSRAM
void SystemInit_ExtMemCtl(void)
{
省略了代码
}#endif /* DATA_IN_ExtSRAM */
如果定义DATA_IN_ExtSRAM了,则使用外部文件定义的SystemInit_ExtMemCtl函数。
第二个栈
栈顶地址是__initial_sp,用于用户的应用程序。
2.3建立堆
堆用来存放程序运行中动态分布的内存段,比如malloc函数。
Heap_Size EQU 0x00000400
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
- __heap_base:堆的开始地址
- __heap_limit:堆的结束地址
- Heap_Size:堆的空间
2.4建立中断向量表
PRESERVE8THUMB
- PRESERVE8:指定当前文件所占空间按照8字节对齐
- THUMB:表示后面的指令兼容THUMB指令集
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size__Vectors
DCD __initial_spTop ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
中间内容有省略
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
- AREA RESET, DATA, READONLY:开辟数据段,段名是RESET,只读。
- DCD:以字为单位分配内存。多个DCD,用于分配多个连续的空间。
- __Vectors:连续空间的开始地址。
- __Vectors_End:连续空间的结束地址。
- __Vectors_Size:连续空间的大小。
- __initial_spTop:为启动过程中,用到的SystemInit_ExtMemCtl函数分配的栈。当启动流程完成,切换到用户main函数之前,需要将栈修改为用户栈__initial_sp。
STM32有三个特殊的寄存器:SP、PC、LR。
SP:栈顶指针。栈用于存放局部变量、函数形参和返回值,为函数运行的必要条件。
PC:程序计数器。用于读取程序指令。
LR:保存函数调用的返回地址。
芯片在上电的时候,在内部时序逻辑的控制下,将0x0000 0000地址的内容保存到SP;将0x0000 0004地址的内容保存到PC。
中断向量表存储在0x0000 0000空间上,所以SP将保存栈顶地址__initial_spTop,PC将保存复位处理函数地址Reset_Handler。所以芯片上电以后,首先执行的便是Reset_Handler函数。
; Reset handler routine
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
LDR R0, = SystemInit_ExtMemCtl ; initialize external memory controller
BLX R0
LDR R1, = __initial_sp ; restore original stack pointer
MSR MSP, R1
LDR R0, =__main
BX R0
ENDP
在Reset_Handler函数中:
- 程序首先执行SystemInit_ExtMemCtl函数。如果没有定义DATA_IN_ExtSRAM,程序将会直接从SystemInit_ExtMemCtl函数跳出。
- 接下来,程序顺序执行
LDR R1, = __initial_sp; restore original stack pointer
MSR MSP, R1
将栈顶指针替换为__initial_sp。因为此时SystemInit_ExtMemCtl已经执行完成,需要将此时的栈顶地址修改为用户栈区。
3.执行__main函数。在__main函数中,将跳入用户主程序main。
|