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