启动文件源码如下:
; 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 ;EQU定义栈空间的大小 1024字节,栈空间由编译器自动分配变量所占内存,该汇编语句等效于#define Stack_Size 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个名为STACK的内存单元,它是可读写的,对齐方式为2^3=8字节对齐
Stack_Mem SPACE Stack_Size ;SPACE分配连续的栈存储空间,大小为0x00000400(1K),把首地址赋给Stack_Mem
__initial_sp ;初始化堆栈指针,指向堆栈顶。
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
;堆初始化
Heap_Size EQU 0x00000200 ;EQU定义堆空间的大小,堆空间由人为手动分配变量所占内存,该汇编语句等效于#define Heap_Size 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;定义一个名为HEAP的内存单元,它是可读写的,对齐方式为2^3=8字节对齐
__heap_base ;堆起始地址
Heap_Mem SPACE Heap_Size ;SPACE分配连续的堆存储空间,大小为0x00000200(512Bytes),把首地址赋给Heap_Mem
__heap_limit ;堆终止地址,与__heap_base配合限制堆的大小
PRESERVE8 ;告诉编译器以8字节对齐,命令指定当前文件保持栈的八字节对齐
THUMB ;告诉编译器使用THUMB指令集,THUMB 必须位于使用新语法的任何Thumb代码之前;定义复位段(中断向量表),并初始化
;中断向量表定义
; Vector Table Mapped to Address 0 at Reset ;中断向量表默认从地址0开始,如果使用了向量表重定位,则从重定位地址开始。假设STM32从FLASH启动,则此中断向量表起始地址即为0x8000000。
AREA RESET, DATA, READONLY ;定义一个名为RESET的数据段,它是只读类型的。
EXPORT __Vectors ;声明全局变量_Vectors,该标号可在其他文件中使用,表示中断向量表入口地址
EXPORT __Vectors_End ;向量表终止地址
EXPORT __Vectors_Size ;向量表空间大小
__Vectors DCD __initial_sp ; Top of Stack Top of Stack 第一个表项是栈顶地址;该处物理地址值即为 __Vetors 标号所表示的值;该地址中存储__initial_sp所表示的地址值,
;大小为一个字(32bit)
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 ;得到向量表的大小 共76个中断向量,304个字节也就是0x130个字节
;地址重映射及中断向量表的转移
AREA |.text|, CODE, READONLY ;定义一个代码段,可读,段名字是.text 段名若以数字开头,则该段名需用"|"括起来,如|1_test|。 |.text| 表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
;定义只读数据段,实际上是在CODE区,如果在FLASH区起动,则 中断向量起始地址为0X8000000
;复位向量处理函数
; Reset handler
Reset_Handler PROC ;标记一个函数的开始;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
EXPORT Reset_Handler [WEAK] ;此处[WEAK]表示弱定义,优先执行其他文件的定义
IMPORT __main ;伪指令用于通知编译器要使用的标号在其他的源文件中定义
IMPORT SystemInit
LDR R0, =SystemInit ; 装载寄存器指令
BLX R0 ; 带链接的跳转,切换指令集。将处理器的工作状态由ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器R14值复制到PC中来完成。
LDR R0, =__main ; 切换指令集,main函数不返回
BX R0 ;BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。
ENDP
;虚构的异常处理程序(可以修改的无限循环)
; 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 . ; 点“.”,它表示跳转到当前的指令地址处(即当前的 PC 值),也就是进入到当前的死循环中了。
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
; 输出异常向量表标号,方便外部实现异常的具体功能 , [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字节对齐的
;*******************************************************************************
; User Stack and Heap initialization 用户堆栈初始化
;*******************************************************************************
IF :DEF:__MICROLIB ;判断是否使用DEF:__MICROLIB(micro lib)在keil中选中 option--target--USE MicroLIB
EXPORT __initial_sp ;使用的话则将栈顶地址,堆始末地址赋予全局属性使外部程序可以使用
EXPORT __heap_base
EXPORT __heap_limit
ELSE ;如果使用默认C库运行时
IMPORT __use_two_region_memory ;定义全局标号__use_two_region_memory
EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap,这样外部程序也可调用此标号,进行堆栈和堆的赋值,在__main函数执行过程中调用
__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 ;LR连接寄存器,也就是R14,用于函数或者子程序调用返回地址的保存。 等同于 mov pc,lr 即跳转到lr中存放的地址处
ALIGN ;设置对齐方式,在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐
ENDIF
END
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
下面开始对关键代码逐行分析
栈初始化:
要理解这三行代码,首先要了解下这几条汇编指令的含义
1、EQU
EQU定义栈空间的大小,栈空间由编译器自动分配变量所占内存,该汇编语句等效于#define Stack_Size 0x00000400。也就是在此处首先定义了栈的空间大小为 0x400,也就是1024个字节。
2、AREA
所以第二行代码作用是 :定义一个名为STACK的的内存单元,它是可读写的,对齐方式为2^3=8字节对齐
3、SPACE
第三行代码的含义就是,将刚才定义的STACK内存空间,全部初始化为0.
4、最后一行代码 __initial_sp 含义是初始化堆栈指针,并指向栈顶。
堆初始化:
1、EQU定义堆空间的大小(512字节),堆空间由人为手动分配变量所占内存,该汇编语句等效于#define Heap_Size 0x00000200
2、定义一个名为HEAP的内存单元,它是可读写的,对齐方式为2^3=8字节对齐
3、设置堆起始地址
4、SPACE分配连续的堆存储空间,大小为0x00000200(512Bytes),把首地址赋给Heap_Mem
5、堆终止地址,与__heap_base配合限制堆的大小
6、告诉编译器以8字节对齐,命令指定当前文件保持栈的八字节对齐
7、告诉编译器下面代码开始使用THUMB指令集,THUMB 必须位于使用新语法的任何Thumb代码之前;定义复位段(中断向量表),并初始化。
中断向量表定义:
中断向量表默认从地址0开始,如果使用了向量表重定位,则从重定位地址开始。假设STM32从FLASH启动,则此中断向量表起始地址即为0x8000000。
1、定义一个名为RESET的数据段,它是只读类型的。
2、声明全局变量_Vectors,该标号可在其他文件中使用,表示中断向量表入口地址
3、向量表终止地址
4、向量表空间大小
中断向量表入口地址:
Top of Stack 第一个表项是栈顶地址;该处物理地址值即为 __Vetors 标号所表示的值;该地址中存储__initial_sp所表示的地址值。
依次给各个中断向量分配地址,分配后地址如下:
__Vectors_End 为向量表结束标志。
地址重映射及中断向量表的转移 :
定义一个代码段,可读,段名字是.text 段名若以数字开头,则该段名需用"|"括起来,如|1_test|。 |.text| 表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。定义只读数据段,实际上是在CODE区,如果在FLASH区起动,则 中断向量起始地址为0X8000000。
复位向量处理函数:
利用PROC、ENDP这一对伪指令把程序段分割,相当于C语言中的 {}作用。
利用EXPORT 声明全局变量Reset_Handler,此处[WEAK]表示弱定义,假如程序中定义了Reset_Handler函数,就会执行用户定义的Reset_Handler函数,如果用户没有定义Reset_Handler函数,就执行当前函数。弱定义的含义就是当两个函数名相同时,优先执行用户定义的函数。
这样是为了防止用户使能了中断而没有中断服务函数,从而造成程序崩溃。假设使能了中断,而用户又没有定义这个中断服务函数则会进入默认中断,默认中断为死循环。
利用IMPORT来声明main()函数和SystemInit()函数。
利用LDR命令,将函数SystemInit的地址加载到寄存器R0中。
通过BLX指令跳转到SystemInit()函数中。
接下来通过LDR指令装载main()函数地址。
通过BX指令,跳转到main()函数中。
虚构的异常处理程序:
输出异常向量表标号:
此处也使用了弱定义,如果外部定义了,优先执行外部定义,否则执行下面的函数定义。
实现弱定义函数:
实现各个中断函数,不过在这里中断函数都是空函数,没有具体实现,需要用户在代码中实现。
用户堆栈初始化:
通过if和else来判断是否选择了微库
如果在keil中选择了USE MicroLIB 选项的话,就执行if语句,否则就执行else语句,使用默认的C库。
IF :DEF:__MICROLIB ;判断是否使用DEF:__MICROLIB(micro lib)在keil中选中 option--target--USE MicroLIB
EXPORT __initial_sp ;使用的话则将栈顶地址,堆始末地址赋予全局属性使外部程序可以使用
EXPORT __heap_base
EXPORT __heap_limit
ELSE ;如果使用默认C库运行时
IMPORT __use_two_region_memory ;定义全局标号__use_two_region_memory
EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap,这样外部程序也可调用此标号,进行堆栈和堆的赋值,在__main函数执行过程中调用
__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 ;LR连接寄存器,也就是R14,用于函数或者子程序调用返回地址的保存。 等同于 mov pc,lr 即跳转到lr中存放的地址处
ALIGN ;设置对齐方式,在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐
ENDIF