;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
;* File Name : startup_stm32f10x_hd.s
;* Author : MCD Application Team
;* Version : V3.5.0
;* Date : 11-March-2011
;* Description : STM32F10x High Density Devices vector table for MDK-ARM
;* toolchain.
;* This module performs:
;* - Set the initial SP
;* - Set the initial PC == Reset_Handler
;* - Set the vector table entries with the exceptions ISR address
;* - Configure the clock system and also configure the external
;* SRAM mounted on STM3210E-EVAL board to be used as data
;* memory (optional, to be enabled by user)
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* After Reset the CortexM3 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>
;*******************************************************************************
; THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
; WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME.
; AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
; INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
; CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
; INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
;*******************************************************************************
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
;Stack Configuration
; Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;汇编语言的注释是每行从分号";"开始的,分号之后的内容都属于注释,本文注释基于正点原提
;供的的启动程序,红色部分为个人注释内容,仅供参考。
;堆栈大小设置,如果局部变量过多,这里应该相应改大.默认是1024字节.
Stack_Size EQU 0x00000400
; 此语句为数字常量0x00000400定义一个字符名称Stack_Size详解请看标注1,
; 等价为:#define Stack_Size 0x00000400
; EQU:给数字常量取一个符号名,相当于C语言中的define
; 0x00000400: 16进制,256*4=1024,为1Kbyte
AREA STACK, NOINIT, READWRITE, ALIGN=3
; AREA:汇编一个新的代码段或者数据段,详解请看标注2。
;STACK, NOINIT, READWRITE, ALIGN=3:堆栈段,未初始化,允许读写,ALIGN=3按8字节
;边界对齐;定义一个数据段,名为STACK,NOINIT 仅仅保留内存单元,还没有写入值
Stack_Mem SPACE Stack_Size
; Stack_Mem:分配内存,用户模式栈名为StackMem,详解请看标注3。
;SPACE:分配内存(堆栈)空间,把首地址赋给Stack_Mem
__initial_sp
; __initial_sp ;初始化堆栈指针,指向堆栈顶
;_initial_sp 是个标签,代表当前指令的地址,由汇编器计算出的,代码里使用space
;配了Stack_Size个内存单元后再在之后加上_initial_sp,后面会用DCD __initial_sp 在向量表
;0初始化栈顶地址,使用堆栈时栈顶地址减1恰好是分配的栈区间
;Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;未用到编译器自带的内存管理(malloc,free等),设置Heap_Szie为0
Heap_Size EQU 0x00000000
; 定义堆的大小,为0x00000000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
; 堆段,未初始化,允许读写,堆数据段8字节边界对齐
__heap_base
; 指定堆的开头,堆空间起始地址
Heap_Mem SPACE Heap_Size
; 分配堆空间,用户模式堆名为HeapMem,分配0个连续字节
__heap_limit
; 与__heap_base配合限制堆的大小,指定堆的结尾
PRESERVE8
; 命令指定当前文件保持栈的8字节对齐,preserve:vt 保存、保留、维护;n:保护区
THUMB
; 告诉编译器使用THUMB指令集,THUMB 必须位于使用新语法的任何Thumb代码之前
; Vector Table Mapped to Address 0 at Reset以下为向量表在复位时被映射到FLASH的0地址
AREA RESET, DATA, READONLY
; 定义reset只读代码段,THUMB ;告诉汇编器下面是32为的Thumb指令,如果需要汇编器
;将插入位以保证对齐
EXPORT __Vectors
; 在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用__Vectors,中断向
;量表开始,详解见注释4,export:输出,出口(n&vi)
EXPORT __Vectors_End
; 中断向量表结束
EXPORT __Vectors_Size
; 中断向量表大小
__Vectors DCD __initial_sp ; Top of Stack
; 标号__Vectors,表示中断向量表入口地址
; DCD 命令分配一个或多个字的存储器,在四个字节的边界上对齐,并定义存储器的运行
;时初值, Top of Stack 栈顶指针,被放在向量表的开始,FLASH的0地址,复位后首先装载
;栈顶指针,详解见标注5
DCD Reset_Handler ; Reset Handler
; Reset Handler 复位异常,装载完栈顶后,第一个执行的,并且不返回。
DCD NMI_Handler ; NMI Handler
; NMI Handler 不可屏蔽中断
DCD HardFault_Handler ; Hard Fault Handler
; Hard Fault Handler 硬件错误中断
DCD MemManage_Handler ; MPU Fault Handler
; MPU Fault Handler 内存管理错误中断
DCD BusFault_Handler ; Bus Fault Handler
; Bus Fault Handler 总线错误中断,一般发生在数据访问异常,比如fsmc访问不当
DCD UsageFault_Handler ; Usage Fault Handler
; Usage Fault Handler 用法错误中断,一般是预取值,或者位置指令,数据处理等错误
DCD 0 ; Reserved
; 保留
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
; SVCall Handler 系统调用异常,主要是为了调用操作系统内核服务
DCD DebugMon_Handler ; Debug Monitor Handler
; Debug Monitor Handler 调试监视异常
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
; PendSV Handler 挂起异常,此处可以看见用作了uCOS-II的上下文切换异常,这是被推
;荐使用的,因为Cortex-M3会在异常发生时自动保R0-R3,R12,R13(堆栈指针SP),R14(链接
;地址,也叫返回地址LR,在异常返回时使用),R15(程序计数器PC,为当前应用程序+4)
;和中断完成时自动回复,;我们只需保存R4-R11,大大减少了中断响应和上下文切换的时间。
;说明:此处涉及到一个中断保存寄存器问题:因为在所有的运行模式下,未分组寄存器都
;指向同一个物理寄存器,他们未被系统用作特殊的用途,因此,在中断或者异常处理进行
;模式;转换时,由于不同模式(此处为"线程"和"特权")均使用相同的物理寄存器,可能会
;造成寄;存器中数据的破坏。这也是常说的"关键代码段"和"l临界区"保护的原因
DCD SysTick_Handler ; 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
; 计算向量表地址空间大小,详解见标注6
AREA |.text|, CODE, READONLY
; 标记代码段的开始,读,两个"|"必须要加上,因为"text"前面有个".",AREA后面第一个参数为
;字段的标号,;若以数值或标点开始标号就要用两个"|"夹起来, |.text| 用于表示由 C 编译
;程序产生的代码段,或用于以某种方式与 C 库关联的代码段。定义C编译器源代码的代码
;段,只读
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
; 声明复位中断向量Reset_Handler为全局属性,这样外部文件就可以调用此复位中断服务。
; 此处[WEAK]表示弱定义,优先执行其他文件的定义
IMPORT __main
; 声明__main标号,引用其它文件中定义的符号MAIN,详解见标注7
;寄存器版本代码,因为没有用到SystemInit函数,所以注释掉以下代码为防止报错!
;库函数版本代码,建议加上这里(外部必须实现SystemInit函数),以初始化stm32时钟等。
;IMPORT SystemInit
; IMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义但要在当前源文件中
;引用,而且无论当前源文件是否引用该标号该标号均会被加入到当前源文件的符号表中
;import:进口,输入(n&vi&vt)
;LDR R0, =SystemInit
;装载寄存器指令,详解见标注8
;BLX R0
;带链接的跳转,切换指令集,详解见标注9
LDR R0, =__main
;__main为 运行时库提供的函数;完成堆栈,堆的初始话等工作,会调用下面定义
;的__user_initial_stackheap
BX R0
; 跳转__main地址执行,切换指令集,main函数不返回,跳到__main,进入C的世界,详解
;见注释10
ENDP
; Dummy Exception Handlers (infinite loops which can be modified),B指令见注释11
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
; 输出异常向量表标号,方便外部实现异常的具体功能 , [WEAK] 是弱定义的意思,如果
;外部定义了,优先执行外部定义,否则下面的函数定义,weak:虚弱的(adj)
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字节对齐的,align:使成一行,使结盟,匹配
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
; 判断是否使用DEF:__MICROLIB(╩icro lib),使用的话则将栈顶地址,堆始末地址赋予全局
;属性,使外部程序可以使用,IF指令详解见注释12
EXPORT __initial_sp
; 声明输出全局标号__initial_sp 栈地址
EXPORT __heap_base
; 输出堆的基地址
EXPORT __heap_limit
; 堆的末地址
ELSE
; 如果没定义MICROLIB,默认C库运行
IMPORT __use_two_region_memory
; 定义全局标号__use_two_region_memory
EXPORT __user_initial_stackheap
; 声明全局标号_user_initial_stackheap,这样外程序也可调用此标号,则进行堆栈和堆的赋
;值,在__main函数执行过程中调用
__user_initial_stackheap
; 此处是初始化两区的堆栈空间,堆是从由低到高的增长,栈是由高向低生长的,两个是互
;相独立的数据段,并不能交叉使用
LDR R0, = Heap_Mem
; LDR:从存储器中加载字到一个寄存器中, 保存堆始地址,r0 中的堆基址,
;参考:LDR R3, =MY_NUMBER ; R3= MY_NUMBER
LDR R1, =(Stack_Mem + Stack_Size)
; 保存栈的大小,r1 中的栈基址,即堆栈区中的最高地址
LDR R2, = (Heap_Mem + Heap_Size)
; 保存堆的大小
LDR R3, = Stack_Mem
; 堆栈区中的最低地址
BX LR
; 跳出这段代码,返回到原程序,这个是为程序调用传递参数,参数就是R0-3中的值,初始化堆
;栈用
ALIGN
ENDIF
END
; END 命令指示汇编器,已到达一个源文件的末尾。
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
; 以上便是STM32的启动代码的完整解析,接下来对几个小地方做解释:
话题转到STM32微控制器,无论是keil
uvision4还是IAR EWARM开发环境,ST公司都提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行C应用程序的开发。这样能大大减小开发人员从其它微控制器平台跳转至STM32平台,也降低了适应STM32微控制器的难度(对于上一代ARM的当家花旦ARM9,启动文件往往是第一道难啃却又无法逾越的坎)。
相对于ARM上一代的主流ARM7/ARM9内核架构,新一代Cortex内核架构的启动方式有了比较大的变化。ARM7/ARM9内核的控制器在复位后,CPU会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为0x000000(PC = 0x000000)同时中断向量表的位置并不是固定的。而Cortex-M3内核则正好相反,有3种情况:
1、 通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处;
2、 通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处;
3、 通过boot引脚设置可以将中断向量表定位于内置Bootloader区,本文不对这种情况做论述;
而Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断向量表的位置而起始地址是可变化的。
1、 AREA指令:伪指令,用于定义代码段或数据段,后跟属性标号。其中比较重要的一个标号为"READONLY"或者"READWRITE",其中"READONLY"表示该段为只读属性,联系到STM32的内部存储介质,可知具有只读属性的段保存于FLASH区,即0x8000000地址后。而"READONLY"表示该段为"可读写"属性,可知"可读写"段保存于SRAM区,即0x2000000地址后。由此可以从第3、7行代码知道,堆栈段位于SRAM空间。从第82行可知,中断向量表放置与FLASH区,而这也是整片启动代码中最先被放进FLASH区的数据。因此可以得到一条重要的信息:0x8000000地址存放的是栈顶地址__initial_sp,0x8000004地址存放的是复位中断向量Reset_Handler(STM32使用32位总线,因此存储空间为4字节对齐)。
2、 DCD指令:作用是开辟一段空间,其意义等价于C语言中的地址符"&"。因此从第84行开始建立的中断向量表则类似于使用C语言定义了一个指针数组,其每一个成员都是一个函数指针,分别指向各个中断服务函数。
3、 标号:前文多处使用了"标号"一词。标号主要用于表示一片内存空间的某个位置,等价于C语言中的"地址"概念。地址仅仅表示存储空间的一个位置,从C语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。
4、 第202行中的__main标号并不表示C程序中的main函数入口地址,因此第204行也并不是跳转至main函数开始执行C程序。__main标号表示C/C++标准实时库函数里的一个初始化子程序__main的入口地址。该程序的一个主要作用是初始化堆栈(对于程序清单一来说则是跳转__user_initial_stackheap标号进行初始化堆栈的),并初始化映像文件,最后跳转C程序中的main函数。这就解释了为何所有的C程序必须有一个main函数作为程序的起点------因为这是由C/C++标准实时库所规定的------并且不能更改,因为C/C++标准实时库并不对外界开发源代码。因此,实际上在用户可见的前提下,程序在第204行后就跳转至.c文件中的main函数,开始执行C程序了。
至此可以总结一下STM32的启动文件和启动过程。首先对栈和堆的大小进行定义,并在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址。然后在复位中断服务程序中跳转??C/C++标准实时库的__main函数,完成用户堆栈等的初始化后,跳转.c文件中的main函数开始执行C程序。假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。
说明:本文参考网上各种资料归类总结而成。
uvision4还是IAR EWARM开发环境,ST公司都提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行C应用程序的开发。这样能大大减小开发人员从其它微控制器平台跳转至STM32平台,也降低了适应STM32微控制器的难度(对于上一代ARM的当家花旦ARM9,启动文件往往是第一道难啃却又无法逾越的坎)。
相对于ARM上一代的主流ARM7/ARM9内核架构,新一代Cortex内核架构的启动方式有了比较大的变化。ARM7/ARM9内核的控制器在复位后,CPU会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为0x000000(PC = 0x000000)同时中断向量表的位置并不是固定的。而Cortex-M3内核则正好相反,有3种情况: 1、 通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处; 2、 通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处; 3、 通过boot引脚设置可以将中断向量表定位于内置Bootloader区,本文不对这种情况做论述; 而Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断向量表的位置而起始地址是可变化的。
启动文件由汇编编写,是系统上电复位后第一个执行的程序。主要做了以下工作:1、初始化堆栈指针SP=_initial_sp2、初始化PC指针=Reset_Handler 3、初始化中断向量表4、配置系统时钟5、调用C库函数_main初始化用户堆栈,从而最终调用main函数去到C的世界
详解1:EQU
语法格式:
名称 EQU 表达式{,类型}
EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,:类似于C语言中的#define。 其中EQU可用" *" 代替。名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可
以指定表达式的数据类型,可以有以下三种类型:CODE16、 CODE32和DATA
使用示例:
Test EQU 50 ;定义标号Test的值为50
Addr EQU 0x55, CODE32 定义Addr的值为0x55,且该处为32
位的ARM指令
标注2: AREA
语法格式:
AREA 段名 属性1,属性2,......
AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用" |" 括起来,如|1_test|。 属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:
●CODE属性:用于定义代码段,默认为READONLY。
●DATA属性:用于定义数据段,默认为READWRITE。
●READONLY属性:指定本段为只读,代码段默认为READONLY。
●READWRITE属性:指定本段为可读可写,数据段的默认属性为READWRITE。
●ALIGN属性: 使用方式为ALIGN 表达式。 在默认时, ELF( 可执行连接文件) 的代码段和数据段是按字对齐的, 表达式的取值范围为0~31,相应的对齐方式为2表达式次方。
●COMMON属性: 该属性定义一个通用的段, 不包含任何的用户代码和数据。 各源文件中同名的COMMON段共享同一段存储单元。 一个汇编语言程序至少要包含一个段, 当程序太长时, 也可以将程序分为多个代码段和数据段。
使用示例:
AREA Init, CODE, READONLY
指令序列
;该伪指令定义了一个代码段,段名为Init,属性为只读
ALIGN
语法格式:
ALIGN {表达式{,偏移量}}
ALIGN 伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式。其中, 表达式的值用于指定对齐方式,可能的取值为2的幂,如1、 2、 4、 8、 16等。 若未指定表达式, 则将当前位置对齐到下一个字的位置。 偏移量也为一个数字表达式, 若使用该字段, 则当前位置的对齐方式为: 2的表达式次幂+偏移量。
使用示例:
AREA Init, CODE, READONLY, ALIEN=3 ; 指定后面的指
令为8字节对齐。
指令序列
END
标注3: SPACE
语法格式:标号 SPACE 表达式
SPACE伪指令用于分配一片连续的存储区域并初始化为0。其中,表达式为要分配的字节数。 SPACE也可用" %" 代替。
使用示例:
DataSpace SPACE 100; 分配连续100字节的存储单元并初始化为0
标注4:、 EXPORT(或GLOBAL)
语法格式:
EXPORT 标号{[WEAK]}
EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。EXPORT可用GLOBAL代替。标号在程序中区分大小写,[WEAK]选项声明其他的同名标号优先于该标号被引用。
使用示例:
AREA Init, CODE, READONLY
EXPORT Stest ;声明一个可全局引用的标号
Stest
......
END
标注5: DCD(或DCDU)
语法格式:标号 DCD(或DCDU) 表达式
DCD(或DCDU)伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。
DCD也可用" &"代替。 用DCD分配的字存储单元是字对齐的, 而用DCDU分配的字存储单元并不严格字对齐。
使用示例:
DataTest DCD 4, 5, 6 ; 分配一片连续的字存储单元并初始化
标注6:
EQU
语法格式:
名称 EQU 表达式{,类型}
EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。 其中EQU可用" *" 代替。名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可
以指定表达式的数据类型,可以有以下三种类型:
CODE16、 CODE32和DATA
使用示例:
Test EQU 50 ;定义标号Test的值为50
Addr EQU 0x55, CODE32 定义Addr的值为0x55,且该处为32位的ARM指令。
标注7、 IMPORT
语法格式:
IMPORT 标号{[WEAK]}
IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用, 而且无论当前源文件是否引用该标号, 该标号均会被加入到当前源文件的符号表中。标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时, 编译器
也不给出错误信息,在多数情况下将该标号置为 0,若该标号为B 或BL 指令引用,则将B 或BL 指令置为NOP 操作。
使用示例:
AREA Init, CODE, READONLY
IMPORT Main ;通知编译器当前文件要引用标号Main,但Main 在其他源文件中定义
......
END
标注8: LDR指令
LDR指令的格式为:
LDR{条件} 目的寄存器, <存储器地址>
LDR指令用于从存储器中将一个 32位的字数据传送到目的寄存器中。
该指令通常用于从存储器中读取 32位的字数据到通用寄存器, 然后对数据进行处理。 当程序计数器 PC作为目的寄存器时, 指令从存储器中读取的字数据被当作目的地址, 从而可以实现程序流程的跳转。 该指令在程序设计中比较常用,且寻址方式灵活多样,请读者认真掌握。
指令示例:
LDR R0, [R1] ;将存储器地址为R1的字数据读入寄存器R0。
LDR R0, [R1, R2] ; 将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0, [R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0, [R1, R2] ! ; 将存储器地址为R1+R2的字数据读入寄存器R0, 并将新地址R1+R2写入R1。
LDR R0, [R1,#8]! ;将存储器地址为 R1+8的字数据读存器R0。
LDR R0, [R1], R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR R0, [R1, R2, LSL#2]!;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR R0, [R1], R2, LSL#2 ; 将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1
标注9:、 BLX 指令
BLX 指令的格式为:
BLX 目标地址
BLX 指令从ARM 指令集跳转到指令中所指定的目标地址, 并将处理器的工作状态有ARM 状态切换到Thumb 状态,该指令同时将PC 的当前内容保存到寄存器R14 中。因此,当子程序使用Thumb 指令集,而调用者使用ARM 指令集时,可以通过BLX 指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器 R14值复制到PC 中来完成。
注释10: BX 指令
BX 指令的格式为:
BX{条件} 目标地址
BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令
注释11: B指令
B指令的格式为:
B{条件} 目标地址
B指令是最简单的跳转指令。一旦遇到一个 B 指令, ARM 处理器将立即跳转到给定的目标地址, 从那里继续执行。 注意存储在跳转指令中的实际值是相对当前PC值的一个偏移量, 而不是一个绝对地址, 它的值由汇编器来计算( 参考寻址方式中的相对寻址)。 它是 24 位有符号数, 左移两位后有符号扩展为 32 位, 表示的有效偏移为 26 位(前后
32MB的地址空间)。以下指令:
B Label ;程序无条件跳转到标号Label处执行
CMP R1,#0 ;当CPSR寄存器中的Z条件码置位时, 程序跳转到标号Label处执行BEQ Label
注释12: IF、 ELSE、 ENDIF
语法格式:
IF 逻辑表达式
指令序列1
ELSE
指令序列2
ENDIF
IF、 ELSE、 ENDIF伪指令能根据条件的成立与否决定是否执行某个指令序列。当 IF后面的逻辑表达式为真,则执行指令序列 1,否则执行指令序列 2。其中, ELSE及指令序列 2可以没有,此时,当 IF后面的逻辑表达式为真,则执行指令序列 1,否则继续执行后面的指令。
IF、 ELSE、 ENDIF伪指令可以嵌套使用。
使用示例:
GBLL Test ;声明一个全局的逻辑变量,变量名为Test
......
IF Test = TRUE
指令序列 1
ELSE
指令序列 2
ENDIF