04. STM32F1的启动文件
一、启动模式
复位方式有三种:上电复位,硬件复位 和 软件复位。当产生复位,并且离开复位状态后,CM3 内核做的第一件事就是读取下列两个 32 位整数的值:
- 从地址 0x00000000 处取出 堆栈指针MSP 的初始值,该值就是栈顶地址
- 从地址 0x00000004 处取出 程序计数器指针PC 的初始值,该值指向复位后执行的第一条指令
上述过程中,内核是从 0x00000000 和 0x00000004 两个的地址获取 堆栈指针SP 和 程序计数器指针PC。事实上,0x00000000 和 0x00000004 两个的地址可以被重映射到其他的地址空间。将 0x00000000 和 0x00000004 两个地址重映射到其他的地址空间,就是启动模式选择。
对于 STM32F1 的启动模式(也称自举模式)的选择:
| 启动模式选择引脚电平 | 启动模式 | 0x00000000映射地址 | 0x00000004映射地址 | |
|---|---|---|---|---|
| BOOT0 | BOOT1 | |||
| 0 | x | 内部 FLASH | 0x08000000 | 0x01000004 |
| 1 | 1 | 内部 SRAM | 0x20000000 | 0x20000004 |
| 1 | 0 | 系统存储器 | 0x1FFFF000 | 0x1FFFF004 |
启动引脚的电平:0:低电平;1:高电平;x:任意电平,即高低电平均可;
在系统复位后,SYSCLK 的第 4 个 上升沿,BOOT 引脚的值将被锁存。STM32F1 根据 BOOT 引脚的电平选择启动模式,这两个 BOOT 引脚根据外部施加的电平来决定芯片的启动地址。
【1】、内部 FLASH 启动方式
当芯片上电后采样到 BOOT0 引脚为 低电平 时,0x00000000 和 0x00000004 地址被映射到 内部FLASH 的首地址0x08000000 和 0x08000004。因此,内核离开复位状态后,读取 内部FLASH 的 0x08000000 地址空间存储的内容,赋值给 栈指针MSP,作为 栈顶地址,再读取 内部FLASH 的 0x08000004 地址空间存储的内容,赋值给 程序指针PC,作为将要执行的第一条指令所在的地址。完成这两个操作后,内核就可以开始从 PC 指向的地址中读取指令执行了,就是开始执行复位中断服务程序 Reset_Handler。
【2】、内部 SRAM 启动方式
类似于 内部Flash,当芯片上电后采样到 BOOT0 和 BOOT1 引脚均为 高电平 时,地址 0x00000000 和 0x00000004 被映射到 内部SRAM 的首地址 0x20000000 和 0x20000004,内核从 SRAM 空间获取内容进行自举。在实际应用中,由启动文件starttup_stm32f103xe.s 决定了 0x00000000 和 0x00000004 地址存储什么内容,链接时,由分散加载文件(sct)决定这些内容的绝对地址,即分配到 内部FLASH 还是 内部SRAM。
【3】、系统存储器启动方式
当芯片上电后采样到 BOOT0 = 1,BOOT1 = 0 的组合时,内核将从系统存储器的 0x1FFFF000 及 0x1FFFF004 获取 MSP 及 PC值 进行自举。系统存储器是一段特殊的空间,用户不能访问,ST公司 在芯片出厂前就在系统存储器中固化了一段代码。因而使用系统存储器启动方式时,内核会执行该代码,该代码运行时,会为 ISP(InSystemProgram)提供支持,在 STM32F1 上最常见的是检测 USART1 传输过来的信息,并根据这些信息更新自己 内部FLASH 的内容,达到升级产品应用程序的目的,因此这种启动方式也称为 ISP启动方式。
二、启动文件分析
STM32 启动文件由 ST 官方提供,在官方的 STM32Cube 固件包里。启动文件用汇编编写,是系统上电复位后第一个执行的程序。
启动文件主要做了以下工作:
- 初始化堆栈指针 SP=_initial_sp
- 初始化程序计数器指针 PC =Reset_Handler
- 设置堆和栈的大小
- 初始化中断向量表
- 配置外部 SRAM 作为数据存储器(可选)
- 配置系统时钟,通过调用 SystemInit 函数(可选)
- 调用 C 库中的 _main 函数初始化用户堆栈,最终调用 main 函数
2.1、启动文件中的一些指令
| 指令名称 | 作用 |
|---|---|
| EQU | 给数字常量取一个符号名,相当于 C 语言中的 define |
| AREA | 汇编一个新的代码段或者数据段 |
| ALIGN | 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4字节对齐。 这个不是ARM的指令,是编译器的 |
| SPACE | 分配内存空间 |
| PRESERVE8 | 当前文件堆栈需要按照 8 字节对齐 |
| THUMB | 表示后面指令兼容THUMB指令。 在ARM以前的指令集中有16位的THUMBM指令, 现在Cortex-M系列使用的都是THUMB-2指令集, THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超级版。 |
| EXPORT | 声明一个标号具有全局属性,可被外部的文件使用 |
| DCD | 以字节为单位分配内存,要求 4 字节对齐,并要求初始化这些内存 |
| PROC | 定义子程序,与 ENDP 成对使用,表示子程序结束 |
| WEAK | 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。 这个不是ARM的指令,是编译器的,这里放到一起为了方便。 |
| IMPORT | 声明标号来自外部文件,跟 C 语言中的 extern 关键字类似 |
| LDR | 从存储器中加载字到一个存储器中 |
| BLX | 跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR |
| BX | 跳转到由寄存器/标号给出的地址,不用返回 |
| B | 跳转到一个标号 |
| IF,ELSE,ENDIF | 汇编条件分支语句,跟C语言的类似 |
| END | 到达文件的末尾,文件结束 |
2.2、启动文件代码讲解
2.2.1、栈空间的开辟
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
开辟一段大小为 0x0000 0400(1KB)的栈空间,段名为 STACK,NOINIT 表示不初始化;READWRITE 表示可读可写;ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐。AREA 汇编一个新的代码段或者数据段。
SPACE 分配内存指令,分配大小为 Stack_Size 字节连续的存储单元给栈空间。
__initial_sp 紧挨着 SPACE 放置,表示栈的结束地址,栈是从高往低生长,所以结束地址就是栈顶地址。
栈主要用于存放局部变量,函数形参等,属于编译器自动分配和释放的内存,栈的大小不能超过 内部SRAM 的大小。如果工程的程序量比较大,定义的局部变量比较多,那么就需要在启动代码中修改栈的大小,即修改 Stack_Size 的值。如果程序出现了莫名其妙的错误,并进入了 HardFault 的时候,你就要考虑下是不是栈空间不够大,溢出了的问题。
2.2.2、堆空间的开辟
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
开辟一段大小为 0x0000 0200(512字节)的堆空间,段名为 HEAP,不初始化,可读可写,8 字节对齐。
__heap_base 表示堆的起始地址,__heap_limit 表示堆的结束地址。堆和栈的生长方向相反的,堆是由低向高生长,而栈是从高往低生长。
堆主要用于动态内存的分配,像 malloc()、calloc()和realloc() 等函数申请的内存就在堆上面。堆中的内存一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。
PRESERVE8:指示编译器按照8字节对齐。
THUMB:指示编译器之后的指令为THUMB指令。
2.2.3、中断向量表定义
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
定义一个数据段,名字为 RESET, READONLY 表示只读。EXPORT 表示声明一个标号具有全局属性,可被外部的文件使用。这里是声明了__Vectors、__Vectors_End 和 __Vectors_Size 三个标号具有全局性,可被外部的文件使用。
STM32F103 的中断向量表定义代码:
__Vectors DCD __initial_sp ; Top of Stack
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
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
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_TIM9_IRQHandler ; TIM1 Break and TIM9
DCD TIM1_UP_TIM10_IRQHandler ; TIM1 Update and TIM10
DCD TIM1_TRG_COM_TIM11_IRQHandler ; TIM1 Trigger and Commutation and TIM11
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 RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_TIM12_IRQHandler ; TIM8 Break and TIM12
DCD TIM8_UP_TIM13_IRQHandler ; TIM8 Update and TIM13
DCD TIM8_TRG_COM_TIM14_IRQHandler ; TIM8 Trigger and Commutation and TIM14
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
__Vectors 为向量表起始地址,__Vectors_End 为向量表结束地址,__Vectors_Size 为向量表大小,__Vectors_Size =__Vectors_End-__Vectors。
DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。中断向量表被放置在代码段的最前面。DCD 以四字节对齐分配内存,也就是下个地址是 0x0800 0004,存放的是 Reset_Handler 中断函数入口地址。
2.2.4、复位程序
定义一个段命为 .text,只读的代码段,在 CODE 区。
AREA |.text|, CODE, READONLY
复位子程序代码:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
利用 PROC、ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。复位子程序是复位后第一个被执行的程序,主要是调用 SystemInit 函数配置系统时钟、还有就是初始化 FSMC 总线上外挂的 SRAM(可选)。然后在调用 C 库函数 __main,最终调用 main 函数去到 C 的世界。
EXPORT 声明复位中断向量 Reset_Handler 为全局属性,这样外部文件就可以调用此复位中断服务。
WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用外部定义的标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT 表示该标号来自外部文件。这里表示 SystemInit 和 __main 这两个函数均来自外部的文件。
LDR 表示从存储器中加载字到一个存储器中。
BLX 表示跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。
BX 表示跳转到由寄存器/标号给出的地址,不用返回。这里表示切换到 __main 地址,最终调用 main 函数,不返回,进入 C 的世界。
2.2.5、中断服务程序
; Dummy Exception Handlers (infinite loops which can be modified)
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
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_TIM9_IRQHandler [WEAK]
EXPORT TIM1_UP_TIM10_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_TIM11_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 RTC_Alarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
EXPORT TIM8_BRK_TIM12_IRQHandler [WEAK]
EXPORT TIM8_UP_TIM13_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_TIM14_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_TIM9_IRQHandler
TIM1_UP_TIM10_IRQHandler
TIM1_TRG_COM_TIM11_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
RTC_Alarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_TIM12_IRQHandler
TIM8_UP_TIM13_IRQHandler
TIM8_TRG_COM_TIM14_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
这些中断服务函数都被 [WEAK] 声明为弱定义函数,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。
这些中断函数分为系统异常中断和外部中断,外部中断根据不同芯片有所变化。B指令是跳转到一个标号,这里跳转到一个‘.’,表示无限循环。
在启动文件代码中,已经把我们所有中断的中断服务函数写好了,但都是声明为弱定义,所以真正的中断服务函数需要我们在外部实现。
如果我们开启了某个中断,但是忘记写对应的中断服务程序函数又或者把中断服务函数名写错,那么中断发生时,程序就会跳转到启动文件预先写好的弱定义的中断服务程序中,并且在B指令作用下跳转到一个‘.’中,无限循环。
这里的系统异常中断部分是内核的,外部中断部分是外设的。
2.2.6、用户堆栈初始化
ALIGN
ALIGN 表示对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4字节对齐。要注意的是,这个不是 ARM 的指令,是编译器的。
用户堆栈初始化代码:
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
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
2.3、系统启动流程
启动模式不同,启动的起始地址是不一样的,下面我们以代码下载到 内部FLASH 的情况举例,即代码从地址 0x0800 0000 开始被执行。
当产生复位,并且离开复位状态后,CM3 内核做的第一件事就是读取下列两个 32 位整数的值:
- 从地址 0x08000000 处取出 堆栈指针MSP 的初始值,该值就是栈顶地址。
- 从地址 0x08000004 处取出 程序计数器指针PC 的初始值,该值指向中断服务程序 Reset_Handler。
传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。而在 CM3 内核中,0 地址处提供 MSP 的初始值,然后就是向量表(向量表在以后还可以被移至其它位置)。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是 Reset_Handler 这个函数。
三、map文件分析
3.1、MDK编译生成文件简介
MDK编译工程,会生成一些中间文件(如.o、.axf、.map 等),最终生成hex文件,以便下载到MCU上面执行。
| 文件类型 | 说明 |
|---|---|
| .o | 可重定向 1 对象文件,每个源文件(.c/.s 等)编译都会生成一个 .o 文件 |
| .axf | 由 ARMCC 编译生产的可执行对象文件,不可重定向2 (绝对地址) 多个 .o 文件链接生成 .axf 文件,我们在仿真的时候,需要用到该文件 |
| .hex | Intel Hex 格式文件,可用于下载到 MCU,.hex 文件由 .axf 文件转换而来 |
| .crf | 交叉引用文件,包含浏览信息(定义、标识符、引用) |
| .d | 由 ARMCC/GCC 编译生产的依赖文件(.o 文件所对应的依赖文件) 每个 .o 文件,都有一个对应的 .d 文件 |
| .dep | 整个工程的依赖文件 |
| .lnp | MDK 生成的链接输入文件,用于命令输入 |
| .lst | C 语言或汇编编译器生成的列表文件 |
| .htm | 链接生成的列表文件 |
| .build_log.htm | 最近一次编译工程时的日志记录文件 |
| .map | 连接器生成的列表文件 .map 文件 |
3.2、map文件分析
3.2.1、map文件的组成部分
.map 文件是编译器链接时生成的一个文件,它主要包含了交叉链接信息。通过 .map 文件,我们可以知道整个工程的函数调用关系、 FLASH 和 RAM 占用情况及其详细汇总信息,能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可以对代码进行优化。.map 文件可以分为以下 5 个组成部分:
| 组成部分 | 简介 |
|---|---|
| 程序段交叉引用关系 (Section Cross References) |
描述各文件之间函数调用关系 |
| 删除映像未使用的程序段 (Removing Unused input sections from the image) |
描述工程中未用到而被删除的冗余程序段(函数/数据) |
| 映像符号表 (Image Symbol Table) |
描述各符号(程序段/数据)在存储器中的地址、类型、大小等 |
| 映像内存分布图 (Memory Map of the image) |
描述各个程序段(函数)在存储器中的地址及占用大小 |
| 映像组件大小 (Image component sizes) |
给出整个映像代码(.o)占用空间汇总信息 |
3.2.2、map文件的基础概念
- Section:描述映像文件的代码或数据块,我们简称程序段
- RO:Read Only 的缩写,包括只读数据(RO data)和代码(RO code)两部分内容,占用 FLASH 空间
- RW:Read Write 的缩写,包含可读写数据(RW data,有初值,且不为0),占用 FLASH(存储初值)和 RAM(读写操作)
- ZI:Zero initialized 的缩写,包含初始化为 0 的数据(ZI data),占用 RAM 空间。
- .text:相当于 RO code
- .constdata:相当于 RO data
- .bss:相当于 ZI data
- .data:相当于 RW data
【1】、程序段交叉引用关系(S S ection Cross References s )
这部分内容描述了各个文件(.c/.s等)之间函数(程序段)的调用关系。clock_init 表示:main.c 文件中的 main 函数,调用了sys.c 中的sys_stm32_clock_init 函数。其中:i.main 表示 main 函数的入口地址,同理 i.sys_stm32_clock_init 表示 sys_stm32_clock_init 函数的入口地址。
【2】、删除映像未使用的程序段(Removing Unused input sections from the image)
这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据)。
【3】、映像符号表(Image Symbol Table)
映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器中的存储地址、类型、大小等信息。映像符号表分为两类:本地符号(Local Symbols)和全局符号(Global Symbols)。
本地符号(Local Symbols)记录了用 static 声明的全局变量地址和大小,c 文件中函数的地址和用 static 声明的函数代码大小,汇编文件中的标号地址(作用域:限本文件)。
全局符号(Global Symbols)记录了全局变量的地址和大小,C文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域:全工程)。
【4】、映像内存分布图(Memory Map of the image)
映像文件分为 加载域(Load Region)和 运行域(Execution Region)。一个加载域必须有至少一个运行域(可以有多个运行域),而一个程序又可以有多个加载域。加载域为映像程序的实际存储区域,而运行域则是MCU上电后的运行状态。
RW 区也是存放在 ROM(FLASH)里面的,在执行main函数之前,RW(有初值且不为 0 的变量)数据会被拷贝到 RAM 区,同时还会在 RAM 里面创建 ZI 区(初始化为 0 的变量)。
【5】、映像组件大小(Image component sizes)
映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇总信息。这部分是程序实际功能可执行代码的存储空间。