新聞中心

簡析STM32的啟動過程

作者: 時間:2017-10-11 來源:網(wǎng)絡(luò) 收藏

  當(dāng)前的嵌入式應(yīng)用程序開發(fā)過程里,C語言已成為了絕大部分場合的最佳選擇。如此一來main函數(shù)似乎成為了理所當(dāng)然的起點——因為C程序往往從main函數(shù)開始執(zhí)行。但一個經(jīng)常會被忽略的問題是:微控制器(單片機)上電后,是如何尋找到并執(zhí)行main函數(shù)的呢?很顯然微控制器無法從硬件上定位main函數(shù)的入口地址,因為使用C語言作為開發(fā)語言后,變量/函數(shù)的地址便由編譯器在編譯時自行分配,這樣一來main函數(shù)的入口地址在微控制器的內(nèi)部存儲空間中不再是絕對不變的。相信讀者都可以回答這個問題,答案也許大同小異,但肯定都有個關(guān)鍵詞,叫“啟動文件”,用英文單詞來描述是“Bootloader”。

本文引用地址:http://butianyuan.cn/article/201710/365472.htm

  無論性能高下,結(jié)構(gòu)簡繁,價格貴賤,每一種微控制器(處理器)都必須有啟動文件,啟動文件的作用便是負責(zé)執(zhí)行微控制器從“復(fù)位”到“開始執(zhí)行main函數(shù)”中間這段時間(稱為啟動過程)所必須進行的工作。最為常見的51,AVR或MSP430等微控制器當(dāng)然也有對應(yīng)啟動文件,但開發(fā)環(huán)境往往自動完整地提供了這個啟動文件,不需要開發(fā)人員再行干預(yù)啟動過程,只需要從main函數(shù)開始進行應(yīng)用程序的設(shè)計即可。

  關(guān)于“啟動模式”

  話題轉(zhuǎn)到STM32微控制器,無論是keil uvision4還是IAR EWARM開發(fā)環(huán)境,ST公司都提供了現(xiàn)成的直接可用的啟動文件,程序開發(fā)人員可以直接引用啟動文件后直接進行C應(yīng)用程序的開發(fā)。這樣能大大減小開發(fā)人員從其它微控制器平臺跳轉(zhuǎn)至STM32平臺,也降低了適應(yīng)STM32微控制器的難度(對于上一代ARM的當(dāng)家花旦ARM9,啟動文件往往是第一道難啃卻又無法逾越的坎)。 相對于ARM上一代的主流ARM7/ARM9內(nèi)核架構(gòu),新一代Cortex內(nèi)核架構(gòu)的啟動方式有了比較大的變化。ARM7/ARM9內(nèi)核的控制器在復(fù)位后,CPU會從存儲空間的絕對地址0x000000取出第一條指令執(zhí)行復(fù)位中斷服務(wù)程序的方式啟動,即固定了復(fù)位后的起始地址為0x000000(PC =0x000000)同時中斷向量表的位置并不是固定的。而Cortex-M3內(nèi)核則正好相反,有3種情況:

  1、 通過boot引腳設(shè)置可以將中斷向量表定位于SRAM區(qū),即起始地址為0x2000000,同時復(fù)位后PC指針位于0x2000000處;

  2、 通過boot引腳設(shè)置可以將中斷向量表定位于FLASH區(qū),即起始地址為0x8000000,同時復(fù)位后PC指針位于0x8000000處;

  3、 通過boot引腳設(shè)置可以將中斷向量表定位于內(nèi)置Bootloader區(qū),本文不對這種情況做論述;

  Cortex-M3內(nèi)核規(guī)定,起始地址必須存放堆頂指針,而第二個地址則必須存放復(fù)位中斷入口向量地址,這樣在Cortex-M3內(nèi)核復(fù)位后,會自動從起始地址的下一個32位空間取出復(fù)位中斷入口向量,跳轉(zhuǎn)執(zhí)行復(fù)位中斷服務(wù)程序。對比ARM7/ARM9內(nèi)核,Cortex-M3內(nèi)核則是固定了中斷向量表的位置而起始地址是可變化的。

  細說STM32的啟動過程

  下面就從ST的啟動文件說起,由于庫中的startup_f10x_md.s與編譯環(huán)境有關(guān),所以針對的是庫中的

  STM32F10x_StdPeriph_Lib_V3.5.0LibrariesCMSISCM3DeviceSupportSTSTM32F10xstartupTrueSTUDIO路徑下的文件進行分析。

  system_f10x.c

  SystemInit():在“startup_f10x_xx.s”文件中被調(diào)用,功能是設(shè)置系統(tǒng)時鐘(包括時鐘源,PLL系數(shù),AHB/APBx的預(yù)分頻系數(shù)還有 flash的設(shè)定),這個函數(shù)會在系統(tǒng)復(fù)位之后首先被執(zhí)行。文件中默認的一些設(shè)置:允許HSE(外部時鐘),F(xiàn)LASH(允許預(yù)取緩沖區(qū),設(shè)置2個等待周 期),PLL系數(shù)為9,開啟PLL并選擇PLL輸出作為時鐘源(SYSCLK),后續(xù)設(shè)置SYSCLK = HCLK = APB2 = 72MHz,APB1 = HCLK/2 = 36MHz,HCLK即AHB的時鐘。

  SystemCoreClockUpdate():在系統(tǒng)時鐘HCLK變化的時候調(diào)用,以更新一個全局變量SystemCoreClock,這個變量可以為應(yīng)用程序使用,必須保證正確。默認不調(diào)用這個函數(shù),因為SystemCoreClock默認被設(shè)置為設(shè)定的頻率了(72MHz)

  另外,下面這種設(shè)置寄存器的方法值得借鑒,先用位名清除相應(yīng)的位,再進行設(shè)置,例如:

  #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)品)

  這個文件里面首先定義了復(fù)位中斷(復(fù)位入口矢量被硬件固定在地址0x0000_0004)的處理函數(shù):Reset_Handler,它的作用就是將保存于flash中的初始化數(shù)據(jù)復(fù)制到sram中,調(diào)用上面說到的SystemInit來初始化時鐘,接著跳轉(zhuǎn)到main執(zhí)行。

  接著定義了Default_Handler, 這個是作為其他所有中斷的默認處理函數(shù),作用就是死循環(huán),所以你假如開啟了某個中斷,請按照這里面的中斷函數(shù)名給它寫中斷處理函數(shù),例如串口中斷處理函數(shù)名是 USART1_IRQHandler,你開了串口中斷,如果不重寫USART1_IRQHandler,就默認執(zhí)行Default_Handler,死循環(huán)了。而如果你有重寫,那么中斷向量表中的處理函數(shù)的地址就會更新為你自己寫的那個函數(shù)的地址了。為什么會這樣呢?因為此文件的末尾用了類似這樣的語句:

  .weak USART1_IRQHandler

  .thumb_set USART1_IRQHandler,Default_Handler

  它給中斷處理函數(shù)提供了弱(weak)別名(Default_Handler),如果不重寫,中斷了默認執(zhí)行Default_Handler,如果重寫了,因為是弱別名,所以會被你寫的同名函數(shù)覆蓋。

  最后定義了一個中斷向量表的段(.secTIon .isr_vector,“a”,%progbits),這個表將會放置在0x0000 0000那里,第二個字(0x0000 0004)是復(fù)位向量,復(fù)位之后從這地址所指的函數(shù)執(zhí)行。

  那么,如何保證.isr_vector這個段將放在flash的最開始(0x08000000)呢?這就需要鏈接腳本了,即我們用的那個stm32_flash.ld文件了,查看一下就知道了,里面先定義了堆棧的地址:

  _estack

  /* Highest address of the user mode stack */

  _estack = 0x20005000; /* end of 20K RAM */

  接著定義了堆和棧的大?。?/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 */

  接著聲明了各個內(nèi)存的區(qū)域(定義了幾個代表某個線性空間的名字,如下面的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 }

  接著下面再介紹上面這三個名字里面都放了什么東西,首先是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中定義的這個段.isr_vector確實放在了最開頭的位置。

  但是我們前面說復(fù)位向量在0x0000 0004,現(xiàn)在其地址是在flash中,所以地址是0x0800 0004,這個怎么回事呢?原來,stm32可以通過boot0、boot1引腳的配置將flash映射到0x0000 0000處。具體可參考stm32的用戶手冊2.4節(jié)。

  從主閃存存儲器啟動(BOOT0 = 0,BOOT1 = X):主閃存存儲器被映射到啟動空間(0x0000 0000),但仍然能夠在它原有的地址(0x0800 0000)訪問它,即閃存存儲器的內(nèi)容可以在兩個地址區(qū)域訪問,0x0000 0000或0x0800 0000。

  至此,整個STM32的啟動過程以及程序結(jié)構(gòu)都可以比較清晰了。



關(guān)鍵詞: stm32

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