• 注册 / 登录
  • 切换到窄版
  • 查看: 1664|回复: 0

    STM32F10x启动文件详解

    [复制链接]

    668

    主题

    682

    帖子

    6599

    积分

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    6599
    发表于 2023-8-16 00:06:37 | 显示全部楼层 |阅读模式

    路线栈欢迎您!

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    一、前言

    启动文件主要用于初始化芯片的各种寄存器和外设,以及设置堆栈和中断向量表等。它是整个程序的起点,负责将芯片从复位状态转换到正常工作状态;再将控制权转移到主函数,开始执行用户程序。

    二、文件说明

    0.png

    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)初始值。实际流程如下图所示:

    1.png

    需要说明的是,因为每个存储单元大小为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 (固定)Reset0x00000004复位
    -2 (固定)NMI0x00000008不可屏蔽中断
    -1 (固定)HardFault0x0000000C硬件失效
    0 (可设置)MemManage0x00000010存储管理
    1 (可设置)BusFault0x00000014预取指失败或存储器访问失败
    2 (可设置)BusFault0x00000018预取指失败或存储器访问失败
    --0x0000001C-0x0000002B保留
    3 (可设置)SVCall0x0000002C通过SWI指令的系统服务调用
    4 (可设置)DebugMonitor0x00000030调试监控器
    --0x00000034保留
    5 (可设置)PendSV0x00000038可挂起的系统服务
    6 (可设置)SysTick0x0000003C系统嘀嗒定时器
    7 (可设置)WWDG0x00000040窗口定时器中断
    8 (可设置)PVD0x00000044连到EXTI的电源电压检测(PVD)中断
    9 (可设置)TAMPER0x00000048侵入检测中断
    10 (可设置)RTC0x0000004C实时时钟(RTC)全局中断
    11 (可设置)FLASH0x00000050闪存全局中断
    12 (可设置)RCC0x00000054复位和时钟控制(RCC)中断
    13 (可设置)EXTI00x00000058EXTI线0中断
    14 (可设置)EXTI10x0000005CEXTI线1中断
    15 (可设置)EXTI20x00000060EXTI线2中断
    16 (可设置)EXTI30x00000064EXTI线3中断
    17 (可设置)EXTI40x00000068EXTI线4中断
    18 (可设置)DMA1 通道10x0000006CDMA1 通道1 全局中断
    19 (可设置)DMA1 通道20x00000070DMA1 通道2 全局中断
    20 (可设置)DMA1 通道30x00000074DMA1 通道3 全局中断
    21 (可设置)DMA1 通道40x00000078DMA1 通道4 全局中断
    22 (可设置)DMA1 通道50x0000007CDMA1 通道5 全局中断
    23 (可设置)DMA1 通道60x00000080DMA1 通道6 全局中断
    24 (可设置)DMA1 通道70x00000084DMA1 通道7 全局中断
    25 (可设置)ADC1_20x00000088ADC1和ADC2的全局中断
    26 (可设置)USB_HP_CAN_TX0x0000008CUSB高优先级或CAN发送中断
    27 (可设置)USB_LP_CAN_RX00x00000090USB低优先级或CAN接收0中断
    28 (可设置)CAN_RX10x00000094CAN接收1中断
    29 (可设置)CAN_SCE0x00000098CAN SCE中断
    30 (可设置)EXTI9_50x0000009CEXTI线[9:5]中断
    31 (可设置)TIM1_BRK0x000000A0TIM1刹车中断
    32 (可设置)TIM1_UP0x000000A4TIM1更新中断
    33 (可设置)TIM1_TRG_COM0x000000A8TIM1触发和通信中断
    34 (可设置)TIM1_CC0x000000ACTIM1捕获比较中断
    35 (可设置)TIM20x000000B0TIM2全局中断
    36 (可设置)TIM30x000000B4TIM3全局中断
    37 (可设置)TIM40x000000B8TIM4全局中断
    38 (可设置)I2C1_EV0x000000BCI2C1事件中断
    39 (可设置)I2C1_ER0x000000C0I2C1错误中断
    40 (可设置)I2C2_EV0x000000C4I2C2事件中断
    41 (可设置)I2C2_ER0x000000C8I2C2错误中断
    42 (可设置)SPI10x000000CCSPI1全局中断
    43 (可设置)SPI20x000000D0SPI2全局中断
    44 (可设置)USART10x000000D4USART1全局中断
    45 (可设置)USART20x000000D8USART2全局中断
    46 (可设置)USART30x000000DCUSART3全局中断
    47 (可设置)EXTI15_100x000000E0EXTI线[15:10]中断
    48 (可设置)RTCAlarm0x000000E4TIM4全局中断
    49 (可设置)USB唤醒0x000000E8连到EXTI的从USB待机唤醒中断
    50 (可设置)TIM8_BRK0x000000EC连到EXTI的RTC闹钟中断
    51 (可设置)TIM8_UP0x000000F0TIM8更新中断
    52 (可设置)TIM8_TRG_COM0x000000F4TIM8触发和通信中断
    53 (可设置)TIM8_CC0x000000F8TIM8捕获比较中断
    54 (可设置)ADC30x000000FCADC3全局中断
    55 (可设置)FSMC0x00000100FSMC全局中断
    56 (可设置)SDIO0x00000104SDIO全局中断
    67 (可设置)TIM50x00000108TIM5全局中断
    58 (可设置)SPI30x0000010CSPI3全局中断
    59 (可设置)UART40x00000110UART4全局中断
    60 (可设置)UART50x00000114UART5全局中断
    61 (可设置)TIM60x00000118TIM6全局中断
    62 (可设置)TIM70x0000011CTIM7全局中断
    63 (可设置)DMA2通道10x00000120DMA2通道1全局中断
    64 (可设置)DMA2通道10x00000124DMA2通道2全局中断
    65 (可设置)DMA2通道10x00000128DMA2通道3全局中断
    66 (可设置)TIM40x0000012CDMA2通道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定义为 “程序调用过程中备份寄存器”,其他的特殊寄存器则功能不变。两个环境下寄存器功能如下图所示。

    2.png

    四、启动文件 startup_stm32f10x_md.s 详解

    因为stm32的启动文件具有一般性,本文将startup_stm32f10x_md.s文件作为解释对象。。

    1.文件头

    文件开头比较好理解,是ST官方编写者做的版权声明和文件功能的说明,翻译后如下所示:

    1. ;******************** (C) 版权所有 2011 STMicroelectronics ********************
    2. ;* 文件名称           : startup_stm32f10x_md.s
    3. ;* 作者               : MCD 应用团队
    4. ;* 版本               : V3.5.0
    5. ;* 日期               : 2011年3月11日
    6. ;* 说明               : 基于MDK-ARM工具链的STM32F10x中容量器件矢量表
    7. ;*                      这个文件执行:
    8. ;*                      - 初始化 SP(堆栈)
    9. ;*                      - 初始化 PC(程序指针)指向 Reset_Handler
    10. ;*                      - 设置除ISR地址外的向量表条
    11. ;*                      - 配置时钟系统
    12. ;*                      - 在C库中分支到__main(最终调用main())
    13. ;*                      重置后,CortexM3处理器处于线程模式,优先级为特权,堆栈设置为Main。
    14. ;* <<< 使用上下文菜单中的配置向导 >>>   
    15. ;*******************************************************************************
    16. ;本固件仅供参考,旨在为客户提供有关其产品的编码信息,以节省时间。
    17. ;因此,意法半导体公司不对因此类固件内容和/或客户使用本文中包含的
    18. ;与其产品相关的编码信息而引起的任何直接、间接或后果性损害承担责任。
    19. ;*******************************************************************************
    复制代码

    2.堆栈空间分配

    以下为栈空间的配置,为ram上的一段连续空间(RAM 地址0x2000 0000开始),用于在寄存器不够用时保存数据使用,常用的指令PUSH就是入栈操作,将数据临时保存到这段空间去,POP是将栈内的某些数据取出来。保存数据时,是由高地址向低地址增长。

    1. ; <h> 配置栈
    2. ;   <o> 栈大小(以字节为单位)可以在 <0x0-0xFFFFFFFF:要保证8字节对齐>范围内
    3. ; </h>

    4. Stack_Size     EQU     0x00000600

    5.                     AREA    STACK, NOINIT, READWRITE, ALIGN=3
    6. Stack_Mem    SPACE   Stack_Size
    7. __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库中的内存管理时,则可以不分配堆空间。与栈不同的是,保存数据时,是由低地址向高地址增长。

    1. ; <h> 配置堆
    2. ;   <o>  堆大小(以字节为单位)可以在 <0x0-0xFFFFFFFF:要保证8字节对齐>范围内
    3. ; </h>

    4. Heap_Size       EQU       0x00000200

    5.                       AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    6. __heap_base
    7. Heap_Mem      SPACE   Heap_Size
    8. __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能够对应中断跳转到中断服务函数进行相应的处理,下面是实际代码。

    1. ; 在重置时映射到地址0的向量表
    2. ; 实际上就是一些中断服务函数
    3.                    AREA    RESET, DATA, READONLY
    4.                    EXPORT  __Vectors
    5.                    EXPORT  __Vectors_End
    6.                    EXPORT  __Vectors_Size

    7. __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 第一段

    1.                  AREA    |.text|, CODE, READONLY

    2. ;上电复位的处理程序
    3. Reset_Handler    PROC
    4.                  EXPORT  Reset_Handler             [WEAK]
    5.      IMPORT  __main
    6.      IMPORT  SystemInit
    7.                  LDR     R0, =SystemInit
    8.                  BLX     R0
    9.                  LDR     R0, =__main
    10.                  BX      R0
    11.                  ENDP

    12. ; 异常处理程序,全是弱定义,可重新自定义 (下面的无限循环可自行修改)

    13. NMI_Handler     PROC
    14.                 EXPORT  NMI_Handler                [WEAK]
    15.                 B       .
    16.                 ENDP
    17. HardFault_Handler\
    18.                 PROC
    19.                 EXPORT  HardFault_Handler          [WEAK]
    20.                 B       .
    21.                 ENDP
    22. MemManage_Handler\
    23.                 PROC
    24.                 EXPORT  MemManage_Handler          [WEAK]
    25.                 B       .
    26.                 ENDP
    27. BusFault_Handler\
    28.                 PROC
    29.                 EXPORT  BusFault_Handler           [WEAK]
    30.                 B       .
    31.                 ENDP
    32. UsageFault_Handler\
    33.                 PROC
    34.                 EXPORT  UsageFault_Handler         [WEAK]
    35.                 B       .
    36.                 ENDP
    37. SVC_Handler     PROC
    38.                 EXPORT  SVC_Handler                [WEAK]
    39.                 B       .
    40.                 ENDP
    41. DebugMon_Handler\
    42.                 PROC
    43.                 EXPORT  DebugMon_Handler           [WEAK]
    44.                 B       .
    45.                 ENDP
    46. PendSV_Handler  PROC
    47.                 EXPORT  PendSV_Handler             [WEAK]
    48.                 B       .
    49.                 ENDP
    50. SysTick_Handler PROC
    51.                 EXPORT  SysTick_Handler            [WEAK]
    52.                 B       .
    53.                 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 第二段

    接下来这段看起来就比较简单了,是其他全部外设正常中断服务函数的集合,因为此种中断不会产生严重后果,在默认的情况下,可以使其为同种功能。则下方就是将全部的外设正常中断服务函数写为了一个函数,如下:

    1. Default_Handler PROC
    2.                 EXPORT  WWDG_IRQHandler            [WEAK]
    3.                 EXPORT  PVD_IRQHandler             [WEAK]
    4.                 EXPORT  TAMPER_IRQHandler          [WEAK]
    5.                 EXPORT  RTC_IRQHandler             [WEAK]
    6.                 EXPORT  FLASH_IRQHandler           [WEAK]
    7.                 EXPORT  RCC_IRQHandler             [WEAK]
    8.                 EXPORT  EXTI0_IRQHandler           [WEAK]
    9.                 EXPORT  EXTI1_IRQHandler           [WEAK]
    10.                 EXPORT  EXTI2_IRQHandler           [WEAK]
    11.                 EXPORT  EXTI3_IRQHandler           [WEAK]
    12.                 EXPORT  EXTI4_IRQHandler           [WEAK]
    13.                 EXPORT  DMA1_Channel1_IRQHandler   [WEAK]
    14.                 EXPORT  DMA1_Channel2_IRQHandler   [WEAK]
    15.                 EXPORT  DMA1_Channel3_IRQHandler   [WEAK]
    16.                 EXPORT  DMA1_Channel4_IRQHandler   [WEAK]
    17.                 EXPORT  DMA1_Channel5_IRQHandler   [WEAK]
    18.                 EXPORT  DMA1_Channel6_IRQHandler   [WEAK]
    19.                 EXPORT  DMA1_Channel7_IRQHandler   [WEAK]
    20.                 EXPORT  ADC1_2_IRQHandler          [WEAK]
    21.                 EXPORT  USB_HP_CAN1_TX_IRQHandler  [WEAK]
    22.                 EXPORT  USB_LP_CAN1_RX0_IRQHandler [WEAK]
    23.                 EXPORT  CAN1_RX1_IRQHandler        [WEAK]
    24.                 EXPORT  CAN1_SCE_IRQHandler        [WEAK]
    25.                 EXPORT  EXTI9_5_IRQHandler         [WEAK]
    26.                 EXPORT  TIM1_BRK_IRQHandler        [WEAK]
    27.                 EXPORT  TIM1_UP_IRQHandler         [WEAK]
    28.                 EXPORT  TIM1_TRG_COM_IRQHandler    [WEAK]
    29.                 EXPORT  TIM1_CC_IRQHandler         [WEAK]
    30.                 EXPORT  TIM2_IRQHandler            [WEAK]
    31.                 EXPORT  TIM3_IRQHandler            [WEAK]
    32.                 EXPORT  TIM4_IRQHandler            [WEAK]
    33.                 EXPORT  I2C1_EV_IRQHandler         [WEAK]
    34.                 EXPORT  I2C1_ER_IRQHandler         [WEAK]
    35.                 EXPORT  I2C2_EV_IRQHandler         [WEAK]
    36.                 EXPORT  I2C2_ER_IRQHandler         [WEAK]
    37.                 EXPORT  SPI1_IRQHandler            [WEAK]
    38.                 EXPORT  SPI2_IRQHandler            [WEAK]
    39.                 EXPORT  USART1_IRQHandler          [WEAK]
    40.                 EXPORT  USART2_IRQHandler          [WEAK]
    41.                 EXPORT  USART3_IRQHandler          [WEAK]
    42.                 EXPORT  EXTI15_10_IRQHandler       [WEAK]
    43.                 EXPORT  RTCAlarm_IRQHandler        [WEAK]
    44.                 EXPORT  USBWakeUp_IRQHandler       [WEAK]
    45. WWDG_IRQHandler
    46. PVD_IRQHandler
    47. TAMPER_IRQHandler
    48. RTC_IRQHandler
    49. FLASH_IRQHandler
    50. RCC_IRQHandler
    51. EXTI0_IRQHandler
    52. EXTI1_IRQHandler
    53. EXTI2_IRQHandler
    54. EXTI3_IRQHandler
    55. EXTI4_IRQHandler
    56. DMA1_Channel1_IRQHandler
    57. DMA1_Channel2_IRQHandler
    58. DMA1_Channel3_IRQHandler
    59. DMA1_Channel4_IRQHandler
    60. DMA1_Channel5_IRQHandler
    61. DMA1_Channel6_IRQHandler
    62. DMA1_Channel7_IRQHandler
    63. ADC1_2_IRQHandler
    64. USB_HP_CAN1_TX_IRQHandler
    65. USB_LP_CAN1_RX0_IRQHandler
    66. CAN1_RX1_IRQHandler
    67. CAN1_SCE_IRQHandler
    68. EXTI9_5_IRQHandler
    69. TIM1_BRK_IRQHandler
    70. TIM1_UP_IRQHandler
    71. TIM1_TRG_COM_IRQHandler
    72. TIM1_CC_IRQHandler
    73. TIM2_IRQHandler
    74. TIM3_IRQHandler
    75. TIM4_IRQHandler
    76. I2C1_EV_IRQHandler
    77. I2C1_ER_IRQHandler
    78. I2C2_EV_IRQHandler
    79. I2C2_ER_IRQHandler
    80. SPI1_IRQHandler
    81. SPI2_IRQHandler
    82. USART1_IRQHandler
    83. USART2_IRQHandler
    84. USART3_IRQHandler
    85. EXTI15_10_IRQHandler
    86. RTCAlarm_IRQHandler
    87. USBWakeUp_IRQHandler

    88.                 B       .

    89.                 ENDP
    复制代码

    可以看到,此中断服务函数的标签名称为 “Default_Handler”,意思为默认的中断函数。与其他函数相同,都是由 PROC 指令开始,ENDP 指令结束。

    函数开始,将包含的全部外设中断函数使用 EXPORT 指令向全局外弱声明,再给我给每个函数起一个指定名称的标签,因为是连续放置,则这些标签的地址全部相同。此函数内部的实际功能也是 “B .” ,B后加点表示始终跳转到本指令,即死循环。

    4.3 第三段

    此段是一些关于处理由汇编转到C环境的相关配置,如下:

    1. ALIGN
    2. ;*******************************************************************************
    3. ; 用户栈和堆初始化
    4. ;*******************************************************************************
    5.                  IF      :DEF:__MICROLIB           ;如果使用了MICROLIB(C库)
    6.                
    7.                  EXPORT  __initial_sp
    8.                  EXPORT  __heap_base
    9.                  EXPORT  __heap_limit
    10.                
    11.                  ELSE
    12.                
    13.                  IMPORT  __use_two_region_memory
    14.                  EXPORT  __user_initial_stackheap
    15.                  
    16. __user_initial_stackheap
    17.                  LDR     R0, =  Heap_Mem
    18.                  LDR     R1, =(Stack_Mem + Stack_Size)
    19.                  LDR     R2, = (Heap_Mem +  Heap_Size)
    20.                  LDR     R3, = Stack_Mem
    21.                  BX      LR

    22.                  ALIGN

    23.                  ENDIF

    24.                  END

    25. ;******************* (C) 版权 2011 STMicroelectronics *****文件结束*****
    复制代码

    可以看到,第一句话是 ALIGN 指令,ALIGN 为字节对齐指令,后边未跟数字,表示默认的4字节对齐,即从此指令开始,如果地址不是4字节对齐的,将会填充一些空数据,使下一条指令从四字节对齐处开始。再下方是一个 IF-ELSE-ENDIF 结构,IF后跟着 “ EF:__MICROLIB ” ,表示如果使用了C语言的标准库函数,则执行 IF 和 ELSE中间的指令,否则执行ELSE 和ENDIF中间的指令。是否使用C语言的标准库函数,再keil中的魔术棒中设置,如下:

    3.png

    从代码中的两种情况可以看到,如果使用了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() 函数中。

    五、结语

    到此,启动文件全部解释完毕,每一句的原因和意义都说清楚了。最后欢迎大家回复,一起交流学习。

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|路丝栈 ( 粤ICP备2021053448号 )

    GMT+8, 2024-11-3 21:15 , Processed in 0.075892 second(s), 21 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表