1. 程式人生 > 實用技巧 >STM32啟動檔案分析

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函式,之後就進入比較熟悉的部分了。

之後有什麼再補充。