本文为使用汇编开发STM32系列文章之----启动文件详解篇,全部文章目录点此跳转。
本文不会像其他文章一样只是简单的说一下启动文件的每个部分是什么,说了很多却又像没说一样。本文将对启动文件中的每句话的作用及其如此编写的原因进行深入探究,使大家真正的理解和掌握启动文件的编写,而不是只懂修改堆栈大小。
目录
- 一、基础设定
- 1.Cortex-M3内核的复位序列
- 2.向量表
- 3.AAPCS协定
- 二、启动文件 startup_stm32f10x_md.s 详解
-
一、基础设定
在开始正式解释STM32F10x启动文件前,应首先对其Cortex-M3内核的复位序列以及中断向量等设定进行说明,才可能充分理解启动文件中每句代码的必要性。
1.Cortex-M3内核的复位序列
Cortex-M3内核上电后,首先对系统进行复位操作,保证初始状态的正确。离开复位状态后,首先要做的两件事是取出栈顶(MSP)的初始值以及程序计数器(PC)的初始值。Cortex-M3规定,在地址0x00000000处存放32位的栈顶(MSP)初始值,在地址0x00000004处存放32位的程序计数器(PC)初始值。实际流程如下图所示。
需要说明的是,因为每个存储单元大小为8位,一个32位的值占用4个存储单元也就是4个地址偏移,所以栈顶(MSP)初始值和程序计数器(PC)初始值实际上是在存储单元上连续存放的。
2.向量表
Cortex-M3内核拥有11个系统异常和最多240个外部中断,这些都是可以在代码执行的任何阶段对其打断,并进行异常的处理。每当发生异常时,异常产生部分会返回给Cortex-M3内核一个编号,每个编号对应着固定的异常,以此可以判断所产生的是哪一个异常。
而,向量表中记录的就是每个异常发生后应该跳转到的代码执行地址-----即中断服务函数地址。通过固定向量表中异常的位置,加上发生中断时的编号(n),即可在发生异常时通过向量表的基地址(base_add)加偏移地址计算出中断服务函数的地址(add)存放位置:add = bas_add + n * 4。
STM32f10xxx固定的向量表顺序如下所示,其中开始执行时的地址是固定的,但是在运行开始以后,向量表是可以移动至其他位置的。(因为在flash中是无法在程序中更改向量地址的,则可以在运行开始后将向量表移动至ram中,即可随时修改向量地址。)
优先级 | 名称 | 地址 | 说明 |
---|
- | - | 0x00000000 | 保留,为迎合Cortex-M3内核设定,此处放置MSP初值 |
-3 (固定) | Reset | 0x00000004 | 复位 |
-2 (固定) | NMI | 0x00000008 | 不可屏蔽中断 |
-1 (固定) | HardFault | 0x0000000C | 硬件失效 |
0 (可设置) | MemManage | 0x00000010 | 存储管理 |
1 (可设置) | BusFault | 0x00000014 | 预取指失败或存储器访问失败 |
2 (可设置) | BusFault | 0x00000018 | 预取指失败或存储器访问失败 |
- | - | 0x0000001C-0x0000002B | 保留 |
3 (可设置) | SVCall | 0x0000002C | 通过SWI指令的系统服务调用 |
4 (可设置) | DebugMonitor | 0x00000030 | 调试监控器 |
- | - | 0x00000034 | 保留 |
5 (可设置) | PendSV | 0x00000038 | 可挂起的系统服务 |
6 (可设置) | SysTick | 0x0000003C | 系统嘀嗒定时器 |
7 (可设置) | WWDG | 0x00000040 | 窗口定时器中断 |
8 (可设置) | PVD | 0x00000044 | 连到EXTI的电源电压检测(PVD)中断 |
9 (可设置) | TAMPER | 0x00000048 | 侵入检测中断 |
10 (可设置) | RTC | 0x0000004C | 实时时钟(RTC)全局中断 |
11 (可设置) | FLASH | 0x00000050 | 闪存全局中断 |
12 (可设置) | RCC | 0x00000054 | 复位和时钟控制(RCC)中断 |
13-17 (可设置) | EXTI0至EXTI4 | 0x00000058至0x00000068 | EXTI线0至4中断 |
18-24 (可设置) | DMA1至DMA7 | 0x0000006C至0x00000084 | DMA1 通道1 至 通道7 全局中断 |
… | … | 0x00000088至0x00000012C | 其他25到66优先级的中断 |
上表并不完全,因为篇幅原因,就不放完整向量表了。想看完整向量表的可以查看文档《STM32F10xxx中文参考手册》,或者点此查看。
3.AAPCS协定
在进行STM32的编程时,使用的都是C语言。但是芯片上电后首先执行的是启动文件,是汇编语言编写的,之后由汇编语言环境跳转到C语言环境进行执行。在某些情况下也需要从C语言环境跳转到汇编语言执行一些C语言做不到的事情。由此就产生了两种新的环境转换情景,那么就需要两个环境在进行转换时按照一定的约束或者说是规则,保证跳转后可正常执行,保证还可正常跳转回之前状态。
则AAPCS(ARM Architecture Procedure Call Standard)诞生了,即 “ARM架构程序调用标准” 。其中约定了调用函数时的参数、返回值以及某些寄存器在C语言环境下的作用等等。
我们知道在汇编语言环境下可以访问的通用寄存器有R0-R15,除了一些特殊功能寄存器例如R13(MSP/PSP)、R14(LR)以及R15(PC)外,其他寄存器一般都可用于存储计算过程的数据使用。但是在C语言环境下,有函数类型代码,其包含参数和返回值,则AAPCS规定了其R0-R15的使用方式。其中R0-R4用于传递参数和返回结果,R4-R11用于保存函数内部的局部变量,R12定义为 “程序调用过程中备份寄存器”,其他的特殊寄存器则功能不变。两个环境下寄存器功能如下图所示。
二、启动文件 startup_stm32f10x_md.s 详解
因为stm32的启动文件具有一般性,本文将startup_stm32f10x_md.s文件作为解释对象。由于本人水平有限,难免会有错误之处,欢迎指正。
1.文件头
文件开头比较好理解,是ST官方编写者做的版权声明和文件功能的说明,翻译后如下所示。
;******************** (C) 版权所有 2011 STMicroelectronics ********************
;* 文件名称 : startup_stm32f10x_md.s
;* 作者 : MCD 应用团队
;* 版本 : V3.5.0
;* 日期 : 2011年3月11日
;* 说明 : 基于MDK-ARM工具链的STM32F10x中容量器件矢量表
;* 这个文件执行:
;* - 初始化 SP(堆栈)
;* - 初始化 PC(程序指针)指向 Reset_Handler
;* - 设置除ISR地址外的向量表条
;* - 配置时钟系统
;* - 在C库中分支到__main(最终调用main())
;* 重置后,CortexM3处理器处于线程模式,优先级为特权,堆栈设置为Main。
;* <<< 使用上下文菜单中的配置向导 >>>
;*******************************************************************************
;本固件仅供参考,旨在为客户提供有关其产品的编码信息,以节省时间。
;因此,意法半导体公司不对因此类固件内容和/或客户使用本文中包含的
;与其产品相关的编码信息而引起的任何直接、间接或后果性损害承担责任。
;*******************************************************************************
2.堆栈空间分配
以下为栈空间的配置,为ram上的一段连续空间(RAM 地址0x2000 0000开始),用于在寄存器不够用时保存数据使用,常用的指令PUSH就是入栈操作,将数据临时保存到这段空间去,POP是将栈内的某些数据取出来。保存数据时,是由高地址向低地址增长。
; <h> 配置栈
; <o> 栈大小(以字节为单位)可以在 <0x0-0xFFFFFFFF:要保证8字节对齐>范围内
; </h>
Stack_Size EQU 0x00000600
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
①第一句中 “EQU” 指令是一个伪指令,用于告诉编译器,在编译时,把此句后面的所有符号 “Stack_Size” 替换为值 0x00000600。相当于C语言中的#define的功能。
②第二句中 “AREA” 指令用于定义一个新的数据段或者代码段,编译器在编译时,会把这部分的代码编译为一个独立的部分,之后在链接时按段按需链接。
后面的一些参数表示,段名为STACK,NOINIT为此段的内存空间不进行初始化,READWRITE表示此段为可读可写的数据,ALIGN=3表示此段的数据为2的3次方,即8字节对齐。
③第三句中 “SPACE” 指令用于分配一段连续的内存空间,空间大小为 Stack_Size = 0x00000600。“Stack_Mem” 则为标签,是 “SPACE” 指令分配的内存空间的首地址,可以用来跳转以及位置定位。
④第四句中 “__initial_sp” 也为标签,是 “SPACE” 指令分配的内存空间的末地址,可以用来跳转以及位置定位。
以下为堆空间的配置,与栈类似,也是ram上的一段连续空间,用于使用C库中的内存管理函数使用,分配的空间可供数据存放,所以在不使用C库中的内存管理时,则可以不分配堆空间。与栈不同的是,保存数据时,是由低地址向高地址增长。
; <h> 配置堆
; <o> 堆大小(以字节为单位)可以在 <0x0-0xFFFFFFFF:要保证8字节对齐>范围内
; </h>
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
堆的配置代码与栈的代码基本一致。“EQU” 告诉编译器,在编译时,把此句后面的所有符号 “Heap_Size” 替换为值 0x00000200,相当于C语言中的#define的功能。“AREA” 指令用于定义一个新的数据段或者代码段,编译器在编译时,会把这部分的代码编译为一个独立的部分,之后在链接时按段按需链接。后面的一些参数表示,段名为HEAP,NOINIT为此段的内存空间不进行初始化,READWRITE表示此段为可读可写的数据,ALIGN=3表示此段的数据为2的3次方,即8字节对齐。“SPACE” 指令用于分配一段连续的内存空间,空间大小为 Heap_Size = 0x00000200。“__heap_base” 和 “Heap_Mem” 则为标签,是 “SPACE” 指令分配的内存空间的首地址,可以用来跳转以及位置定位。“__heap_limit” 也为标签,是 “SPACE” 指令分配的内存空间的末地址,可以用来跳转以及位置定位。
3.向量表段
所谓的向量表,其实就是按规定格式放置一些中断服务函数的入口地址,以在发生中断时CPU能够对应中断跳转到中断服务函数进行相应的处理,下面是实际代码。
; 在重置时映射到地址0的向量表
; 实际上就是一些中断服务函数
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; 栈顶
DCD Reset_Handler ; Reset 处理程序
DCD NMI_Handler ; NMI 处理程序
DCD HardFault_Handler ; Hard Fault 处理程序
DCD MemManage_Handler ; MPU Fault 处理程序
DCD BusFault_Handler ; Bus Fault 处理程序
DCD UsageFault_Handler ; Usage Fault 处理程序
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD SVC_Handler ; SVCall 处理程序
DCD DebugMon_Handler ; Debug Monitor 处理程序
DCD 0 ; 保留
DCD PendSV_Handler ; PendSV 处理程序
DCD SysTick_Handler ; SysTick 处理程序
;cortex-m3内核的外部中断源
DCD WWDG_IRQHandler ; 窗看门狗
DCD PVD_IRQHandler ; 电压检测
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1_2
DCD USB_HP_CAN1_TX_IRQHandler ; 高优先级USB 或 CAN1的TX
DCD USB_LP_CAN1_RX0_IRQHandler ; 低优先级USB 或 CAN1的RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; 外部中断线 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 触发器和变换
DCD TIM1_CC_IRQHandler ; TIM1 捕获比较
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 事件
DCD I2C1_ER_IRQHandler ; I2C1 错误
DCD I2C2_EV_IRQHandler ; I2C2 事件
DCD I2C2_ER_IRQHandler ; I2C2 错误
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; 外部中断线 15..10
DCD RTCAlarm_IRQHandler ; RTC 通过外部中断线报警
DCD USBWakeUp_IRQHandler ; USB 从挂起唤醒
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
①首先第一句还是定义一个段,名称为RESET,属性为数据段、并且是只读的。因为是只读的,所以可以知道这个段在编译时就会被分配到flash处。
②第2-4句,是声明全局标签,告诉其他段要找 __Vectors、__Vectors_End 和 __Vectors_size 的话可以来这里找。然后可以看到的是,__Vectors 和 __Vectors_End 分别在这个向量表的最前方和最后方,标签代表位置,即地址,则可知 __Vectors 是向量表的首地址,__Vectors_End 是向量表的末地址。最后一句话 “__Vectors_Size EQU __Vectors_End - __Vectors”,表示__Vectors_Size 是 __Vectors_End 的值减去 __Vectors 的值,则 __Vectors_Size 是整个向量表占用的大小。
③剩下的就全是 “DCD…” 的代码了,DCD指令表示在存储器上分配一片连续的字存储单元,并把 DCD 后面跟的值赋值到刚分配的存储单元内。在stm32上,字的大小是32位。
这段DCD代码需要着重说的是前两句。第一句 “DCD __initial_sp” ,此处是分配32位的空间大小,并放置栈空间栈顶的地址。第二句 “DCD Reset_Handler” ,分配32位的空间大小,并放置复位中断服务函数的地址。因为此段代码是在flash上分配空间,且是第一次分配,则第一句分配的地址一定是0x0000 0000,并放置__initial_sp的值,第二句的地址一定是0x0000 0004,放置 Reset_Handler 的值。前面 “Cortex-M3内核的复位序列” 说过,Cortex-M3内核上电复位后,会从0x0000 0000处取出MSP值,也就是栈顶值。会从0x0000 0004中取出PC值,也就是程序指针计数器的值。那么,此时PC的值则会对应Reset_Handler,PC的作用就是指向下一次程序执行的地址。则stm32在运行时会首先跳转到Reset_Handler函数处开始程序的执行。
而其他的DCD是用来分配其他stm32所有的中断服务函数,而且是固定的顺序,不可改变。
4.代码段
代码段也只是将所有的中断服务函数列出,发生中断时,完成最基本的死循环行为,防止因为中断异常而对设备造成损害。对于代码段我把它分成三部分讲解,下面是第一段。
4.1 第一段
AREA |.text|, CODE, READONLY
;上电复位的处理程序
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
; 异常处理程序,全是弱定义,可重新自定义 (下面的无限循环可自行修改)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
这一段包含的是全部的系统必要中断服务函数,属于系统内核级的。首先第一句话,还是老样子,定义了一个名叫 |.text| 的 代码 段,并且是只读的。下面就是10个中断服务函数,Reset_Handler、NMI_Handler、HardFault_Handler、MemManage_Handler、BusFault_Handler、UsageFault_Handler、SVC_Handler、DebugMon_Handler、PendSV_Handler、SysTick_Handler。每个函数都是由标签指明函数名,PROC表示函数的开始,ENDP表示函数的结束,中间使用 EXPORT 指令声明全局标签,可供其他段使用时来此寻找。指令最后加 [WEAK] 表示,此声明是弱声明,如果有其他相同函数定义,其他的函数有效,此弱声明函数作废。除 Reset_Handler 中断服务函数外,每个函数内部都是 “B .” ,指令B是跳转,B后加点,表示跳转地址是本指令地址,即死循环。
其中 “IMPORT …” 跟 EXPORT 指令正好相反,EXPORT 是声明外部全局标签,而 IMPORT 引入外部全局标签。例如 “IMPORT __main” 是将外部其他段的 __main 函数链接到此处,“IMPORT SystemInit” 是将外部其他段的 SystemInit 函数链接到此处,在本段中需要跳转至 __main 函数 和 SystemInit 函数时,编译器可以找到函数原型的位置。
其中 Reset_Handler 是stm32复位后执行的第一个中断服务函数,函数中的程序会起到很重要的作用。其中:
① “LDR R0, =SystemInit” 是将函数 SystemInit 的地址放到寄存器 R0中保存。
② “BLX R0” 是跳转到 R0 寄存器存储的地址处运行。B是直接跳转指令,B后加L表示保存PC的值到寄存器 R14,可以在执行完跳转全部指令后返回跳转前的位置。B后加X是表示根据跳转的地址改变当前的状态,地址的最低位 add[0] 若是1,则将存储器状态更改为 Thumb 状态,反之地址的最低位 add[0] 若是0,则将存储器状态更改为 ARM 状态。( 注意Cortex-M3内核不允许进入ARM 状态,否则将会产生一个硬件异常中断 HardFault_Handler )。
则执行完此句后,程序将跳转到 SystemInit()函数执行系统初始化的操作,执行完后回到此处继续执行下面的第三句话。
③ “LDR R0, =__main” 同样的,此句是将 __main 函数的地址放到寄存器 R0中保存。( 注意: __main 和 main 不是一个函数。__main 是库函数,用于由非C语言环境转换为C语言环境时使用的,C语言环境配置函数 )
④ “BX R0” 是跳转到 R0 寄存器存储的地址处运行。B是直接跳转指令,B后未加L,则执行完跳转后不会返回到此处。B后加X是表示根据跳转的地址改变当前的状态,地址的最低位 add[0] 若是1,则将存储器状态更改为 Thumb 状态,反之地址的最低位 add[0] 若是0,则将存储器状态更改为 ARM 状态。
则执行完此句后,程序将跳转到 __main() 函数执行C语言环境初始化的操作,包括堆栈、寄存器等的设置,初始化完成后,将会跳转至咱们常见的 main() 函数,自此进入C语言的世界一去不复返。
4.2 第二段
接下来这段看起来就比较简单了,是其他全部外设正常中断服务函数的集合,因为此种中断不会产生严重后果,在默认的情况下,可以使其为同种功能。则下方就是将全部的外设正常中断服务函数写为了一个函数。如下。
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
EXPORT RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_IRQHandler [WEAK]
EXPORT DMA1_Channel3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_IRQHandler [WEAK]
EXPORT DMA1_Channel5_IRQHandler [WEAK]
EXPORT DMA1_Channel6_IRQHandler [WEAK]
EXPORT DMA1_Channel7_IRQHandler [WEAK]
EXPORT ADC1_2_IRQHandler [WEAK]
EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]
EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_IRQHandler [WEAK]
EXPORT TIM1_UP_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTCAlarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
B .
ENDP
可以看到,此中断服务函数的标签名称为 “Default_Handler”,意思为默认的中断函数。与其他函数相同,都是由 PROC 指令开始,ENDP 指令结束。函数开始,将包含的全部外设中断函数使用 EXPORT 指令向全局外弱声明,再给我给每个函数起一个指定名称的标签,因为是连续放置,则这些标签的地址全部相同。此函数内部的实际功能也是 “B .” ,B后加点表示始终跳转到本指令,即死循环。
4.3 第三段
此段是一些关于处理由汇编转到C环境的相关配置,如下。
ALIGN
;*******************************************************************************
; 用户栈和堆初始化
;*******************************************************************************
IF :DEF:__MICROLIB ;如果使用了MICROLIB(C库)
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
;******************* (C) 版权 2011 STMicroelectronics *****文件结束*****
可以看到,第一句话是 ALIGN 指令,ALIGN 为字节对齐指令,后边未跟数字,表示默认的4字节对齐,即从此指令开始,如果地址不是4字节对齐的,将会填充一些空数据,使下一条指令从四字节对齐处开始。再下方是一个 IF-ELSE-ENDIF 结构,IF后跟着 “ :DEF:__MICROLIB ” ,表示如果使用了C语言的标准库函数,则执行 IF 和 ELSE中间的指令,否则执行ELSE 和ENDIF中间的指令。是否使用C语言的标准库函数,再keil中的魔术棒中设置,如下。
从代码中的两种情况可以看到,如果使用了C语言的标准库函数,只需要将 标签 __initial_sp、__heap_base 和 __heap_limit 向外声明为全局标签即可,在 __main() 函数中就会自动将C语言环境配置好。如果未使用C语言的标准库函数,则需要编写一个初始化堆栈的函数,标签为 __user_initial_stackheap ,并将其声明为外部全局标签,以供 __main() 函数在初始化C语言环境时使用。其中 __user_initial_stackheap 函数中的 R0、R1、R2 和 R3 寄存器,由前面说过的 AAPCS协定 所规定,按其编写即可。
最后是指令 END ,代表整个代码的结束处,与其对应的是整个代码的入口处 ENTRY 指令,但是 ENTRY 指令并未在启动文件中出现。其实 ENTRY 指令是在 __main() 函数中的,真正的入口处也是在 __main() 函数中。
至此,启动文件全部解释完毕,每一句的原因和意义都说清楚了。因为本人水平有限,如果错误,欢迎指正。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)