基于IAP和Keil MDK的遠程升級設計
寫在前面:三個周之前,我突然想寫一個遠程升級的程序。那個時候我只是大概知道IAP的意思是在應用編程,但怎么編,我還一無所知。我給自己定下一個個階段目標,從最基礎的代碼一點點寫起,解決一個又一個的問題。三個周之后,我用自己設計的方法實驗了50多次,無一例升級失敗。
本文引用地址:http://www.104case.com/article/201612/341760.htm三個周來,遇到了很多的不解、困惑,甚至是想放棄,但我現在想說的是:很多未知的困難會擋在我們面前,我們會感覺毫無頭緒甚至覺得毫無出路忍不住要放棄,但多堅持一下,那些困難不但能煙消云散還能帶給我們進步。
本設計是基于LPC2114和Keil MDK(V4.10),但所有支持IAP的處理器都可借鑒本方案,重要的是思想,而不是用什么。
0 引言
在應用編程(IAP)技術為系統在線升級和遠程升級提供了良好的解決方案,也為數據存儲和現場固件的升級都帶來了極大的靈活性。通常可利用芯片的串行口接到計算機的RS232口、通過現有的Internet或、無線網絡或者其他通信方式很方便地實現在線以及遠程升級和維護。
本文以NXP的LPC2114 ARM微處理器為平臺,以Keil MDK為開發工具,闡述IAP的原理、Flash的劃分、分散加載機制、中斷重映射以及在線升級的實現方案及其優化。本方案使用多種校驗技術,最大限度的保障傳輸數據的正確性;使用bootloader機制,即使因意外事件(斷電,編程Flash失敗等)造成升級失敗后,程序也能返回到升級前的狀態。
1 LPC2114的Flash規劃
1.1 扇區描述
LPC2114共有128KB片內Flash,共分為16個扇區,分別為0扇區~15扇區,每個扇區為8KB存儲空間。其中第15扇區出廠時被固化為Boot Block區,控制復位后的初始化操作,并提供實現Flash 編程的方法。所以用戶可用的Flash空間只有120KB。IAP程序固化于Boot Block中,IAP操作是以扇區為單位,并占用片內RAM的高32字節。下表列出LPC2114器件所包含的扇區數和存儲器地址.
表1.1 LPC2114 Flash 器件中的扇區

1.2 Flash的扇區劃分
本設計將Flash劃分為四個區,扇區0存放跳轉程序和升級引導程序(Bootloader)。分站上電后執行跳轉程序,跳轉到用戶程序處。用戶程序運行過程中,如果接收到升級指令,會從用戶程序跳轉到引導程序區(Bootloader),接收新程序數據包,完成Flash編程并跳轉到新程序區執行程序。扇區1~扇區7為程序存儲低區;扇區8~扇區13為程序存儲高區;扇區14存放當前程序運行區域標志,如果當前程序運行在高區,該標志區的最低四個字節為0x00010000,如果當前程序運行在低區,該標志區的最低四個字節為0x00008000。
2 IAP的原理與軟件設計
2.1 IAP的原理
IAP函數是固化在微處理器內部flash上的一些函數代碼,最終的用戶程序可以直接通過調用這些函數來對內部flash進行擦除和編程操作。LPC2114微處理器的內部flash有一個塊稱為Boot Block,位于flash的頂端,可供調用的IAP函數就位于該塊中。上電后Boot Block被映射到內部地址空間的頂端,同樣IAP函數人口地址也被映射到地址0x7ffffff0處。用戶可通過跳轉到該地址來調用相應的lAP函數。
2.2 IAP 命令
對于在應用編程來說,應當通過寄存器r0 中的字指針指向存儲器(RAM)包含的命令代碼和參數來調用IAP 程序。IAP 命令的結果返回到寄存器r1 所指向的返回表。用戶可通過傳遞寄存器r0 和r1 中的相同指針重用命令表來得到結果。參數表應當大到足夠保存所有的結果以防結果的數目大于參數的數目。參數傳遞見圖2-1。參數和結果的數目根據IAP命令而有所不同。參數的最大數目為5,由“將RAM 內容復制到Flash”命令傳遞。結果的最大數目為2,由“扇區查空”命令返回。命令處理程序在接收到一個未定義的命令時發送狀態代碼INVALID_COMMAND。IAP 程序是thumb 代碼,位于地址0x7FFFFFF0。

