目 錄
1. 前言
2. 初識BootLoader
2.1 百度百科的BootLoader
2.2 BootLoader的簡單理解
2.3 BootLoader的作用
3. BootLoader預備知識
3.1 復位序列
3.1.1 棧指針
3.1.2 復位向量
3.2 重定位中斷向量表
3.2.1 STM32的中斷向量表
3.2.2 設置中斷向量表偏移
3.3 分散加載文件相關
3.3.1 C語言的函數地址
3.3.2 BootLoader占用的ROM
3.3.3 修改ROM起始地址
3.4 hex文件和bin文件
3.4.1 hex文件
3.4.2 bin文件
3.5 Bin文件生成
4. 分幾步實現BootLoader
4.1 跑FAT文件系統
4.2 讀寫Flash程序
4.2.1 Flash寫入步驟
4.2.2 讀寫Flash調用的庫函數
4.2.3 實現Flash讀寫
4.3 跳轉到新程序運行
4.3.1 跳轉到復位向量
4.3.2 App開始運行
5. Bootloader具體流程
5.1 主函數流程
5.2 BootLoader流程
5.3 跳轉到新程序流程
附錄A主函數
附錄B更新說明
參考文獻
1. 前言自從幾個月前接觸到有Bootloader這回事,就有一種強烈的沖動,想寫一個BootLoader出來。很快在飛思卡爾的Cortex-M4單片機上實現,已經是好幾個月前的事情了。然后關于BootLoader的事擱在一邊好久了,這次弄個STM32的BootLoader出來,Cortex-M3的,順便發表下博客,跟大家分享一下。
。。。
又過了大半年了吧,慢慢對BootLoader的認識也有點長進啦。特別是跟網友討論后發現BootLoader的實現還是需要靠BootLoader程序和App程序的配合才能正常使用。在這里特別感謝網友cary_yingj ,對本BootLoader的研究后發現App程序需要重定位中斷向量表,才能正常工作。
在其他網友的反饋下,本人準備再將次文檔完善,把不夠詳細的地方寫得再詳細,并且力求通俗易懂一點。希望對學習BootLoader的同學們有所幫助。
2. 初識BootLoader可能有的同學聽說過BootLoader,有的同學沒有聽說過,這個都很正常。關于BootLoader的概念大家可以上網查一下,有比較詳細的說明,我在這里說說我自己比較片面的理解,并且是針對CortexM3說明的,實現平臺為STM32F103VET6。
2.1 百度百科的BootLoader這里借用一下百度百科對BootLoader的解釋。在嵌入式操作系統中,BootLoader是在操作系統內核運行之前運行。可以初始化硬件設備、建立內存空間映射圖,從而將系統的軟硬件環境帶到一個合適狀態,以便為最終調用操作系統內核準備好正確的環境。在嵌入式系統中,通常并沒有像BIOS那樣的固件程序(注,有的嵌入式CPU也會內嵌一段短小的啟動程序),因此整個系統的加載啟動任務就完全由BootLoader來完成。在一個基于ARM7TDMIcore的嵌入式系統中,系統在上電或復位時通常都從地址0x00000000處開始執行,而在這個地址處安排的通常就是系統的BootLoader程序。
2.2 BootLoader的簡單理解BootLoader就是單片機啟動時候運行的一段小程序,這段程序負責單片機固件更新,也就是單片機選擇性的自己給自己下程序。可以更新也可以不更新,更新的話,BootLoader更新完程序后,跳轉到新程序運行;不更新的話,BootLoader直接跳轉到原來的程序去運行。
需要注意的是:BootLoader下載新程序后并不擦除自己(BootLoader程序還在),下次啟動依然先運行BootLoader程序,又可以選擇性的更新或者不更新程序,所以BootLoader就是用來管理單片機程序的更新。 這是本人的大概理解,大家有不明白請網上搜索一下更詳細介紹吧。 2.3 BootLoader的作用BootLoader使單片機能自己給自己下載程序,所以在程序升級方面非常有作用。比如我們的BootLoader是通過GSM更新程序的,我們在升級單片機程序的時候,只要把新程序通過GSM發送給單片機,單片機自己實現程序更新,然后跳轉到新程序執行,這樣就省去我們很多升級的功夫啦。
可以想象一下如果把單片機安裝在非常高的地方,或者危險的工業現場,或者封裝得很難拆下來,我們很難直接給單片機下載程序,那么BootLoader的作用就體現出來了。簡單的說,有了BootLoader,我們更新程序的話是省心又省力。
想想是不是很高級,還帶點小興奮哈哈。不用急,下面我們會繼續介紹,讓大家都能自己實現BootLoader。至于是通過什么方式升級,這個大家自由發揮,相信會設計出豐富多彩的BootLoader升級方式呢。
3.BootLoader預備知識我們這里是為ARM的Cortex-M3單片機寫的BootLoader,需要了解一下M3內核的架構,并且要了解M3單片機是怎么啟動的等等。這個方面的知識,可以參考《Cortex-M3權威指南》,這里的話我只是為了實現BootLoader簡單介紹一下,大家有什么不清楚的請參考權威指南。并且這里是以STM32為例說明問題的,使用的開發環境是RVMDK(Keil)。
3.1 復位序列這里參考的是《Cortex-M3權威指南》的3.8節,復位序列。
M3單片機復位后,從0x00000000取棧指針(SP),從0x00000004取復位向量(PC),有了棧指針和復位向量后,單片機就按照正常流程運行了,在BootLoader里面,我們更新完程序后需要做的步驟之一就是設置棧指針,跳轉到復位向量。
3.1.1 棧指針棧是一種數據結構,后進先出LIFO。借用百度百科的解釋:棧由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。它使用的是一級緩存,他們通常都是被調用時處于存儲空間中,調用完畢立即釋放。
3.1.2 復位向量復位向量是一個函數地址,在CortexM3單片機里是復位函數的地址。也就是單片機啟動后第一個執行的函數。
3.2 重定位中斷向量表這里參考《Cortex-M3權威指南》的7.3節,向量表。
BootLoader是一個完整的程序,下載的新程序(以下稱為App)也是一個完整的程序。都包含中斷向量表,所以的話,我們是有兩個中斷向量表,相信因為有兩個向量表,大家都知道我們應該需要對這兩個向量表做點什么吧。
3.2.1 STM32的中斷向量表我們只看前16個向量,因為其余的向量屬于外設使用,與CortexM3內核無關。
__Vectors DCD __initial_spTop ;Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
__initial_spTop就是棧指針,Reset_Handler是復位向量。這里只顯示了16個向量,CortexM3單片機的話總共有256個向量,也就是從棧指針的地址開始有1KB的區域屬于中斷向量表。 單片機啟動默認先運行BootLoader,所以默認的中斷向量表位置是BootLoader的中斷向量表。為了App可以正常運行,下載完App后,我們還需要把中斷向量表重新定位到App程序那里。根據《CortexM3權威指南》,介紹一下怎樣重定位中斷向量表。
3.2.2 設置中斷向量表偏移Cortex-M3單片機有一個管理中斷向量表的寄存器,叫做向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)。具體可以看看截圖:
STM332程序的起始地址一般在0x08000000。所以BootLoader程序是在0x08000000,不是在0x00000000是因為STM32的重映射技術(不符合Cortex-M3的設計,有點搞另類的感覺)。所以BootLoader的中斷向量表在0x08000000那里。如果我們的App程序起始地址在0x08070000,并且App的中斷向量表在起始地址,那么BootLoader程序下載App后,為了App程序能正確運行,開始App程序的運行后第一步,就要把中斷向量表重定位到0x08070000那里。
具體實現下面會再介紹,接下來介紹分散加載文件相關內容。
3.3 分散加載文件相關這一節涉及的內容主要屬于分散加載文件,大家具體上網了解,這里只是介紹了能夠實現BootLoader的一小部分。
3.3.1 C語言的函數地址我們知道C語言的函數名就是函數的地址,并且STM32單片機ROM的起始地址是在0x08000000,那么使用編譯器編譯程序的話(這里使用的是RVMDK),函數的地址默認都在以0x08000000為首的一段ROM里面了。比如我們一個函數Delay(),它的地址可以是0x08000167(CortexM3中函數的地址0bit位一般是1),也就是Delay函數的代碼在0x08000167,C語言函數調用Delay時,就是執行0x08000167的代碼。
3.3.2 BootLoader占用的ROM我們需要注意的問題是,如果不修改程序默認的起始地址的話,那么BootLoader和新App程序的起始地址都是0x08000000,也就是他們重疊了(代碼重疊),這樣的話肯定相互之間有影響,程序是不能正常工作的。
這里的解決方法是,BootLoader程序依然占用0x08000000為首的那段ROM,因為STM32的默認就是從0x08000000運行程序的。保持BootLoader程序先能正確運行。然后App使用除BootLoader占用ROM以外的空間。這里需要知道BootLoader到底占用了多少ROM,很簡單,查看MAP文件就行了。這里以我的BootLoader的MAP文件為例說明一下,看截圖:
Memory Map of the image
Image Entry point :0x08000131
Load Region LR_IROM1 (Base: 0x08000000,Size: 0x00006da4, Max: 0x00080000, ABSOLUTE)
Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00006d54, Max:0x00080000, ABSOLUTE)
主要是這句話“Base:0x08000000, Size: 0x00006da4, Max: 0x00080000”,這句話說明了我的BootLoader程序是從0x08000000開始,占用了0x00006DA4大小。只要我們的App不要和BootLoader程序占用的空間沖突就可以了。我的App程序的起始地址選擇為0x08070000,不與BootLoader程序沖突。具體怎么修改ROM起始地址,下面介紹。
3.3.3 修改ROM起始地址編譯新程序的時候,我們要修改程序的起始地址,我的修改方法如下(開發環境是RVMDK):打開TargetOption...,切換到Target選項卡,如下
修改IROM1的起始地址和長度:
比如,為了不產生地址沖突,我將起始地址0x08000000修改成0x0807000,將ROM長度0x80000修改成0x10000。如下圖所示(左圖為修改前、右圖為修改后):
注意:BootLoader程序是不需要修改的,只是App需要修改(App就是使用BootLoader下載的程序)。
3.4 hex文件和bin文件3.4.1 hex文件平時我們用j-Link或者串口下載程序的話,都是打開hex文件下載的,因為hex文件包含地址信息,下載程序的時候知道程序下載到ROM的哪個區域。從另一個角度上說,也就是hex文件是不能直接寫進ROM的,一邊寫需要一邊轉換(解碼出地址信息,將對應內容寫入ROM)。
3.4.2 bin文件bin文件的話,很好理解,是直接的可執行代碼。也就是bin文件的內容跟下載ROM里面的內容是一樣的。bin文件是沒有包含地址信息的,所以在下載之前要知道bin文件是要下載到ROM的那個區域。
我們的BootLoader下載的是bin文件,直接寫進STM32的Flash里面,地址信息的話就是上一節的IROM,0x08070000,從0x08070000開始連續寫入,中間不間斷。
3.5 Bin文件生成默認情況下編譯后生成的是hex文件,不過很輕松可以生成bin文件。介紹具體怎么生成bin文件,工具的話是使用fromelf.exe(目錄一般是在Keil安裝目錄里面,本人的fromelf.exe目錄是在C:\Keil\ARM\ARMCC\bin),我們是使用fromelf工具將axf文件轉換為bin文件。
熟悉命令行的同學可能會選擇直接敲命令,不過這里介紹使用RVMDK提供的用戶命令(編譯時可以自動生成bin,省去每次生成bin文件都要敲命令的過程)。
打開TargetOption...,切換到User選項卡,如下
主要是在運行用戶命令,Run#1
具體命令是(記得在Run#1前打勾,才會在編譯后執行用戶命令生成bin文件):
C:\Keil\ARM\ARMCC\bin\fromelf.exe --bin -o .\Output\MY_STM32.bin .\Output\MY_STM32.axf 命令可以分為五部分,簡化后是fromelf --bin -o xxx.bin xxx.axf,需要注意的是命令的五個部分之間要有空格。還需要說明的是路徑問題,這里的路徑都是相對.uvproj文件的,下面是我的目錄(注意MY_STM32.uvproj文件和Output文件夾)。

我的bin文件和axf文件都在Output文件夾里面,并且路徑是相對MY_STM32.uvproj的,Output文件夾里的bin文件(MY_STM32.bin)相對于MY_STM32.uvproj應該寫成“.\Output\MY_STM32.bin”。
l 第一部分
這部分是fromelf.exe文件的路徑,根據自己的安裝目錄而變。我這里因為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文件是根據axf文件生成的,也就是說axf文件相當于輸入,bin文件相當于輸出。我的axf文件也在Output文件夾的。
參考命令:.\Output\MY_STM32.axf
介紹了這些基本知識后,我們可以來實現BootLoader了。
4. 分幾步實現BootLoader有了前面的基礎知識后,應該是比較容易理解BootLoader需要怎么實現了。這一章,我們分幾個步驟,一步一步實現BootLoader。
4.1 跑FAT文件系統我們的BootLoader是從SD卡更新程序的,把在電腦上編譯后的App程序,也就是bin文件,復制到SD卡中,然后讓單片機讀取相應的bin文件,就可以實現程序的更新。需要注意的是,App程序需要修改ROM的起始地址,再編譯,并且要生成bin文件才支持正常下載。
我跑的文件系統是FATFS_R0.07c,很經典的一個版本。如果大家對文件系統方面不了解的話,請自己網上查找教程,或者說很多同學對這一步應該已經很熟悉啦。
只要單片機上實現讀取bin文件,結合Flash寫入程序,就可以實現程序更新。下面介紹讀寫Flash。
4.2 讀寫Flash程序要實現BootLoader,還有一個前提是可以寫入Flash了。如果是STM32單片機的話是很容易實現的,因為我們有官方庫。本人使用的是3.0.0版本,參考官方例程,很容易實現Flash的讀寫,這里同樣是為了實現BootLoader簡單介紹一下。
4.2.1 Flash寫入步驟l 解鎖Flash
l 擦除Flash
l 寫入Flash
l 驗證讀寫是否正確
4.2.2 讀寫Flash調用的庫函數l voidFLASH_Unlock(void) Flash解鎖
l FLASH_Status FLASH_ErasePage(uint32_tPage_Address) Flash擦除
l FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_tData) Flash寫入
4.2.3 實現Flash讀寫稍微封裝一下STM32的官方庫函數,就能實現Flash的讀寫,并驗證讀寫是否正確,具體我實現的接口函數為以下截圖,大家可以參考一下:
來到這里,我們可以實現在bin文件寫入Flash了,寫入完后,就要跳轉到App程序執行了,接下來繼續介紹。
4.3 跳轉到新程序運行 這一節要結合上面提到過的,Cortex-M3啟動做了什么事情,然后我們的BootLoader下載App程序后,App程序就需要做同樣的事情。主要有三個步驟,其中BootLoader程序需要做的是: l 跳轉到復位向量
App需要做的是: l 重定位中斷向量表
l 設置棧指針
4.3.1 跳轉到復位向量BootLoader程序需要做的是跳轉到復位向量,具體實現可以參考以下代碼。
( (void (*)()) (Reset))(); //跳轉到復位向量
注意( (void (*)()) (Reset) )();是一去就不返回的,執行完這條語句,單片機就直接跳轉到App程序運行的,所以BootLoader程序下載完App后,做一些簡單的處理(根據自己的應用,也可以不做任何處理),就用這條語句跳轉到App執行。
4.3.2 App開始運行BootLoader跳轉到App后,App需要做的是先設置棧指針,然后重定位中斷向量表地址,具體可以參考以下代碼。
__set_MSP( Msp); //設置棧指針
NVIC_SetVectorTable( base, offset); //重定位中斷向量表
其中Msp是棧指針,也就是中斷向量表第一個字的內容,我們這里的內容是*((uint32_t)(0x08070000) )。
base是中斷向量表的基地址,一般情況下就是ROM的起始地址,這里是0x08070000。
至此,BootLoader實現步驟完了,相信熟悉了這幾個步驟后,大家可以自己給自己的單片機寫個BootLoader。順便說一下,Cortex-M4的BootLoader跟Cortex-M3幾乎是一樣的。我在STM32上的實現完全是參考自己上次在飛思卡爾Cortex-M4上的實現。下面說一下我的主函數吧,我們再看看具體的BootLoader流程,再熟悉一下BootLoader。
5.Bootloader具體流程 先看看我的主函數,再啰嗦一下具體流程,可能有的同學已經有點厭煩啦,其實感覺有點多余。 5.1 主函數流程先看截圖。
主函數的流程如下所示:
l 時鐘初始化
l LED初始化(無關緊要)
l 調試接口初始化(無關緊要)
l Flash初始化(解鎖Flash)
l FAT初始化(掛載文件系統)
l 我們的BootLoader(重點,下面展開繼續介紹)
l 主循環(實際不會運行到這里)
然后在具體講解BootLoader_FromSDCard函數,這就是我們的重點,傳說中STM32的BootLoader從SD卡更新固件。
5.2 BootLoader流程老樣子,先上截圖: 具體流程如下所示:
l 打開bin文件,檢查文件打開是否正確
l 設置Flash下載起始地址(App程序起始地址)
l 讀取bin文件,檢查讀取是否正確
l 獲取棧指針SP和復位向量PC
l 進入循環(這里是第5步),條件為如果讀取bin文件字節數不為零
l 將讀取到的bin寫入Flash,并判寫入狀態
l 調整Flash地址,根據寫入字節調整
l 繼續讀取bin文件,檢查讀取是否正確,回到5繼續循環
來到這里已經是退出循環了,也就是說我們已經將bin寫入Flash完成了,準備跳轉到新程序運行
5.3 跳轉到新程序流程其實上面已經講過了,這里繼續啰嗦,截圖:
l 重定位中斷向量
l 設置棧指針
l 跳轉到復位向量(開始運行App程序)
說明一下,在這里重定位中斷向量其實是多余的,App程序執行初始化后,又回到STM32初始狀態,所以在App程序中需要執行重定位中斷向量表操作,具體同以上操作相同。
啰嗦了又一遍,BootLoader完全結束,感謝大家都支持啦~
附錄A 主函數
#include "main.h"
int main(void)
{
SystemInit(); //配置系統時鐘為72M
LED_GPIO_Config(); //初始化LED端口
Debug_TraceIOEnable(); //使能調試printf的IO口
Flash_Init(); //初始化Flash
FAT_Init(); //初始化文件系統
BootLoader_FromSDCard(); //Bootloader從SD卡更新固件
while(1)
{
LED_StatShow( FuncErr); //LED顯示Bootloader狀態
}
}
附錄B 更新說明 l 版本:V1.01
l 時間:2014-4-8
l 作者:coolweedman
l 更新:說明Bootloader程序和App程序需要配合才能實現BootLoader,App程序需要重定位中斷向量表
l 特別感謝:網友cary_yingj的研究和分享
參考文獻
[1] 《CM3權威指南CnR2》Joseph Yiu 著 宋巖 譯
[2] ST官方庫3.0.0
[3] 《C和指針》KennethA. Reek著 徐波譯
[4] FATFS文件系統
[5] 其他互聯網資料
|