Cortex-M3学习(1)-官方启动文件分析

此启动文件来自Keil官方的startup_stm32f10x_hd.s

启动文件功能

普遍的说法:

  1. 堆栈初始化
  2. 定位中断向量表
  3. 调用Reset Handler

翻译自Keil官方启动文件中的注释的说法:

  • 设置SP初始值
  • 设置PC初始值,即Reset_Handler标号代表的地址
  • 设置向量表中各向量的入口点,即各异常ISR的地址
  • 设置时钟系统;设置板载外部SRAM为data memory(可选,由用户使能)
  • 跳转到C库中的__main函数(最终会调用main()

1. 开辟栈空间

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

开辟大小为0x00000400(1KB)的栈,名为STACK,NOINIT即不初始化,READWRITE表示可读写,ALIGN=3代表8(2^3)字节对齐。
EQU: 宏定义的伪指令,类似于c语言中的#define
AREA命令指示汇编器汇编一个新的代码段或者数据段。段是独立的、命名的、不可分割的代码或数据序列。一个代码段是生成一个应用程序的最低要求。

AREA伪指令用法:
语法 : AREA sectionname{,attr}{,attr}…
其中:
sectionname 是将要指定的段名。
可以为段选择任何段名。但是,以一个数字开始的名称必须包含在竖杠号内,否则会产生一个缺失段名错误。例如,|1_DataArea|。
有些名称是习惯性的名称。例如,|.text| 用于表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
attr 是一个或多个用逗号分隔的段属性。其中部分属性解读:
ALIGN = expression
默认情况下,ELF 段在四字节边界上对齐。expression 可以拥有 0 到 31 的任何整数。段在 2^expression 字节边界上对齐。例如,如果 expression 是 10,则段是在 1KB 边界上对齐。
注意:这里的ALIGN参数ALIGN命令所指定的方式并不相同,ALIGN命令用于段内部调整ALIGN指令下一条命令或数据的对齐位置,后面跟着要对齐的字节数,如ALIGN 4,代表4字节对齐。

SPACE: 用于分配一个一定大小的内存空间,以字节为单位,并初始化为0
语法:标号 SPACE 表达式 ,表达式为要分配的字节数,SPACE也可用“%”代替。这里指定的大小为Stack_size。
__initial_sp: 这是一个标号,它紧接在SPACE之后,代表栈的开始地址。注意,栈的开始地址是栈顶(地址最大的位置),ARM是满减栈,也就是栈由高向低生长。

2. 开辟堆空间

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

开辟大小为0x00000200(512字节)的堆,名字为HEAP,不初始化,可读可写,8字节对齐。
__heap_base表示堆的起始地址,__heap_limit表示堆的结束地址。
堆是由低向高生长的。
PRESERVE8表指定当前文件的堆栈按照8字节对齐,THUMB表示接下来的指令兼容THUMB指令。THUMB是ARM老的16bit的指令集,现在Cortex-M系列的ARM都使用32bit的THUMB-2指令集,它兼容16bit和32bit的指令。

3. 栈对齐和THUMB

                PRESERVE8
                THUMB

PRESERVE8声明当前文件的栈按照8字节是对齐的。它不会对栈做任何修改,只是一个声明,具体的对齐要靠ALIGN来保证。与之对应的是REQUIRE8,他们都是供编译器使用的信息。
The REQUIRE8 and PRESERVE8 directives specify that the current file requires or preserves eight-byte alignment of the stack.
The REQUIRE8 directive sets the REQ8 build attribute to inform the linker.
The PRESERVE8 directive sets the PRES8 build attribute to inform the linker.
The linker checks that any code that requires eight-byte alignment of the stack is only called, directly or indirectly, by code that preserves eight-byte alignment of the stack.
关于栈字节对齐的其他资料:
https://www.cnblogs.com/qiyuexin/p/12608661.html
https://blog.csdn.net/hsl416604093/article/details/80223261
THUMB告诉编译器使用THUMB指令集,THUMB 必须位于使用新语法的任何Thumb代码之前

4. 中断向量表

; Vector Table Mapped to Address 0 at Reset
           AREA RESET, DATA, READONLY ;定义RESET段,数据段,只读
           EXPORT __Vectors           ;中断向量表入口地址
           EXPORT __Vectors_End       ;向量表终止地址
           EXPORT __Vectors_Size      ;向量表空间大小

“__Vectors”、”__Vectors_End”、”__Vectors_Size”用EXPORT声明的标号,使其具有全局属性,可供外部文件调用。
内部中断/异常:

__Vectors   DCD __initial_sp ; Top of Stack ,栈顶指针,被放在向量表的开始,FLASH的0地址,复位后首先装载栈顶指针
            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, 总线错误中断,一般发生在数据访问异常,比如fsmc访问不当
            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,挂起异常,此处可以用作RTOS的上下文切换异常,这是被推荐使用的,因为Cortex-M4会在异常发生时自动保存R0-R3,R12,R13(堆栈指针SP),R14(链接地址,也叫返回地址LR,在异常返回时使用),R15(程序计数器PC,为当前应用程序+4)和中断完成时自动回复,我们只需保存R4-R11,大大减少了中断响应和上下文切换的时间。
            DCD SysTick_Handler ; SysTick Handler,滴答定时器,为操作系统内核时钟

外部中断:


            ; External Interrupts
            DCD     WWDG_IRQHandler            ; Window Watchdog
            DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
            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 & ADC2
            DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TX
            DCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0
            DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1
            DCD     CAN1_SCE_IRQHandler        ; CAN1 SCE
            DCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5
            DCD     TIM1_BRK_IRQHandler        ; TIM1 Break
            DCD     TIM1_UP_IRQHandler         ; TIM1 Update
            DCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and Commutation
            DCD     TIM1_CC_IRQHandler         ; TIM1 Capture Compare
            DCD     TIM2_IRQHandler            ; TIM2
            DCD     TIM3_IRQHandler            ; TIM3
            DCD     TIM4_IRQHandler            ; TIM4
            DCD     I2C1_EV_IRQHandler         ; I2C1 Event
            DCD     I2C1_ER_IRQHandler         ; I2C1 Error
            DCD     I2C2_EV_IRQHandler         ; I2C2 Event
            DCD     I2C2_ER_IRQHandler         ; I2C2 Error
            DCD     SPI1_IRQHandler            ; SPI1
            DCD     SPI2_IRQHandler            ; SPI2
            DCD     USART1_IRQHandler          ; USART1
            DCD     USART2_IRQHandler          ; USART2
            DCD     USART3_IRQHandler          ; USART3
            DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
            DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line
            DCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
            DCD     TIM8_BRK_IRQHandler        ; TIM8 Break
            DCD     TIM8_UP_IRQHandler         ; TIM8 Update
            DCD     TIM8_TRG_COM_IRQHandler    ; TIM8 Trigger and Commutation
            DCD     TIM8_CC_IRQHandler         ; TIM8 Capture Compare
            DCD     ADC3_IRQHandler            ; ADC3
            DCD     FSMC_IRQHandler            ; FSMC
            DCD     SDIO_IRQHandler            ; SDIO
            DCD     TIM5_IRQHandler            ; TIM5
            DCD     SPI3_IRQHandler            ; SPI3
            DCD     UART4_IRQHandler           ; UART4
            DCD     UART5_IRQHandler           ; UART5
            DCD     TIM6_IRQHandler            ; TIM6
            DCD     TIM7_IRQHandler            ; TIM7
            DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
            DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
            DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
            DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

5. 复位处理程序(复位中断函数)

; Reset handler
Reset_Handler PROC
        EXPORT Reset_Handler [WEAK]      ;此处[WEAK]表示弱定义,优先执行其他文件的定义
    IMPORT SystemInit
    IMPORT __main

        LDR R0, =SystemInit            ; 装载寄存器指令
        BLX R0                         ; 带链接的跳转
        LDR R0, =__main
        BX R0                          ; BX表示跳转到R0地址不用返回
        ENDP

PROC和ENDP是子程序定义伪指令,用法:

子程序名 PROC NEAR ( 或 FAR )
……
子程序名 ENDP

NEAR属性(段内近调用): 调用程序和子程序在同一代码段中,只能被相同代码段的其他程序调用;
FAR属性(段间远调用): 调用程序和子程序不在同一代码段中,可以被相同或不同代码段的程序调用.

WEAK声明,它表示弱定义:如果外部文件优先定义了该标号则首先引用外部文件定义的标号,反之就引用此处用WEAK声明的标号。
IMPORT表示该标号来自外部文件,跟c语言关键字的extern类似。这里声明__main和SystemInit这两个标号表明均来自外部文件。
SystemInit在system_stm32f10x.c文件中定义并实现,是ST公司为我们写好的,其主要作用是配置系统时钟为72MHz。当然如果想要自己配置时钟的话可以使用自己定义的函数,或者直接屏蔽掉它放到main()里面实现。
__main函数不等于main函数,它是一个库函数,实现的是初始化用户堆栈,在函数最后才是去调用main函数进入c语言运行环境。
__main函数介绍
__main中包含以下两个大的函数:

  • __scatterload(): 负责把RW/RO输出段从装载域地址复制到运行域地址,并完成ZI运行域的初始化工作。
  • __rt_entry(): 负责初始化堆栈(会调用下文将会看到的__user_initial_stackheap函数),完成库函数的初始化,最后跳转到main()函数。

__scatterload()详细解释:
完成对映像文件的初始化操作。
在介绍映像文件的初始化操作之前,先介绍以下几个概念:
1. 映像文件
链接器把多个目标文件链接成一个映像文件。
2. 加载地址和执行地址
映像文件可以有两种地址:加载地址和执行地址。加载地址是映像文件在存储器中的存储地址;执行地址就是映像文件运行时的地址。
3. 加载域和执行域
文件加载的存储区叫加载域,文件运行的存储区叫执行域。
4. 从加载地址到执行地址
在结构比较简单的系统中,加载地址就是执行地址;而在复杂系统中,程序运行前,常常会把映像文件的一部分或全部从存储区域移出去,此时执行地址就不再是加载地址。
知道以上几个概念,__main函数对映像文件的初始操作就不难理解了。对于加载地址和执行地址不同的映像文件,__main函数会把加载地址的代码和数据复制到执行地址中,并且对被链接器指定为需要初始化为0的段,进行清零操作。

注意:有些应用中会要求在进入main函数之前先初始化一些外设或者变量区,如:初始化时钟、初始化SDRAM。在初始化他们的时候一定不要使用全局变量,部分库函数,HAL库。因为在__main之前,全局变量还没有初始化,使用会异常,甚至发生内存错误。因此建议使用寄存器来初始化。
更进一步的对__main的分析:http://m.eeworld.com.cn/ic_article/268/483981.html

6. 其他内部异常/中断处理函数

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC
                EXPORT  NMI_Handler         [WEAK]
                B       .                          ;B表跳转到一个标号,"."表无限循环
                ENDP
                HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler    [WEAK]
                B       .
                ENDP
                ......

从注释可以看出,这里定义的异常处理子程序都是假的异常处理函数,即函数内不做任何事,只有一个死循环,起到占位的作用。真正的中断服务函数需要我们在外部的.c文件中实现(当然也可以直接修改这里的函数),当我们开启某个中断后,没有写对应的中断服务函数或者函数名有误,当中断来临时程序还是跳转到启动文件预先写好的中断服务函数中,在这个函数中无限循环。

7. 外部中断处理函数

Default_Handler PROC
; 输出异常向量表标号,方便外部实现异常的具体功能 , [WEAK] 是弱定义的意思,如果外部定义了,优先执行外部定义,否则执行下面的函数定义
                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]
                EXPORT  TIM8_BRK_IRQHandler        [WEAK]
                EXPORT  TIM8_UP_IRQHandler         [WEAK]
                EXPORT  TIM8_TRG_COM_IRQHandler    [WEAK]
                EXPORT  TIM8_CC_IRQHandler         [WEAK]
                EXPORT  ADC3_IRQHandler            [WEAK]
                EXPORT  FSMC_IRQHandler            [WEAK]
                EXPORT  SDIO_IRQHandler            [WEAK]
                EXPORT  TIM5_IRQHandler            [WEAK]
                EXPORT  SPI3_IRQHandler            [WEAK]
                EXPORT  UART4_IRQHandler           [WEAK]
                EXPORT  UART5_IRQHandler           [WEAK]
                EXPORT  TIM6_IRQHandler            [WEAK]
                EXPORT  TIM7_IRQHandler            [WEAK]
                EXPORT  DMA2_Channel1_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel4_5_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
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .

                ENDP

                ALIGN   ; 默认是字对齐方式,也说明了代码是4字节对齐的

8. 堆栈初始化

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 IF      :DEF:__MICROLIB    ;这个宏在KEIL里定义
                
                 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

IF, ELSE, ENDIF分支语句
END到达文件末尾,文件结束

判断是否定义了__MICROLIB,这个宏是在KEIL里面可配置的: Options - Target - Code Generation - Use MicroLIB
选中它表示使用c库的备选库,里面有一个__mian函数。若定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆的起始地址)、__heap_limit(堆的结束地址)为外部文件可调用的变量,即可供外部c库中的__main调用,由__main初始化堆栈,否则需要用户自己实现__user_initial_stackheap函数初始化堆栈。
__user_initial_stackheap是一个被导出的函数,它把堆栈地址赋值给对应的寄存器以方便其他程序使用,分别是R0:堆基址(heap base),R1:栈基址(stack base,一般为栈的最高地址),R2:堆顶(heap limit),R3:栈顶(stack limit)
那么这个函数在哪里被调用?
答案是在复位中断向量函数中被__main调用

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值