圖2-1 IAP的參數傳遞
表2-1描述了IAP的命令。
表2-1 IAP 命令匯總
IAP命令命令代碼描述
準備編程扇區50該命令必須在執行“將 RAM 內容復制到Flash”或“擦除扇區”命令之前執行。這兩個命令的成功執行會導致相關的扇區再次被保護。該命令不能用于boot 扇區。要準備單個扇區,可將起始和結束扇區號設置為相同值。
將RAM內容復制到Flash51該命令用于編程 Flash 存儲器。受影響的扇區應當先通過調用“準備寫操作的扇區”命令準備。當成功執行復制命令后,扇區將自動受到保護。該命令不能寫boot 扇區。
擦除扇區52該命令用于擦除片內 Flash 存儲器的一個或多個扇區。boot 扇區不能由該命令擦除。要擦除單個扇區可將起始和結束扇區號設定為相同值。
扇區查空53該命令用于對片內 Flash 存儲器的一個或多個扇區進行查空。要查空單個扇區可將起始和結束扇區號設定為相同值。
讀器件ID54該命令用于讀取器件的 ID 號。
讀Boot版本55該命令用于讀取 boot 代碼版本號。
IAP比較56該命令用來比較兩個地址單元的存儲器內容。當源或目標地址包含從地址0 開始的前64字節中的任意一個時,比較的結果不一定正確。前64字節重新映射到Flash boot 扇區。
2.3 IAP 編程函數接口
IAP 功能可用下面的C 代碼來調用。
定義 IAP 程序的入口地址。由于IAP 地址的第0 位是1,因此,當程序計數器轉移到該地址時會引起Thumb 指令集的變化。
#define IAP_LOCATION 0x7ffffff1
定義數據結構或指針,將IAP 命令表和結果表傳遞給IAP 函數
unsigned long command[5];
unsigned long result[2];
定義函數類型指針,函數包含2 個參數,無返回值。注意:IAP 將函數結果和R1 中的表格基址一同返回。
typedef void (*IAP) (unsigned int [ ] , unsigned int [ ]);
IAP iap_entry;
設置函數指針
iap_entry=(IAP) IAP_LOCATION;
使用下面的語句來調用IAP。
iap_entry (command , result);
Flash 存儲器在寫或擦除操作過程中不可被訪問。執行Flash 寫/擦除操作的IAP 命令
使用片內RAM 頂端的32 個字節空間。如果應用程序中允許IAP 編程,那么用戶程序不應
使用該空間。
3 LPC2114升級實現過程
由于在升級程序軟件設計中,分散加載機制、中斷向量的重映射、軟中斷等的實現還與所使用的編譯器緊密相關,因此,本文結合Keil MDK(V4.10)編譯工具,來詳細闡述升級程序的實現過程。
3.1 總體思路
分站上電后,首先運行位于Flash 0x000~0x3FF中的跳轉程序。跳轉程序會讀取位于14扇區的當前程序運行標志,如果該扇區的最低四個字節為0x00010000,表示當前程序運行在高區,跳轉程序會跳轉到Flash的0x00010000處執行用戶程序;如果該標志區的最低四個字節為0x00008000,表示當前程序運行在低區,跳轉程序會跳轉到Flash的0x00002000處執行用戶程序。用戶程序正常執行后,會按照設計進行正常的程序采集、數據處理傳送。當接收到升級命令后,用戶程序會跳轉到Flash的0x00000400處的Bootloader處進行升級的一些操作。當升級成功后,Bootloader程序更新當前程序運行區標志,程序跳轉到新程序處運行,如果升級不成功,返回升級前的程序。
流程圖如下所示:

3.2 跳轉程序的設計
跳轉程序是分站上電后最先運行的程序,根據當前程序運行區標志,跳轉到相應的用戶程序區執行。本段程序占用Flash的最低1K字節空間,與Bootloader同在第0扇區。
跳轉程序的啟動代碼僅初始化堆棧,不使用PLL和存儲加速功能。代碼1描述了跳轉程序的主要啟動代碼。
; 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:跳轉程序啟動代碼
當跳轉程序確定要跳轉到高區用戶程序或者低區用戶程序后,使用函數指針跳轉到0x00010000處(高區用戶函數入口地址)或0x00002000處(低區用戶函數入口地址)。
定義函數指針:
void (*UserProgram)() ;
指定入口地址:
UserProgram = (void (*)()) (0x00010000);
UserProgram = (void (*)()) (0x00002000);
實現跳轉:
(*UserProgram)() ;
要將用戶代碼精確定位到Flash的0x00010000處(高區用戶函數入口地址)或0x00002000處(低區用戶函數入口地址),需要使用編譯器的分散加載機制,將在Bootloader中詳細描述實現過程。
另外,跳轉程序還在燒錄代碼的同時初始化當前程序運行區標志,即對Flash的0x0001C000地址處寫入0x00008000,表示當前用戶程序在低區。主要使用了編譯器的__at關鍵字:精確定位變量。需要注意的是,使用該關鍵字必須包含頭文件absacc.h。
const uint32 x __at(0x0001C000)=0x00008000; //初始化用戶程序標志區,默認運行低區
3.3 升級程序Bootloader的設計
升級程序的好壞,在很大程度上取決于Bootloader設計的好壞。
一個優秀的IAP升級Bootloader,必須做好升級中出現故障等異常的處理。保證系統不會崩潰,即使升級失敗,也能返回升級前的程序。
? 有升級指令,進行初始化工作(串口、定時器、看門狗)
? 接收升級數據包,檢測幀頭、長度、幀號、數據區校驗,最大程度的保證升級數據的完整性、正確性。
? 實時檢測接收狀態,10 S內沒有接收到數據或接收到的數據包都是錯的,則退出升級,返回原程序。
? 接收的數據按照512字節一組寫入Flash,寫入后再讀出與原數據進行對比校驗,校
驗成功后,本次編程Flash成功。允許連續3次編程Flash,三次都不成功,退出升級程序,執行原程序。
? 升級成功后,更新當前程序運行區標志,跳轉到新程序,同時原程序保存。
本設計的Bootload位于Flash的0x400開始的扇區0存儲區內,使用分散加載機制,將程序的入口地址定位到0x00000400處。當用戶程序接收到升級指令后,就會使用函數指針跳轉到這個入口處。
3.3.1 使用IAP
圖3-1 描述了使用IAP編程Flash所必須的步驟。

3.3.1.1 定義系統參數
在使用IAP前,需要定義一些系統參數,比如系統時鐘、IAP中斷入口、輸入輸出緩存。
#define IAP_CLK 11059200UL
#define IAP_LOCATION 0x7FFFFFF1
typedef void(*IAP)(uint32 [],uint32 []); //定義函數類型指針
IAP iap_entry=(IAP)IAP_LOCATION; //設置函數指針
unsigned long command[5] = {0,0,0,0,0};
unsigned long result[2]= {0,0};
代碼3-1:定義系統參數
評論