基于IAP和Keil MDK的遠程升級設(shè)計
寫在前面:三個周之前,我突然想寫一個遠程升級的程序。那個時候我只是大概知道IAP的意思是在應用編程,但怎么編,我還一無所知。我給自己定下一個個階段目標,從最基礎(chǔ)的代碼一點點寫起,解決一個又一個的問題。三個周之后,我用自己設(shè)計的方法實驗了50多次,無一例升級失敗。
本文引用地址:http://cafeforensic.com/article/201612/341760.htm三個周來,遇到了很多的不解、困惑,甚至是想放棄,但我現(xiàn)在想說的是:很多未知的困難會擋在我們面前,我們會感覺毫無頭緒甚至覺得毫無出路忍不住要放棄,但多堅持一下,那些困難不但能煙消云散還能帶給我們進步。
本設(shè)計是基于LPC2114和Keil MDK(V4.10),但所有支持IAP的處理器都可借鑒本方案,重要的是思想,而不是用什么。
0 引言
在應用編程(IAP)技術(shù)為系統(tǒng)在線升級和遠程升級提供了良好的解決方案,也為數(shù)據(jù)存儲和現(xiàn)場固件的升級都帶來了極大的靈活性。通??衫眯酒拇锌诮拥接嬎銠C的RS232口、通過現(xiàn)有的Internet或、無線網(wǎng)絡(luò)或者其他通信方式很方便地實現(xiàn)在線以及遠程升級和維護。
本文以NXP的LPC2114 ARM微處理器為平臺,以Keil MDK為開發(fā)工具,闡述IAP的原理、Flash的劃分、分散加載機制、中斷重映射以及在線升級的實現(xiàn)方案及其優(yōu)化。本方案使用多種校驗技術(shù),最大限度的保障傳輸數(shù)據(jù)的正確性;使用bootloader機制,即使因意外事件(斷電,編程Flash失敗等)造成升級失敗后,程序也能返回到升級前的狀態(tài)。
1 LPC2114的Flash規(guī)劃
1.1 扇區(qū)描述
LPC2114共有128KB片內(nèi)Flash,共分為16個扇區(qū),分別為0扇區(qū)~15扇區(qū),每個扇區(qū)為8KB存儲空間。其中第15扇區(qū)出廠時被固化為Boot Block區(qū),控制復位后的初始化操作,并提供實現(xiàn)Flash 編程的方法。所以用戶可用的Flash空間只有120KB。IAP程序固化于Boot Block中,IAP操作是以扇區(qū)為單位,并占用片內(nèi)RAM的高32字節(jié)。下表列出LPC2114器件所包含的扇區(qū)數(shù)和存儲器地址.
表1.1 LPC2114 Flash 器件中的扇區(qū)
1.2 Flash的扇區(qū)劃分
本設(shè)計將Flash劃分為四個區(qū),扇區(qū)0存放跳轉(zhuǎn)程序和升級引導程序(Bootloader)。分站上電后執(zhí)行跳轉(zhuǎn)程序,跳轉(zhuǎn)到用戶程序處。用戶程序運行過程中,如果接收到升級指令,會從用戶程序跳轉(zhuǎn)到引導程序區(qū)(Bootloader),接收新程序數(shù)據(jù)包,完成Flash編程并跳轉(zhuǎn)到新程序區(qū)執(zhí)行程序。扇區(qū)1~扇區(qū)7為程序存儲低區(qū);扇區(qū)8~扇區(qū)13為程序存儲高區(qū);扇區(qū)14存放當前程序運行區(qū)域標志,如果當前程序運行在高區(qū),該標志區(qū)的最低四個字節(jié)為0x00010000,如果當前程序運行在低區(qū),該標志區(qū)的最低四個字節(jié)為0x00008000。
2 IAP的原理與軟件設(shè)計
2.1 IAP的原理
IAP函數(shù)是固化在微處理器內(nèi)部flash上的一些函數(shù)代碼,最終的用戶程序可以直接通過調(diào)用這些函數(shù)來對內(nèi)部flash進行擦除和編程操作。LPC2114微處理器的內(nèi)部flash有一個塊稱為Boot Block,位于flash的頂端,可供調(diào)用的IAP函數(shù)就位于該塊中。上電后Boot Block被映射到內(nèi)部地址空間的頂端,同樣IAP函數(shù)人口地址也被映射到地址0x7ffffff0處。用戶可通過跳轉(zhuǎn)到該地址來調(diào)用相應的lAP函數(shù)。
2.2 IAP 命令
對于在應用編程來說,應當通過寄存器r0 中的字指針指向存儲器(RAM)包含的命令代碼和參數(shù)來調(diào)用IAP 程序。IAP 命令的結(jié)果返回到寄存器r1 所指向的返回表。用戶可通過傳遞寄存器r0 和r1 中的相同指針重用命令表來得到結(jié)果。參數(shù)表應當大到足夠保存所有的結(jié)果以防結(jié)果的數(shù)目大于參數(shù)的數(shù)目。參數(shù)傳遞見圖2-1。參數(shù)和結(jié)果的數(shù)目根據(jù)IAP命令而有所不同。參數(shù)的最大數(shù)目為5,由“將RAM 內(nèi)容復制到Flash”命令傳遞。結(jié)果的最大數(shù)目為2,由“扇區(qū)查空”命令返回。命令處理程序在接收到一個未定義的命令時發(fā)送狀態(tài)代碼INVALID_COMMAND。IAP 程序是thumb 代碼,位于地址0x7FFFFFF0。
圖2-1 IAP的參數(shù)傳遞
表2-1描述了IAP的命令。
表2-1 IAP 命令匯總
IAP命令命令代碼描述
準備編程扇區(qū)50該命令必須在執(zhí)行“將 RAM 內(nèi)容復制到Flash”或“擦除扇區(qū)”命令之前執(zhí)行。這兩個命令的成功執(zhí)行會導致相關(guān)的扇區(qū)再次被保護。該命令不能用于boot 扇區(qū)。要準備單個扇區(qū),可將起始和結(jié)束扇區(qū)號設(shè)置為相同值。
將RAM內(nèi)容復制到Flash51該命令用于編程 Flash 存儲器。受影響的扇區(qū)應當先通過調(diào)用“準備寫操作的扇區(qū)”命令準備。當成功執(zhí)行復制命令后,扇區(qū)將自動受到保護。該命令不能寫boot 扇區(qū)。
擦除扇區(qū)52該命令用于擦除片內(nèi) Flash 存儲器的一個或多個扇區(qū)。boot 扇區(qū)不能由該命令擦除。要擦除單個扇區(qū)可將起始和結(jié)束扇區(qū)號設(shè)定為相同值。
扇區(qū)查空53該命令用于對片內(nèi) Flash 存儲器的一個或多個扇區(qū)進行查空。要查空單個扇區(qū)可將起始和結(jié)束扇區(qū)號設(shè)定為相同值。
讀器件ID54該命令用于讀取器件的 ID 號。
讀Boot版本55該命令用于讀取 boot 代碼版本號。
IAP比較56該命令用來比較兩個地址單元的存儲器內(nèi)容。當源或目標地址包含從地址0 開始的前64字節(jié)中的任意一個時,比較的結(jié)果不一定正確。前64字節(jié)重新映射到Flash boot 扇區(qū)。
2.3 IAP 編程函數(shù)接口
IAP 功能可用下面的C 代碼來調(diào)用。
定義 IAP 程序的入口地址。由于IAP 地址的第0 位是1,因此,當程序計數(shù)器轉(zhuǎn)移到該地址時會引起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 個參數(shù),無返回值。注意:IAP 將函數(shù)結(jié)果和R1 中的表格基址一同返回。
typedef void (*IAP) (unsigned int [ ] , unsigned int [ ]);
IAP iap_entry;
設(shè)置函數(shù)指針
iap_entry=(IAP) IAP_LOCATION;
使用下面的語句來調(diào)用IAP。
iap_entry (command , result);
Flash 存儲器在寫或擦除操作過程中不可被訪問。執(zhí)行Flash 寫/擦除操作的IAP 命令
使用片內(nèi)RAM 頂端的32 個字節(jié)空間。如果應用程序中允許IAP 編程,那么用戶程序不應
使用該空間。
3 LPC2114升級實現(xiàn)過程
由于在升級程序軟件設(shè)計中,分散加載機制、中斷向量的重映射、軟中斷等的實現(xiàn)還與所使用的編譯器緊密相關(guān),因此,本文結(jié)合Keil MDK(V4.10)編譯工具,來詳細闡述升級程序的實現(xiàn)過程。
3.1 總體思路
分站上電后,首先運行位于Flash 0x000~0x3FF中的跳轉(zhuǎn)程序。跳轉(zhuǎn)程序會讀取位于14扇區(qū)的當前程序運行標志,如果該扇區(qū)的最低四個字節(jié)為0x00010000,表示當前程序運行在高區(qū),跳轉(zhuǎn)程序會跳轉(zhuǎn)到Flash的0x00010000處執(zhí)行用戶程序;如果該標志區(qū)的最低四個字節(jié)為0x00008000,表示當前程序運行在低區(qū),跳轉(zhuǎn)程序會跳轉(zhuǎn)到Flash的0x00002000處執(zhí)行用戶程序。用戶程序正常執(zhí)行后,會按照設(shè)計進行正常的程序采集、數(shù)據(jù)處理傳送。當接收到升級命令后,用戶程序會跳轉(zhuǎn)到Flash的0x00000400處的Bootloader處進行升級的一些操作。當升級成功后,Bootloader程序更新當前程序運行區(qū)標志,程序跳轉(zhuǎn)到新程序處運行,如果升級不成功,返回升級前的程序。
流程圖如下所示:
3.2 跳轉(zhuǎn)程序的設(shè)計
跳轉(zhuǎn)程序是分站上電后最先運行的程序,根據(jù)當前程序運行區(qū)標志,跳轉(zhuǎn)到相應的用戶程序區(qū)執(zhí)行。本段程序占用Flash的最低1K字節(jié)空間,與Bootloader同在第0扇區(qū)。
跳轉(zhuǎn)程序的啟動代碼僅初始化堆棧,不使用PLL和存儲加速功能。代碼1描述了跳轉(zhuǎn)程序的主要啟動代碼。
; 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)程序啟動代碼
當跳轉(zhuǎn)程序確定要跳轉(zhuǎn)到高區(qū)用戶程序或者低區(qū)用戶程序后,使用函數(shù)指針跳轉(zhuǎn)到0x00010000處(高區(qū)用戶函數(shù)入口地址)或0x00002000處(低區(qū)用戶函數(shù)入口地址)。
定義函數(shù)指針:
void (*UserProgram)() ;
指定入口地址:
UserProgram = (void (*)()) (0x00010000);
UserProgram = (void (*)()) (0x00002000);
實現(xiàn)跳轉(zhuǎn):
(*UserProgram)() ;
要將用戶代碼精確定位到Flash的0x00010000處(高區(qū)用戶函數(shù)入口地址)或0x00002000處(低區(qū)用戶函數(shù)入口地址),需要使用編譯器的分散加載機制,將在Bootloader中詳細描述實現(xiàn)過程。
另外,跳轉(zhuǎn)程序還在燒錄代碼的同時初始化當前程序運行區(qū)標志,即對Flash的0x0001C000地址處寫入0x00008000,表示當前用戶程序在低區(qū)。主要使用了編譯器的__at關(guān)鍵字:精確定位變量。需要注意的是,使用該關(guān)鍵字必須包含頭文件absacc.h。
const uint32 x __at(0x0001C000)=0x00008000; //初始化用戶程序標志區(qū),默認運行低區(qū)
3.3 升級程序Bootloader的設(shè)計
升級程序的好壞,在很大程度上取決于Bootloader設(shè)計的好壞。
一個優(yōu)秀的IAP升級Bootloader,必須做好升級中出現(xiàn)故障等異常的處理。保證系統(tǒng)不會崩潰,即使升級失敗,也能返回升級前的程序。
? 有升級指令,進行初始化工作(串口、定時器、看門狗)
? 接收升級數(shù)據(jù)包,檢測幀頭、長度、幀號、數(shù)據(jù)區(qū)校驗,最大程度的保證升級數(shù)據(jù)的完整性、正確性。
? 實時檢測接收狀態(tài),10 S內(nèi)沒有接收到數(shù)據(jù)或接收到的數(shù)據(jù)包都是錯的,則退出升級,返回原程序。
? 接收的數(shù)據(jù)按照512字節(jié)一組寫入Flash,寫入后再讀出與原數(shù)據(jù)進行對比校驗,校
驗成功后,本次編程Flash成功。允許連續(xù)3次編程Flash,三次都不成功,退出升級程序,執(zhí)行原程序。
? 升級成功后,更新當前程序運行區(qū)標志,跳轉(zhuǎn)到新程序,同時原程序保存。
本設(shè)計的Bootload位于Flash的0x400開始的扇區(qū)0存儲區(qū)內(nèi),使用分散加載機制,將程序的入口地址定位到0x00000400處。當用戶程序接收到升級指令后,就會使用函數(shù)指針跳轉(zhuǎn)到這個入口處。
3.3.1 使用IAP
圖3-1 描述了使用IAP編程Flash所必須的步驟。
3.3.1.1 定義系統(tǒng)參數(shù)
在使用IAP前,需要定義一些系統(tǒng)參數(shù),比如系統(tǒng)時鐘、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ù)
評論