本文主要講述BootLoader程序升級原理及一些代碼的解析,力圖用通俗易懂的語言描述清楚BootLoader升級的主要關(guān)鍵點。
BootLoader 升級原理概述
首次接觸這一塊時,有一個概念叫IAP(在應(yīng)用編程),通俗一點講便是通過一段已有的程序(我們稱之為BootLoader程序)去升級另外的一段程序(用戶程序)。升級的方式多種多樣,可以通過串口、USB、SPI等等多種接口去升級。實際上,我們是把我們需要升級的芯片里面分為兩個區(qū)域,暫且稱之為A區(qū)域和B區(qū)域。
A區(qū)域主要存放BootLoader程序,B區(qū)域主要存放用戶程序,也就是我們希望升級或修改的程序。
一般情況下,為了升級流程的方便,我們會把A區(qū)域布置在芯片flash(有人喜歡稱ROM,就是存放代碼的區(qū)域)的起始位置,也就是0x0開始的位置,至于A區(qū)域在哪里結(jié)束,這需要看你的BootLoader程序有多大了,它能占用多少的代碼量了。比如你的BootLoader程序編譯完后有2.5KB左右的大小,那么你可以計算一下:
2.5K = 2.5 X 1024 = 2560B = A00(H)
也就是說,你的這段代碼如果從零地址開始存放的話,他會在0xA00的位置結(jié)束,0xA00之后的區(qū)域你便可以用來存放需要升級的用戶代碼了。但有時我們并不會緊接著0xA01的位置開始放置用戶代碼,而是會留出一定的空間,比如從0xB00處開始存放代碼,這主要是因為BootLoader程序在flash中存放時不一定會緊挨著存放,有時代碼段之間會有空閑區(qū)域,這一點可以參考之前的文章 IAR編譯器如何節(jié)省代碼占用的flash空間?然后,我們通過在BootLoader程序中設(shè)置相關(guān)參數(shù)(應(yīng)用程序起始位置等),使應(yīng)用程序升級時按照我們設(shè)置的位置存放在B區(qū)域,從而完成升級。這一點我會在后面的代碼詳解中介紹。
我們可以通過下面的圖解來理解bootloader程序升級時的區(qū)域占用情況。
需要了解的關(guān)鍵點
進(jìn)行BootLoader程序編寫之前,我們需要了解并熟悉以下幾個關(guān)鍵點:
文件傳輸協(xié)議。因為升級時需要上位機軟件配合下位機的BootLoader程序進(jìn)行應(yīng)用程序代碼的傳輸,因此文件傳輸協(xié)議至關(guān)重要,筆者推薦使用Xmodem 1K協(xié)議。 這個協(xié)議的好處便是上位機可以自動打包數(shù)據(jù),每一包數(shù)據(jù)含有1k字節(jié)的代碼,傳輸效率很高,傳輸時間很短。
2. 芯片空間map。 做BootLoader升級相關(guān)的項目,肯定離不開對芯片空間的了解,需要對自己所用芯片的RAM、ROM以及向量表(如果有的話)等占用情況有比較深入的了解。
3. 跳轉(zhuǎn)函數(shù)。這個是程序從BootLoader程序跳轉(zhuǎn)到應(yīng)用程序運行的關(guān)鍵。筆者在做項目時曾經(jīng)在這一塊浪費了不少的時間。文末筆者會提供實用的跳轉(zhuǎn)函數(shù)。
升級代碼解析
其實前面說的再多,脫離了代碼都是紙上談兵。下面通過一個實際的BootLoader升級例子,結(jié)合筆者自己編寫的BootLoader代碼,對這一過程進(jìn)行解析。
代碼主要包括
main.c
BootLoader.c
xmodem-1k.c
跳轉(zhuǎn)函數(shù)
其中,
main.c主要是升級執(zhí)行初始化及升級完成后初始化升級環(huán)境及跳轉(zhuǎn)代碼實現(xiàn)的部分。
BootLoader.c部分主要是升級流程的代碼控制。
xmodem-1k.c主要是文件傳輸協(xié)議的代碼實現(xiàn)。
至于其他部分的代碼,比如串口相關(guān)以及時鐘相關(guān)的代碼,每種芯片的編程方式都不盡相同,因此筆者不詳細(xì)介紹這部分,該部分代碼大家可以從需要升級的應(yīng)用程序中直接移植即可。
main.c
int main (void)
{
SystemCoreClockUpdate(); // 時鐘初始化
WatchDog_Initial(); // 看門狗初始化
vBootLoader(&vScene_Init,&vScene_Renew); // BootLoader主程序
}
在vBootloader()這個函數(shù)中用到了兩個函數(shù)指針,分別指向初始化函數(shù)vScene_Init()和環(huán)境重置函數(shù)vScene_Renew()。初始化函數(shù)很好理解,在運行程序之前,先對芯片時鐘、管腳等初始化,或者有些參數(shù)需要初始化,這個根據(jù)自己的代碼情況進(jìn)行選擇。
那么環(huán)境重置函數(shù)什么意思呢?這主要是為了和需要升級的應(yīng)用程序的運行想配合,因為我們的bootloader程序的相關(guān)配置有時候并不一定會和應(yīng)用程序的配置完全一致,如果運行完BootLoader之后,沒有把BootLoader程序的相關(guān)配置關(guān)閉掉或者恢復(fù)到默認(rèn)值,運行到應(yīng)用程序之后,還可能會執(zhí)行BootLoader程序的配置,這樣會出現(xiàn)問題。舉個栗子,在BootLoader程序中用中斷喂狗,跳轉(zhuǎn)到應(yīng)用程序之前,沒有關(guān)閉喂狗中斷,如果在應(yīng)用程序中沒有配置相關(guān)喂狗中斷的程序,那么應(yīng)用程序仍然會按照bootloader的配置執(zhí)行中斷喂狗,這樣會導(dǎo)致應(yīng)用程序中的喂狗失效,因為中斷喂狗是很準(zhǔn)時的,往往起不到喂狗的效果,有時會影響程序的復(fù)位操作。因此,環(huán)境重置函數(shù)說白了,就是把bootloader用到的配置關(guān)掉。筆者建議把用到的所有的東西全部關(guān)閉(包括但不限于串口、時鐘、看門狗、IO等),因為在應(yīng)用程序中會根據(jù)自己的應(yīng)用程序配置相關(guān)的代碼。
BootLoader.c
/**********************************************************
* BootLoader流程控制函數(shù)
** 參 數(shù): pfunSenceInitCallBack 初始化芯片指針函數(shù)
** pfunSenceRenewCallBack 重新初始化代碼環(huán)境指針函數(shù)
** 返回值: 無
**********************************************************/
void vBootLoader(void(* pfunSenceInitCallBack)(void), void (* pfunSenceRenewCallBack)(void))
{
uint8_t ucMessage = 0;
unsigned int sp;
unsigned int pc;
uint16_t bootflag_read;
sp = APP_START_Flash;
pc = sp + 4;
pfunSenceInitCallBack(); //初始化函數(shù)指針,具體函數(shù)怎么寫這里不再贅述
while(1)
{
wdt_feed();
do{
ucMessage = u8UpdateMode(); // 此函數(shù)為升級主函數(shù)
if (UPDATE_OK == ucMessage) /* 升級成功 */
{
memcpy(erase_pg_buf, bootflag_OK, sizeof(bootflag_OK));
update_bootflag();
break;
}
else if (UPDATE_NO == ucMessage) /* 沒有升級 */
{
break;
}
else /* 升級錯誤 */
{
memcpy(erase_pg_buf, bootflag_ERROR, sizeof(bootflag_ERROR));
update_bootflag();
break;
}
} while (1);
bootflag_read = *( volatile uint16_t *)(BOOT_FLAG_ADDR); /* 讀取存放在bootflag地址的值 */
if (u8UserCodeEffect() == USERCODE_OK) // 代碼判斷
{
if (bootflag_read == 0xAA55)
{
pfunSenceRenewCallBack(); // 環(huán)境重置函數(shù)
vControlSwitch(sp,pc); // 跳轉(zhuǎn)函數(shù)
}
}
}
}
上面是bootloader程序擦寫完flash之后判斷是否升級成功以及執(zhí)行跳轉(zhuǎn)函數(shù)的代碼。流程主要是,升級主函數(shù)u8UpdateMode()(下面是詳細(xì)代碼)中進(jìn)行數(shù)據(jù)接收校驗以及flash擦寫工作,如果擦寫成功,該函數(shù)返回0(UPDATE-OK),擦寫失敗,該函數(shù)返回1(UPDATE-ERROR),沒有擦寫操作,該函數(shù)返回2(UPDATE-NO)。在這個函數(shù)中根據(jù)相關(guān)返回標(biāo)志進(jìn)行處理。處理完去讀取flash中存放BootLoader標(biāo)志的地方的數(shù)據(jù),如果使我們希望的數(shù)據(jù),我們就執(zhí)行跳轉(zhuǎn)函數(shù),讓程序從BootLoader跳轉(zhuǎn)到應(yīng)用程序中,如果標(biāo)志不正確,說明升級過程出了問題,我們就不跳轉(zhuǎn),一直運行在BootLoader程序中。當(dāng)然,在跳轉(zhuǎn)之前需要執(zhí)行我們之前提到的環(huán)境重置函數(shù)pfunSenceRenewCallBack()。
// 升級主函數(shù)
static uint8_t u8UpdateMode(void)
{
uint8_t ret;
if (iap_prepare_sector(APP_START_SECTOR, APP_END_SECTOR) == CMD_SUCCESS) // 準(zhǔn)備扇區(qū)
{
if (iap_erase_sector(APP_START_SECTOR, APP_END_SECTOR) == CMD_SUCCESS) // 擦除扇區(qū)
{
ret = u8Xmodem1kClient(ProgramFlash, (uint16_t)BOOT_DELAYTIME_C, (uint16_t)BOOT_WAITTIME_UPDATE);// 編程指針+X-Modem協(xié)議識別
if (0 == ret)
{
return UPDATE_OK;// 返回標(biāo)志
}
if (2 == ret)
{
return UPDATE_NO;
}
}
}
return UPDATE_ERROR;
}
主函數(shù)代碼不難理解,進(jìn)來之后先準(zhǔn)備相應(yīng)的扇區(qū),然后擦除(FLash內(nèi)部值置全FF),然后啟動X-Modem協(xié)議接收數(shù)據(jù),數(shù)據(jù)接收完成啟動寫flash函數(shù)ProgramFlash()進(jìn)行代碼燒寫,這一部分在下一節(jié)X-Modem-1K.c中講。不同的片子的燒寫流程不同,這個得看芯片手冊,有的需要準(zhǔn)備扇區(qū),有的不需要,但是大多數(shù)流程都保留了先擦除后燒寫的內(nèi)容。
注意:擦除和燒寫之前需要看技術(shù)手冊搞明白芯片是支持區(qū)塊擦除還是支持頁擦除。
X-modem-1k.c
關(guān)于X-Modem協(xié)議是什么,大家可以自行去百度,這里也不再贅述。
先看代碼,比較長:
/**********************************************************
** Xmodem1k協(xié)議傳輸程序
** 參 數(shù): pfunPktHandle,: Xmodem1k協(xié)議傳輸所需函數(shù)結(jié)構(gòu)體指針
** u16ShortDly: 輪詢發(fā)送C字符的時間間隔
** u8LongDly: 等待傳輸開始超時時限
** 返回值: 傳輸結(jié)果: 0--成功,1--升級失敗(錯誤或取消升級),2--沒有升級
**********************************************************/
uint8_t u8Xmodem1kClient(pFunPKTHAND pfunPktHandle, uint16_t u16ShortDly, uint16_t u8LongDly)
{
uint32_t u32ByteCnt = 0; /* 位計數(shù)器 計數(shù)一包的第幾個字節(jié)數(shù)據(jù) */
uint8_t u8TimeoutCnt = 0; /* 超時次數(shù) */
uint8_t u8DataerrCnt = 0; /* 數(shù)據(jù)錯誤次數(shù) */
uint8_t u8PktIndex = 1; /* 包序號期望值 */
uint8_t u8STATE = STAT_IDLE_C; /* 狀態(tài)變量 */
uint8_t u8Data; /* 存放接收數(shù)據(jù)及發(fā)送命令 */
volatile uint16_t u16PktLen; /* 包中有效數(shù)據(jù)的長度 */
uint8_t u8Message;
sysTimerClr(1);
while (1)
{
wdt_feed();
switch (u8STATE)
{
case STAT_IDLE_C: /* 輪詢發(fā)C狀態(tài) */
if (sysTimerGet(1) >= u8LongDly )
{
u8STATE = STAT_TIMEOUT_C; /* 等待開始超時,跳到結(jié)束狀態(tài) */
}
上一篇:ARM USB 通信
下一篇:Cortex-M3 的SVC、PendSV異常,與操作系統(tǒng)(ucos實時系統(tǒng))
推薦閱讀
史海拾趣
設(shè)計資源 培訓(xùn) 開發(fā)板 精華推薦
- Microchip 升級數(shù)字信號控制器(DSC)產(chǎn)品線 推出PWM 分辨率和 ADC 速度業(yè)界領(lǐng)先的新器件
- 意法半導(dǎo)體STM32MP23x:突破成本限制的工業(yè)AI應(yīng)用核心
- 意法半導(dǎo)體推出用于匹配遠(yuǎn)距離無線微控制器STM32WL33的集成的匹配濾波芯片
- ESP32開發(fā)板連接TFT顯示屏ST7789跳坑記
- 如何讓ESP32支持analogWrite函數(shù)
- LGVL配合FreeType為可變字體設(shè)置字重-ESP32篇
- 使用樹莓派進(jìn)行 ESP32 Jtag 調(diào)試
- ESP32怎么在SPIFFS里面存儲html,css,js文件,以及網(wǎng)頁和arduino的通訊
- ESP32 freeRTOS使用測試
- 上汽大眾:汽車網(wǎng)絡(luò)安全漏洞防護
- 恩智浦推出全新電池控制IC系列 助力新能源解決方案發(fā)展
- 全球首條GWh級新型固態(tài)電池生產(chǎn)線樣件下線
- 總投資455億元!三大動力電池項目齊刷進(jìn)度條
- 現(xiàn)代汽車韓國建氫燃料電池廠,2028年投產(chǎn)
- 6月融資一覽:智能汽車芯片、第三代半導(dǎo)體、機器人成資本焦點
- 艙駕一體“點燃”新戰(zhàn)事
- 汽車智能化2.0引爆「萬億蛋糕」,誰在改寫游戲規(guī)則?
- 2025研華智能系統(tǒng)產(chǎn)業(yè)伙伴峰會成功舉辦
- 意法半導(dǎo)體公布2025年第二季度財報和電話會議時間安排
- 《射頻技術(shù) For Dummies系列》書籍讀后感征集
- Intel白皮書下載有獎
- Discover mmWave 走進(jìn) TI 毫米波雷達(dá)世界 快速獲得設(shè)計技能
- TIDesigns 有獎?wù){(diào)查輕松贏好禮!
- 網(wǎng)友票選TI課程熱榜出爐 學(xué)習(xí)推薦有禮!
- 有獎直播|TI 工業(yè)多協(xié)議通信應(yīng)用中的優(yōu)化解決方案
- 有獎直播|ST資料中心與通訊網(wǎng)路電源管理解決方案
- 【新年樂分享】EEWORLD優(yōu)秀主題/回復(fù)第18期來啦~~
- 搶鮮體驗| 兩款GD RISC-V開發(fā)板
- 什么是真正的物聯(lián)網(wǎng)
- 貿(mào)澤推出Analog Devices LTM2810 μModule隔離器
- Chirp:以Arm 為基礎(chǔ)的技術(shù)將有助于發(fā)展
- AI+繪本識別,國科微賦能兒童早教市場
- 高精度導(dǎo)航定位、全局環(huán)境語義,雙目視覺導(dǎo)航方案更適合掃地機器人
- 暢享360度自由旋轉(zhuǎn),ViewSonic優(yōu)派推出新款M1便攜投影機
- 意法半導(dǎo)體第 21 年發(fā)布可持續(xù)發(fā)展報告
- 外媒曝歐盟擬斥資4.4億英鎊研發(fā)“殺手機器人”
- 多協(xié)議無線解決方案 助力中國企業(yè)加速智能照明應(yīng)用
- 點亮智慧城市 Innodisk創(chuàng)新物聯(lián)網(wǎng)解決方案將亮相臺北電腦展