1. 前言自從幾個月前接觸到有Bootloader這回事,就有一種強烈的沖動,想寫一個BootLoader出來。很快在飛思卡爾的Cortex-M4單片機上實現(xiàn),已經(jīng)是好幾個月前的事情了。然后關(guān)于BootLoader的事擱在一邊好久了,這次弄個STM32的BootLoader出來,Cortex-M3的,順便發(fā)表下博客,跟大家分享一下。 。。。 又過了大半年了吧,慢慢對BootLoader的認識也有點長進啦。特別是跟網(wǎng)友討論后發(fā)現(xiàn)BootLoader的實現(xiàn)還是需要靠BootLoader程序和App程序的配合才能正常使用。在這里特別感謝網(wǎng)友cary_yingj 在其他網(wǎng)友的反饋下,本人準(zhǔn)備再將次文檔完善,把不夠詳細的地方寫得再詳細,并且力求通俗易懂一點。希望對學(xué)習(xí)BootLoader的同學(xué)們有所幫助。 2. 初識BootLoader可能有的同學(xué)聽說過BootLoader,有的同學(xué)沒有聽說過,這個都很正常。關(guān)于BootLoader的概念大家可以上網(wǎng)查一下,有比較詳細的說明,我在這里說說我自己比較片面的理解,并且是針對Cortex M3說明的,實現(xiàn)平臺為STM32F103VET6。 2.1
這里借用一下百度百科對BootLoader的解釋。在嵌入式操作系統(tǒng)中,BootLoader是在操作系統(tǒng)內(nèi)核運行之前運行。可以初始化硬件設(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。在嵌入式系統(tǒng)中,通常并沒有像BIOS那樣的固件程序(注,有的嵌入式CPU也會內(nèi)嵌一段短小的啟動程序),因此整個系統(tǒng)的加載啟動任務(wù)就完全由BootLoader來完成。在一個基于ARM7TDMI core的嵌入式系統(tǒng)中,系統(tǒng)在上電或復(fù)位時通常都從地址0x00000000處開始執(zhí)行,而在這個地址處安排的通常就是系統(tǒng)的BootLoader程序。 2.2
BootLoader就是單片機啟動時候運行的一段小程序,這段程序負責(zé)單片機固件更新,也就是單片機選擇性的自己給自己下程序。可以更新也可以不更新,更新的話,BootLoader更新完程序后,跳轉(zhuǎn)到新程序運行;不更新的話,BootLoader直接跳轉(zhuǎn)到原來的程序去運行。 2.3
BootLoader使單片機能自己給自己下載程序,所以在程序升級方面非常有作用。比如我們的BootLoader是通過GSM更新程序的,我們在升級單片機程序的時候,只要把新程序通過GSM發(fā)送給單片機,單片機自己實現(xiàn)程序更新,然后跳轉(zhuǎn)到新程序執(zhí)行,這樣就省去我們很多升級的功夫啦。 可以想象一下如果把單片機安裝在非常高的地方,或者危險的工業(yè)現(xiàn)場,或者封裝得很難拆下來,我們很難直接給單片機下載程序,那么BootLoader的作用就體現(xiàn)出來了。簡單的說,有了BootLoader,我們更新程序的話是省心又省力。 想想是不是很高級,還帶點小興奮哈哈。不用急,下面我們會繼續(xù)介紹,讓大家都能自己實現(xiàn)BootLoader。至于是通過什么方式升級,這個大家自由發(fā)揮,相信會設(shè)計出豐富多彩的BootLoader升級方式呢。 3. BootLoader預(yù)備知識我們這里是為ARM的Cortex-M3單片機寫的BootLoader,需要了解一下M3內(nèi)核的架構(gòu),并且要了解M3單片機是怎么啟動的等等。這個方面的知識,可以參考《Cortex-M3權(quán)威指南》,這里的話我只是為了實現(xiàn)BootLoader簡單介紹一下,大家有什么不清楚的請參考權(quán)威指南。并且這里是以STM32為例說明問題的,使用的開發(fā)環(huán)境是RVMDK(Keil)。 3.1
這里參考的是《Cortex-M3權(quán)威指南》的3.8節(jié),復(fù)位序列。 M3單片機復(fù)位后,從0x00000000取棧指針(SP), 從0x00000004取復(fù)位向量(PC),有了棧指針和復(fù)位向量后,單片機就按照正常流程運行了,在BootLoader里面,我們更新完程序后需要做的步驟之一就是設(shè)置棧指針,跳轉(zhuǎn)到復(fù)位向量。 3.1.1
棧是一種數(shù)據(jù)結(jié)構(gòu),后進先出LIFO。借用百度百科的解釋:棧由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。它使用的是一級緩存,他們通常都是被調(diào)用時處于存儲空間中,調(diào)用完畢立即釋放。 3.1.2
復(fù)位向量是一個函數(shù)地址,在Cortex M3單片機里是復(fù)位函數(shù)的地址。也就是單片機啟動后第一個執(zhí)行的函數(shù)。 3.2
這里參考《Cortex-M3權(quán)威指南》的7.3節(jié),向量表。 BootLoader是一個完整的程序,下載的新程序(以下稱為App)也是一個完整的程序。都包含中斷向量表,所以的話,我們是有兩個中斷向量表,相信因為有兩個向量表,大家都知道我們應(yīng)該需要對這兩個向量表做點什么吧。 3.2.1
我們只看前16個向量,因為其余的向量屬于外設(shè)使用,與Cortex M3內(nèi)核無關(guān)。 __Vectors __initial_spTop就是棧指針,Reset_Handler是復(fù)位向量。這里只顯示了16個向量,CortexM3單片機的話總共有256個向量,也就是從棧指針的地址開始有1KB的區(qū)域?qū)儆谥袛嘞蛄勘怼?/span> 單片機啟動默認先運行BootLoader,所以默認的中斷向量表位置是BootLoader的中斷向量表。為了App可以正常運行,下載完App后,我們還需要把中斷向量表重新定位到App程序那里。根據(jù)《Cortex M3權(quán)威指南》,介紹一下怎樣重定位中斷向量表。 3.2.2
Cortex-M3單片機有一個管理中斷向量表的寄存器,叫做向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)。具體可以看看截圖: STM332程序的起始地址一般在0x08000000。所以BootLoader程序是在0x08000000,不是在0x00000000是因為STM32的重映射技術(shù)(不符合Cortex-M3的設(shè)計,有點搞另類的感覺)。所以BootLoader的中斷向量表在0x08000000那里。如果我們的App程序起始地址在0x08070000,并且App的中斷向量表在起始地址,那么BootLoader程序下載App后,為了App程序能正確運行,開始App程序的運行后第一步,就要把中斷向量表重定位到0x08070000那里。 具體實現(xiàn)下面會再介紹,接下來介紹分散加載文件相關(guān)內(nèi)容。 3.3
這一節(jié)涉及的內(nèi)容主要屬于分散加載文件,大家具體上網(wǎng)了解,這里只是介紹了能夠?qū)崿F(xiàn)BootLoader的一小部分。 3.3.1
我們知道C語言的函數(shù)名就是函數(shù)的地址,并且STM32單片機ROM的起始地址是在0x08000000,那么使用編譯器編譯程序的話(這里使用的是RVMDK),函數(shù)的地址默認都在以0x08000000為首的一段ROM里面了。比如我們一個函數(shù)Delay(),它的地址可以是0x08000167(Cortex M3中函數(shù)的地址0bit位一般是1),也就是Delay函數(shù)的代碼在0x08000167,C語言函數(shù)調(diào)用Delay時,就是執(zhí)行0x08000167的代碼。 3.3.2
我們需要注意的問題是,如果不修改程序默認的起始地址的話,那么BootLoader和新App程序的起始地址都是0x08000000,也就是他們重疊了(代碼重疊),這樣的話肯定相互之間有影響,程序是不能正常工作的。 這里的解決方法是,BootLoader程序依然占用0x08000000為首的那段ROM,因為STM32的默認就是從0x08000000運行程序的。保持BootLoader程序先能正確運行。然后App使用除BootLoader占用ROM以外的空間。這里需要知道BootLoader到底占用了多少ROM,很簡單,查看MAP文件就行了。這里以我的BootLoader的MAP文件為例說明一下,看截圖: Memory Map of the image 主要是這句話“Base: 0x08000000, Size: 0x00006da4, Max: 0x00080000”,這句話說明了我的BootLoader程序是從0x08000000開始,占用了0x00006DA4大小。只要我們的App不要和BootLoader程序占用的空間沖突就可以了。我的App程序的起始地址選擇為0x08070000,不與BootLoader程序沖突。具體怎么修改ROM起始地址,下面介紹。 3.3.3
編譯新程序的時候,我們要修改程序的起始地址,我的修改方法如下(開發(fā)環(huán)境是RVMDK):打開Target Option...,切換到Target選項卡,如下 修改IROM1的起始地址和長度: 比如,為了不產(chǎn)生地址沖突,我將起始地址0x08000000修改成0x0807000,將ROM長度0x80000修改成0x10000。如下圖所示(左圖為修改前、右圖為修改后):
注意:BootLoader程序是不需要修改的,只是App需要修改(App就是使用BootLoader下載的程序)。 3.4
3.4.1
平時我們用j-Link或者串口下載程序的話,都是打開hex文件下載的,因為hex文件包含地址信息,下載程序的時候知道程序下載到ROM的哪個區(qū)域。從另一個角度上說,也就是hex文件是不能直接寫進ROM的,一邊寫需要一邊轉(zhuǎn)換(解碼出地址信息,將對應(yīng)內(nèi)容寫入ROM)。 3.4.2
bin文件的話,很好理解,是直接的可執(zhí)行代碼。也就是bin文件的內(nèi)容跟下載ROM里面的內(nèi)容是一樣的。bin文件是沒有包含地址信息的,所以在下載之前要知道bin文件是要下載到ROM的那個區(qū)域。 我們的BootLoader下載的是bin文件,直接寫進STM32的Flash里面,地址信息的話就是上一節(jié)的IROM,0x08070000,從0x08070000開始連續(xù)寫入,中間不間斷。 3.5
默認情況下編譯后生成的是hex文件,不過很輕松可以生成bin文件。介紹具體怎么生成bin文件,工具的話是使用fromelf.exe(目錄一般是在Keil安裝目錄里面,本人的fromelf.exe目錄是在C:\Keil\ARM\ARMCC\bin),我們是使用fromelf工具將axf文件轉(zhuǎn)換為bin文件。 熟悉命令行的同學(xué)可能會選擇直接敲命令,不過這里介紹使用RVMDK提供的用戶命令(編譯時可以自動生成bin,省去每次生成bin文件都要敲命令的過程)。 打開Target Option...,切換到User選項卡,如下 主要是在運行用戶命令,Run #1 具體命令是(記得在Run #1前打勾,才會在編譯后執(zhí)行用戶命令生成bin文件): C:\Keil\ARM\ARMCC\bin\fromelf.exe --bin -o .\Output\MY_STM32.bin .\Output\MY_STM32.axf 命令可以分為五部分,簡化后是fromelf 我的bin文件和axf文件都在Output文件夾里面,并且路徑是相對MY_STM32.uvproj的,Output文件夾里的bin文件(MY_STM32.bin)相對于MY_STM32.uvproj應(yīng)該寫成“.\Output\MY_STM32.bin”。 l 這部分是fromelf.exe文件的路徑,根據(jù)自己的安裝目錄而變。我這里因為Keil是安裝在C盤的,所以我的路徑如下所示。 參考命令:C:\Keil\ARM\ARMCC\bin\fromelf.exe l 這部分是固定的,--bin表示生成bin文件。 參考命令:--bin l 這部分也是固定的,-o表示輸出。 參考命令:-o l 這部分是生成文件的目錄和文件名,我是輸出在Output文件夾的,也就是bin文件在Output文件夾里面。 參考命令:.\Output\MY_STM32.bin l 這部分是axf文件的目錄和文件名,我們的bin文件是根據(jù)axf文件生成的,也就是說axf文件相當(dāng)于輸入,bin文件相當(dāng)于輸出。我的axf文件也在Output文件夾的。 參考命令:.\Output\MY_STM32.axf 介紹了這些基本知識后,我們可以來實現(xiàn)BootLoader了。 4. 分幾步實現(xiàn)BootLoader有了前面的基礎(chǔ)知識后,應(yīng)該是比較容易理解BootLoader需要怎么實現(xiàn)了。這一章,我們分幾個步驟,一步一步實現(xiàn)BootLoader。 4.1
我們的BootLoader是從SD卡更新程序的,把在電腦上編譯后的App程序,也就是bin文件,復(fù)制到SD卡中,然后讓單片機讀取相應(yīng)的bin文件,就可以實現(xiàn)程序的更新。需要注意的是,App程序需要修改ROM的起始地址,再編譯,并且要生成bin文件才支持正常下載。 我跑的文件系統(tǒng)是FATFS_R0.07c,很經(jīng)典的一個版本。如果大家對文件系統(tǒng)方面不了解的話,請自己網(wǎng)上查找教程,或者說很多同學(xué)對這一步應(yīng)該已經(jīng)很熟悉啦。 只要單片機上實現(xiàn)讀取bin文件,結(jié)合Flash寫入程序,就可以實現(xiàn)程序更新。下面介紹讀寫Flash。 4.2
要實現(xiàn)BootLoader,還有一個前提是可以寫入Flash了。如果是STM32單片機的話是很容易實現(xiàn)的,因為我們有官方庫。本人使用的是3.0.0版本,參考官方例程,很容易實現(xiàn)Flash的讀寫,這里同樣是為了實現(xiàn)BootLoader簡單介紹一下。 4.2.1
l l l l 4.2.2
l l l 4.2.3
稍微封裝一下STM32的官方庫函數(shù),就能實現(xiàn)Flash的讀寫,并驗證讀寫是否正確,具體我實現(xiàn)的接口函數(shù)為以下截圖,大家可以參考一下: 來到這里,我們可以實現(xiàn)在bin文件寫入Flash了,寫入完后,就要跳轉(zhuǎn)到App程序執(zhí)行了,接下來繼續(xù)介紹。 4.3
l App需要做的是: l l 4.3.1
BootLoader程序需要做的是跳轉(zhuǎn)到復(fù)位向量,具體實現(xiàn)可以參考以下代碼。 注意 ( (void (*)()) (Reset) )();是一去就不返回的,執(zhí)行完這條語句,單片機就直接跳轉(zhuǎn)到App程序運行的,所以BootLoader程序下載完App后,做一些簡單的處理(根據(jù)自己的應(yīng)用,也可以不做任何處理),就用這條語句跳轉(zhuǎn)到App執(zhí)行。 4.3.2
BootLoader跳轉(zhuǎn)到App后,App需要做的是先設(shè)置棧指針,然后重定位中斷向量表地址,具體可以參考以下代碼。 __set_MSP( Msp ); NVIC_SetVectorTable( base, offset ); 其中Msp是棧指針,也就是中斷向量表第一個字的內(nèi)容,我們這里的內(nèi)容是*( (uint32_t)(0x08070000) )。 base是中斷向量表的基地址,一般情況下就是ROM的起始地址,這里是0x08070000。 至此,BootLoader實現(xiàn)步驟完了,相信熟悉了這幾個步驟后,大家可以自己給自己的單片機寫個BootLoader。順便說一下,Cortex-M4的BootLoader跟Cortex-M3幾乎是一樣的。我在STM32上的實現(xiàn)完全是參考自己上次在飛思卡爾Cortex-M4上的實現(xiàn)。下面說一下我的主函數(shù)吧,我們再看看具體的BootLoader流程,再熟悉一下BootLoader。 5. Bootloader具體流程 5.1
先看截圖。 主函數(shù)的流程如下所示: l l l l l l l 然后在具體講解BootLoader_FromSDCard函數(shù),這就是我們的重點,傳說中STM32的BootLoader從SD卡更新固件。 5.2
老樣子,先上截圖: 具體流程如下所示: l l l l l l l l 來到這里已經(jīng)是退出循環(huán)了,也就是說我們已經(jīng)將bin寫入Flash完成了,準(zhǔn)備跳轉(zhuǎn)到新程序運行 5.3
其實上面已經(jīng)講過了,這里繼續(xù)啰嗦,截圖: l l l 說明一下,在這里重定位中斷向量其實是多余的,App程序執(zhí)行初始化后,又回到STM32初始狀態(tài),所以在App程序中需要執(zhí)行重定位中斷向量表操作,具體同以上操作相同。 啰嗦了又一遍,BootLoader完全結(jié)束,感謝大家都支持啦~ #include "main.h" int main(void) { } l l l l l [1] [2] [3] [4] [5] 最后來一個廣告,本工程源代碼等資料,歡迎索取:
|
|
來自: ada_lib > 《arm_stm32》