一、简介
在STM32微处理器(包括其他类型微处理器或CPU)上电后,运行的第一行代码并不是C代码,而是一段汇编代码,因为此时的环境还无法运行C代码,比如堆栈此时还未设置,RW段、ZI段等还未拷贝、就绪等。针对STM32这类微控制器而言,启动文件就是用来完成这一工作的。对于一些高端的微控制器,也会有专门的Bootloader(如U-Boot)来完成此类工作。
启动文件其实还有一个隐含的功能。我们都知道, STM32系列微处理器都是内置FLASH和RAM的,程序被编译、链接成bin或者hex文件,然后被烧写到FLASH中。在嵌入式系统中,代码通常有链接地址(也叫运行地址,在链接脚本中指定)和加载地址(也叫存储地址,代码被实际存放的存储器地址)之分,正常情况下,链接地址和存储地址应该是一样的,这样程序才能正常工作(除非使用位置无关码技术)。对于STM32而言,代码(TEXT段)的链接地址和加载地址都位于FLASH中,但是RW和ZI等数据段的链接地址在RAM中(为了加速访问,理论上位于FLASH中也是可以的)。那么这些位于FLASH中的RW、ZI是怎么被放入RAM中的呢?是谁负责完成对ZI段清零的?这其实也是启动文件中做的,下文会做详细介绍。
二、启动文件
下面是stm32f429系列微控制器使用的启动文件:
;******************** (C) COPYRIGHT 2015 STMicroelectronics ********************
;* File Name : startup_stm32f429_439xx.s
;* Author : MCD Application Team
;* @version : V1.5.0
;* @date : 06-March-2015
;* Description : STM32F429xx/439xx devices vector table for MDK-ARM toolchain.
;* This module performs:
;* - Set the initial SP
;* - 初始化栈指针SP(ARM R13)
;* - Set the initial PC == Reset_Handler
;* - 初始化程序计数器PC == 复位异常(ARM USER模式下的R15)
;* - Set the vector table entries with the exceptions ISR address
;* - 把异常中断服务程序入口地址设置到中断向量表中
;* - Configure the system clock and the external SRAM/SDRAM mounted
;* on STM324x9I-EVAL boards to be used as data memory
;* (optional, to be enabled by user)
;* - 配置系统时钟和用作数据存储的外部挂载的SRAM/SDRAM(可选,由用户使能)
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* - 设置分支到C库中的__main(用来最后调用main()函数)
;* After Reset the CortexM4 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>
;*******************************************************************************
;
; Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
; You may not use this file except in compliance with the License.
; You may obtain a copy of the License at:
;
; http://www.st.com/software_license_agreement_liberty_v2
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
;*******************************************************************************
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400
;栈大小Stack_Size为0x00000400,即1KB。
AREA STACK, NOINIT, READWRITE, ALIGN=3
;AREA伪指令新建代码段或数据段,STACK表示可变的段名,未初始化,可读可写,2^3=8字节(双字)边界对齐。
Stack_Mem SPACE Stack_Size
;Stack_Mem为标号,SPACE伪指令分配了一片连续的大小为Stack_Size字节的存储区域并初始化为0。
__initial_sp
;__initial_sp是个标号,代表当前指令的地址,即栈顶地址。
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
;堆大小Heap_Size为0x00000200,即512B。
AREA HEAP, NOINIT, READWRITE, ALIGN=3
;新建堆段,未初始化,可读可写,8字节对齐。
__heap_base
;标号,堆的起始地址。
Heap_Mem SPACE Heap_Size
;分配并初始化一片连续的存储空间。
__heap_limit
;标号,堆的结束地址。堆是由低向高生长的,跟栈相反。
PRESERVE8
;等于PRESERVE8 {TRUE},设置文件的PRSE8编译属性,使代码保持堆栈8字节对齐。
THUMB
;THUMB必须位于使用新语法的任何Thumb代码之前,后面指令兼容THUMB指令。
; Vector Table Mapped to Address 0 at Reset
;向量表在复位时映射到地址0
AREA RESET, DATA, READONLY
;定义一个数据段RESET,只读。
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
;声明三个全局标号,该标号可在其他的文件中被引用。
__Vectors DCD __initial_sp ; Top of Stack 栈顶
;中断向量表起始地址,DCD伪指令用于分配一篇连续的字存储单元并用伪指令中制定的表达式初始化。
;用DCD分配的字存储单元是字(4字节)对齐的。
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 系统调用异常
;SVC是用户模式代码中的主进程
;用于创造对特权操作系统代码的调用
DCD DebugMon_Handler ; Debug Monitor Handler ???
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler 挂起异常
;用作上下文切换异常。
DCD SysTick_Handler ; SysTick Handler 系统滴答定时器
;操作系统内核时钟
; External Interrupts 以上都是Coretex M3内核自带的,以下为外部中断向量表
DCD WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
;PVD = Programmable Voltage Detector
;可编程电压监测器(Vdd和Vdda都要参与比较)
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line ???
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
DCD EXTI2_IRQHandler ; EXTI Line2
DCD EXTI3_IRQHandler ; EXTI Line3
DCD EXTI4_IRQHandler ; EXTI Line4
DCD DMA1_Stream0_IRQHandler ; DMA1 Stream 0
DCD DMA1_Stream1_IRQHandler ; DMA1 Stream 1
DCD DMA1_Stream2_IRQHandler ; DMA1 Stream 2
DCD DMA1_Stream3_IRQHandler ; DMA1 Stream 3
DCD DMA1_Stream4_IRQHandler ; DMA1 Stream 4
DCD DMA1_Stream5_IRQHandler ; DMA1 Stream 5
DCD DMA1_Stream6_IRQHandler ; DMA1 Stream 6
DCD ADC_IRQHandler ; ADC1, ADC2 and ADC3s
DCD CAN1_TX_IRQHandler ; CAN1 TX
DCD CAN1_RX0_IRQHandler ; CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; External Line[9:5]s
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 ; External Line[15:10]s
DCD RTC_Alarm_IRQHandler ; RTC Alarm (A and B) through EXTI Line
DCD OTG_FS_WKUP_IRQHandler ; USB OTG FS Wakeup through EXTI line
;OTG = On-The-Go
;主要应用于各种不同的设备或移动设备间的联接和数据交换
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 DMA1_Stream7_IRQHandler ; DMA1 Stream7
DCD FMC_IRQHandler ; FMC ???
DCD SDIO_IRQHandler ; SDIO
;SDIO = Secure Digital Input and Output
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_DAC_IRQHandler ; TIM6 and DAC1&2 underrun errors
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Stream0_IRQHandler ; DMA2 Stream 0
DCD DMA2_Stream1_IRQHandler ; DMA2 Stream 1
DCD DMA2_Stream2_IRQHandler ; DMA2 Stream 2
DCD DMA2_Stream3_IRQHandler ; DMA2 Stream 3
DCD DMA2_Stream4_IRQHandler ; DMA2 Stream 4
DCD ETH_IRQHandler ; Ethernet
DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line
DCD CAN2_TX_IRQHandler ; CAN2 TX
DCD CAN2_RX0_IRQHandler ; CAN2 RX0
DCD CAN2_RX1_IRQHandler ; CAN2 RX1
DCD CAN2_SCE_IRQHandler ; CAN2 SCE
DCD OTG_FS_IRQHandler ; USB OTG FS
DCD DMA2_Stream5_IRQHandler ; DMA2 Stream 5
DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6
DCD DMA2_Stream7_IRQHandler ; DMA2 Stream 7
DCD USART6_IRQHandler ; USART6
DCD I2C3_EV_IRQHandler ; I2C3 event
DCD I2C3_ER_IRQHandler ; I2C3 error
DCD OTG_HS_EP1_OUT_IRQHandler ; USB OTG HS End Point 1 Out
DCD OTG_HS_EP1_IN_IRQHandler ; USB OTG HS End Point 1 In
DCD OTG_HS_WKUP_IRQHandler ; USB OTG HS Wakeup through EXTI
DCD OTG_HS_IRQHandler ; USB OTG HS
DCD DCMI_IRQHandler ; DCMI
;DCMI = Digital Camera Interface
;快速摄像头接口
DCD CRYP_IRQHandler ; CRYP crypto
;CRYP = Cryptographic Processor
;加密处理器
DCD HASH_RNG_IRQHandler ; Hash and Rng
;Hash Processor 哈希处理器
;RNG = Random Number Generator
;随机数发生器
DCD FPU_IRQHandler ; FPU
;FPU = Float Point Unit
;浮点运算单元
DCD UART7_IRQHandler ; UART7
DCD UART8_IRQHandler ; UART8
DCD SPI4_IRQHandler ; SPI4
DCD SPI5_IRQHandler ; SPI5
DCD SPI6_IRQHandler ; SPI6
DCD SAI1_IRQHandler ; SAI1
;SAI = Serial Audio Interface
;串行音频接口
DCD LTDC_IRQHandler ; LTDC 液晶分层显示功能
DCD LTDC_ER_IRQHandler ; LTDC error
DCD DMA2D_IRQHandler ; DMA2D 2D图形加速器
__Vectors_End
;中断向量表结束地址
__Vectors_Size EQU __Vectors_End - __Vectors
;计算中断向量表地址空间大小
AREA |.text|, CODE, READONLY
;|.text|表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
; Reset handler
Reset_Handler PROC
;PROC、ENDP为过程定义伪指令,一个过程可以被其他程序所调用(用CALL指令)。
EXPORT Reset_Handler [WEAK]
;EXPORT伪指令声明一个全局标号,[WEAK]声明其他的同名标号优先于该标号被引用。
IMPORT SystemInit
;IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,
;而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
IMPORT __main
LDR R0, =SystemInit
;LDR将跳转地址放入寄存器R0,准备调用SystemInit
BLX R0
;带链接和状态切换的跳转,根据最低位切换指令集,调用完子程序后返回。
LDR R0, =__main
BX R0
;带状态切换的跳转,根据最低位切换指令集,不返回。
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
;虚拟的中断处理程序(可被修改的无限循环)
;这些中断服务程序都是死循环,真正的中断服务函数需要我们在外部C文件里重新实现。
;如果开了某个中断但没有重新实现中断服务函数,或者写错函数名,
;则当中断到来时程序将跳转到这里进入死循环。
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 TAMP_STAMP_IRQHandler [WEAK]
EXPORT RTC_WKUP_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_Stream0_IRQHandler [WEAK]
EXPORT DMA1_Stream1_IRQHandler [WEAK]
EXPORT DMA1_Stream2_IRQHandler [WEAK]
EXPORT DMA1_Stream3_IRQHandler [WEAK]
EXPORT DMA1_Stream4_IRQHandler [WEAK]
EXPORT DMA1_Stream5_IRQHandler [WEAK]
EXPORT DMA1_Stream6_IRQHandler [WEAK]
EXPORT ADC_IRQHandler [WEAK]
EXPORT CAN1_TX_IRQHandler [WEAK]
EXPORT 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 OTG_FS_WKUP_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 DMA1_Stream7_IRQHandler [WEAK]
EXPORT FMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_DAC_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Stream0_IRQHandler [WEAK]
EXPORT DMA2_Stream1_IRQHandler [WEAK]
EXPORT DMA2_Stream2_IRQHandler [WEAK]
EXPORT DMA2_Stream3_IRQHandler [WEAK]
EXPORT DMA2_Stream4_IRQHandler [WEAK]
EXPORT ETH_IRQHandler [WEAK]
EXPORT ETH_WKUP_IRQHandler [WEAK]
EXPORT CAN2_TX_IRQHandler [WEAK]
EXPORT CAN2_RX0_IRQHandler [WEAK]
EXPORT CAN2_RX1_IRQHandler [WEAK]
EXPORT CAN2_SCE_IRQHandler [WEAK]
EXPORT OTG_FS_IRQHandler [WEAK]
EXPORT DMA2_Stream5_IRQHandler [WEAK]
EXPORT DMA2_Stream6_IRQHandler [WEAK]
EXPORT DMA2_Stream7_IRQHandler [WEAK]
EXPORT USART6_IRQHandler [WEAK]
EXPORT I2C3_EV_IRQHandler [WEAK]
EXPORT I2C3_ER_IRQHandler [WEAK]
EXPORT OTG_HS_EP1_OUT_IRQHandler [WEAK]
EXPORT OTG_HS_EP1_IN_IRQHandler [WEAK]
EXPORT OTG_HS_WKUP_IRQHandler [WEAK]
EXPORT OTG_HS_IRQHandler [WEAK]
EXPORT DCMI_IRQHandler [WEAK]
EXPORT CRYP_IRQHandler [WEAK]
EXPORT HASH_RNG_IRQHandler [WEAK]
EXPORT FPU_IRQHandler [WEAK]
EXPORT UART7_IRQHandler [WEAK]
EXPORT UART8_IRQHandler [WEAK]
EXPORT SPI4_IRQHandler [WEAK]
EXPORT SPI5_IRQHandler [WEAK]
EXPORT SPI6_IRQHandler [WEAK]
EXPORT SAI1_IRQHandler [WEAK]
EXPORT LTDC_IRQHandler [WEAK]
EXPORT LTDC_ER_IRQHandler [WEAK]
EXPORT DMA2D_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMP_STAMP_IRQHandler
RTC_WKUP_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Stream0_IRQHandler
DMA1_Stream1_IRQHandler
DMA1_Stream2_IRQHandler
DMA1_Stream3_IRQHandler
DMA1_Stream4_IRQHandler
DMA1_Stream5_IRQHandler
DMA1_Stream6_IRQHandler
ADC_IRQHandler
CAN1_TX_IRQHandler
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
OTG_FS_WKUP_IRQHandler
TIM8_BRK_TIM12_IRQHandler
TIM8_UP_TIM13_IRQHandler
TIM8_TRG_COM_TIM14_IRQHandler
TIM8_CC_IRQHandler
DMA1_Stream7_IRQHandler
FMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_DAC_IRQHandler
TIM7_IRQHandler
DMA2_Stream0_IRQHandler
DMA2_Stream1_IRQHandler
DMA2_Stream2_IRQHandler
DMA2_Stream3_IRQHandler
DMA2_Stream4_IRQHandler
ETH_IRQHandler
ETH_WKUP_IRQHandler
CAN2_TX_IRQHandler
CAN2_RX0_IRQHandler
CAN2_RX1_IRQHandler
CAN2_SCE_IRQHandler
OTG_FS_IRQHandler
DMA2_Stream5_IRQHandler
DMA2_Stream6_IRQHandler
DMA2_Stream7_IRQHandler
USART6_IRQHandler
I2C3_EV_IRQHandler
I2C3_ER_IRQHandler
OTG_HS_EP1_OUT_IRQHandler
OTG_HS_EP1_IN_IRQHandler
OTG_HS_WKUP_IRQHandler
OTG_HS_IRQHandler
DCMI_IRQHandler
CRYP_IRQHandler
HASH_RNG_IRQHandler
FPU_IRQHandler
UART7_IRQHandler
UART8_IRQHandler
SPI4_IRQHandler
SPI5_IRQHandler
SPI6_IRQHandler
SAI1_IRQHandler
LTDC_IRQHandler
LTDC_ER_IRQHandler
DMA2D_IRQHandler
B .
ENDP
ALIGN
;ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对齐方式。
;后面的表达式未指定则将当前位置对齐到下一个字的位置。
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
;如果定义了__MICROLIB,则为以下三个标号赋予全局属性。
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
;否则,则引入在其他源文件中定义的标号__use_two_region_memory,
;声明全局标号__user_initial_stackheap。
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
;堆栈初始化,R0保存堆起始地址,R1保存栈顶地址,R2保存堆的结束地址,R3保存栈底地址。
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
;ENTRY伪指令标识程序的入口点,END指示代码段结束。
;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****
三、启动文件分析
由上文可以看到,启动文件主要完成的事情如下:
1、初始化堆栈指针 SP=_initial_sp
2、初始化 PC 指针=Reset_Handler
3、初始化中断向量表
4、配置系统时钟
5、调用 C 库函数_main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界
启动代码中,会涉及到 ARM 的汇编指令和 Cortex 内核的指令,有关 Cortex 内核的指令我们可以参考《CM3 权威指南 CnR2》第四章:指令集。下面列 出了启动文件中使用到的 ARM 汇编指令,该列表的指令全部从 ARM Development Tools 这个帮助文档里面检索而来。其中编译器相关的指令 WEAK 和 ALIGN 为了方便也放在同 一个表格了。
Stack—栈
Stack_Size EQU 0x00000400
;栈大小Stack_Size为0x00000400,即1KB。
AREA STACK, NOINIT, READWRITE, ALIGN=3
;AREA伪指令新建代码段或数据段,STACK表示可变的段名,未初始化,可读可写,2^3=8字节(双字)边界对齐。
Stack_Mem SPACE Stack_Size
;Stack_Mem为标号,SPACE伪指令分配了一片连续的大小为Stack_Size字节的存储区域并初始化为0。
__initial_sp
;__initial_sp是个标号,代表当前指令的地址,即栈顶地址。
开辟栈的大小为 0X00000400(1KB),名字为 STACK,NOINIT 即不初始化,可读可 写,8(2^3)字节对齐。
栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部 SRAM 的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。 如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬 fault 的时候,这时你就要考 虑下是不是栈不够大,溢出了。
EQU:宏定义的伪指令,相当于等于,类似与 C 中的 define。
AREA:告诉汇编器汇编一个新的代码段或者数据段。STACK 表示段名,这个可以任 意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐。
SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于 Stack_Size。
标号__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由 高向低生长的。
Heap 堆
Heap_Size EQU 0x00000200
;堆大小Heap_Size为0x00000200,即512B。
AREA HEAP, NOINIT, READWRITE, ALIGN=3
;新建堆段,未初始化,可读可写,8字节对齐。
__heap_base
;标号,堆的起始地址。
Heap_Mem SPACE Heap_Size
;分配并初始化一片连续的存储空间。
__heap_limit
;标号,堆的结束地址。堆是由低向高生长的,跟栈相反。
开辟堆的大小为 0X00000200(512 字节),名字为 HEAP,NOINIT 即不初始化,可 读可写,8(2^3)字节对齐。__heap_base 表示对的起始地址,__heap_limit 表示堆的结束 地址。堆是由低向高生长的,跟栈的生长方向相反。
堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆上面。这个在 STM32 里面用的比较少。
PRESERVE8
;等于PRESERVE8 {TRUE},设置文件的PRSE8编译属性,使代码保持堆栈8字节对齐。
THUMB
;THUMB必须位于使用新语法的任何Thumb代码之前,后面指令兼容THUMB指令。
PRESERVE8:指定当前文件的堆栈按照 8 字节对齐。
THUMB:表示后面指令兼容 THUMB 指令。THUBM 是 ARM 以前的指令集,16bit, 现在 Cortex-M 系列的都使用 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位 的指令,是 THUMB 的超级。
向量表
; Vector Table Mapped to Address 0 at Reset
;向量表在复位时映射到地址0
AREA RESET, DATA, READONLY
;定义一个数据段RESET,只读。
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
;声明三个全局标号,该标号可在其他的文件中被引用。
定义一个数据段,名字为RESET,可读。并声明 __Vectors、__Vectors_End和 __Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。
EXPORT:声明一个标号可被外部的文件使用,使标号具有全局属性。如果是 IAR 编 译器,则使用的是 GLOBAL 这个指令。
当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR 的入口地址, 内核使用了―向量表查表机制‖。这里使用一张向量表。向量表其实是一个 WORD( 32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地 址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向 量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0)处必须包 含一张向量表,用于初始时的异常分配。要注意的是这里有个另类: 0 号类型并不是什么 入口地址,而是给出了复位后 MSP 的初值。
下图是stm32f4的部分向量表:
下面是向量表的分配:
__Vectors DCD __initial_sp ; Top of Stack 栈顶
;中断向量表起始地址,DCD伪指令用于分配一篇连续的字存储单元并用伪指令中制定的表达式初始化。
;用DCD分配的字存储单元是字(4字节)对齐的。
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 系统调用异常
;SVC是用户模式代码中的主进程
;用于创造对特权操作系统代码的调用
DCD DebugMon_Handler ; Debug Monitor Handler ???
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler 挂起异常
;用作上下文切换异常。
DCD SysTick_Handler ; SysTick Handler 系统滴答定时器
;操作系统内核时钟
; External Interrupts 以上都是Coretex M3内核自带的,以下为外部中断向量表
DCD WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
;PVD = Programmable Voltage Detector
;可编程电压监测器(Vdd和Vdda都要参与比较)
中间省略其他代码
__Vectors_End
;中断向量表结束地址
__Vectors_Size EQU __Vectors_End - __Vectors
;计算中断向量表地址空间大小
__Vectors 为向量表起始地址,__Vectors_End 为向量表结束地址,两个相减即可算出向量 表大小。
向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈 顶地址,0X04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都 是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。
DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内 存。在向量表中,DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。
复位程序
AREA |.text|, CODE, READONLY
;|.text|表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
定义一个名称为.text 的代码段,可读。
; Reset handler
Reset_Handler PROC
;PROC、ENDP为过程定义伪指令,一个过程可以被其他程序所调用(用CALL指令)。
EXPORT Reset_Handler [WEAK]
;EXPORT伪指令声明一个全局标号,[WEAK]声明其他的同名标号优先于该标号被引用。
IMPORT SystemInit
;IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,
;而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
IMPORT __main
LDR R0, =SystemInit
;LDR将跳转地址放入寄存器R0,准备调用SystemInit
BLX R0
;带链接和状态切换的跳转,根据最低位切换指令集,调用完子程序后返回。
LDR R0, =__main
BX R0
;带状态切换的跳转,根据最低位切换指令集,不返回。
ENDP
复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,
然后调用 C 库函数_mian,最终调用 main 函数去到 C 的世界。 WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部
文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并 不是唯一的。
IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表 示 SystemInit 和__main 这两个函数均来自外部的文件。
SystemInit()是一个标准的库函数,在 system_stm32f4xx.c 这个库文件总定义。主要作 用是配置系统时钟,这里调用这个函数之后,F429 的系统时钟配被配置为 180M。
__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,最终调用 main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。如果我们在这里不调用__main,那么程序最终就不会调用我们 C 文件里面的 main,如果是调皮的用户就可以修改主函数的名称,然后在这里面 IMPORT 你写的主函数名称即可。
__main是由编译器提供的,完成了我们前文所提的疑问,其主要功能如下图所示:
LDR、BLX、BX 是 CM4 内核的指令,可在《CM3 权威指南 CnR2》第四章-指令集里面查询到,具体作用见下表:
中断服务程序
在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务 函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里 面重新实现,这里只是提前占了一个位置而已。
如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。
; Dummy Exception Handlers (infinite loops which can be modified)
;虚拟的中断处理程序(可被修改的无限循环)
;这些中断服务程序都是死循环,真正的中断服务函数需要我们在外部C文件里重新实现。
;如果开了某个中断但没有重新实现中断服务函数,或者写错函数名,
;则当中断到来时程序将跳转到这里进入死循环。
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
中间省略其他代码
LTDC_ER_IRQHandler
DMA2D_IRQHandler
B .
ENDP
B:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环。
用户堆栈初始化
ALIGN
;ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对齐方式。
;后面的表达式未指定则将当前位置对齐到下一个字的位置。
ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字 节对齐。
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
;如果定义了__MICROLIB,则为以下三个标号赋予全局属性。
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
;否则,则引入在其他源文件中定义的标号__use_two_region_memory,
;声明全局标号__user_initial_stackheap。
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
;堆栈初始化,R0保存堆起始地址,R1保存栈顶地址,R2保存堆的结束地址,R3保存栈底地址。
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
;ENTRY伪指令标识程序的入口点,END指示代码段结束。
判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、 __heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。 如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的 C 库,然后初始 化用户堆栈大小,这部分有 C 库函数__main 来完成,当初始化完堆栈之后,就调用 main 函数去到 C 的世界。
四、stm32(ARM Cortex M3)系统启动流程
在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:
1、从地址 0x0000,0000 处取出 MSP 的初始值。
2、从地址 0x0000,0004处取出 PC的初始值——这个值是复位向量, LSB必须是 1。 然后从这个值所对应的地址处取指。
请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统 的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。 在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。 向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是 我们刚刚分析的 Reset_Handler 这个函数。
因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加 1。举例 来说,如果我们的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值 就必须是 0x20008000。
向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在 Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为 这个原因,图 14-3 中使用 0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正 式开始了程序的执行(即去到 C 的世界)。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的 服务例程准备好了堆栈。
现在,程序就进入了我们熟悉的 C 世界,现在我们也应该明白 main 并不是系统执 行的第一个程序了。