STM32启动库文件学习笔记

一、Stack–栈

EQU:宏定义的伪指令,就是给数字取一个符号名,相当于C语言中的define
SPACE:分配内存空间
AREA:告诉汇编语言一个新的代码段或数据段
PRESERVE8:当前文件堆栈需要按八字节对齐。
ALIGN:编译器对指令或数据的存放地址进行对齐,不写的话默认4字节对齐,这个不是ARM指令,是编译器的指令。
比如:ALIGN=3 表示8(2^3)字节对齐

例程
:Stack_Size   EQU   0X00000400   //定义了一个量Stack_Size表示1k,即0x00000400
		   
						AREA STACK,NOINIT,READWRITE,ALIGN=3;//STACK表示段名,NOINIT表示不初始化,READWRITE表示可读可写
		   Stack_Mem	SPACE  Stack_Size
__initial_sp    //__initial_sp紧挨着SPACE语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长,
	                  //而堆则是由低向高生长,这样可以充分利用空闲的地址空间

注意:栈的作用就是为了存放局部变量,函数调用,函数形参等,栈的大小不能超过内部SRAM的大小。如果程序比较大,
定义的局部变量很多,那么就要修改栈的大小,不然会出错。

二、Heap–堆

例程:

Heap_Size    EQU      0X00000200 //开辟堆的大小是512字节,即0x00000200

				  AREA   HEAP,NOINIT,READWRITE,ALIGN=3 //堆的名字是HEAP,
      __heap_base
      Heap_Mem     SPACE   Heap_Size
	  __heap_limit
	  
	               PRESERVE8
				   THUMB

解析: __heap_base表示堆的起始地址,__heap_limit表示堆的结束地址。堆主要用来分配动态内存,比如malloc()函数申请的
内存就是在堆上面,PRESERVE8表示当前文件堆栈按8字节对齐,THUMB表示后面的指令兼容THUMB指令,THUMB是ARM以前的指令
集,16bit,现在Cortex-M系列都使用THUMB-2指令集,THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超集。

三、向量表

例程:

AREA        RESET, DATA, READONLY

	EXPORT      __Vectors
	EXPORT      __Vectors_End
	EXPORT      __Vectors_Size

解析:定义一个数据段,名字是RESET,可读。声明__Vectors、__Vectors_End、__Vectors_Size这三个标号具有全局变量
EXPORT:声明一个标号可以被外部的文件使用,使标号具有全局属性。如果使用的是IAR编译器,则用GLOBAL这个指令。
该段函数的作用:比如我们的内核响应了一个发生的异常,对应的异常服务例程(ESR)就会被执行。为了决定ESR的入口地址,
内核使用了“向量表查表机制”,所以这里使用一张向量表。向量表其实是一个WORD(32位整数)数组,
每个下表对应一种异常,该下标元素的值就是该ESP的入口地址。向量表在地址空间中的位置是可以通过NVIC
中的一个重定位寄存器来设置的。复位后该寄存器的值为0。所以在地址0的地方必须包含一张向量表,用于初始
时候的异常分配。

__Vectors     DCD    __initial_sp     //栈顶地址
				   DCD    Reset_Handler  //复位程序地址
				   DCD    NMI_Handler
				   DCD    HardFault_Handler
				   DCD    MemManage_Handler
				   DCD    BusFault_Handler
				   DCD    UsageFault_Handler
				   DCD    0
				   DCD    0
                   DCD    0	  
				   DCD    0
				   DCD    SVC_Handler
				   DCD    DebugMon_Handler
				   DCD    0
				   DCD    PendSV_Handler
				   DCD    SysTick_Handler
		//外部中断开始
                   DCD    WWDG_IRQHandler
				   DCD    PVD_IRQHandler
				   ....//限于篇幅,中间省略
				   DCD    DMA2_Channe14_5_IRQHandler
	 __Vectors_End
	 __Vectors_Size EQU __Vectors_End - __Vectors

