前言
上電之后,CPU首先根據boot引腳選擇存儲器重映射區域,將該區域的地址重映射為地址偏移量為0;
CPU從地址偏移量為0的地址處開始執行;該地址燒錄的代碼必須是xx.s啟動文件,使用匯編語言編寫;
上電之后,具體步驟截圖如下;執行完以下步驟之后單片機就可以開始使用外設,運行邏輯代碼了;
另外,MDK並沒有將啟動文件的所有配置開源,比如一部分的配置由__main閉源執行;
我們只能配置開源的一部分啟動文件的參數;本文我們了解一下完整的啟動流程及其原理;
1 boot的啟動方式
1.1 對於STM32的F0和F4開發板而言,一共有3種啟動方式;以下的存儲空間大小和地址是以大容量F1芯片作為參考的;不同芯片有一些差別;
注意下面存儲空間大小的單位都是byte,這是因為內存的數據線是8bit的;內存的數據線和單片機的數據線是不同的總線;
boot[0:1] | 啟動地址 | 存儲空間 | 啟動方式 | 事項 | |
[0:x] | 0x0800 0000- 0x0807 FFFF |
512K bytes |
Flash啟動 | 使用JTAG或SWD下載 較為常用; |
|
[1:0] | 0x1FFF F000- 0x1FFF F7FF |
2K bytes |
系統存儲器啟動 | 使用串口下載,代碼是通過bootloader搬運到flash中; 該區域內存儲了廠家燒錄的bootloader程序(即ISP程序),需要配合st提供的下載軟件; |
|
[1:1] | 0x2000 0000- 0x2001 0000 |
64K bytes |
內嵌SRAM啟動 | 使用JTAG或SWD下載,僅支持調試模式;較為少用; 沒有掉電存儲程序的功能,需要添加宏定義VECT_TAB_SRAM,配合腳本文件使用; |
1.2 對於 STM32H7x3 而言,只有一個boot引腳,配合BOOT_ADD0/BOOT_ADD1 的組合配置,可以讓系統從兩個不同的區域啟動;
boot引腳 | 啟動地址 | 默認值 | 默認值啟動方式 | 事項 |
0 | BOOT_ADD0[15:0] | 0x0800 | Flash啟動 | 啟動地址高16位由BOOT_ADD0寄存器決定,低16位為0x0000; BOOT_ADD0寄存器可以修改,修改后掉電不丟失; 0x0000 0000 到 0x3FFF 0000 的存儲器地址都可以設置; |
1 | BOOT_ADD1[15:0] | 0x1FF0 | 系統存儲器啟動 |
2 startup_stm32h743xx.s的堆棧
2.1 堆棧的匯編程序
1 ;********************** 目的是分配數據段用來做"棧"********************** 2 ; 表示即將聲明數據段STACK用來做"棧",不用填入初始數據,可讀寫,首地址按照2^3對齊(8字節對齊); 3 Stack_Size EQU 0x00001000 4 AREA STACK, NOINIT, READWRITE, ALIGN=3 5 Stack_Mem SPACE Stack_Size 6 __initial_sp 7 8 ; **********************目的是分配數據段用來做"堆"********************** 9 Heap_Size EQU 0x0000800 10 AREA HEAP, NOINIT, READWRITE, ALIGN=3 11 __heap_base 12 Heap_Mem SPACE Heap_Size 13 __heap_limit
2.2 那么什么是堆棧呢?總結了一下,大概概括成了下面的表格形式;
STACK 棧 | 主要用來存儲局部變量,變量的內存塊;棧空間是從高地址開始向低地址生長的; 對於多級調用的函數入口地址,需要使用棧空間配合連接寄存器來存儲主調函數的地址; |
|
Stack_Mem | 表示棧的首地址,應該是給microLIB庫使用的; | |
__initial_sp | 棧標號,表示棧頂地址;也就是棧的內存最大地址; | |
HEAP 堆 | 動態分配時使用的內存塊;堆空間是從低地址向高地址生長的; 如果程序中沒有使用到動態內存分配的話,編譯器是不會編譯出"堆"空間的; |
|
__heap_base | 堆標號,表示"堆"的起始地址 | |
Heap_Mem | 表示堆的首地址,應該是給microLIB庫使用的; | |
__heap_limit | 堆標號,表示"堆"的結束地址 |
2.3 至於堆棧標號Stack_Mem和Stack_Size大概是標識堆棧地址給microLIB庫使用的把,應該沒什么用;如下所示
;MDK針對嵌入式推出了microLIB小型庫,在功能上是用來替代C標准庫的; ;下面代碼的功能主要是決定要不要啟用microLIB庫;以下代碼比較不重要; IF :DEF:__MICROLIB 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
3 startup_stm32h743xx.s的中斷向量表
;**************中斷向量表***************************************************************** AREA RESET, DATA, READONLY ; 即將聲明一個內存區域RESET,是數據段,只讀; EXPORT __Vectors ; 聲明"標識__Vectors"可以被外部文件調用 EXPORT __Vectors_End ; 聲明"標識__Vectors_End"可以被外部文件調用 EXPORT __Vectors_Size ; 聲明"標識__Vectors_Size"可以被外部文件調用 __Vectors DCD __initial_sp ; 分配4字節內存,初始化為棧頂地址,該內存開始的地址標識為"__Vectors" DCD Reset_Handler ; 分配4字節內存,放入復位中斷服務函數的"地址標識Reset Handler" DCD NMI_Handler ; ;..... DCD 0 ; DCD WAKEUP_PIN_IRQHandler ; __Vectors_End ; 內存結束的地址標識為"__Vectors_End" __Vectors_Size EQU __Vectors_End - __Vectors ; 聲明標識"__Vectors_Size"用來表示中斷向量表的大小 ;**************匯編代碼段:上電復位中斷服務程序舉例*************************************************************** AREA |.text|, CODE, READONLY ; 即將聲明一個內存區域.text,是代碼段,只讀; Reset_Handler PROC ;偽指令PROC和ENDP用來聲明一個程序,當前程序段標識為"Reset_Handler" EXPORT Reset_Handler [WEAK] ;聲明當前程序可被外部函數調用,為弱函數 IMPORT SystemInit ;引入文件外部聲明的函數SystemInit() IMPORT __main ;引入文件外部聲明的函數__main() LDR R0, =SystemInit ;將32bit函數入口地址放入R0寄存器中? BLX R0 ;跳轉到R0寄存器內的地址去執行? LDR R0, =__main ;__main為編譯器自動生成的函數,主要是解析代碼段table的數據,然后跳轉到main函數執行, BX R0 ;給需要初始化的變量和不需要初始化的變量分配堆棧運行空間; ENDP ; ;**************匯編代碼段:NMI中斷服務程序舉例*************************************************************** NMI_Handler PROC ; EXPORT NMI_Handler [WEAK] ;這里導入了中斷服務程序,如果外部文件寫了,那此處的弱函數就不使用了 B . ;B. 表示在這里跳轉當前程序? 就是跳轉到前面導入的程序 ENDP ;
EQU | 聲明常數,匯編的常數單位不是bit,而是byte |
AREA | 表示即將分配一個數據段或代碼段; 需要跟分配內存的SPACE指令一起使用; |
EXPORT | 聲明為可被外部文件引用,主要提供給鏈接器用於連接庫文件; |
IMPORT | 引入外部文件聲明 |
WEAK | 弱聲明,如果外部文件有相同的聲明,則編譯外部文件的聲明,不編譯弱聲明 |
ALIGN | 聲明編譯器需要對指令或數據的存放地址進行對齊;默認缺省值為32bit對齊 |
PRESERVE8 | 當前文件的堆棧需按照8字節對齊,也就是64bit數據線對齊; |
THUMB | 表示接下來的指令都是thumb指令集,cm3,cm7采用的是thumb-2指令集, |
3.2 匯編指令
SPACE | 分配一段內存空間,並初始化這些內存空間為0; 需要跟聲明內存屬性的AREA偽指令一起使用; |
DCD | Define Constant Double-words;分配4字節的內存空間用來存儲一個32bit的數據;並初始化這些內存空間; |
LDR | Load word;加載指令,將32bit數據存入目的寄存器中; |
B | Branch,跳轉到目標地址執行,執行指令集為ARM指令集 |
BX | Branch with Exchange;跳轉到目的地址執行,並且指令集從ARM指令集切換到thumb指令集; |
BLX | Branch with Link and Exchange;thumb-2兼容許多thumb指令,但是cortex-m3不支持當前指令; |
零散1 | [地址]:地址加了[],用來表示地址內的數據; |
零散2 | 匯編指令中的常數都是地址,自然數的格式為#常數; |
4 main函數的初始化
在配置外設之前,單片機首先還需要配置一下工作環境;主要涉及到以下幾個c文件的函數;具體的話也不要求精確分析,輪廓了解一下;
4.1 stm32h7xx_hal.c
主要是IO電壓的配置函數,boot引腳的啟動配置,以及systick的hal庫函數;
/* stm32h7xx_hal.h: line528: Peripheral Control functions systick使用和配置,電壓配置,boot配置等******************/ void HAL_IncTick(void); //SysTick_Handler()中斷服務函數調用的函數,裝載systick為1ms(HAL_TICK_FREQ_1KHZ), void HAL_Delay(__IO uint32_t Delay); //systick時鍾延時,單位ms; uint32_t HAL_GetTick(void); uint32_t HAL_GetTickPrio(void); HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq); HAL_TickFreqTypeDef HAL_GetTickFreq(void); void HAL_SuspendTick(void); //掛起systick void HAL_ResumeTick(void); //恢復systick
void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling); //line575:配置IO口輸出的電壓范圍
4.2 stm32h7xx_hal_rcc.c
主要是rcc相關的晶振使能,以及總線時鍾的配置;以及一些查看配置參數的函數;
/*stm32h7xx_hal_rc.h line2814*/ void HAL_RCC_DeInit(void);//復位RCC默認配置;使用HSI;關閉HSE,PLL;根據時鍾樹可知外設及總線為HSI-64Mhz HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);//配置晶振是否使能,以及PLL1不為sysclk時PLL1的參數配置; HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);//配置CPU(sysclk),AHB,APB..分頻系數;
4.3 stm32h7xx_hal_cortex.c
主要是cortex內核的外設NVIC和MPU的配置函數;以及一些systick的使用函數;之后再分析內核外設的時候一起講把;
不知道為什么在stm32h7xx_hal.c已經有systick的函數了,這里又有,沒有將他們放一個文件里;先留着疑問;
5 小結
1)對於啟動配置而言一直以來只使用過flash啟動;感覺其他的具體也沒試過,等需要的時候看看安富萊的視頻把;
之前由於代碼配置錯誤導致芯片不能flash下載的時候,試過把boot引腳切換到ISP下載模式,然后使用SWD下載;
當然下載之后是跑不起來的,然后再重新把boot引腳改為flash啟動,此時芯片可以重新flash下載;
2)對於堆棧,注意一下,我們平常使用的局部數組什么的不要超出棧的內存大小,超出的話程序卡死,然后就跑不動了;
平常說的push和pop應該只針對棧而言;如果堆也能push和pop,針對一整塊動態內存,怎么搞呢?從低地址到高地址搞;
3)至於中斷向量表,主要是從偏移量0地址處開始依次分配內存,用來存儲各種中斷服務程序入口地址;
一些內核級別的中斷程序的弱聲明,這些程序除了Reset_Handler中斷的弱函數執行了操作外,其他的都是循壞自己,使用時需要在用戶程序中重新編寫;
后面還有外設中斷服務程序的弱聲明,這些外設中斷服務程序執行都是不斷遞歸循壞自己;使用時需要在用戶程序中重新編寫;
4)啟動文件本質來說,除了分配中斷向量表空間之外,只主動執行了一個Reset_Handler一個函數,
Reset_Handler函數跳轉到system_init函數執行,然后跳轉到MDK的__main函數執行;其他的函數都是弱聲明,不斷遞歸調用自己,使用的話需要重新寫函數;