|
路线栈欢迎您!
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
一、前言
启动文件主要用于初始化芯片的各种寄存器和外设,以及设置堆栈和中断向量表等。它是整个程序的起点,负责将芯片从复位状态转换到正常工作状态;再将控制权转移到主函数,开始执行用户程序。
二、文件说明
ST提供的3个启动文件(startup_stm32f10x_ld.s、startup_stm32f10x_md.s、startup_stm32f10x_hd.s),适用于不同容量的STM32F1芯片。
其中,ld.s适用于小容量 产品;md.s适用于中等容量产品;hd适用于大容量产品;这里的容量是指FLASH的大小(小容量:FLASH≤32K、中容量:64K≤FLASH≤128K、大容量:256K≤FLASH)。
请大家根据自己所用的STM32F1芯片选择不同的.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中,即可随时修改向量地址。)
STM32F10xxx完整向量表,共有66+3=69个向量:
优先级 | 名称 | 地址 | 说明 | - | - | 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 (可设置) | EXTI0 | 0x00000058 | EXTI线0中断 | 14 (可设置) | EXTI1 | 0x0000005C | EXTI线1中断 | 15 (可设置) | EXTI2 | 0x00000060 | EXTI线2中断 | 16 (可设置) | EXTI3 | 0x00000064 | EXTI线3中断 | 17 (可设置) | EXTI4 | 0x00000068 | EXTI线4中断 | 18 (可设置) | DMA1 通道1 | 0x0000006C | DMA1 通道1 全局中断 | 19 (可设置) | DMA1 通道2 | 0x00000070 | DMA1 通道2 全局中断 | 20 (可设置) | DMA1 通道3 | 0x00000074 | DMA1 通道3 全局中断 | 21 (可设置) | DMA1 通道4 | 0x00000078 | DMA1 通道4 全局中断 | 22 (可设置) | DMA1 通道5 | 0x0000007C | DMA1 通道5 全局中断 | 23 (可设置) | DMA1 通道6 | 0x00000080 | DMA1 通道6 全局中断 | 24 (可设置) | DMA1 通道7 | 0x00000084 | DMA1 通道7 全局中断 | 25 (可设置) | ADC1_2 | 0x00000088 | ADC1和ADC2的全局中断 | 26 (可设置) | USB_HP_CAN_TX | 0x0000008C | USB高优先级或CAN发送中断 | 27 (可设置) | USB_LP_CAN_RX0 | 0x00000090 | USB低优先级或CAN接收0中断 | 28 (可设置) | CAN_RX1 | 0x00000094 | CAN接收1中断 | 29 (可设置) | CAN_SCE | 0x00000098 | CAN SCE中断 | 30 (可设置) | EXTI9_5 | 0x0000009C | EXTI线[9:5]中断 | 31 (可设置) | TIM1_BRK | 0x000000A0 | TIM1刹车中断 | 32 (可设置) | TIM1_UP | 0x000000A4 | TIM1更新中断 | 33 (可设置) | TIM1_TRG_COM | 0x000000A8 | TIM1触发和通信中断 | 34 (可设置) | TIM1_CC | 0x000000AC | TIM1捕获比较中断 | 35 (可设置) | TIM2 | 0x000000B0 | TIM2全局中断 | 36 (可设置) | TIM3 | 0x000000B4 | TIM3全局中断 | 37 (可设置) | TIM4 | 0x000000B8 | TIM4全局中断 | 38 (可设置) | I2C1_EV | 0x000000BC | I2C1事件中断 | 39 (可设置) | I2C1_ER | 0x000000C0 | I2C1错误中断 | 40 (可设置) | I2C2_EV | 0x000000C4 | I2C2事件中断 | 41 (可设置) | I2C2_ER | 0x000000C8 | I2C2错误中断 | 42 (可设置) | SPI1 | 0x000000CC | SPI1全局中断 | 43 (可设置) | SPI2 | 0x000000D0 | SPI2全局中断 | 44 (可设置) | USART1 | 0x000000D4 | USART1全局中断 | 45 (可设置) | USART2 | 0x000000D8 | USART2全局中断 | 46 (可设置) | USART3 | 0x000000DC | USART3全局中断 | 47 (可设置) | EXTI15_10 | 0x000000E0 | EXTI线[15:10]中断 | 48 (可设置) | RTCAlarm | 0x000000E4 | TIM4全局中断 | 49 (可设置) | USB唤醒 | 0x000000E8 | 连到EXTI的从USB待机唤醒中断 | 50 (可设置) | TIM8_BRK | 0x000000EC | 连到EXTI的RTC闹钟中断 | 51 (可设置) | TIM8_UP | 0x000000F0 | TIM8更新中断 | 52 (可设置) | TIM8_TRG_COM | 0x000000F4 | TIM8触发和通信中断 | 53 (可设置) | TIM8_CC | 0x000000F8 | TIM8捕获比较中断 | 54 (可设置) | ADC3 | 0x000000FC | ADC3全局中断 | 55 (可设置) | FSMC | 0x00000100 | FSMC全局中断 | 56 (可设置) | SDIO | 0x00000104 | SDIO全局中断 | 67 (可设置) | TIM5 | 0x00000108 | TIM5全局中断 | 58 (可设置) | SPI3 | 0x0000010C | SPI3全局中断 | 59 (可设置) | UART4 | 0x00000110 | UART4全局中断 | 60 (可设置) | UART5 | 0x00000114 | UART5全局中断 | 61 (可设置) | TIM6 | 0x00000118 | TIM6全局中断 | 62 (可设置) | TIM7 | 0x0000011C | TIM7全局中断 | 63 (可设置) | DMA2通道1 | 0x00000120 | DMA2通道1全局中断 | 64 (可设置) | DMA2通道1 | 0x00000124 | DMA2通道2全局中断 | 65 (可设置) | DMA2通道1 | 0x00000128 | DMA2通道3全局中断 | 66 (可设置) | TIM4 | 0x0000012C | DMA2通道4和DMA2通道5全局中断 |
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 ; 栈顶
复制代码
①首先第一句还是定义一个段,名称为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后跟着 “ EF:__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() 函数中。
五、结语
到此,启动文件全部解释完毕,每一句的原因和意义都说清楚了。最后欢迎大家回复,一起交流学习。
|
|