赞
踩
此启动文件来自Keil官方的startup_stm32f10x_hd.s
普遍的说法:
翻译自Keil官方启动文件中的注释的说法:
__main
函数(最终会调用main()
)Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
开辟大小为0x00000400(1KB)的栈,名为STACK,NOINIT即不初始化,READWRITE表示可读写,ALIGN=3代表8(2^3)字节对齐。
EQU: 宏定义的伪指令,类似于c语言中的#define
AREA
命令指示汇编器汇编一个新的代码段或者数据段。段是独立的、命名的、不可分割的代码或数据序列。一个代码段是生成一个应用程序的最低要求。
AREA
伪指令用法:
语法 : AREA sectionname{,attr}{,attr}…
其中:
sectionname 是将要指定的段名。
可以为段选择任何段名。但是,以一个数字开始的名称必须包含在竖杠号内,否则会产生一个缺失段名错误。例如,|1_DataArea|。
有些名称是习惯性的名称。例如,|.text| 用于表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
attr 是一个或多个用逗号分隔的段属性。其中部分属性解读:
ALIGN = expression
默认情况下,ELF 段在四字节边界上对齐。expression 可以拥有 0 到 31 的任何整数。段在 2^expression 字节边界上对齐。例如,如果 expression 是 10,则段是在 1KB 边界上对齐。
注意:这里的ALIGN参数与ALIGN命令所指定的方式并不相同,ALIGN命令用于段内部调整ALIGN指令下一条命令或数据的对齐位置,后面跟着要对齐的字节数,如ALIGN 4,代表4字节对齐。
SPACE: 用于分配一个一定大小的内存空间,以字节为单位,并初始化为0
语法:标号 SPACE 表达式
,表达式为要分配的字节数,SPACE也可用“%”代替。这里指定的大小为Stack_size。
__initial_sp: 这是一个标号,它紧接在SPACE之后,代表栈的开始地址。注意,栈的开始地址是栈顶(地址最大的位置),ARM是满减栈,也就是栈由高向低生长。
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
开辟大小为0x00000200(512字节)的堆,名字为HEAP,不初始化,可读可写,8字节对齐。
__heap_base表示堆的起始地址,__heap_limit表示堆的结束地址。
堆是由低向高生长的。
PRESERVE8表指定当前文件的堆栈按照8字节对齐,THUMB表示接下来的指令兼容THUMB指令。THUMB是ARM老的16bit的指令集,现在Cortex-M系列的ARM都使用32bit的THUMB-2指令集,它兼容16bit和32bit的指令。
PRESERVE8
THUMB
PRESERVE8
声明当前文件的栈按照8字节是对齐的。它不会对栈做任何修改,只是一个声明,具体的对齐要靠ALIGN来保证。与之对应的是REQUIRE8
,他们都是供编译器使用的信息。
The REQUIRE8 and PRESERVE8 directives specify that the current file requires or preserves eight-byte alignment of the stack.
The REQUIRE8 directive sets the REQ8 build attribute to inform the linker.
The PRESERVE8 directive sets the PRES8 build attribute to inform the linker.
The linker checks that any code that requires eight-byte alignment of the stack is only called, directly or indirectly, by code that preserves eight-byte alignment of the stack.
关于栈字节对齐的其他资料:
https://www.cnblogs.com/qiyuexin/p/12608661.html
https://blog.csdn.net/hsl416604093/article/details/80223261
THUMB
告诉编译器使用THUMB指令集,THUMB 必须位于使用新语法的任何Thumb代码之前
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY ;定义RESET段,数据段,只读
EXPORT __Vectors ;中断向量表入口地址
EXPORT __Vectors_End ;向量表终止地址
EXPORT __Vectors_Size ;向量表空间大小
“__Vectors”、”__Vectors_End”、”__Vectors_Size”用EXPORT声明的标号,使其具有全局属性,可供外部文件调用。
内部中断/异常:
__Vectors DCD __initial_sp ; Top of Stack ,栈顶指针,被放在向量表的开始,FLASH的0地址,复位后首先装载栈顶指针 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
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ;此处[WEAK]表示弱定义,优先执行其他文件的定义
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit ; 装载寄存器指令
BLX R0 ; 带链接的跳转
LDR R0, =__main
BX R0 ; BX表示跳转到R0地址不用返回
ENDP
PROC和ENDP是子程序定义伪指令,用法:
子程序名 PROC NEAR ( 或 FAR )
……
子程序名 ENDP
NEAR属性(段内近调用): 调用程序和子程序在同一代码段中,只能被相同代码段的其他程序调用;
FAR属性(段间远调用): 调用程序和子程序不在同一代码段中,可以被相同或不同代码段的程序调用.
WEAK
声明,它表示弱定义:如果外部文件优先定义了该标号则首先引用外部文件定义的标号,反之就引用此处用WEAK
声明的标号。
IMPORT表示该标号来自外部文件,跟c语言关键字的extern类似。这里声明__main和SystemInit这两个标号表明均来自外部文件。
SystemInit在system_stm32f10x.c文件中定义并实现,是ST公司为我们写好的,其主要作用是配置系统时钟为72MHz。当然如果想要自己配置时钟的话可以使用自己定义的函数,或者直接屏蔽掉它放到main()里面实现。
__main函数不等于main函数,它是一个库函数,实现的是初始化用户堆栈,在函数最后才是去调用main函数进入c语言运行环境。
__main函数介绍:
__main中包含以下两个大的函数:
__scatterload()详细解释:
完成对映像文件的初始化操作。
在介绍映像文件的初始化操作之前,先介绍以下几个概念:
1. 映像文件
链接器把多个目标文件链接成一个映像文件。
2. 加载地址和执行地址
映像文件可以有两种地址:加载地址和执行地址。加载地址是映像文件在存储器中的存储地址;执行地址就是映像文件运行时的地址。
3. 加载域和执行域
文件加载的存储区叫加载域,文件运行的存储区叫执行域。
4. 从加载地址到执行地址
在结构比较简单的系统中,加载地址就是执行地址;而在复杂系统中,程序运行前,常常会把映像文件的一部分或全部从存储区域移出去,此时执行地址就不再是加载地址。
知道以上几个概念,__main函数对映像文件的初始操作就不难理解了。对于加载地址和执行地址不同的映像文件,__main函数会把加载地址的代码和数据复制到执行地址中,并且对被链接器指定为需要初始化为0的段,进行清零操作。
注意:有些应用中会要求在进入main函数之前先初始化一些外设或者变量区,如:初始化时钟、初始化SDRAM。在初始化他们的时候一定不要使用全局变量,部分库函数,HAL库。因为在__main之前,全局变量还没有初始化,使用会异常,甚至发生内存错误。因此建议使用寄存器来初始化。
更进一步的对__main的分析:http://m.eeworld.com.cn/ic_article/268/483981.html
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B . ;B表跳转到一个标号,"."表无限循环
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
......
从注释可以看出,这里定义的异常处理子程序都是假的异常处理函数,即函数内不做任何事,只有一个死循环,起到占位的作用。真正的中断服务函数需要我们在外部的.c文件中实现(当然也可以直接修改这里的函数),当我们开启某个中断后,没有写对应的中断服务函数或者函数名有误,当中断来临时程序还是跳转到启动文件预先写好的中断服务函数中,在这个函数中无限循环。
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 ;这个宏在KEIL里定义 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
IF, ELSE, ENDIF
分支语句
END
到达文件末尾,文件结束
判断是否定义了__MICROLIB,这个宏是在KEIL里面可配置的: Options - Target - Code Generation - Use MicroLIB
选中它表示使用c库的备选库,里面有一个__mian函数。若定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆的起始地址)、__heap_limit(堆的结束地址)为外部文件可调用的变量,即可供外部c库中的__main调用,由__main初始化堆栈,否则需要用户自己实现__user_initial_stackheap函数初始化堆栈。
__user_initial_stackheap
是一个被导出的函数,它把堆栈地址赋值给对应的寄存器以方便其他程序使用,分别是R0:堆基址(heap base),R1:栈基址(stack base,一般为栈的最高地址),R2:堆顶(heap limit),R3:栈顶(stack limit)
那么这个函数在哪里被调用?
答案是在复位中断向量函数中被__main调用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。