寫在前面:三個(gè)周之前,我突然想寫一個(gè)遠(yuǎn)程升級(jí)的程序。那個(gè)時(shí)候我只是大概知道IAP的意思是在應(yīng)用編程,但怎么編,我還一無(wú)所知。我給自己定下一個(gè)個(gè)階段目標(biāo),從最基礎(chǔ)的代碼一點(diǎn)點(diǎn)寫起,解決一個(gè)又一個(gè)的問(wèn)題。三個(gè)周之后,我用自己設(shè)計(jì)的方法實(shí)驗(yàn)了50多次,無(wú)一例升級(jí)失敗。
三個(gè)周來(lái),遇到了很多的不解、困惑,甚至是想放棄,但我現(xiàn)在想說(shuō)的是:很多未知的困難會(huì)擋在我們面前,我們會(huì)感覺毫無(wú)頭緒甚至覺得毫無(wú)出路忍不住要放棄,但多堅(jiān)持一下,那些困難不但能煙消云散還能帶給我們進(jìn)步。
本設(shè)計(jì)是基于LPC2114和Keil MDK(V4.10),但所有支持IAP的處理器都可借鑒本方案,重要的是思想,而不是用什么。
0 引言
在應(yīng)用編程(IAP)技術(shù)為系統(tǒng)在線升級(jí)和遠(yuǎn)程升級(jí)提供了良好的解決方案,也為數(shù)據(jù)存儲(chǔ)和現(xiàn)場(chǎng)固件的升級(jí)都帶來(lái)了極大的靈活性。通??衫眯酒拇锌诮拥接?jì)算機(jī)的RS232口、通過(guò)現(xiàn)有的Internet或、無(wú)線網(wǎng)絡(luò)或者其他通信方式很方便地實(shí)現(xiàn)在線以及遠(yuǎn)程升級(jí)和維護(hù)。 本文以NXP的LPC2114 ARM微處理器為平臺(tái),以Keil MDK為開發(fā)工具,闡述IAP的原理、Flash的劃分、分散加載機(jī)制、中斷重映射以及在線升級(jí)的實(shí)現(xiàn)方案及其優(yōu)化。本方案使用多種校驗(yàn)技術(shù),最大限度的保障傳輸數(shù)據(jù)的正確性;使用bootloader機(jī)制,即使因意外事件(斷電,編程Flash失敗等)造成升級(jí)失敗后,程序也能返回到升級(jí)前的狀態(tài)。
1 LPC2114的Flash規(guī)劃
1.1 扇區(qū)描述
LPC2114共有128KB片內(nèi)Flash,共分為16個(gè)扇區(qū),分別為0扇區(qū)~15扇區(qū),每個(gè)扇區(qū)為8KB存儲(chǔ)空間。其中第15扇區(qū)出廠時(shí)被固化為Boot Block區(qū),控制復(fù)位后的初始化操作,并提供實(shí)現(xiàn)Flash 編程的方法。所以用戶可用的Flash空間只有120KB。IAP程序固化于Boot Block中,IAP操作是以扇區(qū)為單位,并占用片內(nèi)RAM的高32字節(jié)。下表列出LPC2114器件所包含的扇區(qū)數(shù)和存儲(chǔ)器地址. 表1.1 LPC2114 Flash 器件中的扇區(qū)
1.2 Flash的扇區(qū)劃分 本設(shè)計(jì)將Flash劃分為四個(gè)區(qū),扇區(qū)0存放跳轉(zhuǎn)程序和升級(jí)引導(dǎo)程序(Bootloader)。分站上電后執(zhí)行跳轉(zhuǎn)程序,跳轉(zhuǎn)到用戶程序處。用戶程序運(yùn)行過(guò)程中,如果接收到升級(jí)指令,會(huì)從用戶程序跳轉(zhuǎn)到引導(dǎo)程序區(qū)(Bootloader),接收新程序數(shù)據(jù)包,完成Flash編程并跳轉(zhuǎn)到新程序區(qū)執(zhí)行程序。扇區(qū)1~扇區(qū)7為程序存儲(chǔ)低區(qū);扇區(qū)8~扇區(qū)13為程序存儲(chǔ)高區(qū);扇區(qū)14存放當(dāng)前程序運(yùn)行區(qū)域標(biāo)志,如果當(dāng)前程序運(yùn)行在高區(qū),該標(biāo)志區(qū)的最低四個(gè)字節(jié)為0x00010000,如果當(dāng)前程序運(yùn)行在低區(qū),該標(biāo)志區(qū)的最低四個(gè)字節(jié)為0x00008000。 2 IAP的原理與軟件設(shè)計(jì)
2.1 IAP的原理
IAP函數(shù)是固化在微處理器內(nèi)部flash上的一些函數(shù)代碼,最終的用戶程序可以直接通過(guò)調(diào)用這些函數(shù)來(lái)對(duì)內(nèi)部flash進(jìn)行擦除和編程操作。LPC2114微處理器的內(nèi)部flash有一個(gè)塊稱為Boot Block,位于flash的頂端,可供調(diào)用的IAP函數(shù)就位于該塊中。上電后Boot Block被映射到內(nèi)部地址空間的頂端,同樣IAP函數(shù)人口地址也被映射到地址0x7ffffff0處。用戶可通過(guò)跳轉(zhuǎn)到該地址來(lái)調(diào)用相應(yīng)的lAP函數(shù)。 2.2 IAP 命令 對(duì)于在應(yīng)用編程來(lái)說(shuō),應(yīng)當(dāng)通過(guò)寄存器r0 中的字指針指向存儲(chǔ)器(RAM)包含的命令代碼和參數(shù)來(lái)調(diào)用IAP 程序。IAP 命令的結(jié)果返回到寄存器r1 所指向的返回表。用戶可通過(guò)傳遞寄存器r0 和r1 中的相同指針重用命令表來(lái)得到結(jié)果。參數(shù)表應(yīng)當(dāng)大到足夠保存所有的結(jié)果以防結(jié)果的數(shù)目大于參數(shù)的數(shù)目。參數(shù)傳遞見圖2-1。參數(shù)和結(jié)果的數(shù)目根據(jù)IAP命令而有所不同。參數(shù)的最大數(shù)目為5,由“將RAM 內(nèi)容復(fù)制到Flash”命令傳遞。結(jié)果的最大數(shù)目為2,由“扇區(qū)查空”命令返回。命令處理程序在接收到一個(gè)未定義的命令時(shí)發(fā)送狀態(tài)代碼INVALID_COMMAND。IAP 程序是thumb 代碼,位于地址0x7FFFFFF0。 圖2-1 IAP的參數(shù)傳遞
表2-1描述了IAP的命令。
表2-1 IAP 命令匯總
IAP命令
命令代碼 描述 準(zhǔn)備編程扇區(qū) 50 該命令必須在執(zhí)行“將 RAM 內(nèi)容復(fù)制到Flash”或“擦除扇區(qū)”命令之前執(zhí)行。這兩個(gè)命令的成功執(zhí)行會(huì)導(dǎo)致相關(guān)的扇區(qū)再次被保護(hù)。該命令不能用于boot 扇區(qū)。要準(zhǔn)備單個(gè)扇區(qū),可將起始和結(jié)束扇區(qū)號(hào)設(shè)置為相同值。 將RAM內(nèi)容復(fù)制到Flash 51 該命令用于編程 Flash 存儲(chǔ)器。受影響的扇區(qū)應(yīng)當(dāng)先通過(guò)調(diào)用“準(zhǔn)備寫操作的扇區(qū)”命令準(zhǔn)備。當(dāng)成功執(zhí)行復(fù)制命令后,扇區(qū)將自動(dòng)受到保護(hù)。該命令不能寫boot 扇區(qū)。 擦除扇區(qū) 52 該命令用于擦除片內(nèi) Flash 存儲(chǔ)器的一個(gè)或多個(gè)扇區(qū)。boot 扇區(qū)不能由該命令擦除。要擦除單個(gè)扇區(qū)可將起始和結(jié)束扇區(qū)號(hào)設(shè)定為相同值。 扇區(qū)查空 53 該命令用于對(duì)片內(nèi) Flash 存儲(chǔ)器的一個(gè)或多個(gè)扇區(qū)進(jìn)行查空。要查空單個(gè)扇區(qū)可將起始和結(jié)束扇區(qū)號(hào)設(shè)定為相同值。 讀器件ID 54 該命令用于讀取器件的 ID 號(hào)。 讀Boot版本 55 該命令用于讀取 boot 代碼版本號(hào)。 IAP比較 56 該命令用來(lái)比較兩個(gè)地址單元的存儲(chǔ)器內(nèi)容。當(dāng)源或目標(biāo)地址包含從地址0 開始的前64字節(jié)中的任意一個(gè)時(shí),比較的結(jié)果不一定正確。前64 字節(jié)重新映射到Flash boot 扇區(qū)。 2.3 IAP 編程函數(shù)接口
IAP 功能可用下面的C 代碼來(lái)調(diào)用。 定義 IAP 程序的入口地址。由于IAP 地址的第0 位是1,因此,當(dāng)程序計(jì)數(shù)器轉(zhuǎn)移到該地址時(shí)會(huì)引起Thumb 指令集的變化。
#define IAP_LOCATION 0x7ffffff1
定義數(shù)據(jù)結(jié)構(gòu)或指針,將IAP 命令表和結(jié)果表傳遞給IAP 函數(shù)
unsigned long command[5];
unsigned long result[2];
定義函數(shù)類型指針,函數(shù)包含2 個(gè)參數(shù),無(wú)返回值。注意:IAP 將函數(shù)結(jié)果和R1 中的表格基址一同返回。
typedef void (*IAP) (unsigned int [ ] , unsigned int [ ]);
IAP iap_entry;
設(shè)置函數(shù)指針
iap_entry=(IAP) IAP_LOCATION;
使用下面的語(yǔ)句來(lái)調(diào)用IAP。
iap_entry (command , result);
Flash 存儲(chǔ)器在寫或擦除操作過(guò)程中不可被訪問(wèn)。執(zhí)行Flash 寫/擦除操作的IAP 命令
使用片內(nèi)RAM 頂端的32 個(gè)字節(jié)空間。如果應(yīng)用程序中允許IAP 編程,那么用戶程序不應(yīng)
使用該空間。
3 LPC2114升級(jí)實(shí)現(xiàn)過(guò)程
由于在升級(jí)程序軟件設(shè)計(jì)中,分散加載機(jī)制、中斷向量的重映射、軟中斷等的實(shí)現(xiàn)還與所使用的編譯器緊密相關(guān),因此,本文結(jié)合Keil MDK(V4.10)編譯工具,來(lái)詳細(xì)闡述升級(jí)程序的實(shí)現(xiàn)過(guò)程。 3.1 總體思路
分站上電后,首先運(yùn)行位于Flash 0x000~0x3FF中的跳轉(zhuǎn)程序。跳轉(zhuǎn)程序會(huì)讀取位于14扇區(qū)的當(dāng)前程序運(yùn)行標(biāo)志,如果該扇區(qū)的最低四個(gè)字節(jié)為0x00010000,表示當(dāng)前程序運(yùn)行在高區(qū),跳轉(zhuǎn)程序會(huì)跳轉(zhuǎn)到Flash的0x00010000處執(zhí)行用戶程序;如果該標(biāo)志區(qū)的最低四個(gè)字節(jié)為0x00008000,表示當(dāng)前程序運(yùn)行在低區(qū),跳轉(zhuǎn)程序會(huì)跳轉(zhuǎn)到Flash的0x00002000處執(zhí)行用戶程序。用戶程序正常執(zhí)行后,會(huì)按照設(shè)計(jì)進(jìn)行正常的程序采集、數(shù)據(jù)處理傳送。當(dāng)接收到升級(jí)命令后,用戶程序會(huì)跳轉(zhuǎn)到Flash的0x00000400處的Bootloader處進(jìn)行升級(jí)的一些操作。當(dāng)升級(jí)成功后,Bootloader程序更新當(dāng)前程序運(yùn)行區(qū)標(biāo)志,程序跳轉(zhuǎn)到新程序處運(yùn)行,如果升級(jí)不成功,返回升級(jí)前的程序。 流程圖如下所示:
3.2 跳轉(zhuǎn)程序的設(shè)計(jì) 跳轉(zhuǎn)程序是分站上電后最先運(yùn)行的程序,根據(jù)當(dāng)前程序運(yùn)行區(qū)標(biāo)志,跳轉(zhuǎn)到相應(yīng)的用戶程序區(qū)執(zhí)行。本段程序占用Flash的最低1K字節(jié)空間,與Bootloader同在第0扇區(qū)。 跳轉(zhuǎn)程序的啟動(dòng)代碼僅初始化堆棧,不使用PLL和存儲(chǔ)加速功能。代碼1描述了跳轉(zhuǎn)程序的主要啟動(dòng)代碼。
; Enter User Mode and set its Stack Pointer
MSR CPSR_c, #Mode_USR
MOV SP, R0
SUB SL, SP, #USR_Stack_Size
; Enter the C code
IMPORT __main
LDR R0, =__main
BX R0
代碼1:跳轉(zhuǎn)程序啟動(dòng)代碼
當(dāng)跳轉(zhuǎn)程序確定要跳轉(zhuǎn)到高區(qū)用戶程序或者低區(qū)用戶程序后,使用函數(shù)指針跳轉(zhuǎn)到0x00010000處(高區(qū)用戶函數(shù)入口地址)或0x00002000處(低區(qū)用戶函數(shù)入口地址)。 定義函數(shù)指針:
void (*UserProgram)() ;
指定入口地址:
UserProgram = (void (*)()) (0x00010000);
UserProgram = (void (*)()) (0x00002000);
實(shí)現(xiàn)跳轉(zhuǎn):
(*UserProgram)() ;
要將用戶代碼精確定位到Flash的0x00010000處(高區(qū)用戶函數(shù)入口地址)或0x00002000處(低區(qū)用戶函數(shù)入口地址),需要使用編譯器的分散加載機(jī)制,將在Bootloader中詳細(xì)描述實(shí)現(xiàn)過(guò)程。
另外,跳轉(zhuǎn)程序還在燒錄代碼的同時(shí)初始化當(dāng)前程序運(yùn)行區(qū)標(biāo)志,即對(duì)Flash的0x0001C000地址處寫入0x00008000,表示當(dāng)前用戶程序在低區(qū)。主要使用了編譯器的__at關(guān)鍵字:精確定位變量。需要注意的是,使用該關(guān)鍵字必須包含頭文件absacc.h。
const uint32 x __at(0x0001C000)=0x00008000; //初始化用戶程序標(biāo)志區(qū),默認(rèn)運(yùn)行低區(qū)
3.3 升級(jí)程序Bootloader的設(shè)計(jì)
升級(jí)程序的好壞,在很大程度上取決于Bootloader設(shè)計(jì)的好壞。 一個(gè)優(yōu)秀的IAP升級(jí)Bootloader,必須做好升級(jí)中出現(xiàn)故障等異常的處理。保證系統(tǒng)不會(huì)崩潰,即使升級(jí)失敗,也能返回升級(jí)前的程序。
Ø 有升級(jí)指令,進(jìn)行初始化工作(串口、定時(shí)器、看門狗)
Ø 接收升級(jí)數(shù)據(jù)包,檢測(cè)幀頭、長(zhǎng)度、幀號(hào)、數(shù)據(jù)區(qū)校驗(yàn),最大程度的保證升級(jí)數(shù)據(jù)的完整性、正確性。
Ø 實(shí)時(shí)檢測(cè)接收狀態(tài),10 S內(nèi)沒(méi)有接收到數(shù)據(jù)或接收到的數(shù)據(jù)包都是錯(cuò)的,則退出升級(jí),返回原程序。
Ø 接收的數(shù)據(jù)按照512字節(jié)一組寫入Flash,寫入后再讀出與原數(shù)據(jù)進(jìn)行對(duì)比校驗(yàn),校驗(yàn)成功后,本次編程Flash成功。允許連續(xù)3次編程Flash,三次都不成功,退出升級(jí)程序,執(zhí)行原程序。
Ø 升級(jí)成功后,更新當(dāng)前程序運(yùn)行區(qū)標(biāo)志,跳轉(zhuǎn)到新程序,同時(shí)原程序保存。
本設(shè)計(jì)的Bootload位于Flash的0x400開始的扇區(qū)0存儲(chǔ)區(qū)內(nèi),使用分散加載機(jī)制,將程序的入口地址定位到0x00000400處。當(dāng)用戶程序接收到升級(jí)指令后,就會(huì)使用函數(shù)指針跳轉(zhuǎn)到這個(gè)入口處。
3.3.1 使用IAP
圖3-1 描述了使用IAP編程Flash所必須的步驟。
3.3.1.1 定義系統(tǒng)參數(shù)
在使用IAP前,需要定義一些系統(tǒng)參數(shù),比如系統(tǒng)時(shí)鐘、IAP中斷入口、輸入輸出緩存。 #define IAP_CLK 11059200UL
#define IAP_LOCATION 0x7FFFFFF1
typedef void(*IAP)(uint32 [],uint32 []); //定義函數(shù)類型指針
IAP iap_entry=(IAP)IAP_LOCATION; //設(shè)置函數(shù)指針
unsigned long command[5] = {0,0,0,0,0};
unsigned long result[2]= {0,0};
代碼3-1:定義系統(tǒng)參數(shù)
3.3.1.2 選擇扇區(qū) 在任何擦除和編程Flash之前,必須選中扇區(qū),可以選中一個(gè)或多個(gè)。 /******************************************************************
* 名稱:SelSector()
* 功能:IAP操作扇區(qū)選擇,命令代碼50。
* 入口參數(shù):sec1 起始扇區(qū)
* sec2 終止扇區(qū)
* 出口參數(shù):IAP返回值(paramout緩沖區(qū)) CMD_SUCCESS,BUSY,INVALID_SECTOR
*********************************************************************/
void SelSector(uint8 sec1, uint8 sec2)
{
paramin[0] = IAP_SELSECTOR; // 設(shè)置命令字
paramin[1] = sec1; // 設(shè)置參數(shù)
paramin[2] = sec2;
iap_entry(paramin, paramout); // 調(diào)用IAP服務(wù)程序
}
代碼3-2 選擇扇區(qū)
3.3.1.3 擦除扇區(qū) 在編程Flash前必須執(zhí)行擦除操作,如果某個(gè)扇區(qū)已經(jīng)擦除,就不需要再次擦除。可以一次擦除一個(gè)或多個(gè)扇區(qū)。 /******************************************************************
* 名稱:EraseSector()
* 功能:扇區(qū)擦除,命令代碼52。
* 入口參數(shù):sec1 起始扇區(qū)
* sec2 終止扇區(qū)
* 出口參數(shù):IAP返回值(paramout緩沖區(qū)) CMD_SUCCESS,BUSY,INVALID_SECTOR ************************************************************************/
void EraseSector(uint8 sec1, uint8 sec2)
{ paramin[0] = IAP_ERASESECTOR; // 設(shè)置命令字
paramin[1] = sec1; // 設(shè)置參數(shù)
paramin[2] = sec2;
paramin[3] = Fosc/1000; // 當(dāng)不使用PLL功能時(shí),F(xiàn)cclk=Fosc
iap_entry(paramin, paramout); // 調(diào)用IAP服務(wù)程序
代碼3-3 擦除扇區(qū)
3.3.1.4 編程扇區(qū) 通過(guò)這個(gè)過(guò)程,數(shù)據(jù)可以從RAM中編程到片內(nèi)Flash中。 注:
1. 數(shù)據(jù)只能從片內(nèi)SRAM編程到片內(nèi)Flash。 2. 片內(nèi)Flash的地址必須512字節(jié)對(duì)齊。 3. 片內(nèi)RAM應(yīng)位于局部總線,即USB或以太網(wǎng)的SRAM不可以使用。 4. 每一次編程字節(jié)應(yīng)該是512、1024、4096、8192中的一個(gè)。 /*********************************************************************
* 名稱:RamToFlash()
* 功能:復(fù)制RAM的數(shù)據(jù)到FLASH,命令代碼51。
* 入口參數(shù):dst 目標(biāo)地址,即FLASH起始地址。以512字節(jié)為分界
* src 源地址,即RAM地址。地址必須字對(duì)齊
* no 復(fù)制字節(jié)個(gè)數(shù),為512/1024/4096/8192
* 出口參數(shù):IAP返回值(paramout緩沖區(qū)) CMD_SUCCESS,SRC_ADDR_ERROR,DST_ADDR_ERROR,
SRC_ADDR_NOT_MAPPED,DST_ADDR_NOT_MAPPED,COUNT_ERROR,BUSY,未選擇扇區(qū)
********************************************************************/
void RamToFlash(uint32 dst, uint32 src, uint32 no)
{ paramin[0] = IAP_RAMTOFLASH; // 設(shè)置命令字
paramin[1] = dst; // 設(shè)置參數(shù)
paramin[2] = src;
paramin[3] = no;
paramin[4] = Fosc/1000; // 當(dāng)不使用PLL功能時(shí),F(xiàn)cclk=Fosc
iap_entry(paramin, paramout); // 調(diào)用IAP服務(wù)程序
}
代碼3-4 編程扇區(qū) 3.3.1.5 比較數(shù)據(jù)
通過(guò)這個(gè)函數(shù),可以檢查寫入Flash中的數(shù)據(jù)和RAM中的是否相同。 注意源地址、目標(biāo)地址和字節(jié)數(shù)必須是4的倍數(shù)??墒褂肒eil MDK提供的關(guān)鍵字__align(n) 來(lái)指定n字節(jié)對(duì)齊。
/********************************************************************
* 名稱:Compare()
* 功能:校驗(yàn)數(shù)據(jù),命令代碼56。
* 入口參數(shù):dst 目標(biāo)地址,即RAM/FLASH起始地址。地址必須字對(duì)齊
* src 源地址,即FLASH/RAM地址。地址必須字對(duì)齊
* no 復(fù)制字節(jié)個(gè)數(shù),必須能被4整除
* 出口參數(shù):IAP返回值(paramout緩沖區(qū)) CMD_SUCCESS,COMPARE_ERROR,ADDR_ERROR
******************************************************************/
void Compare(uint32 dst, uint32 src, uint32 no)
{ paramin[0] = IAP_COMPARE; // 設(shè)置命令字
paramin[1] = dst; // 設(shè)置參數(shù)
paramin[2] = src;
paramin[3] = no;
iap_entry(paramin, paramout); // 調(diào)用IAP服務(wù)程序
代碼3-5 比較數(shù)據(jù)
3.3.2 IAP編程期間的中斷管理 LPC2114片上Flash在擦除/編程期間絕不可被中斷打斷。但Bootloader中定時(shí)和串口接收又使用了中斷,因此必須在擦除/編程之前禁止總中斷,待操作完成后再使能總中斷。Bootloader運(yùn)行在用戶模式下,不具有禁止/使能中斷的權(quán)力,所以在本設(shè)計(jì)中使用軟中斷禁止/使能總中斷。Keil MDK提供了關(guān)鍵字__svc來(lái)觸發(fā)軟中斷。 軟中斷函數(shù)聲明:
__svc(0x00) void EnableIrq(void); //使能中斷,軟中斷0
__svc(0x01) void DisableIrq(void); //禁止中斷,軟中斷1
軟中斷函數(shù)代碼:
/*
*********************************************************************
* 功 能:禁止中斷
* 描 述:利用軟中斷實(shí)現(xiàn)在用戶模式下調(diào)用函數(shù)關(guān)中斷
*********************************************************************/
void DisableIrqFunc(void)
{
int temp;
__asm
{
MRS temp,SPSR
ORR temp,temp,#0x80
MSR SPSR_c,temp
}
}
/*
********************************************************************
* 功 能:使能中斷
* 描 述:利用軟中斷實(shí)現(xiàn)在用戶模式下調(diào)用函數(shù)開中斷
********************************************************************
*/
void EnableIrqFunc(void)
{
int temp;
__asm
{
MRS temp,SPSR
BIC temp,temp,#0x80
MSR SPSR_c,temp
}
}
代碼3-6 禁止/使能總中斷
更改啟動(dòng)代碼,掛接軟中斷入口: ;軟中斷入口
EXPORT SWI_Handler
extern EnableIrq1
extern DisableIrq1
SWI_Handler
STMFD SP!, {R0,R12,LR} ;入棧
LDR R0, [LR,#-4] ;取軟中斷指令,軟中斷號(hào)就包含其中
BIC R0,R0,#0xFF000000
CMP R0,#0 ;判斷是否軟中斷0
BLEQ EnableIrqFunc
BLNE DisableIrqFunc
LDMFD SP!,{R0,R12,PC}^
代碼3-7 掛接軟中斷入口
在程序中,如果想禁止中斷,只需使用DisableIrq();若是能中斷,只需使用EnableIrq()。 3.3.3 使用分散加載機(jī)制精確定位入口地址
應(yīng)用程序接收到升級(jí)指令后,會(huì)跳轉(zhuǎn)到0x00000400處執(zhí)行Bootloader升級(jí)程序。因此Bootloader程序的入口地址必須精確定位到0x00000400處。這可以使用Keil MDK提供的分散加載機(jī)制來(lái)完成。 分散加載代碼見代碼3-8.
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x00000400 0x00001C00 { ; load region size_region
ER_IROM1 0x00000400 0x00001C00 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x40000040 0x00003FA0 { ; RW data
.ANY (+RW +ZI)
}
}
代碼3-8 分散加載代碼
這段代碼顯示出Bootloader程序從0x00000400處開始執(zhí)行,最多占用0x1C00字節(jié)的Flash空間。另外,該程序的RAM從0x40000040開始,長(zhǎng)度為0x3FA0個(gè)字節(jié)。這樣RAM的低64字節(jié)保留給中斷向量映射使用,高32字節(jié)保留給IAP編程使用。 3.3.4 中斷向量的重映射
Bootloader的起始地址位于0x00000400,中斷向量也從這一地址開始存儲(chǔ)。但默認(rèn)情況下ARM發(fā)生異常時(shí),會(huì)跳轉(zhuǎn)到0x00000000處的64字節(jié)中斷向量區(qū)域執(zhí)行相應(yīng)操作,所以為了使Bootloader能相應(yīng)中斷,必須將位于0x00000400開始的64字節(jié)中斷向量表重映射到RAM的低區(qū)。LPC2114使用向寄存器MEMMAP寫入0x02來(lái)完成這一過(guò)程。 代碼3-9 描述了中斷向量重映射的過(guò)程。
; Copy Exception Vectors to Internal RAM ---------------------------------------
ADR R8, Vectors ; 源地址
LDR R9, =RAM_BASE ; 目標(biāo)地址,這里是0x40000000
LDMIA R8!, {R0-R7} ; 裝載向量表
STMIA R9!, {R0-R7} ; 存儲(chǔ)向量表
LDMIA R8!, {R0-R7} ; 裝載處理程序地址
STMIA R9!, {R0-R7} ; 存儲(chǔ)處理程序地址
; Memory Mapping (when Interrupt Vectors are in RAM)
MEMMAP EQU 0xE01FC040 ; Memory Mapping Control
IF :DEF:REMAP
LDR R0, =MEMMAP
IF :DEF:EXTMEM_MODE
MOV R1, #3
ELIF :DEF:RAM_MODE
MOV R1, #2
ELSE
MOV R1, #1
ENDIF
STR R1, [R0]
ENDIF
代碼3-9 中斷向量重映射
由于Keil MDK提供的啟動(dòng)代碼中使用條件編譯指令,所以,要想正確的執(zhí)行中斷向量重映射,還需要在Keil MDK編譯器工程設(shè)置Options for target“你的工程目標(biāo)名”下的Asm標(biāo)簽中找到Define編輯框,在編輯框中鍵入“REMAP RAM_MODE”。如圖3-2所示 圖3-2
注意:在擦除/編程Flash的時(shí)候還應(yīng)該禁止PLL、存儲(chǔ)器加速模塊。
3.4 用戶程序的設(shè)計(jì)
用戶程序運(yùn)行在高區(qū)(扇區(qū)8~13)或者低區(qū)(扇區(qū)1~7),用于實(shí)現(xiàn)數(shù)據(jù)的采集、處理和上傳等等,用戶程序除本身功能的要求外,還需要注意: Ø 使用分散加載機(jī)制,將程序入口精確定位到0x00010000(高區(qū))或0x00008000(低區(qū))。
Ø 進(jìn)行中斷向量重映射,映射到RAM最底處。
4 通訊協(xié)議與上位機(jī)軟件
4.1 Intel的hex格式
Intel hex文件是記錄文本行的ASCII文本文件,在Intel HEX文件中,每一行是一個(gè)HEX記錄,由十六進(jìn)制數(shù)組成的機(jī)器碼或者數(shù)據(jù)常量。一個(gè)數(shù)據(jù)記錄以一個(gè)回車和一個(gè)換行結(jié)束。 一個(gè)Intel HEX文件可以包含任意多的十六進(jìn)制記錄,每條記錄有五個(gè)域,下面是一個(gè)記錄的格式.
: LL AAAA TT [DD...] CC
每一組字母是獨(dú)立的一域,每一個(gè)字母是一個(gè)十六進(jìn)制數(shù)字,每一域至少由兩個(gè)十六進(jìn)制數(shù)字組成,下面是字節(jié)的描述.
:冒號(hào) 是每一條Intel HEX記錄的開始
LL 是這條記錄的長(zhǎng)度域,他表示數(shù)據(jù)(dd)的字節(jié)數(shù)目.
AAAA 是地址域,他表示數(shù)據(jù)的起始地址
TT 這個(gè)域表示這條HEX記錄的類型,他有可能是下面這幾種類型
00 ----數(shù)據(jù)記錄
01 ----文件結(jié)束記錄
02 ----擴(kuò)展段地址記錄
04 ----擴(kuò)展線性地址記錄
DD 是數(shù)據(jù)域,表示一個(gè)字節(jié)的數(shù)據(jù),一個(gè)記錄可能有多個(gè)數(shù)據(jù)字節(jié),字節(jié)數(shù)目可以查看LL域的說(shuō)明。
CC 是效驗(yàn)和域,表示記錄的效驗(yàn)和,計(jì)算方法是將本條記錄冒號(hào)開始的所有字母(包括校驗(yàn)字節(jié))相加之后等于0x00。
一個(gè)Intel HEX文件必須有一個(gè)文件結(jié)束記錄,這個(gè)記錄的類型域必須是01,
一個(gè)EOF記錄總是這樣:
:00000001FF
00是記錄中數(shù)據(jù)字節(jié)的數(shù)目
0000這個(gè)地址對(duì)于EOF記錄來(lái)說(shuō)無(wú)任何意義
01記錄類型是01(文件結(jié)束記錄標(biāo)示)
4.2 對(duì)上位機(jī)軟件的要求
Ø 上位機(jī)具備解析重組Intel HEX文件的能力. Ø 上位機(jī)軟件應(yīng)能識(shí)別分站發(fā)來(lái)的應(yīng)答信號(hào)并做出正確的響應(yīng)。
Ø 上位機(jī)應(yīng)能夠檢驗(yàn)代碼的完整性。
Ø 上位機(jī)能根據(jù)分站發(fā)出的程序所在高區(qū)或低區(qū)標(biāo)志,自動(dòng)判別當(dāng)前升級(jí)程序是否和升級(jí)區(qū)域相對(duì)應(yīng)。
5 實(shí)驗(yàn)數(shù)據(jù)
為驗(yàn)證升級(jí)程序的穩(wěn)定性,對(duì)分站進(jìn)行重上電、復(fù)位、遠(yuǎn)程升級(jí)等一些列實(shí)驗(yàn),實(shí)驗(yàn)記錄及如下。 1. 測(cè)試程序跳轉(zhuǎn)功能.程序在上電或復(fù)位之后,應(yīng)順利跳轉(zhuǎn)到用戶程序。
2. 測(cè)試Bootloader(一)。上位機(jī)發(fā)送升級(jí)命令但不發(fā)送升級(jí)數(shù)據(jù)包,程序應(yīng)能進(jìn)入Bootloader并發(fā)送當(dāng)前程序所在的區(qū)域(高區(qū)或者低區(qū)代號(hào)),10S后程序應(yīng)跳轉(zhuǎn)到用戶程序。
3.測(cè)試Bootloader(二)。上位機(jī)發(fā)送升級(jí)命令,發(fā)送升級(jí)數(shù)據(jù)包,但發(fā)送到一半時(shí)停止發(fā)送。程序在10S后應(yīng)能跳轉(zhuǎn)到用戶程序區(qū)。
4.測(cè)試Bootloader(三)。上位機(jī)發(fā)送升級(jí)命令,發(fā)送升級(jí)數(shù)據(jù)包,但發(fā)送中途給分站斷電,重新上電后,應(yīng)還能執(zhí)行原來(lái)的程序。
5.測(cè)試Bootloader(四)。上位機(jī)發(fā)送升級(jí)命令,發(fā)送完成升級(jí)數(shù)據(jù)包。程序應(yīng)能接收升級(jí)數(shù)據(jù)包并編程Flash,完成用戶程序的更新,更新用戶程序后,跳轉(zhuǎn)到新的用戶程序。
6.總結(jié)
本次升級(jí)方案雖然是以LPC2114為基礎(chǔ)的,但任何具有IAP功能的單片機(jī)、ARM都可使用本設(shè)計(jì)方案。 設(shè)計(jì)的重點(diǎn)在于如何保證升級(jí)的安全性,分站采取了一些列校驗(yàn)、超時(shí)處理以及看門狗等措施,一是保障升級(jí)數(shù)據(jù)包的正確傳送,二是即使升級(jí)失敗也能退回原升級(jí)程序。上位機(jī)的校驗(yàn)措施需相關(guān)部門配合。從實(shí)驗(yàn)數(shù)據(jù)來(lái)看,進(jìn)行了幾十次的遠(yuǎn)程升級(jí),未有一例失敗,安全性能可以得到保證。
7.參考文獻(xiàn):
1. 周立功等 ARM微控制器基礎(chǔ)與實(shí)戰(zhàn)(第二版) 北京航空航天大學(xué)出版社 2005 2. LPC2114/2124/2212/2214 使用指南.Pdf 廣州周立功單片機(jī)發(fā)展有限公司
3. 韋文祥 朱志杰 車琳娜 郭寶泉 基于LPC21 24的一個(gè)遠(yuǎn)程系統(tǒng)軟件升級(jí)方案 單片機(jī)與嵌入式系統(tǒng)應(yīng)用 2006第三期
4. 許文杰 丁志岡 張 泉基于ARM 處理器的IAP設(shè)計(jì)及應(yīng)用 計(jì)算機(jī)應(yīng)用與軟件 2009第3期
5. 姜曉梅 李祥和 任朝榮 姚明基于ARM的IAP在線及遠(yuǎn)程升級(jí)技術(shù) 計(jì)算機(jī)應(yīng)用 2008第二期
6. RealView 編譯工具-編譯器參考指南.pdf ARM Limited 2009.3
7. RealView Compilation Tools(連接器用戶指南).pdf ARM Limited 2009.3
8. RealView 編譯工具-編譯器用戶指南.pdf ARM Limited 2009.1
9. RealView 編譯工具-鏈接器參考指南.pdf ARM Limited 2008.9
10. Intel HEX文件格式
11. LPC2000 secondary bootloader for code update using IAP NXP Semiconductors 2009.5.26
后記:分散加載文件,軟中斷,中斷向量表重映射,變量對(duì)齊,精確定位變量等等這些東西的詳細(xì)講解在我的參考資料上都能找的到,發(fā)現(xiàn)問(wèn)題并能解決它,是件很美妙的事情,所以我沒(méi)打算也沒(méi)時(shí)間詳細(xì)寫這些東西的用法。
需要說(shuō)的是,我在設(shè)計(jì)的時(shí)候走了一個(gè)彎路,現(xiàn)在想想還覺得挺可笑。我以為上面講的東西要在一個(gè)工程里面實(shí)現(xiàn)才好,這樣才能生成一個(gè).hex可燒錄文件,可以一次性的將用戶程序、Bootloader程序燒寫進(jìn)處理器,我想彎了。正確的做法是建四個(gè)工程:跳轉(zhuǎn)程序、Bootloader、用戶低區(qū)程序、用戶高區(qū)程序。如果你懂了.hex文件的格式,就完全可以將跳轉(zhuǎn)程序、Bootloader和用戶低區(qū)程序(或者跳轉(zhuǎn)程序、Bootloader和用戶高區(qū)程序)這三個(gè)工程生成的.hex文件合成一個(gè)。靈活多變的處理問(wèn)題,這是我最大的收獲。
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/zhzht19861011/archive/2010/11/14/6008702.aspx
|
|