STM32启动文件分析
前言
使用MDK设计stm32工程时我们主要关注的是main函数的设计,而当真正从上电开始分析时就会发现有很多东西被忽略掉了,本文从 startup_stm32f10x_hd.s 文件开始分析stm32上电后的启动流程。
平台
keil5
STM32F103VE
启动文件段组成
启动文件分段结构为:
1、RESET
段属性:只读数据段
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
...
...
...
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
该段内设置了中断向量表
2、.text
段属性:只读代码段
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
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 .
ENDP
...
...
...
该段内设置了各中断的服务函数,只是这些服务函数都是空的,而且这些函数都是弱定义,可以在.c文件中编写同名函数取代他们,一旦进入这些空的中断服务函数将会在其中不停循环。
3、STACK
段属性:可读可写,段内8字节对齐
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
该段设定了栈容量,__initial_sp的值会在向量表的第一项被引用,作为栈顶指针。栈的容量会由芯片型号不同而改变,或者自己更改。
4、HEAP
段属性:可读可写,段内8字节对齐
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
对应的设置堆容量,与设置栈类似。
链接脚本
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
链接脚本内将这几个段做了分散加载,加载基地址为0x08000000,也是主Flash的起始地址,加载容量为0x00080000,一般等于Flash容量,之后可以看到,RESET段的运行地址等于加载地址,其他属性为READONLY的段放在之后,而属性为READWRITE和ZI的段的运行地址在RAM里,RAM的基地址为0x20000000,这里READWRITE属性的段即包括我们在启动文件中见到的STACK段和HEAP段。
所以,当STACK段被放进RAM,根据容量就可以得到__initial_sp,所以当容量为0x400的时候,栈顶指针为0x20000408
此外当时还有个疑问,我们知道cm3规定上电后CPU会去0地址读取0x00和0x04分别装载入SP和PC寄存器,但是很显然现在的程序和向量表的起始位置都被链接在了0x08000000,而实际上的0x00~0x0800 0000存储的是ISP等出厂自带的程序,这样这样很显然找不到复位向量和堆栈指针,但实际上,这与启动状态有关:
当从主闪存存储器启动时,即BOOT0=0启动时,主闪存存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x0800 0000)访问它,即闪存存储器的内容可以在两个地址区域访问, 0x00000000或0x0800 0000。这在STM32F10X参考手册2.4节有说明。简单的说0x0800 0000就是0x00的别名了。
启动流程
从0地址(0x0800 0000)开始,程序首先取向量表前两项分别装入SP和PC寄存器,这样就初始化了堆栈指针,同时也跳转进入Reset_Handler函数,该函数内将进行一系列配置之后进入main函数的C语言世界了。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
这其中首先引入了__main和SystemInit函数,前者在MDK的库中,具体为c_w.l文件中,如果使用微库就在mc_w.l中,而SystemInit函数在标准库里。
首先执行SystemInit函数,该函数是用C写的,有兴趣的可以看看,常用的就是配置时钟,其中最后有一段会有
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
这一部分是配置NVIC的VTOR用来设置中断向量表的偏移,也就是说我们自己也可以设定中断向量表在什么位置,只是这个偏移地址设置有一定要求。
之后进入__main函数,这个函数主要是将READWRITE属性的段搬移到RAM中,方便读写,这也是和链接脚本的呼应,在链接脚本中已经设置了READWRITE属性段的运行地址在RAM中,所以一定要有对应的代码将他们搬移到RAM中,刚烧写时他们还是在加载地址也就是Flash中的。具体的实现有兴趣可以参考。
http://www.openedv.com/forum.php?mod=viewthread&tid=273753&extra=page=29
讲的比较细致。
__main函数的最后会跳转进入main函数,之后就进入比较熟悉的部分了。
之后有什么再补充。

浙公网安备 33010602011771号