04. STM32F1的启动文件

kurome / 2023-07-21 / 原文

一、启动模式

  复位方式有三种:上电复位硬件复位软件复位。当产生复位,并且离开复位状态后,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 引脚为 低电平 时,0x000000000x00000004 地址被映射到 内部FLASH 的首地址0x080000000x08000004。因此,内核离开复位状态后,读取 内部FLASH0x08000000 地址空间存储的内容,赋值给 栈指针MSP,作为 栈顶地址,再读取 内部FLASH0x08000004 地址空间存储的内容,赋值给 程序指针PC,作为将要执行的第一条指令所在的地址。完成这两个操作后,内核就可以开始从 PC 指向的地址中读取指令执行了,就是开始执行复位中断服务程序 Reset_Handler。

【2】、内部 SRAM 启动方式

  类似于 内部Flash,当芯片上电后采样到 BOOT0BOOT1 引脚均为 高电平 时,地址 0x000000000x00000004 被映射到 内部SRAM 的首地址 0x200000000x20000004,内核从 SRAM 空间获取内容进行自举。在实际应用中,由启动文件starttup_stm32f103xe.s 决定了 0x00000000 和 0x00000004 地址存储什么内容,链接时,由分散加载文件(sct)决定这些内容的绝对地址,即分配到 内部FLASH 还是 内部SRAM。

【3】、系统存储器启动方式

  当芯片上电后采样到 BOOT0 = 1BOOT1 = 0 的组合时,内核将从系统存储器的 0x1FFFF000 及 0x1FFFF004 获取 MSPPC值 进行自举。系统存储器是一段特殊的空间,用户不能访问,ST公司 在芯片出厂前就在系统存储器中固化了一段代码。因而使用系统存储器启动方式时,内核会执行该代码,该代码运行时,会为 ISP(InSystemProgram)提供支持,在 STM32F1 上最常见的是检测 USART1 传输过来的信息,并根据这些信息更新自己 内部FLASH 的内容,达到升级产品应用程序的目的,因此这种启动方式也称为 ISP启动方式

二、启动文件分析

  STM32 启动文件由 ST 官方提供,在官方的 STM32Cube 固件包里。启动文件用汇编编写,是系统上电复位后第一个执行的程序。

  启动文件主要做了以下工作:

  1. 初始化堆栈指针 SP=_initial_sp
  2. 初始化程序计数器指针 PC =Reset_Handler
  3. 设置堆和栈的大小
  4. 初始化中断向量表
  5. 配置外部 SRAM 作为数据存储器(可选)
  6. 配置系统时钟,通过调用 SystemInit 函数(可选)
  7. 调用 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)占用空间的汇总信息。这部分是程序实际功能可执行代码的存储空间。