本文的源码文件名为:startup_hcf460.S,是一个纯汇编文件。
首先要掌握一些基础的汇编语法,主要用到的列表标出(参考stm32相关资料):
因为和stm32不同,华大芯片的sram3区域有点特殊,参考华大芯片用户手册。
启动文件中使用STR指令,STR指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR指令用亍从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常
用,丏寻址方式灵活多样,使用方式可参考指令LDR。
指令示例:
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。”
str r1, [r0] ;将r1寄存器的值,传送到地址值为r0的(存储器)内存中
注意分号;在汇编里面是注释的标识,类似C++ 的//。
1. 栈区域
- Stack_Size EQU 0x00000400
-
- AREA STACK, NOINIT, READWRITE, ALIGN=3
- Stack_Mem SPACE Stack_Size
- __initial_sp
开辟栈的大小为0x00000400(1k B),名字为STACK,NOINIT即不初始化,可读可写,8(23)字节对齐。栈是用于局部变量、函数调用、函数形参等的开销,栈的大小不能超过内部SRAM的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬fault,这时就要考虑下是不是栈不够大,溢出了。EQU:宏定义的伪指令,相当于等于,类似于C中的define。
AREA:告诉汇编器汇编一个新的代码段或者数据段。
STACK表示段名,这个可以任意命名;NOINIT表示不初始化;READWRITE表示可读可写,ALIGN=3,表示按照2^3对齐,即8字节对齐。
SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于Stack_Size。标号__initial_sp紧挨着SPACE语句放置,表示栈的结束地址,即栈顶地址(栈是由高向低生长的)。
2. 堆区域
- Heap_Size EQU 0x00000200
-
- AREA HEAP, NOINIT, READWRITE, ALIGN=3
- __heap_base
- Heap_Mem SPACE Heap_Size
- __heap_limit
开辟堆的大小为0x00000200(512字节),名字为HEAP,NOINIT即不初始化,可读可写,8(2^3)字节对齐。__heap_base表示堆的起始地址,__heap_limit表示堆的结束地址(堆是由低向高生长的,与栈的生长方向相反)。堆主要用于动态内存的分配,像malloc()函数申请的内存就在堆上面。
- PRESERVE8
- THUMB
PRESERVE8:指定当前文件的堆栈按照8字节对齐。
THUMB:表示后面指令兼容THUMB指令。
THUBM是ARM以前的指令集,16位。现在Cortex-M系列都使用THUMB-2指令集,THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超集。
3. 向量表
- AREA RESET, DATA, READONLY
- EXPORT __Vectors
- EXPORT __Vectors_End
- EXPORT __Vectors_Size
定义一个数据段,名字为RESET,可读,并声明__Vectors、__Vectors_End和__Vectors_Size这3个标号具有全局属性,可供外部的文件调用。
EXPORT:声明一个标号可被外部的文件使用,使标号具有全局属性。如果是IAR编译器,则使用GLOBAL这个指令。当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定ESR的入口地址,内核使用了“向量表查表机制”。这里使用一张向量表,见下表。向量表其实是一个WORD(32位整数)数组,每个下标对应一种异常,该下标元素的值则是该ESR的入口地址。向量表在地址空间中的位置是可以设置的,通过NVIC中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为0。因此,在地址0(即Flash地址0)处必须包含一张向量表,用于初始时的异常分配。要注意的是,这里有个另类:0号类型并不是什么入口地址,而是给出了复位后MSP的初值。
中断向量表可以参考用户手册,注意的是,官网上面已经更新了driver库,用户手册也更新了。
向量表的代码比较长,占据了启动代码的大部分篇幅。
- __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 SVC_Handler ; SVCall Handler
- DCD DebugMon_Handler ; Debug Monitor Handler
- DCD 0 ; Reserved
- DCD PendSV_Handler ; PendSV Handler
- DCD SysTick_Handler ; SysTick Handler
-
- ; Peripheral Interrupts
- DCD IRQ000_Handler ; IRQ000_Handler
- DCD IRQ001_Handler ; IRQ001_Handler
- DCD IRQ002_Handler ; IRQ002_Handler
- DCD IRQ003_Handler ; IRQ003_Handler
- DCD IRQ004_Handler ; IRQ004_Handler
- DCD IRQ005_Handler ; IRQ005_Handler
- DCD IRQ006_Handler ; IRQ006_Handler
- DCD IRQ007_Handler ; IRQ007_Handler
- DCD IRQ008_Handler ; IRQ008_Handler
- DCD IRQ009_Handler ; IRQ009_Handler
- DCD IRQ010_Handler ; IRQ010_Handler
- DCD IRQ011_Handler ; IRQ011_Handler
- DCD IRQ012_Handler ; IRQ012_Handler
- DCD IRQ013_Handler ; IRQ013_Handler
- DCD IRQ014_Handler ; IRQ014_Handler
- DCD IRQ015_Handler ; IRQ015_Handler
- DCD IRQ016_Handler ; IRQ016_Handler
- DCD IRQ017_Handler ; IRQ017_Handler
- DCD IRQ018_Handler ; IRQ018_Handler
- DCD IRQ019_Handler ; IRQ019_Handler
- DCD IRQ020_Handler ; IRQ020_Handler
- DCD IRQ021_Handler ; IRQ021_Handler
- DCD IRQ022_Handler ; IRQ022_Handler
- DCD IRQ023_Handler ; IRQ023_Handler
- DCD IRQ024_Handler ; IRQ024_Handler
- DCD IRQ025_Handler ; IRQ025_Handler
- DCD IRQ026_Handler ; IRQ026_Handler
- DCD IRQ027_Handler ; IRQ027_Handler
- DCD IRQ028_Handler ; IRQ028_Handler
- DCD IRQ029_Handler ; IRQ029_Handler
- DCD IRQ030_Handler ; IRQ030_Handler
- DCD IRQ031_Handler ; IRQ031_Handler
- DCD IRQ032_Handler ; IRQ032_Handler
- DCD IRQ033_Handler ; IRQ033_Handler
- DCD IRQ034_Handler ; IRQ034_Handler
- DCD IRQ035_Handler ; IRQ035_Handler
- DCD IRQ036_Handler ; IRQ036_Handler
- DCD IRQ037_Handler ; IRQ037_Handler
- DCD IRQ038_Handler ; IRQ038_Handler
- DCD IRQ039_Handler ; IRQ039_Handler
- DCD IRQ040_Handler ; IRQ040_Handler
- DCD IRQ041_Handler ; IRQ041_Handler
- DCD IRQ042_Handler ; IRQ042_Handler
- DCD IRQ043_Handler ; IRQ043_Handler
- DCD IRQ044_Handler ; IRQ044_Handler
- DCD IRQ045_Handler ; IRQ045_Handler
- DCD IRQ046_Handler ; IRQ046_Handler
- DCD IRQ047_Handler ; IRQ047_Handler
- DCD IRQ048_Handler ; IRQ048_Handler
- DCD IRQ049_Handler ; IRQ049_Handler
- DCD IRQ050_Handler ; IRQ050_Handler
- DCD IRQ051_Handler ; IRQ051_Handler
- DCD IRQ052_Handler ; IRQ052_Handler
- DCD IRQ053_Handler ; IRQ053_Handler
- DCD IRQ054_Handler ; IRQ054_Handler
- DCD IRQ055_Handler ; IRQ055_Handler
- DCD IRQ056_Handler ; IRQ056_Handler
- DCD IRQ057_Handler ; IRQ057_Handler
- DCD IRQ058_Handler ; IRQ058_Handler
- DCD IRQ059_Handler ; IRQ059_Handler
- DCD IRQ060_Handler ; IRQ060_Handler
- DCD IRQ061_Handler ; IRQ061_Handler
- DCD IRQ062_Handler ; IRQ062_Handler
- DCD IRQ063_Handler ; IRQ063_Handler
- DCD IRQ064_Handler ; IRQ064_Handler
- DCD IRQ065_Handler ; IRQ065_Handler
- DCD IRQ066_Handler ; IRQ066_Handler
- DCD IRQ067_Handler ; IRQ067_Handler
- DCD IRQ068_Handler ; IRQ068_Handler
- DCD IRQ069_Handler ; IRQ069_Handler
- DCD IRQ070_Handler ; IRQ070_Handler
- DCD IRQ071_Handler ; IRQ071_Handler
- DCD IRQ072_Handler ; IRQ072_Handler
- DCD IRQ073_Handler ; IRQ073_Handler
- DCD IRQ074_Handler ; IRQ074_Handler
- DCD IRQ075_Handler ; IRQ075_Handler
- DCD IRQ076_Handler ; IRQ076_Handler
- DCD IRQ077_Handler ; IRQ077_Handler
- DCD IRQ078_Handler ; IRQ078_Handler
- DCD IRQ079_Handler ; IRQ079_Handler
- DCD IRQ080_Handler ; IRQ080_Handler
- DCD IRQ081_Handler ; IRQ081_Handler
- DCD IRQ082_Handler ; IRQ082_Handler
- DCD IRQ083_Handler ; IRQ083_Handler
- DCD IRQ084_Handler ; IRQ084_Handler
- DCD IRQ085_Handler ; IRQ085_Handler
- DCD IRQ086_Handler ; IRQ086_Handler
- DCD IRQ087_Handler ; IRQ087_Handler
- DCD IRQ088_Handler ; IRQ088_Handler
- DCD IRQ089_Handler ; IRQ089_Handler
- DCD IRQ090_Handler ; IRQ090_Handler
- DCD IRQ091_Handler ; IRQ091_Handler
- DCD IRQ092_Handler ; IRQ092_Handler
- DCD IRQ093_Handler ; IRQ093_Handler
- DCD IRQ094_Handler ; IRQ094_Handler
- DCD IRQ095_Handler ; IRQ095_Handler
- DCD IRQ096_Handler ; IRQ096_Handler
- DCD IRQ097_Handler ; IRQ097_Handler
- DCD IRQ098_Handler ; IRQ098_Handler
- DCD IRQ099_Handler ; IRQ099_Handler
- DCD IRQ100_Handler ; IRQ100_Handler
- DCD IRQ101_Handler ; IRQ101_Handler
- DCD IRQ102_Handler ; IRQ102_Handler
- DCD IRQ103_Handler ; IRQ103_Handler
- DCD IRQ104_Handler ; IRQ104_Handler
- DCD IRQ105_Handler ; IRQ105_Handler
- DCD IRQ106_Handler ; IRQ106_Handler
- DCD IRQ107_Handler ; IRQ107_Handler
- DCD IRQ108_Handler ; IRQ108_Handler
- DCD IRQ109_Handler ; IRQ109_Handler
- DCD IRQ110_Handler ; IRQ110_Handler
- DCD IRQ111_Handler ; IRQ111_Handler
- DCD IRQ112_Handler ; IRQ112_Handler
- DCD IRQ113_Handler ; IRQ113_Handler
- DCD IRQ114_Handler ; IRQ114_Handler
- DCD IRQ115_Handler ; IRQ115_Handler
- DCD IRQ116_Handler ; IRQ116_Handler
- DCD IRQ117_Handler ; IRQ117_Handler
- DCD IRQ118_Handler ; IRQ118_Handler
- DCD IRQ119_Handler ; IRQ119_Handler
- DCD IRQ120_Handler ; IRQ120_Handler
- DCD IRQ121_Handler ; IRQ121_Handler
- DCD IRQ122_Handler ; IRQ122_Handler
- DCD IRQ123_Handler ; IRQ123_Handler
- DCD IRQ124_Handler ; IRQ124_Handler
- DCD IRQ125_Handler ; IRQ125_Handler
- DCD IRQ126_Handler ; IRQ126_Handler
- DCD IRQ127_Handler ; IRQ127_Handler
- DCD IRQ128_Handler ; IRQ128_Handler
- DCD IRQ129_Handler ; IRQ129_Handler
- DCD IRQ130_Handler ; IRQ130_Handler
- DCD IRQ131_Handler ; IRQ131_Handler
- DCD IRQ132_Handler ; IRQ132_Handler
- DCD IRQ133_Handler ; IRQ133_Handler
- DCD IRQ134_Handler ; IRQ134_Handler
- DCD IRQ135_Handler ; IRQ135_Handler
- DCD IRQ136_Handler ; IRQ136_Handler
- DCD IRQ137_Handler ; IRQ137_Handler
- DCD IRQ138_Handler ; IRQ138_Handler
- DCD IRQ139_Handler ; IRQ139_Handler
- DCD IRQ140_Handler ; IRQ140_Handler
- DCD IRQ141_Handler ; IRQ141_Handler
- DCD IRQ142_Handler ; IRQ142_Handler
- DCD IRQ143_Handler ; IRQ143_Handler
-
- __Vectors_End
-
- __Vectors_Size EQU __Vectors_End - __Vectors
__Vectors为向量表起始地址,__Vectors_End为向量表结束地址,两个相减即可算出向量表大小。向量表从Flash的0地址开始放置,以4个字节为一个单位,地址0存放的是栈顶地址,0x04存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道C语言中的函数名就是一个地址。DCD:分配一个或者多个以字为单位的内存,以4字节对齐,并要求初始化这些内存。在向量表中,DCD分配了一堆内存,并且以ESR的入口地址初始化它们。
4. 复位程序
AREA |.text|, CODE, READONLY
定义一个名称为.text的代码段,只读。
- Reset_Handler PROC
- EXPORT Reset_Handler [WEAK]
- IMPORT SystemInit
- IMPORT __main
- SET_SRAM3_WAIT
- LDR R0, =0x40050804
- MOV R1, #0x77
- STR R1, [R0]
-
- LDR R0, =0x4005080C
- MOV R1, #0x77
- STR R1, [R0]
-
- LDR R0, =0x40050800
- MOV R1, #0x1100
- STR R1, [R0]
-
- LDR R0, =0x40050804
- MOV R1, #0x76
- STR R1, [R0]
-
- LDR R0, =0x4005080C
- MOV R1, #0x76
- STR R1, [R0]
-
- LDR R0, =SystemInit
- BLX R0
- LDR R0, =__main
- BX R0
- ENDP
复位子程序是系统上电后第一个执行的程序,调用System Init函数初始化系统时钟,然后调用C库函数_mian,最终调用main函数进入C语言世界。
WEAK:表示弱定义,如果外部文件优先定义了该标号,则首先引用该标号,如果外部文件没有声明,也不会出错。这里表示复位子程序可以由用户在其他文件中重新实现,这里并不是唯一的。IMPORT:表示该标号来自外部文件,与C语言中的EXTERN关键字类似。这里表示System Init和__main这两个函数均来自外部的文件。
System Init():一个标准的库函数,在system_hc32f460.c这个库文件中定义。其主要作用是配置系统时钟,这里面的时钟是需要用户自己配置的,华大芯片最高主频支持200M。
__main:一个标准的C库函数,主要作用是初始化用户堆栈,并在函数的最后调用main函数进入C语言世界。这就是为什么我们写的程序都有一个main函数的原因。LDR、BLX、BX是CM4内核的指令,可在《CM3权威指南Cn R2》第4章里面查询到,具体作用见下表。
值得注意的是上面有一段操作R0和R1寄存器的代码,这段代码是什么意思呢?查询手册,找到这个0x40050804的地址,发现是对sram的控制寄存器进行操作,详细对比手册之后,功能就是给sram3这块区域设置读写等待周期为2周期。
5. 中断服务程序
再下面的代码就是中断服务程序,太长了就不粘贴了。启动文件已经帮我们写好所有中断的服务函数,但与我们平时写的中断服务函数不一样,这些函数都是空的,真正的中断服务程序需要在外部的C文件里面重新实现,这里只是提前占了一个位置而已。如果在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无限循环,即程序就“死”在这里。
6. 用户堆栈初始化
- ALIGN
-
-
- ; User Initial Stack & Heap
-
- IF :DEF:__MICROLIB
-
- EXPORT __initial_sp
- EXPORT __heap_base
- EXPORT __heap_limit
-
- ELSE
-
- IMPORT __use_two_region_memory
- EXPORT __user_initial_stackheap
-
- __user_initial_stackheap PROC
- LDR R0, = Heap_Mem
- LDR R1, =(Stack_Mem + Stack_Size)
- LDR R2, = (Heap_Mem + Heap_Size)
- LDR R3, = Stack_Mem
- BX LR
- ENDP
-
- ALIGN
-
- ENDIF
-
-
- END
ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数,缺省表示4字节对齐。
首先判断是否定义了__MICROLIB,如果定义了这个宏,则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。有关这个宏在KEIL里面配置,具体见下图。然后堆栈的初始化就由C库函数_main来完成。
如果没有定义__MICROLIB,则采用双段存储器模式,且声明标号__user_initial_stackheap具有全局属性,让用户自己初始化堆栈。IF、ELSE、ENDIF:汇编的条件分支语句,与C语言中的if、else类似。END:文件结束。
总结:
启动文件由汇编语言编写,是系统上电复位后第一个执行的程序,主要做了以下工作:
1)初始化堆栈指针:SP=_initial_sp。
2)初始化程序指针:PC=Reset_Handler。
3)初始化中断向量表。
4)设定sram3的等待周期,配置系统时钟。
5)调用C库函数_main初始化用户堆栈,最终调用main函数进入C语言世界。