簡(jiǎn)析STM32的啟動(dòng)過(guò)程
當前的嵌入式應用程序開(kāi)發(fā)過(guò)程里,C語(yǔ)言已成為了絕大部分場(chǎng)合的最佳選擇。如此一來(lái)main函數似乎成為了理所當然的起點(diǎn)——因為C程序往往從main函數開(kāi)始執行。但一個(gè)經(jīng)常會(huì )被忽略的問(wèn)題是:微控制器(單片機)上電后,是如何尋找到并執行main函數的呢?很顯然微控制器無(wú)法從硬件上定位main函數的入口地址,因為使用C語(yǔ)言作為開(kāi)發(fā)語(yǔ)言后,變量/函數的地址便由編譯器在編譯時(shí)自行分配,這樣一來(lái)main函數的入口地址在微控制器的內部存儲空間中不再是絕對不變的。相信讀者都可以回答這個(gè)問(wèn)題,答案也許大同小異,但肯定都有個(gè)關(guān)鍵詞,叫“啟動(dòng)文件”,用英文單詞來(lái)描述是“Bootloader”。
本文引用地址:http://dyxdggzs.com/article/201710/365472.htm無(wú)論性能高下,結構簡(jiǎn)繁,價(jià)格貴賤,每一種微控制器(處理器)都必須有啟動(dòng)文件,啟動(dòng)文件的作用便是負責執行微控制器從“復位”到“開(kāi)始執行main函數”中間這段時(shí)間(稱(chēng)為啟動(dòng)過(guò)程)所必須進(jìn)行的工作。最為常見(jiàn)的51,AVR或MSP430等微控制器當然也有對應啟動(dòng)文件,但開(kāi)發(fā)環(huán)境往往自動(dòng)完整地提供了這個(gè)啟動(dòng)文件,不需要開(kāi)發(fā)人員再行干預啟動(dòng)過(guò)程,只需要從main函數開(kāi)始進(jìn)行應用程序的設計即可。
關(guān)于“啟動(dòng)模式”
話(huà)題轉到STM32微控制器,無(wú)論是keil uvision4還是IAR EWARM開(kāi)發(fā)環(huán)境,ST公司都提供了現成的直接可用的啟動(dòng)文件,程序開(kāi)發(fā)人員可以直接引用啟動(dòng)文件后直接進(jìn)行C應用程序的開(kāi)發(fā)。這樣能大大減小開(kāi)發(fā)人員從其它微控制器平臺跳轉至STM32平臺,也降低了適應STM32微控制器的難度(對于上一代ARM的當家花旦ARM9,啟動(dòng)文件往往是第一道難啃卻又無(wú)法逾越的坎)。 相對于A(yíng)RM上一代的主流ARM7/ARM9內核架構,新一代Cortex內核架構的啟動(dòng)方式有了比較大的變化。ARM7/ARM9內核的控制器在復位后,CPU會(huì )從存儲空間的絕對地址0x000000取出第一條指令執行復位中斷服務(wù)程序的方式啟動(dòng),即固定了復位后的起始地址為0x000000(PC =0x000000)同時(shí)中斷向量表的位置并不是固定的。而Cortex-M3內核則正好相反,有3種情況:
1、 通過(guò)boot引腳設置可以將中斷向量表定位于SRAM區,即起始地址為0x2000000,同時(shí)復位后PC指針位于0x2000000處;
2、 通過(guò)boot引腳設置可以將中斷向量表定位于FLASH區,即起始地址為0x8000000,同時(shí)復位后PC指針位于0x8000000處;
3、 通過(guò)boot引腳設置可以將中斷向量表定位于內置Bootloader區,本文不對這種情況做論述;
Cortex-M3內核規定,起始地址必須存放堆頂指針,而第二個(gè)地址則必須存放復位中斷入口向量地址,這樣在Cortex-M3內核復位后,會(huì )自動(dòng)從起始地址的下一個(gè)32位空間取出復位中斷入口向量,跳轉執行復位中斷服務(wù)程序。對比ARM7/ARM9內核,Cortex-M3內核則是固定了中斷向量表的位置而起始地址是可變化的。
細說(shuō)STM32的啟動(dòng)過(guò)程
下面就從ST的啟動(dòng)文件說(shuō)起,由于庫中的startup_stm32f10x_md.s與編譯環(huán)境有關(guān),所以針對的是庫中的
STM32F10x_StdPeriph_Lib_V3.5.0LibrariesCMSISCM3DeviceSupportSTSTM32F10xstartupTrueSTUDIO路徑下的文件進(jìn)行分析。
system_stm32f10x.c
SystemInit():在“startup_stm32f10x_xx.s”文件中被調用,功能是設置系統時(shí)鐘(包括時(shí)鐘源,PLL系數,AHB/APBx的預分頻系數還有 flash的設定),這個(gè)函數會(huì )在系統復位之后首先被執行。文件中默認的一些設置:允許HSE(外部時(shí)鐘),FLASH(允許預取緩沖區,設置2個(gè)等待周 期),PLL系數為9,開(kāi)啟PLL并選擇PLL輸出作為時(shí)鐘源(SYSCLK),后續設置SYSCLK = HCLK = APB2 = 72MHz,APB1 = HCLK/2 = 36MHz,HCLK即AHB的時(shí)鐘。
SystemCoreClockUpdate():在系統時(shí)鐘HCLK變化的時(shí)候調用,以更新一個(gè)全局變量SystemCoreClock,這個(gè)變量可以為應用程序使用,必須保證正確。默認不調用這個(gè)函數,因為SystemCoreClock默認被設置為設定的頻率了(72MHz)
另外,下面這種設置寄存器的方法值得借鑒,先用位名清除相應的位,再進(jìn)行設置,例如:
#define RCC_CFGR_PLLSRC ((uint32_t)0x00010000) /*!《 PLL entry clock source */
#define RCC_CFGR_PLLXTPRE ((uint32_t)0x00020000) /*!《 HSE divider for PLL entry */
#define RCC_CFGR_PLLMULL ((uint32_t)0x003C0000) /*!《 PLLMUL[3:0] bits (PLL mulTIplicaTIon factor) */
#define RCC_CFGR_PLLSRC_HSE ((uint32_t)0x00010000) /*!《 HSE clock selected as PLL entry clock source */
#define RCC_CFGR_PLLMULL9 ((uint32_t)0x001C0000) /*!《 PLL input clock*9 */
/* PLL configuraTIon: PLLCLK = HSE * 9 = 72 MHz */
RCC-》CFGR = (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
RCC-》CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
startup_stm32f10x_md.s:(針對F103RBT6為中容量的產(chǎn)品)
這個(gè)文件里面首先定義了復位中斷(復位入口矢量被硬件固定在地址0x0000_0004)的處理函數:Reset_Handler,它的作用就是將保存于flash中的初始化數據復制到sram中,調用上面說(shuō)到的SystemInit來(lái)初始化時(shí)鐘,接著(zhù)跳轉到main執行。
接著(zhù)定義了Default_Handler, 這個(gè)是作為其他所有中斷的默認處理函數,作用就是死循環(huán),所以你假如開(kāi)啟了某個(gè)中斷,請按照這里面的中斷函數名給它寫(xiě)中斷處理函數,例如串口中斷處理函數名是 USART1_IRQHandler,你開(kāi)了串口中斷,如果不重寫(xiě)USART1_IRQHandler,就默認執行Default_Handler,死循環(huán)了。而如果你有重寫(xiě),那么中斷向量表中的處理函數的地址就會(huì )更新為你自己寫(xiě)的那個(gè)函數的地址了。為什么會(huì )這樣呢?因為此文件的末尾用了類(lèi)似這樣的語(yǔ)句:
.weak USART1_IRQHandler
.thumb_set USART1_IRQHandler,Default_Handler
它給中斷處理函數提供了弱(weak)別名(Default_Handler),如果不重寫(xiě),中斷了默認執行Default_Handler,如果重寫(xiě)了,因為是弱別名,所以會(huì )被你寫(xiě)的同名函數覆蓋。
最后定義了一個(gè)中斷向量表的段(.secTIon .isr_vector,“a”,%progbits),這個(gè)表將會(huì )放置在0x0000 0000那里,第二個(gè)字(0x0000 0004)是復位向量,復位之后從這地址所指的函數執行。
那么,如何保證.isr_vector這個(gè)段將放在flash的最開(kāi)始(0x08000000)呢?這就需要鏈接腳本了,即我們用的那個(gè)stm32_flash.ld文件了,查看一下就知道了,里面先定義了堆棧的地址:
_estack
/* Highest address of the user mode stack */
_estack = 0x20005000; /* end of 20K RAM */
接著(zhù)定義了堆和棧的大?。?/p>
/* Generate a link error if heap and stack don‘t fit into RAM */
_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x100; /* required amount of stack */
接著(zhù)聲明了各個(gè)內存的區域(定義了幾個(gè)代表某個(gè)線(xiàn)性空間的名字,如下面的FLASH):
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K }
接著(zhù)下面再介紹上面這三個(gè)名字里面都放了什么東西,首先是FLASH的:
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
。 = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */
。 = ALIGN(4); } 》FLASH
/* The program code and other data goes into FLASH */
.text :
{
。 = ALIGN(4); *(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
KEEP (*(.init))
KEEP (*(.fini))
。 = ALIGN(4); _etext = 。; /* define a global symbols at end of code */
} 》FLASH
可以看到startup_stm32f10x_md.s中定義的這個(gè)段.isr_vector確實(shí)放在了最開(kāi)頭的位置。
但是我們前面說(shuō)復位向量在0x0000 0004,現在其地址是在flash中,所以地址是0x0800 0004,這個(gè)怎么回事呢?原來(lái),stm32可以通過(guò)boot0、boot1引腳的配置將flash映射到0x0000 0000處。具體可參考stm32的用戶(hù)手冊2.4節。
從主閃存存儲器啟動(dòng)(BOOT0 = 0,BOOT1 = X):主閃存存儲器被映射到啟動(dòng)空間(0x0000 0000),但仍然能夠在它原有的地址(0x0800 0000)訪(fǎng)問(wèn)它,即閃存存儲器的內容可以在兩個(gè)地址區域訪(fǎng)問(wèn),0x0000 0000或0x0800 0000。
至此,整個(gè)STM32的啟動(dòng)過(guò)程以及程序結構都可以比較清晰了。
評論