需要注意的是:BootLoader下載新程序后并不擦除自己(BootLoader程序還在),下次啟動依然先運行BootLoader程序,又可以選擇性的更新或者不更新程序,所以BootLoader就是用來管理單片機程序的更新。
__initial_spTop就是棧指針,Reset_Handler是復(fù)位向量。這里只顯示了16個向量,CortexM3單片機的話總共有256個向量,也就是從棧指針的地址開始有1KB的區(qū)域?qū)儆谥袛嘞蛄勘怼?/div>
單片機啟動默認(rèn)先運行BootLoader
,所以默認(rèn)的中斷向量表位置是BootLoader
的中斷向量表。為了App
可以正常運行,下載完App
后,我們還需要把中斷向量表重新定位到App
程序那里。根據(jù)《CortexM3
權(quán)威指南》,介紹一下怎樣重定位中斷向量表。
3.2.2 設(shè)置中斷向量表偏移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 分散加載文件相關(guān)這一節(jié)涉及的內(nèi)容主要屬于分散加載文件,大家具體上網(wǎng)了解,這里只是介紹了能夠?qū)崿F(xiàn)BootLoader
的一小部分。
3.3.1 C語言的函數(shù)地址我們知道C
語言的函數(shù)名就是函數(shù)的地址,并且STM32
單片機ROM
的起始地址是在0x08000000
,那么使用編譯器編譯程序的話(這里使用的是RVMDK
),函數(shù)的地址默認(rèn)都在以0x08000000
為首的一段ROM
里面了。比如我們一個函數(shù)Delay()
,它的地址可以是0x08000167
(CortexM3
中函數(shù)的地址0bit
位一般是1
),也就是Delay
函數(shù)的代碼在0x08000167
,C
語言函數(shù)調(diào)用Delay
時,就是執(zhí)行0x08000167
的代碼。
3.3.2 BootLoader占用的ROM我們需要注意的問題是,如果不修改程序默認(rèn)的起始地址的話,那么BootLoader
和新App
程序的起始地址都是0x08000000
,也就是他們重疊了(代碼重疊),這樣的話肯定相互之間有影響,程序是不能正常工作的。
這里的解決方法是,BootLoader
程序依然占用0x08000000
為首的那段ROM
,因為STM32
的默認(rèn)就是從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起始地址編譯新程序的時候,我們要修改程序的起始地址,我的修改方法如下(開發(fā)環(huán)境是RVMDK
):打開TargetOption...
,切換到Target
選項卡,如下
修改IROM1
的起始地址和長度:
比如,為了不產(chǎn)生地址沖突,我將起始地址0x08000000
修改成0x0807000
,將ROM
長度0x80000
修改成0x10000
。如下圖所示(左圖為修改前、右圖為修改后):
注意:BootLoader
程序是不需要修改的,只是App
需要修改(App
就是使用BootLoader
下載的程序)。
3.4 hex文件和bin文件3.4.1 hex文件平時我們用j-Link
或者串口下載程序的話,都是打開hex
文件下載的,因為hex
文件包含地址信息,下載程序的時候知道程序下載到ROM
的哪個區(qū)域。從另一個角度上說,也就是hex
文件是不能直接寫進(jìn)ROM
的,一邊寫需要一邊轉(zhuǎn)換(解碼出地址信息,將對應(yīng)內(nèi)容寫入ROM
)。
3.4.2 bin文件bin
文件的話,很好理解,是直接的可執(zhí)行代碼。也就是bin
文件的內(nèi)容跟下載ROM
里面的內(nèi)容是一樣的。bin
文件是沒有包含地址信息的,所以在下載之前要知道bin
文件是要下載到ROM
的那個區(qū)域。
我們的BootLoader
下載的是bin
文件,直接寫進(jìn)STM32
的Flash
里面,地址信息的話就是上一節(jié)的IROM
,0x08070000
,從0x08070000
開始連續(xù)寫入,中間不間斷。
3.5 Bin文件生成默認(rèn)情況下編譯后生成的是hex
文件,不過很輕松可以生成bin
文件。介紹具體怎么生成bin
文件,工具的話是使用fromelf.exe
(目錄一般是在Keil
安裝目錄里面,本人的fromelf.exe
目錄是在C:\Keil\ARM\ARMCC\bin
),我們是使用fromelf
工具將axf
文件轉(zhuǎn)換為bin
文件。
熟悉命令行的同學(xué)可能會選擇直接敲命令,不過這里介紹使用RVMDK
提供的用戶命令(編譯時可以自動生成bin
,省去每次生成bin
文件都要敲命令的過程)。
打開TargetOption...
,切換到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 -o xxx.bin xxx.axf
,需要注意的是命令的五個部分之間要有空格。還需要說明的是路徑問題,這里的路徑都是相對.uvproj
文件的,下面是我的目錄(注意MY_STM32.uvproj
文件和Output
文件夾)。
我的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 跑FAT文件系統(tǒng)我們的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 讀寫Flash程序要實現(xiàn)BootLoader
,還有一個前提是可以寫入Flash
了。如果是STM32
單片機的話是很容易實現(xiàn)的,因為我們有官方庫。本人使用的是3.0.0
版本,參考官方例程,很容易實現(xiàn)Flash
的讀寫,這里同樣是為了實現(xiàn)BootLoader
簡單介紹一下。
4.2.1 Flash寫入步驟l 解鎖Flash
l 擦除Flash
l 寫入Flash
l 驗證讀寫是否正確
4.2.2 讀寫Flash調(diào)用的庫函數(shù)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 實現(xiàn)Flash讀寫稍微封裝一下STM32
的官方庫函數(shù),就能實現(xiàn)Flash
的讀寫,并驗證讀寫是否正確,具體我實現(xiàn)的接口函數(shù)為以下截圖,大家可以參考一下:
來到這里,我們可以實現(xiàn)在bin
文件寫入Flash
了,寫入完后,就要跳轉(zhuǎn)到App
程序執(zhí)行了,接下來繼續(xù)介紹。
4.3 跳轉(zhuǎn)到新程序運行 這一節(jié)要結(jié)合上面提到過的,Cortex-M3啟動做了什么事情,然后我們的BootLoader下載App程序后,App程序就需要做同樣的事情。主要有三個步驟,其中BootLoader程序需要做的是:
l 跳轉(zhuǎn)到復(fù)位向量
App需要做的是:
l 重定位中斷向量表
l 設(shè)置棧指針
4.3.1 跳轉(zhuǎn)到復(fù)位向量BootLoader
程序需要做的是跳轉(zhuǎn)到復(fù)位向量,具體實現(xiàn)可以參考以下代碼。
( (void (*)()) (Reset))(); //
跳轉(zhuǎn)到復(fù)位向量
注意( (void (*)()) (Reset) )();
是一去就不返回的,執(zhí)行完這條語句,單片機就直接跳轉(zhuǎn)到App
程序運行的,所以BootLoader
程序下載完App
后,做一些簡單的處理(根據(jù)自己的應(yīng)用,也可以不做任何處理),就用這條語句跳轉(zhuǎn)到App
執(zhí)行。
4.3.2 App開始運行BootLoader
跳轉(zhuǎn)到App
后,App
需要做的是先設(shè)置棧指針,然后重定位中斷向量表地址,具體可以參考以下代碼。
__set_MSP( Msp); //
設(shè)置棧指針
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具體流程 先看看我的主函數(shù),再啰嗦一下具體流程,可能有的同學(xué)已經(jīng)有點厭煩啦,其實感覺有點多余。
5.1 主函數(shù)流程先看截圖。
主函數(shù)的流程如下所示:
l 時鐘初始化
l LED
初始化(無關(guān)緊要)
l 調(diào)試接口初始化(無關(guān)緊要)
l Flash
初始化(解鎖Flash
)
l FAT
初始化(掛載文件系統(tǒng))
l 我們的BootLoader
(重點,下面展開繼續(xù)介紹)
l 主循環(huán)(實際不會運行到這里)
然后在具體講解BootLoader_FromSDCard
函數(shù),這就是我們的重點,傳說中STM32
的BootLoader
從SD
卡更新固件。
5.2 BootLoader流程老樣子,先上截圖:
具體流程如下所示:
l 打開bin
文件,檢查文件打開是否正確
l 設(shè)置Flash
下載起始地址(App
程序起始地址)
l 讀取bin
文件,檢查讀取是否正確
l 獲取棧指針SP
和復(fù)位向量PC
l 進(jìn)入循環(huán)(這里是第5
步),條件為如果讀取bin
文件字節(jié)數(shù)不為零
l 將讀取到的bin
寫入Flash
,并判寫入狀態(tài)
l 調(diào)整Flash
地址,根據(jù)寫入字節(jié)調(diào)整
l 繼續(xù)讀取bin
文件,檢查讀取是否正確,回到5
繼續(xù)循環(huán)
來到這里已經(jīng)是退出循環(huán)了,也就是說我們已經(jīng)將bin
寫入Flash
完成了,準(zhǔn)備跳轉(zhuǎn)到新程序運行
5.3 跳轉(zhuǎn)到新程序流程其實上面已經(jīng)講過了,這里繼續(xù)啰嗦,截圖:
l 重定位中斷向量
l 設(shè)置棧指針
l 跳轉(zhuǎn)到復(fù)位向量(開始運行App
程序)
說明一下,在這里重定位中斷向量其實是多余的,App
程序執(zhí)行初始化后,又回到STM32
初始狀態(tài),所以在App
程序中需要執(zhí)行重定位中斷向量表操作,具體同以上操作相同。
啰嗦了又一遍,BootLoader
完全結(jié)束,感謝大家都支持啦~
附錄A 主函數(shù)
#include "main.h"
int main(void)
{
SystemInit(); //
配置系統(tǒng)時鐘為72M
LED_GPIO_Config(); //
初始化LED
端口
Debug_TraceIOEnable(); //
使能調(diào)試printf
的IO
口
Flash_Init(); //
初始化Flash
FAT_Init(); //
初始化文件系統(tǒng)
BootLoader_FromSDCard(); //Bootloader
從SD
卡更新固件
while(1)
{
LED_StatShow( FuncErr); //LED
顯示Bootloader
狀態(tài)
}
}
附錄B 更新說明
l 版本:V1.01
l 時間:2014-4-8
l 作者:coolweedman
l 更新:說明Bootloader
程序和App
程序需要配合才能實現(xiàn)BootLoader
,App
程序需要重定位中斷向量表
l 特別感謝:網(wǎng)友cary_yingj
的研究和分享
參考文獻(xiàn)
[1]
《CM3
權(quán)威指南CnR2
》Joseph Yiu
著 宋巖 譯
[2]
ST
官方庫3.0.0
[3]
《C
和指針》KennethA. Reek
著 徐波譯
[4]
FATFS
文件系統(tǒng)
[5]
其他互聯(lián)網(wǎng)資料