本應用筆記介紹如何管理帶有頁面擦除(PE)閃存的MAXQ7665閃存微控制器(μC)中的內(nèi)部數(shù)據(jù)和程序閃存。此討論包括有關執(zhí)行程序閃存的應用程序內(nèi)編程 (IAP) 的一般信息。
內(nèi)存映射
本節(jié)詳細介紹MAXQ7665 μC系列各種存儲器尺寸的一般閃存信息和存儲器結構。MAX7665器件提供三種類型的閃存:扇區(qū)擦除、單字擦除和兩頁擦除。本文檔僅介紹帶PE閃存和兩頁擦除數(shù)據(jù)閃存的MAXQ7665器件;該討論不適用于僅扇區(qū)擦除設備或具有單字可擦除數(shù)據(jù)閃存的設備。
表1至表3分別顯示了從程序閃存、實用程序ROM和數(shù)據(jù)SRAM執(zhí)行代碼時16KB器件的存儲器映射。圖 1 和圖 2 顯示了 16KB 和 256B 程序閃存的扇區(qū)和頁面結構。其他閃光燈選項可用;請參考MAXQ7665數(shù)據(jù)資料獲取完整列表。
表 1.16KB 內(nèi)存空間 — 從程序閃存執(zhí)行
表 2.16KB 內(nèi)存空間 — 從實用程序 ROM 執(zhí)行
表 3.16KB 內(nèi)存空間 — 從數(shù)據(jù) SRAM 執(zhí)行
圖1.16KB程序閃存的扇區(qū)/頁面結構。
圖2.256B數(shù)據(jù)閃存的扇區(qū)/頁面結構。
使用數(shù)據(jù)閃存存儲數(shù)據(jù)
閃存可用于可靠地存儲系統(tǒng)數(shù)據(jù),這些數(shù)據(jù)需要在系統(tǒng)運行期間進行一次或定期編程。與EEPROM不同,MAXQ7665上的PE閃存不能被字節(jié)/字擦除。必須一次擦除兩頁。這通常需要 10 毫秒,但在最壞的情況下可能需要更長的時間。在此期間,用戶代碼將停止,因此不會進行其他處理。
有多種存儲數(shù)據(jù)到閃存的技術,在為應用程序選擇適當?shù)姆椒〞r,必須考慮幾個因素。應用程序是否需要確保在新數(shù)據(jù)完全寫入之前當前數(shù)據(jù)保持不變?如果答案是肯定的,那么某種形式的組切換技術是必要的,這樣在寫入新數(shù)據(jù)時不會丟失當前數(shù)據(jù)。在產(chǎn)品的生命周期內(nèi)將發(fā)生多少個擦除/寫入周期?如果擦除/寫入周期數(shù)將超過數(shù)據(jù)手冊中規(guī)定的最大值,則使用綁定隊列方法可以將這些周期分散到數(shù)據(jù)閃存的多頁上,從而顯著增加總擦除/寫入周期。對于大多數(shù)周期性數(shù)據(jù)存儲需求,銀行交換和/或有界隊列技術滿足系統(tǒng)的可靠性要求和需求。以下是每種技術的簡單示例。
銀行切換
存儲體交換是在擦除/寫入周期中防止數(shù)據(jù)丟失或損壞的有效方法。此方法通過始終維護至少一個數(shù)據(jù)副本來很好地工作。組交換的缺點是它需要的數(shù)據(jù)閃存量是有界隊列方法的兩倍。事實上,銀行切換實際上只是隊列大小為 2 的有界隊列方法。因此,有關如何實現(xiàn)有界隊列的詳細信息,請參閱下一節(jié)。
有界隊列
有界隊列是受固定數(shù)量的項限制的隊列,通常在處理定期數(shù)據(jù)時使用。有界隊列是通過創(chuàng)建與所需數(shù)據(jù)塊大小相等的“條目”的固定長度隊列來形成的。條目大小特定于應用程序,必須向上舍入到最接近的頁面擦除邊界。注意:可以擦除的最小大小為兩頁/字。雖然可以根據(jù)應用程序要求以多種方式對數(shù)據(jù)閃存進行分區(qū),但由于數(shù)據(jù)閃存施加的兩頁擦除限制,條目被限制為兩頁的倍數(shù)。例如,512 x 16 數(shù)據(jù)閃存可以分為 32 個 16 字條目,這將產(chǎn)生表 4 中的內(nèi)存映射。
初始化后,啟動例程可以掃描隊列條目以確定隊列中的下一個可用條目。一旦隊列已滿,就可以使其包裝回開頭。擦除數(shù)據(jù)閃存條目后,可以寫入新條目。圖 3 說明了進入有界隊列的條目流。
閃光燈[ ] | |
隊列索引 | 數(shù)據(jù)閃存地址 |
31 | 0xC1F0-0xC1FF |
30 | 0xC1E0-0xC1EF |
29 | 0xC1D0-0xC1DF |
. . . . | . . . . |
2 | 0xC020-0xC05F |
1 | 0xC010-0xC03F |
0 | 0xC000-0xC00F |
圖3.有界隊列流的圖示。
實用程序 ROM 閃存例程
為了編程、擦除和驗證閃存,MAXQ7665微控制器在ROM (只讀存儲器)中提供了片內(nèi)閃存支持程序。有兩種方法可以訪問這些例程:直接訪問和通過查找表間接訪問。最快的方法是直接訪問,即直接調(diào)用例程。為此,請?zhí)峁┌韵滦械念^文件:
u16 flashErasePage(void *); u16 flashEraseSector(void *); u16 flashEraseAll(void); u16 dataFlashWrite(u16 *pAddress, u16 iData); u16 dataFlashErasePage(void *); u16 dataFlashEraseSector(void *); u16 dataFlashEraseAll(void);
接下來,添加鏈接器定義以為每個例程分配適當?shù)牡刂?。對?IAR 鏈接器文件,添加的行如下所示:
-DflashEraseSector=0x8XXX -DflashErasePage=0x8XXX -DflashEraseAll=0x8XXX
將 0x8XXX 替換為每個例程的相應內(nèi)存地址。其他編譯器可能會使用不同的方法來添加這些引用。
flashWrite() 實用程序例程不能直接從 C 調(diào)用,因為參數(shù)傳遞與 C 語言不兼容。必須編寫如下所示的小型程序集例程才能調(diào)用此函數(shù)。
注意:直接訪問方法不提供與未來 ROM 版本的向前兼容性。
第二種方法是通過表查找進行間接訪問。此方法提供了與未來ROM版本的更大兼容性,但消耗更多的執(zhí)行時間。在下面描述的每個例程之后,程序集例程使用表查找方法來獲取 ROM 實用程序例程的地址。表 5 顯示了實用程序 ROM 提供的閃存例程。有關實用程序ROM例程的完整列表,請參考MAXQ7665用戶指南。
例程編號 | 例程名稱 | 入口點可 ROMTable = ROM[800Dh] | 入口點物理地址 |
1 | 閃寫 | 羅姆[可浪漫] | 0x8XXX |
2 | 閃光擦除頁面 | 羅姆[可浪漫 + 1] | 0x8XXX |
3 | 閃光擦除全部 | 羅姆[可浪漫 + 2] | 0x8XXX |
4 | 移動DP0 | 羅姆[可浪漫 + 3] | 0x8XXX |
16 | 閃存擦除扇區(qū) | 羅姆[可浪漫 + 15] | 0x8XXX |
17 | 數(shù)據(jù)閃寫 | 羅姆[可浪漫 + 16] | 0x8XXX |
19 | 數(shù)據(jù)閃光擦除頁面 | 羅姆[可浪漫 + 18] | 0x8XXX |
20 | 數(shù)據(jù)閃存擦除扇區(qū) | 羅姆[可浪漫 + 19] | 0x8XXX |
21 | 數(shù)據(jù)閃存擦除全部 | 羅姆[可浪漫 + 20] | 0x8XXX |
常規(guī): | u16 flashWrite(u16 *pDest, u16 *pSrc) |
總結: | 對單頁(32 字)的程序閃存進行編程。 |
輸入: |
DP[0] - 閃存中的目標地址。 DP[1]—SRAM 中的源地址,包含 32 個要寫入的數(shù)據(jù)字。 |
輸出: |
攜帶:錯誤時設置,成功時清除。如果設置,則 A[0] 包含以下錯誤代碼之一: 1:由于軟件超時而導致的故障 2:硬件 (DQ5/FERR) 報告故障 4: 不支持 命令 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
下面的匯編代碼示例使用間接尋址方法(查找表)調(diào)用 flashWrite() 實用例程。此例程可由 C 代碼調(diào)用。
; This routine is callable by C code using the following prototype ; u16 flashWrite(u16 *pDest, u16 *pSrc); ; 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. push DP[1] ; Save Frame Pointer on the stack. move DP[0],A[0] ; Move argument 0(dest address) to DP[0]. move DP[1],A[1] ; Move argument 1(src address) to DP[1]. call ACC ; Execute the routine. pop DP[1] ; Restore Frame Pointer. ret ; Status returned in A[0].
常規(guī): | u16 閃存擦除頁面(無效 *pAddress) |
總結: | 擦除兩頁的程序閃存塊。 |
輸入: | A[0] - 位于要擦除的兩頁塊中的地址,即要擦除第 0 頁和第 1 頁,A[0] 可以包含從 0x0000 到 0x001F 的任何地址。 |
輸出: |
攜帶:錯誤時設置,成功時清除。如果設置,則 A[0] 包含以下錯誤代碼之一: 1:由于軟件超時而導致的故障 2:硬件 (DQ5/FERR) 報告故障 4: 不支持 命令 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 flashErasePage(void *pAddress); ; flashErasePage: 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].
常規(guī): | 虛空閃光擦除全部(虛空) |
總結: | 擦除整個程序和數(shù)據(jù)閃存。此例程只能從 RAM 調(diào)用。 |
輸入: | 沒有 |
輸出: |
攜帶:錯誤時設置,成功時清除。 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
; 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
常規(guī): | 移動DP0 |
總結: | 讀取閃存的一個字。 |
輸入: | DP[0] - 閃存中的源地址。添加0x8000以讀取程序閃存。 |
輸出: | GR將包含指定地址的數(shù)據(jù)。 |
筆記: | 不能直接從 C 調(diào)用此函數(shù),因為參數(shù)和返回寄存器與 C 調(diào)用約定不兼容。 |
下面的匯編代碼示例將 moveDP0 轉(zhuǎn)換為 C 可調(diào)用例程。如果速度對應用程序至關重要,則應為特定任務編寫自定義匯編語言例程。有幾個像這樣的實用程序ROM例程將有助于從閃存寫入有效的數(shù)據(jù)傳輸。
; This routine is callable by C code using the following prototype ; u16 flashRead(u16 *pAddress); ; flashRead: 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 #3 ; Add the index to the moveDP0 routine. move DP[0], ACC move ACC, @DP[0] ; Retrieve the address of the routine. push DP[1] ; Save Frame Pointer on the stack. move DP[0],A[0] ; Move argument 0(src address) to DP[0]. call ACC ; Execute the routine. pop DP[1] ; Restore Frame Pointer. move A[0],GR ret ; Data word returned in A[0].
常規(guī): | u16 閃存擦除扇區(qū)(無效 *pAddress) |
總結: | 擦除程序閃存的單個扇區(qū)。 |
輸入: | A[0] - 位于要擦除的扇區(qū)中的地址。 |
輸出: |
攜帶:錯誤時設置,成功時清除。如果設置,則 A[0] 包含以下錯誤代碼之一: 1:由于軟件超時而導致的故障 2:硬件 (DQ5/FERR) 報告故障 4: 不支持 命令 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
; 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 #15 ; 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].
常規(guī): | u16 dataFlashWrite(void *pAddress, u16 *pData) |
總結: | 對單個字的數(shù)據(jù)閃存進行編程。 |
輸入: |
A[0] - 閃存中要寫入的字地址。 A[1] — 要寫入閃存的字值。 |
輸出: |
攜帶:錯誤時設置,成功時清除。如果設置,則 A[0] 包含以下錯誤代碼之一: 1:由于軟件超時而導致的故障 2:硬件 (DQ5/FERR) 報告的故障 4: 不支持 命令 SW_FER - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
下面的匯編代碼示例使用間接尋址方法(查找表)調(diào)用 dataFlashWrite() 實用工具例程。此例程可由 C 代碼調(diào)用。
; This routine is callable by C code using the following prototype ; u16 dataFlashWrite(void *pAddress, u16 iData); ; dataFlashWrite: 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 #16 ; 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].
常規(guī): | u16 dataFlashErasePage(void *pAddress) |
總結: | 擦除兩頁數(shù)據(jù)閃存。 |
輸入: | A[0] - 位于要擦除的兩頁塊中的地址,即要擦除第 0 頁和第 1 頁,A[0] 可以包含地址0x4000或0x4001。 |
輸出: |
攜帶:錯誤時設置,成功時清除。如果設置,則 A[0] 包含以下錯誤代碼之一: 1:由于軟件超時而導致的故障 2:硬件 (DQ5/FERR) 報告故障 4: 不支持 命令 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 dataFlashErasePage(void *pAddress); ; dataFlashErasePage: 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 #18 ; Add the index to the dataFlashErasePage 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].
常規(guī): | u16 dataFlashEraseSector(void *pAddress) |
總結: | 擦除數(shù)據(jù)閃存的單個扇區(qū)。 |
輸入: | A[0] - 位于要擦除的扇區(qū)中的地址。 |
輸出: |
攜帶:錯誤時設置,成功時清除。如果設置,則 A[0] 包含以下錯誤代碼之一: 1:由于軟件超時而導致的故障 2:硬件 (DQ5/FERR) 報告故障 4: 不支持 命令 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
; This routine is callable by C code using the following prototype ; u16 dataFlashEraseSector(void *pAddress); ; dataFlashEraseSector: 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 #19 ; Add the index to the dataFlashEraseSector 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].
常規(guī): | void dataFlashEraseAll(void) |
總結: | 擦除整個數(shù)據(jù)閃存。 |
輸入: | 沒有 |
輸出: |
攜帶:錯誤時設置,成功時清除。 SW_FERR - 出錯時設置,成功時清除。 |
筆記: | 監(jiān)視器不得處于活動狀態(tài),或者監(jiān)視器超時必須設置足夠長的時間才能在不觸發(fā)重置的情況下完成此例程。 |
; This routine is callable by C code using the following prototype ; void dataFlashEraseAll(void); ; dataFlashEraseAll: 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 #20 ; 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
應用程序內(nèi)編程 (IAP)
大多數(shù)基于閃存的系統(tǒng)的一個重要要求是能夠在系統(tǒng)安裝在最終產(chǎn)品中時更新固件。此過程稱為應用程序內(nèi)編程 (IAP)。本節(jié)將概述創(chuàng)建 IAP 應用程序的一般準則。
上一節(jié)中概述的實用程序 ROM 閃存例程執(zhí)行擦除和寫入閃存 ROM 所需的所有操作。因此,最終用戶應用程序可以在閃存上執(zhí)行操作。與任何其他子例程調(diào)用一樣,控件將在例程完成后返回到最終用戶的代碼。
對于可靠的 IAP,引導加載程序應用程序必須與主應用程序分開。這確保了即使在發(fā)生不完整的重編程序列后,也可以重試重編程過程。
引導加載程序
ROM 在初始化后跳轉(zhuǎn)到尋址0x0000。因此,引導加載程序應用程序的入口點必須放在0x0000處。引導加載程序應用程序可以根據(jù)需要擴展到任意數(shù)量的閃存扇區(qū)/頁面,但使用的任何頁面都不適用于用戶的應用程序代碼。表6列出了擦除和寫入閃存時必須滿足的具體要求。
不能從執(zhí)行代碼的同一閃存頁中擦除或編程。這通常不是問題,因為在 IAP 期間絕不應擦除閃存引導加載程序應用程序。 |
在調(diào)用 flashEraseSector() 或 flashErasePage() 例程之前,必須將監(jiān)視器超時設置得足夠長,以便完成此例程,而不會觸發(fā)重置。如果在擦除完成之前出現(xiàn)看門狗超時,它將重置器件。 |
因為系統(tǒng)控制寄存器位,SC。UPA,必須設置為 0 才能訪問實用程序 ROM,實用程序 ROM 例程不能直接從程序內(nèi)存地址調(diào)用≥ 0x8000。如果需要從上層內(nèi)存 (≥ 0x8000) 中的程序訪問實用程序 ROM 例程,則程序必須通過駐留在下層內(nèi)存中的例程間接調(diào)用 ROM 例程 (< 0x8000)。此限制有效地將引導加載程序限制為 = 64KB (32KB x 16)。 |
圖4中的流程圖顯示了MAXQ7665退出復位狀態(tài)時的作用。在診斷ROM本身并驗證閃存已準備就緒后,ROM初始化代碼將直接跳轉(zhuǎn)到地址0x0000。
圖4.簡化ROM初始化的流程圖。
圖 5 流程圖顯示了一個簡單的引導加載程序應用程序如何運行。一個簡單的應用程序標頭可能如下所示:
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;
使用此標頭中的信息,引導加載程序可以檢查主應用程序的有效性,并在請求時報告版本標識。
圖5.簡化的閃存引導加載程序的流程圖。
編程順序本身非常簡單。通過調(diào)用 flashEraseSector() 和/或 flashErasePage() 擦除包含主應用程序代碼的每個扇區(qū)/頁面。然后通過為每 32 個需要編程的單詞調(diào)用 flashWrite() 一次寫一頁。我們建議您先擦除包含應用程序標題的頁面,最后對CRC數(shù)據(jù)進行編程,以最大程度地減少CRC匹配錯誤的可能性。重新刷新通過串行端口獲取數(shù)據(jù)的微控制器的一個非常簡單的例程如下所示:
/* // VerySimpleReFlash() // As simple as it gets. // Step 1. Wait for erase command, then erase flash. // Step 2. Wait for program command, program flash one word at a time. */ void VerySimpleReFlash() { u16 iStatus; // The status returned from flash utility ROM calls s32 iSize; // The size of the main code to program u16 *pAddress = 0x2000; // The starting address of the main application u16 i; InitializeCOMM(); // Can be CAN or UART. WaitForEraseCommand(); // Assume that application starts at the beginning of a sector. for (i=C_START_SECTOR;i0) { u16 iData[32]; Get32WordsFromCOMM(iData); iStatus = flashWrite(pAddress, iData); if (iStatus) break; pAddress += 32; iSize -= 32; UpdateWatchdog(); // Prevent timeout } SendFlashWriteResponse(iStatus); ResetMicro(); }
審核編輯:郭婷
-
微控制器
+關注
關注
48文章
7552瀏覽量
151423 -
閃存
+關注
關注
16文章
1789瀏覽量
114926 -
存儲器
+關注
關注
38文章
7492瀏覽量
163834
發(fā)布評論請先 登錄
相關推薦
評論