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函数,之后就进入比较熟悉的部分了。

之后有什么再补充。

posted @ 2020-12-13 17:54  星灯盏  阅读(386)  评论(0)    收藏  举报