引言
本應(yīng)用筆記介紹了如何管理MAXQ微控制器中、可分區(qū)擦除的內(nèi)部數(shù)據(jù)和程序閃存。一般性地介紹了怎樣構(gòu)建一個引導(dǎo)裝載應(yīng)用,實現(xiàn)程序閃存的在應(yīng)用編程。注意:本文不適于那些使用頁擦除閃存的MAXQ微控制器,即那些只允許少量閃存被擦除的微控制器。各MAXQ數(shù)據(jù)手冊都會說明其所采用的閃存類型。
閃存簡介
存儲器配置本應(yīng)用筆記給出了多種不同尺寸的閃存配置,并不完全和某一特定MAXQ器件相符。這些配置僅用于本文中的實例。各MAXQ器件的數(shù)據(jù)手冊會列出該器件的存儲器配置。
引導(dǎo)、程序和數(shù)據(jù)區(qū)在操作上并沒有區(qū)別。如果引導(dǎo)裝載程序需要的空間超過了第一閃存區(qū)的范圍,那么可將程序擴展到下一分區(qū)中。然而,在下面的實例中,標號會有所不同。
表1. 閃存配置實例
數(shù)據(jù)閃存
數(shù)據(jù)閃存可以被用來可靠地存儲一些系統(tǒng)工作期間需要一次性或周期性保存的系統(tǒng)數(shù)據(jù)。數(shù)據(jù)閃存的容量因特定的MAXQ器件而異,通常在128至2k字之間。
數(shù)據(jù)閃存的使用有一些限制。與EEPROM不同,數(shù)據(jù)閃存不能按字擦除;每次必須擦除一個完整的分區(qū)。擦除一個分區(qū)通常需要0.7秒的時間,最壞情況下可能會長達15秒。在這期間,用戶代碼停止運行,不能進行其他操作。因此,在根據(jù)系統(tǒng)需求選擇軟件技術(shù)時,必須要仔細考慮這些限制。對于絕大多數(shù)周期性數(shù)據(jù)存儲,采用有界隊列和/或分區(qū)切換技術(shù),即可滿足系統(tǒng)可靠性的要求。下面給出分區(qū)交換和有界隊列技術(shù)的簡單實例。
有界隊列
有界隊列是一個包含固定數(shù)量元素的隊列。該方法常用于處理周期性數(shù)據(jù)。例如,可以將一個2k字的數(shù)據(jù)閃存分成32至64個字的條目,如表2所示的存儲器配置。
初始化時,啟動程序掃描隊列,以確定隊列中下一個可用條目。隊列填滿之后只有將其擦處后方可寫入下一個條目。如果要保留全部條目,那么必須改變分區(qū)以保持所有數(shù)據(jù)。閃存擦除后,則可以寫入新的條目。這種方法的缺點是在擦寫過程中如果掉電,所有數(shù)據(jù)將會丟失。圖1示例條目載入有界隊列的流程。附錄A給出了一個簡單的C源代碼實例。
如果這種有界隊列方法還不能滿足您的系統(tǒng)要求,那么還可以采用分區(qū)交換技術(shù)。
表2. 有界隊列存儲器配置舉例
FLASHQueue[ ] | |
Queue Index | Data Flash Address |
31 | 0xF7C0-0xF7FF |
30 | 0xF780-0xF7BF |
29 | 0xF740-0xF77F |
. . . . | . . . . |
2 | 0xF080-0xF0BF |
1 | 0xF040-0xF07F |
0 | 0xF000-0xF03F |
圖1. 有界隊列流程
塊交換
塊交換能夠有效防止數(shù)據(jù)在漫長的分區(qū)擦除過程中丟失或損壞。這里所講的“塊”等同于“分區(qū)”。塊交換方式最適合于分區(qū)尺寸略大于數(shù)據(jù)總量的情況。缺點是至少需要兩個數(shù)據(jù)閃存分區(qū)。當要寫入的數(shù)據(jù)總量遠小于分區(qū)尺寸時,最好將塊交換和有界隊列兩種方法結(jié)合使用。
如果需要采用塊交換,則需要選用至少含有兩個數(shù)據(jù)閃存分區(qū)的MAXQ器件。表3給出了一個包含兩個1K x 16閃存分區(qū)的存儲器配置實例。圖2給出了塊交換寫/擦流程。
附錄A給出了一個簡單的C源代碼實例。
表3. 塊交換存儲器配置實例
Flash Sectors | |
Sector Number | Data Flash Address |
0 | 0xF000-0xF3FF |
1 | 0xE000-0xE3FF |
圖2. 塊交換流程
同時采用有界隊列和塊交換
管理數(shù)據(jù)閃存的最可靠、最靈活的辦法是同時采用有界隊列和塊交換技術(shù)。當需要將少量的數(shù)據(jù)周期性存入閃存,并要保持數(shù)據(jù)完整性的時候,結(jié)合使用這兩種技術(shù)將非常有利。表4給出了一個包含兩個2K x 16分區(qū),每個分區(qū)被劃分為32個相等條目的實例。圖3示例數(shù)據(jù)在兩個分區(qū)之間、有界排隊內(nèi)的流向。
這種組合方法的程序比有界隊列方法稍微復(fù)雜一些。附錄A給出了一個簡單的C源代碼實例。
表4. 塊交換和有界隊列存儲器配置實例
|
|
圖3. 有界隊列和塊交換流程
應(yīng)用ROM閃存例程
MAXQ微控制器具有片內(nèi)閃存支持程序,駐留在ROM (只讀內(nèi)存)中,用來對閃存進行編程、擦寫和校驗。有兩種方法來調(diào)用這些例程。第一種也是最快的方法是直接訪問,只需通過以下代碼提供一個頭文件:
u16 flashEraseSector(void *); u16 flashEraseAll(void); u16 flashWrite(u16 *pAddress, u16 iData);然后,加入鏈接定義給每個例程分配合適的地址。對于IAR鏈接文件,加入下面幾行語句:
-DflashEraseSector=0x8XXX -DflashEraseAll=0x8XXX -DflashWrite=0x8XXX具體使用時,用每個例程相應(yīng)的存儲器地址替代0x8XXX。其他編譯器可能使用不同的方法添加這些聲明。
注意,直接訪問方法與未來的ROM版本無法前向兼容。
第二種為查表法。盡管這種方法兼容性較好,但是執(zhí)行時間較長。在下面每一段例程說明的后面,匯編例程采用查表法獲取ROM應(yīng)用例程的地址。表5所示為應(yīng)用ROM提供的幾個閃存例程。關(guān)于ROM應(yīng)用例程的完整列表,參見特定MAXQ器件的用戶指南。
表5. 應(yīng)用ROM閃存例程
Routine Number | Routine Name | Entry Point ROMTable = ROM[800Dh] |
Entry Point Physical Address |
2 | flashEraseSector | ROM[ROMTable + 1] | 0x8XXX |
3 | flashEraseAll | ROM[ROMTable + 2] | 0x8XXX |
15 | flashWrite | ROM[ROMTable + 14] | 0x8XXX |
flashWrite
Routine | u16 flashWrite(u16 *pAddress, u16 iData) |
Summary | Programs a single word of flash memory. |
Inputs | A[0] - Word address in flash memory to which to write. A[1] - Word value to write to flash memory. |
Outputs | Carry: Set on error and cleared on success. If set, then A[0] contains one of the following error codes: 1 : failure due to software timeout 2 : failure reported by hardware (DQ5/FERR) 4 : command not supportedSW_FERR - Set on error, cleared on success. |
Notes | The watchdog must not be active, or the watchdog timeout must be set long enough to complete this routine without triggering a reset. |
以下匯編代碼實例采用間接尋址方法(查表)調(diào)用flashWrite()應(yīng)用例程。該例程可由C代碼調(diào)用。
; This routine is callable by C code using the following prototype ; u16 flashWrite(u16 *pAddress, u16 iData); ; flashWrite: move APC, #0 ; No auto inc/dec of accumulator. move AP, #2 ; Set ACC to A[2] move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #14 ; Add the index to the flashWrite routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0]flashEraseSector
Routine | u16 flashEraseSector(void *pAddress) |
Summary | Erases a single sector of flash memory |
Inputs | A[0] - Address located in the sector to erase. |
Outputs | Carry: Set on error and cleared on success. If set, then A[0] contains one of the following error codes: 1 : failure due to software timeout 2 : failure reported by hardware (DQ5/FERR) 4 : command not supported SW_FERR - Set on error, cleared on success. |
Notes | The watchdog must not be active, or the watchdog timeout must be set long enough to complete this routine without triggering a reset. |
; This routine is callable by C code using the following prototype ; u16 flashEraseSector(void *pAddress); ; flashEraseSector: move APC, #0 ; No auto inc/dec of accumulator. move AP, #1 ; Set ACC to A[1] move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #1 ; Add the index to the flashEraseSector routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret ; Status returned in A[0]flashEraseAll
Routine | void flashEraseAll(void) |
Summary | Erases the entire program and data flash memory, including the boot loader sector. This routine is not normally used for IAP, as great care must be taken to ensure that the erase/programming sequence is not interrupted. |
Inputs | None |
Outputs | Carry: Set on error and cleared on success.SW_FERR: Set on error, cleared on success. |
Notes | The watchdog must not be active, or the watchdog timeout must be set long enough to complete this routine without triggering a reset. |
; This routine is callable by C code using the following prototype ; void flashEraseAll(void); ; flashEraseAll: move APC, #0 ; No auto inc/dec of accumulator. move AP, #0 ; Set ACC to A[0] move DP[0], #0800Dh ; This is where the address of the table is stored. move ACC, @DP[0] ; Get the location of the routine table. add #2 ; Add the index to the flashEraseAll routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. call ACC ; Execute the routine. ret
在應(yīng)用編程
大多數(shù)基于閃存的系統(tǒng)要求,當系統(tǒng)安裝到終端產(chǎn)品上以后,其固件必須具備升級的能力。這種功能稱為在應(yīng)用編程(IAP)。本節(jié)概述了創(chuàng)建一個IAP應(yīng)用程序的要領(lǐng)。上面列出的應(yīng)用ROM閃存例程,可完成閃存ROM所需要的所有擦除和寫入操作,從而允許用戶代碼對閃存進行操作。與其他子程序調(diào)用相似,程序執(zhí)行完成之后,控制將返回到用戶代碼。
要實現(xiàn)可靠的IAP,引導(dǎo)裝載程序必須與主程序分開。這樣可以確保在經(jīng)歷一次未竟編程之后,可以重新啟動編程過程。
引導(dǎo)裝載程序
初始化以后,由于ROM跳轉(zhuǎn)至地址0x0000,因此,引導(dǎo)裝載程序的入口應(yīng)為0x0000。不同的MAXQ器件,其引導(dǎo)閃存分區(qū)的大小也不相同。可以根據(jù)需要,將引導(dǎo)程序擴展至多個閃存分區(qū),用戶應(yīng)用程序代碼不能使用已被占用的任何分區(qū)。擦除和寫入閃存時必須滿足的特定要求見表6。
表6. 調(diào)用應(yīng)用ROM閃存例程的要求
You cannot erase or program from the same flash sector from which you are executing code. This is not normally a problem since the flash Boot Sector should never be erased during IAP. |
The watchdog must not be enabled or the watchdog timeout must be set long enough to complete this routine without triggering a reset before the flashEraseSector() routine is called. If the watchdog time out occurs before the erase is complete, it will reset the part. Erasing a sector typically takes 0.7 seconds; it can take up to 15 seconds under worst case conditions. |
Since the System Control Register bit SC.UPA must be set to 0 to access the Utility ROM, a ROM Utility routine cannot be called directly from program memory addresses = 0x8000. If access to a Utility ROM routine is required from a program in upper memory (= 0x8000), the program must indirectly call the ROM routine through a routine residing in lower memory (<0x8000). This effectively limits the boot loader to = 64kB (32kB x 16). |
圖4中的流程說明了脫離復(fù)位狀態(tài)后MAXQ的操作。ROM進行自診斷并確定閃存已經(jīng)準備好以后,ROM初始化代碼直接跳轉(zhuǎn)到地址0x0000。請閱讀相關(guān)的數(shù)據(jù)手冊和用戶指南,確定您的MAXQ微控制器是否按照這個啟動順序運行。
圖4. ROM初始化流程示意
圖5給出了一個簡單的引導(dǎo)裝載程序的流程圖。當使用引導(dǎo)裝載程序?qū)崿F(xiàn)IAP時,對于16kB (8K x 16)的引導(dǎo)裝載程序,主應(yīng)用程序的入口通常位于地址0x2000 + 標題偏移,對于32kB (16K x 16)的引導(dǎo)裝載程序,則位于地址0x4000 + 標題偏移。一個簡單的應(yīng)用程序標題如下所示:
typedef struct { u16 iSize; // The size of the application in words u32 iCRC; // The CRC of the application u8 ID[8]; // ID string for current application } APPLICATION_HEADER;引導(dǎo)裝載程序可以利用此標題所提供的信息確定主程序的有效性,如果需要,還可以報告其版本標識。
圖5. 閃存引導(dǎo)裝載流程示意圖
編程過程本身非常簡單。首先調(diào)用flashEraseSector(),擦除含有主程序代碼的每個分區(qū)。然后調(diào)用flashWrite()逐字寫入要編程的代碼字。應(yīng)最先擦除含有應(yīng)用程序標題的塊,而最后編程CRC數(shù)據(jù),以便將出現(xiàn)錯誤CRC匹配的概率降到最低。下面給出通過串口獲取數(shù)據(jù)來刷新微控制器的一個簡單例程:
/* // VerySimpleReFlash() // As simple as it gets. // Step 1. Wait for erase command, then erase flash. // Step 2. Wait for program command, then program flash one word // at a time. */ void VerySimpleReFlash() { u16 iStatus; // The status returned from flash utility ROM calls u16 iSize; // The size of the main code to program u16 *pAddress = 0x2000; // The starting address of the main application InitializeCOMM(); // Can be CAN or UART WaitForEraseCommand(); SlowDownWatchdog(); // If watchdog enabled set update > 15s iStatus = flashEraseSector(C_ADDRESS_SECTOR_1); if (iStatus == 0) iStatus = flashEraseSector(C_ADDRESS_SECTOR_2); UpdateWatchdog(); // Prevent watchdog timeout SendFlashErasedResponse(iStatus); if (iStatus) ResetMicro(); iSize = WaitForProgramCommand(); while (iSize--) { u16 iData = GetWordFromCOMM(); iStatus = flashWrite(pAddress, iData); if (iStatus) break; ++pAddress; UpdateWatchdog(); // Prevent watchdog timeout } SendFlashWriteResponse(iStatus); ResetMicro(); }那些引導(dǎo)裝載程序沒有使用的程序空間,可用于其他例程和/或常數(shù)的存儲。一個很好的例子是將間接調(diào)用應(yīng)用ROM例程的所有子程序保存在這里,例如上面“應(yīng)用ROM閃存例程”中所給出的一些子程序。在引導(dǎo)程序分區(qū)存儲其他信息時需要注意:除非部分或全部擦除引導(dǎo)裝載程序自身,否則無法將其擦除。
采用基于RAM的閃存例程實現(xiàn)IAP
當不要求故障恢復(fù)時,可采用基于RAM的閃存例程來刷新MAXQ微控制器。該方法要求主程序拷貝一個小的、可再定位的閃存編程例程到RAM中,然后再跳轉(zhuǎn)到該例程。表7列出了從RAM中執(zhí)行代碼需要注意的幾個限制。
表7. 從RAM中執(zhí)行代碼的限制
SC.UPA must be set to 0 before executing a RAM-based routine. This means that the application must jump to the RAM routine from the code segments P0 & P1. |
RAM cannot be accessed as data and program at the same time. This means that only the registers and hardware stack are available for data storage. |
The Interrupt Vector must point to a RAM routine if interrupts are enabled. Typically interrupts are turned off and polling is used due to the simplicity of the RAM reflash routine. |
閃存例程一般通過UART或者CAN接口來進行通信。為實現(xiàn)更可靠的錯誤恢復(fù)機制,最好是接收小的數(shù)據(jù)包,并發(fā)送某種類型的確認信息。圖6給出了一個更新例程。記住,在掉電之前,如果沒有成功完成重新編程,需要通過JTAG端口對微控制器重新編程。
圖6. 簡化的RAM更新例程流程圖
評論
查看更多