解析:__Vectors为向量表起始地址,__Vectors_End为向量表结束地址,两个相减即可算出向量表的实际大小。
向量表从FLASH的0地址开始放置,以四个字节为一个单位,地址0存放的是栈顶的地址,0x04存放的是复位
程序的地址,以此类推。从代码上来看,向量表中存放的都是中断服务函数的函数名,其实C语言中
的函数名就是一个地址。
DCD:分配一个或者多个字节为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中,DCD分配了
一些内存,并且以ESR的入口地址初始化它们。

四、复位程序

AREA |.text|, CODE, READONLY    //定义一个名称为.text的代码段,可读
Reset_Handler PROC
			EXPORT  Reset_Handler   [WEAK]
			IMPORT  SystemInit
			IMPORT  __main
			
			LDR     R0, =SystemInit
			BLX     R0
			LDR     R0, =main
			BX      R0
			ENDP

解析:WEAK:表示弱定义,就是如果外部文件优先定义了这个标号,则首先引用该标号,
即使外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,
在这里并不是唯一的。
IMPORT:表示该标号来自外部文件,和c语言中的extern关键字作用类似,这个程序中表示
SystemInit和__main这两个函数均来自外部文件。

LDR、BLX、BX是CM4内核的指令,作用如下:
  LDR:从存储器中加载字到一个寄存器中
  BL:跳转到由寄存器/标号给出的地址,并把跳转前的下条指令的地址保存到LR
  BLX:跳转到由寄存器给出的地址,并根据寄存器的LSE确定处理器的状态,还要
      把跳转前的下条指令地址保存到LR
  BX:跳转到由寄存器/标号给出的地址,不用返回。

说明:复位子程序是系统上电后第一个执行的程序,调用SystemInit函数初始化系统时钟,然后调用c库函数__main,
初始化用户堆栈 该函数的最后进入main()函数,最终去到C的世界,这就是为什么我们写的程序中都
有一个main()函数。
SystemInit()是一个标准的库函数,在system_stm32f10x.c这个库文件中定义,主要作用是配置系统时钟,这里调用
这个函数后,单片机的系统时钟被配置为72M。

五、中断服务程序

NMI_Handler   	PROC // 系统异常
				EXPORT NMI_Handler [WEAK] 
				B .
				ENDP
				// 限于篇幅,中间代码省略
			 
SysTick_Handler PROC
				EXPORT SysTick_Handler [WEAK] B .
				ENDP
Default_Handler PROC // 外部中断
				EXPORT WWDG_IRQHandler [WEAK]
				EXPORT PVD_IRQHandler [WEAK]
				EXPORT TAMP_STAMP_IRQHandler [WEAK]
				// 限于篇幅,中间代码省略
			  
			  LTDC_IRQHandler
              LTDC_ER_IRQHandler
              DMA2D_IRQHandler
              B .

              ENDP

说明:在启动文件里面已经写好了所有的中断服务函数,但是这些函数都是空的,真正的中断程序需要我们在外部的c文件
里面重新实现,这里只是占个位置。但是如果我们使用外设开启了某个中断但是又没有编写配套的中断服务函数
或者函数名写错那么中断来临时候就会进入启动文件预先写好的空的中断服务程序,并且在这个空函数里面无线循环,
程序就死在这里。
解析:B:跳转到一个标号,这里跳转到“.”,即表示无线循环。

六、用户堆栈初始化

例程:
 IF      :DEF:__MICROLIB  //这个宏在keil里面开启
                
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
                
                 ELSE
                
                 IMPORT  __use_two_region_memory  //这个函数由用户自己实现
                 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

                 ALIGN

                 ENDIF

                 END

说明:该程序首先判断是否定义了__MICROLIB,如果定义了则赋予 __initial_sp(栈顶地址)、__heap_base(堆起始地址)、
__heap_limit(堆结束地址)全局属性,可供外部文件调用。这个宏在keil里面设置,之后堆栈的初始化就由c库函数
__main来完成。如果没有定义,则用双段存储器模式,且声明标号__user_initial_stackheap具有全局属性,让用户自己
初始化堆栈。
解析:IF,ELSE,ENDIF:汇编的条件分支语句,跟C语言的if,else类似
END:文件结束。