本文为使用汇编开发STM32系列文章之----启动文件详解篇,全部文章目录点此跳转。
本文不会像其他文章一样只是简单的说一下启动文件的每个部分是什么,说了很多却又像没说一样。本文将对启动文件中的每句话的作用及其如此编写的原因进行深入探究,使大家真正的理解和掌握启动文件的编写,而不是只懂修改堆栈大小。 目录
- 一、基础设定
- 1.Cortex-M3内核的复位序列
- 2.向量表
- 3.AAPCS协定
- 二、启动文件 startup_stm32f10x_md.s 详解
- 1.文件头
- 2.堆栈空间分配
在开始正式解释STM32F10x启动文件前,应首先对其Cortex-M3内核的复位序列以及中断向量等设定进行说明,才可能充分理解启动文件中每句代码的必要性。
1.Cortex-M3内核的复位序列 Cortex-M3内核上电后,首先对系统进行复位操作,保证初始状态的正确。离开复位状态后,首先要做的两件事是取出栈顶(MSP)的初始值以及程序计数器(PC)的初始值。Cortex-M3规定,在地址0x00000000处存放32位的栈顶(MSP)初始值,在地址0x00000004处存放32位的程序计数器(PC)初始值。实际流程如下图所示。
需要说明的是,因为每个存储单元大小为8位,一个32位的值占用4个存储单元也就是4个地址偏移,所以栈顶(MSP)初始值和程序计数器(PC)初始值实际上是在存储单元上连续存放的。
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定义为 “程序调用过程中备份寄存器”,其他的特殊寄存器则功能不变。两个环境下寄存器功能如下图所示。
因为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上的一段连续空间,用于在寄存器不够用时保存数据使用,常用的指令PUSH就是入栈操作,将数据临时保存到这段空间去,POP是将栈内的某些数据取出来。保存数据时,是由高地址向低地址增长。
;配置栈 ; Stack_Size EQU 0x00000600 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp栈大小(以字节为单位)可以在 <0x0-0xFFFFFFFF:要保证8字节对齐>范围内 ;
①第一句中 “EQU” 指令是一个伪指令,用于告诉编译器,在编译时,把此句后面的所有符号 “Stack_Size” 替换为值 0x00000600。相当于C语言中的#define的功能。
②第二句中 “AREA” 指令用于定义一个新的数据段或者代码段,编译器在编译时,会把这部分的代码编译为一个独立的部分,之后在链接时按段按需链接。
以下为堆空间的配置,与栈类似,也是ram上的一段连续空间,用于使用C库中的内存管理函数使用,分配的空间可供数据存放,所以在不使用C库中的内存管理时,则可以不分配堆空间。与栈不同的是,保存数据时,是由低地址向高地址增长。
;配置堆 ; Heap_Size EQU 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit堆大小(以字节为单位)可以在 <0x0-0xFFFFFFFF:要保证8字节对齐>范围内 ;
第一句中 “EQU” 指令是一个伪指令,用于告诉编译器,在编译时,把此句后面的所有符号 “Heap_Size” 替换为值 0x00000200。相当于C语言中的#define的功能。



