声明:这次讲解真的超级详细,根据标准库的启动文件,一步步介绍的。看的时候可以与启动文件一起查看学习。
1、栈
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
【解释EQU】给数字常量取一个名字,相当于C 语言中的define
【解释AREA STACK】 标明汇编一个新的代码段或者数据段 段名字为STACK
【解释NOINIT】 不初始化
【解释READWRITE】 表示这个段内容是可读可写的
【解释ALIGN】编译器对指令或者数据的存放地址进行对齐,这里是8字节对齐(2^3)
【解释SPACE】 这个指令标明分配内容空间
【解释 SPACE Stack_Size】 分配大小为Stack_Size的空间
【解释__initial_sp】在initial_sp之前分配了1KB字节大小的空间,initial_sp表示栈的结束地址。比如我的此时__initial_sp值为0x20000608,因为大小位0x400,所以栈开始地址是0x2000_0208。有的朋友可能会问没什么不是从0x2000_0000开始,我这里使用了堆。堆的空间范围是0x20000008——0x20000208。
拓展:有的同学可能好奇,0x2000_0000与0x2000_0004呢?
0x20000000 存的是 __microlib_freelist
0x20000004 存的是 __microlib_freelist_initialised
我这里是使用了微库(MicroLIB)(KEIL软件的魔术棒->target)
总结:开辟一个大小位0x0000_0400(1KB)大小的栈空间,段名字为STACK,内部是8字节对齐,段内空间数据是可读可写的。
有朋友可能会问,什么东西会存放到栈呢?
所有的局部变量等等。比如我这里写了 int add (int a, int b)函数,那么当程序执行到这个函数的时候,会把这个函数内部定义的局部变量,存放到栈中。
2、堆
首先说明:堆空间的真正开辟是在项目中有使用malloc函数申请内存。如果没有使用malloc,那么虽然在.s文件中定义了分配堆空间,实际情况下,堆空间是没有开辟的。
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
【解释:Heap_Size EQU 0x00000200】 相当于宏定义 Heap_size 0x0000_0200。
【解释:AREA HEAP, NOINIT, READWRITE, ALIGN=3】 与STACK一样,只是这个段的名字为HEAP。
【解释:__heap_base】 此时还没有开辟空间,此时表示堆的起始地址。
【解释:Heap_Mem SPACE Heap_Size】分配大小为Heap_Size的空间为堆空间。
【解释:__heap_limit】此时__heap_limit这个值表示堆空间的结束地址。
3、 PRESERVE8与THUMB指令
PRESERVE8
THUMB
【解释:PRESERVE8】PRESERVE8是ARM汇编指令,确保8字节堆栈对齐。
【解释:THUMB】表明之后指令兼容THUMB指令,其实是ARM指令集的一种,指令长度为16位,需要通过两条指令来访问32位的数据和地址空间。还有THUMB-2指令集,它支持16位和32位的混合指令格式,是THUMB的拓展版。
4、向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
【解释:AREA RESET】 定义一个段,段名为RESET。
【解释:READONLY】 只读,不能写。
【解释:DATA】 DATA表明这个区域存储的是数据。
【解释:EXPORT】 表明之后的标号是全局的,可以被外部文件使用。
【解释:__Vectors】 代表向量表起始地址
【解释:__Vectors_End】代表向量表的结束地址
【解释:__Vectors_Size】代表向量表大小
5、详细向量表
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
【解释:DCD】 其是一个指令,用于分配一个或多个以字为单位的内存,以四字节对齐,并会初始化这些内存。DCD在STM32的启动文件中用于指定各种中断处理程序的入口地址。
我们知道中断向量表是放在代码段的最最前面的,比如当我们程序在flash区域运行时,第一个地址是0x0800_0000,这个其实就是向量表的起始地址,也就是__Vectors。
【解释:__Vectors DCD __initial_sp】分配第一个以字为单位的内存,以四字节对齐,该地址处初始化内容为__initial_sp,__initial_sp是栈的结束地址。也就是0x0800_0000地址处存放的内容为0x20000608。
【解释:DCD Reset_Handler】分配第二个以字为单位的内存,以四字节对齐,该地址处初始化内容为Reset_Handler,我们知道函数名称表示的其实就是地址,所以,此时该内存的内容为Reset_Handler的地址。也就是0x0800_0004 存放的是Reset_Handler函数地址,我的地址为0x08000145,0x08000145说明Reset_Handler函数的地址起始地址了,对吧??
【解释:DCD XXX_Handler】经过前面的介绍你们应该已经懂了,之后的内容其实就是0x0800_0004 + 0x4 * i 存放的是各个中断函数的地址。
6、向量表的大小
__Vectors_Size EQU __Vectors_End - __Vectors
上面这段的意思就是相当于 __Vectors_SIze = __Vectors_End - __Vectors,计算出了向量表的大小。
7、.text段
AREA |.text|, CODE, READONLY
看到AREA,我们就应该知道它是一个定义段名字的指令,定义了一个.text的段。这个段的类型为代码段,是只读的。
注意:在计算机程序中,“.text”段通常用于存放程序的执行代码,也就是机器指令。这是程序执行的核心部分,通常存储在只读内存中,以防止程序运行过程中被修改。|符号在这里用作分隔符,用于区分段名和段的属性。
8、复位中断
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
【解释:PROC ENDP】PROC与ENDP成对使用,相当于规定了一个范围。PROC表示子程序的开始,ENDP表示子程序的结束。
【解释:EXPORT Reset_Handler】 定义Reset_Handler有全局属性,可被外部文件使用。
【解释:WEAK】弱定义,如果你在其他文件中又定义了带有WEAK的函数,那么程序就会执行你自己定义的函数了。注意;weak定义的名字是不能改变的。
【解释:IMPORT】 声明之后的标号来自外部文件,就好像C语言的extern一样。这里的__main可以先简单理解为我们写的main入口函数。
【解释:IMPORT SystemInit】 声明这个SystemInit函数是外部文件的。
【解释:LDR R0, =SystemInit BLX R0】将SystemInit函数的地址给R0寄存器,CPU跳转到R0寄存器中的地址去执行。执行完之后,返回接着执行之后程序。
【解释:LDR R0, =__main BX R0】将main函数地址给R0寄存器,之后CPU执行R0寄存器的地址。并且不会返回了。这样到这就进入到了我们的主程序了。
注意:到此为止,程序就从汇编文件,完成各种初始化配置,进入到我们写的主程序了。
9、Systick函数中断
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
经过介绍上方的复位中断,其他之后的其实都差不多。这里我仅仅介绍一点:
【解释:B .】 B指令跳转到一个标号,这里的标号是‘.’,表示不限循环。
那么这段到底什么意思呢??上面说过WEAK是一个弱定义,如果你在其他函数中定义了Systick_Handler函数,那么程序会执行你定义的Systick_Handle函数。如果说你没有在其他文件定义Systick_Handle函数,那么当Systick中断来临的时候,就会执行启动文件中的Systick_Handle函数,并且不干其他事,只是无限循环。
说明:之后的弱中断,我就不介绍了,其实都一样的。
10、堆栈初始化
ALIGN
在启动文件的初始化栈和堆之前有一个单独的ALIGN,ALIGN表示对地址进行对齐,一般后面是跟数字的,比如上方的ALIGN = 3,表示2^3=8字节对齐,默认情况下是4,所以这里是4字节对齐。
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheapLDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LRALIGN
ENDIF
END
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
这里其实就是在正在进入main函数之前,完成栈和堆空间的初始化。就不详细介绍了。