介紹
在嵌入式微控制器應用中,通常都要用到非易失性存儲器。無論是掉電時維持需要保存的設置,還是存儲公司的重要記錄,可靠的非易失性存儲器都是現代微控制器領域的一個基本單元。非易失性存儲常常采用外部串行存儲器實現。多年以來,該領域用到了數十億顆類似存儲器件,它們的可靠性得到了的廣泛認可。目前,存儲器可以做到幾百字節到1兆字節甚至更大的容量,在每一個需要保持設置的設備中,都能找到這樣一個緊湊、廉價的器件。
包括EEPROM、閃存和旋轉式存儲器在內,所有類型的非易失性存儲器都面臨一個共同的問題:寫周期被中斷時,數據會丟失。一旦在寫周期執行過程中掉電,那么即使再恢復電源,也很難修復損壞的數據。
本文提出了一種基于事務的提交-回退機制,用于保護一個外部串行EEPROM存儲器件的內容。這些措施同樣適用于大多數MAXQ微控制器的內置EEPROM??梢?a href="http://www.1cnz.cn/soft/special/" target="_blank">下載本應用的代碼文件(ZIP,20.5kb)。
I2C EEPROM的特點
串行存儲器件有多種接口,但最常用的接口是I2C接口。這種總線接口有很多優點:高度標準化的接口;控制器和存儲器之間只需兩條線;而且具有靈活的時序要求,可以由軟件驅動。一個I2C主機可以驅動很多I2C從機,從而最大程度減少了主機的引腳數。在所有EEPROM器件中,寫周期都要比讀周期長的多。因為在寫周期過程中,電荷需要借助隧道效應并通過絕緣層進行轉移,而這個過程很費時間。雖然增加電壓可以加快這個過程,但是過高的電壓會導致絕緣層的介質擊穿,從而損壞器件。典型的EEPROM器件寫周期持續10毫秒左右;而讀周期通常需要幾百個納秒。
為了顯著縮短寫周期的時間,許多I2C EEPROM器件采用頁面模式。該模式允許將多個字節傳送到緩存中,然后將數據一次性寫入存儲區。I2C存儲器件的典型頁面尺寸為32字節。因此,可以在一個寫周期內向EEPROM填入32個字節。
這一點非常重要,因為串行EEPROM器件都具有特定的耐久度:即每個頁面所能承受的寫周期次數上限。典型的寫周期次數從10,000到1,000,000次。然而,即使存儲器件能夠承受1百萬次寫周期,軟件也會很快將其損耗殆盡。軟件每秒僅執行100次寫周期,那么不到3個小時就會耗盡器件的寫周期次數。
考慮到這些基本的EEPROM特性,設計者為一個嵌入式處理器設計可靠的非易失存儲系統時,需切記以下幾點:
- 不要在同一頁面上反復執行寫操作。尤其是不要將某個頁面設置成寫入任何其它頁面時都要更新的“目錄”。
- 如果在寫周期過程中電源被中斷,必須提供以下機制:(1) 檢測被中斷的寫操作;(2) 完成被中斷的操作;(3) 或者將事件回退至寫操作之前的狀態。
- 必須通過某些數據校驗機制(校驗和、CRC或消息摘要)來保證數據的完整性。
設計目標
雖然上面提到的EEPROM問題可通過多種非易失文件系統加以解決,但這樣的文件機制對于小型嵌入式微控制器來說負擔過重。很多文件系統需要更多的RAM,遠遠超出了小型微控制器所能提供的容量,而且對于多數應用,也不需要一個完整的文件系統。考慮到這一點,下面列出了EEPROM數據保護機制的設計目標:
- 精簡:保護機制用于存儲校驗數據的空間不應超過EEPROM的10%,它應該只需要少量的計算開銷。
- 塊大小:被保護的塊大小,應該和EEPROM的寫操作頁面大小一樣。由于EEPROM器件的頁面大小通常是2的偶數次冪,因此與每個塊保留1或2個字節的做法相比,相同的尺寸大小更便于軟件編碼。
- 耐久性:每個保護周期不要對同一頁面進行寫操作。
- 可靠性:每次掉電情況下,數據都應是可恢復的。
這里提到的保護機制有6個接口函數:讀、寫、提交、回退、檢查和清理。
讀函數接收一個塊編號和一個指向32字節緩存的指針。如果緩存地址和塊編號處于有效范圍內,程序就會將指定的塊數據讀入緩存,并校驗數據的有效性。它會返回如下狀態:有效讀(valid read)、無效讀(invalid read)、無效緩存地址(invalid buffer address)、無效頁面編號(invalid page number)或保護失敗(protection failure)。
寫函數接收一個塊編號和一個指向填好數據的32字節緩存的指針。如果緩存地址和塊編號處于有效范圍內,程序就會將數據寫入非易失性緩存,并標記緩存狀態以準備提交。
提交和回退函數,是可以在寫操作之后執行的互補型操作。提交函數將最近被寫入的緩存數據復制到對應的存儲區最終位置,并為下一個待寫入的數據塊準備好緩存結構?;赝撕瘮祵嶋H上就是一個“取消”操作。它消除最近一次寫操作產生的效果,并為下一個寫操作準備好緩存子系統。
檢查函數讀取存儲器件的每個數據塊,并檢查存儲數據的有效性。該函數還檢查緩存子系統,以確保沒有未執行的寫操作。任何無效塊或未執行的寫操作都會使檢查函數返回一個錯誤狀態。
清理函數修復一個數據損壞的EEPROM。實際上,它將試圖找出發生的錯誤,并采取相應的解決措施。
關于這些函數的更多細節,參見下面的操作詳解。
圖1. EEPROM存儲器的結構。存儲器被劃分為3個區域:主存儲區,包含實際用戶數據;校驗存儲區,包含主存儲區每1頁的CRC;緩存,包含存儲臨時寫入數據的四個緩存。
EEPROM結構
參考上面圖1給出的EEPROM結構。EEPROM包含三個主要區域:- 主存儲區:EEPROM的最大區域用于存儲用戶數據。在一個16kB器件內,包括512頁、每頁32字節的存儲空間。在這樣的器件中,開始的473個頁面專門用來存儲數據。
- 校驗存儲區:EEPROM的第二個部分,用于校驗主存儲區每個頁面的數據。校驗存儲區的每1頁都包含15個16位的CRC值。每1頁的最后1個CRC用于校驗本頁數據。校驗存儲區占用31頁(從473到503頁)。
- 緩存:EEPROM的最后部分,包含由8個頁面構成的4個寫緩存。每個緩存包含4個域:數據域,它包含32字節數據,執行下一個提交命令時,數據將被寫入主存儲區;地址域,它表示緩存數據要寫入的頁面地址;狀態域,它表示緩存的狀態(包括可用(available)、占用(occupied)和終止(expired)狀態);16位CRC域,用來校驗整個寫緩存。緩存結構見圖1所示。
這種EEPROM結構可以實現主要的設計目標。首先,由于主存儲區每1頁數據的校驗結果都存儲在另一個位置,所以頁面的所有位都用于存儲用戶數據。其次,由于主存儲區的每1頁都通過校驗存儲區的特定字來校驗,因此校驗存儲區不會有單點錯誤,并且也不會在每個寫周期中都去更新整個校驗存儲區的同一頁面。最后,使用4個寫緩存分散了寫周期帶來的損耗。
操作詳解
對于一個不帶保護功能的EEPROM,具體操作非常簡單。一個讀周期簡單地將字節從所選擇的地址傳送給主機;一個寫周期將字節從主機寫入EEPROM,并等待操作完成(大多數器件需要幾個毫秒的時間)。然而,在一個提供保護的EEPROM環境下,讀和寫操作就比較復雜了。在以下各節中,對每個操作進行了分解,以便了解函數被調用時到底是如何操作的。讀操作
圖2. 讀操作的流程圖
讀操作這個最簡單的接口函數,也是相當復雜的。圖2給出了操作流程:
- 檢查頁面地址和緩存地址,以檢驗它們的有效性。如果地址無效,則就此結束操作,函數返回一個無效緩存地址或無效頁面編號錯誤代碼。
- 將所選頁面讀入緩存。
- 計算校驗頁面的地址,并將相應的校驗頁面讀入暫存區。
- 計算校驗頁面的CRC。如果校驗頁面的數據無效,則返回一個保護失敗錯誤代碼。
- 計算數據緩存的CRC,并將其與暫存區中對應讀取頁面的CRC進行比較。如果CRC匹配,則程序返回有效讀代碼;如果CRC不匹配,則程序返回無效讀代碼。無論結果怎樣,實際讀取的數據都保存在返回緩存中,以供調用讀操作的程序使用。
寫操作
圖3. 寫操作的流程圖
如上所述,寫操作并不是真正將數據寫入主存儲區。實際上,寫操作是將數據寫入4個緩存之一。在這種方式下,主存儲區內原先的數據將一直保持到整個有效寫操作流程完成后為止。圖3的流程說明了以下幾點:
- 檢查頁面地址和緩存地址,以檢驗其有效性。如果地址無效,操作在這里結束,函數返回一個無效緩存地址或無效頁面編號錯誤代碼。
- 讀取每個寫緩存的狀態域。如果任何緩存處于占用狀態,則操作失敗并返回寫過程(write sequence)錯誤代碼。
- 4個寫緩存之一應處于終止狀態。如果是這樣,激活下一個緩存。
- 數據被復制到寫緩存的數據域。
- 頁面地址被寫入地址域。計算CRC校驗結果并將其寫入CRC域。將狀態改為占用。將前一個緩存置為可用狀態(即更新原來的終止狀態)。
需要注意,此時對新寫的頁面進行讀操作,將返回頁面原來的數值。只有等提交操作完成后,才會返回新值。
提交操作
圖4. 提交操作的流程
提交函數不需要參數。它的工作就是如實地將數據從寫緩存傳送到主存儲區,然后將寫緩存標記為終止狀態。提交函數的操作流程如圖4所示:
- 讀取每個寫緩存的狀態域。應該只有1個緩存標記為占用狀態。否則,函數在此結束,并返回一個寫過程錯誤代碼。
- 對被占用的緩存進行CRC校驗。如果不匹配,則返回一個數據損壞錯誤代碼。
- 提取頁面地址,并將數據寫入主存儲區的指定頁面。
- 計算緩存的數據部分的CRC。該值被保存在一個臨時寄存器中。
- 找到對應所選主存儲區頁面的校驗頁面,并讀取該校驗頁面的內容。
- 用前面計算的CRC更新校驗頁面,為校驗頁面計算新的CRC。
- 將校驗頁面數據重新寫回校驗存儲區。
- 將寫緩存更新為終止狀態。
回退操作
圖5. 回退操作的流程圖
如圖5所示,回退函數是最簡單的操作之一。由于主存儲區只有在完成一個提交操作后才更新數據,而不是在一個寫操作之后更新的,所以回退操作只需將寫緩存置為無效狀態即可。
- 讀取每個寫緩存的狀態域。應該只有一個緩存被標記為占用。否則,函數在此結束,并返回一個寫過程錯誤代碼。
- 將所選的寫緩存狀態域置為終止。
檢查操作
圖6. 檢查操作的流程圖
在任何上電情況下,都需要調用檢查函數以確保EEPROM可以接受數據。檢查函數檢驗存儲系統的可用性,并報告任何發現的錯誤。該函數的檢查操作如圖6所示:
- 讀取每個寫緩存。確認只有一個緩存不是可用狀態。如果只有一個緩存含有未定義的狀態代碼,則返回一個寫操作中斷(interrupted write)錯誤代碼。如果所有緩存均包含未定義的狀態代碼,則返回EEPROM未初始化(uninitialized EEPROM)錯誤代碼。
- 如果僅有一個緩存包含占用狀態代碼,計算此緩存的CRC。如果CRC不匹配,則返回一個寫操作中斷錯誤代碼。
- 檢查校驗存儲區的每一個頁面。如果任何頁面沒有通過CRC校驗,則返回保護失敗錯誤代碼。
- 最后,檢查主存儲區的每一頁,并與存儲的各頁CRC進行對比。如果有1頁未通過CRC校驗,則返回一個提交中斷(interrupted commit)錯誤代碼。
清理操作
圖7. 清理操作的流程
清理函數解決EEPROM系統存在的任何問題。在清理操作退出時,無論EEPROM子系統先前是何種狀態,都應該可以繼續使用了。所有未提交的寫操作將被回退,并且完成失敗的提交操作。
圖7演示了清理操作是如何工作的:
- 如果檢查操作返回一個EEPROM未初始化的錯誤代碼,則初始化EEPROM。清除所有數據頁面,并且初始化所有校驗頁面。除最后一個寫緩存被初始化為終止狀態外,清除其它所有寫緩存并置為可用狀態。
- 如果檢查操作返回一個寫操作中斷錯誤代碼,則找到那個不是可用狀態的寫緩存。將它的狀態改為終止狀態。
- 如果檢查操作返回一個提交中斷錯誤代碼,則找到CRC不匹配的主頁面。計算出它的CRC并更新相關校驗頁面。
- 如果檢查操作返回保護失敗錯誤代碼,則表示緊隨提交操作的更新校驗頁面操作被中斷。讀出所有與錯誤校驗頁面相關的主存儲區頁面,并刷新校驗頁面。
安全性證明
要證明系統的安全性,需要確定寫操作過程中數據容易損壞的時刻。(讀操作從本質上來說是安全的。讀操作期間不會對EEPROM頁面進行寫操作,因此數據不會被損壞。) 確定了這些易損時刻后,只需要再確定一個恢復過程。如果恢復機制涵蓋了所有可能的數據損失情況,而且如果我們假定在任何一個可能破壞EEPROM寫周期的事件之后,都將首先執行校驗/清理周期(例如上電),那么系統就是安全的。在大多數串行EEPROM器件中,一個寫操作首先將頁面的每位數據都置為已知值,然后將所有需要改變的位設置為需要的值。因此在掉電時,中斷的寫操作極有可能破壞該頁的所有字節。通??梢酝ㄟ^向損壞頁面寫入新數據,進而從這一失效事件中恢復出來。但這會失去原來的數據。
寫操作過程中數據容易損壞的時刻如下所述(按發生的時間順序排列):
- 對數據域進行寫操作:如果此時發生電源失效事件,檢查操作不會檢測到錯誤。正在被寫入的寫緩存仍顯示可用狀態,但可用的緩存不包含有效的CRC值。
- 向當前寫緩存寫入狀態信息:這個操作將狀態域改為占用狀態,設定CRC并為寫操作填入頁面地址。如果這個過程被中斷,可能發生如下情況:(1) 狀態無效,從而導致一個寫操作中斷錯誤;(2) 狀態有效,但CRC錯誤,仍會導致一個寫操作中斷錯誤;(3) 狀態和CRC域有效。在最后這種情形下,系統有未提交處理的寫操作。可以檢測到這一狀態,因為此時一個緩存將處于占用狀態而另一個緩存為終止狀態。如果子系統的其它部分檢查通過,則用戶代碼可通過發出提交或回退操作繼續執行。無論發生何種情況,主存儲區和校驗存儲區都是安全的。
- 前一個緩存狀態清除為可用狀態:緩存可能有損壞的狀態或CRC,而下一個緩存為占用狀態。這意味著清除該緩存的狀態時操作被中斷,這種情況下可以執行提交或回退操作。
- 在寫操作和提交操作之間:只有一個寫緩存將處于占用狀態,并且通過了CRC校驗。用戶代碼可以請求提交或回退操作。寫緩存、校驗存儲區和主存儲區都是安全的。
提交操作過程中數據容易損壞的時刻如下所述:
- 將數據域復制到主存儲區:如果寫操作被中斷,主存儲區的1個頁面數據可能被破壞。檢查函數會檢測到兩種狀態:(1) 一個有效的占用寫緩存;(2) 中斷的提交操作導致主存儲區頁面數據損壞。寫緩存和校驗存儲區是安全的。在這種情況下,清理操作會完成提交操作并返回一個干凈的系統。注意:即使寫操作已經完成,檢查操作仍會因為校驗存儲區的CRC與計算出的CRC不匹配而報錯。
- 更新校驗存儲區的CRC:如果對校驗頁面的寫操作被中斷,則整個頁面的數據都可能被破壞。這意味著主存儲區的15個頁面都對應著無效的CRC。但是由于校驗存儲區的每一頁都有自己的校驗和,而且在寫操作中斷后會產生校驗和錯誤,因此檢查程序會發現這一點。在這種情況下,檢查程序會返回保護失敗。修復方法如下:首先重新計算所有受影響的15個頁面的CRC值。然后將這些值和該頁自身的有效CRC值一起寫入校驗頁面。
- 更新寫緩存的狀態信息:如果當狀態變量從占用狀態變為終止狀態時,寫周期被中斷,那么整個寫緩存頁面的數據都可能被損壞。但是,校驗存儲區和主存儲區都是安全的。檢查操作會找到數據損壞的頁面,并返回寫操作中斷錯誤代碼。當運行清理程序時,它將復位寫緩存子系統,并完成提交操作。
最后,在回退操作中數據容易損壞的時刻為:
- 更新寫緩存的狀態:與提交周期的最終狀態類似,該操作只是簡單地將寫緩存的占用狀態復位至終止狀態。如果它被中斷,則檢查程序會返回寫操作中斷,并且清理程序會重新初始化所有的寫緩存區域。校驗存儲區和主存儲區仍是安全的。
可以看出,無論電源何時掉電或處理器何時被復位,存儲子系統都可保持數據的完整性。發生電源失效事件后,存儲子系統會返回到可進行讀或寫的狀態。如果一個提交操作被中斷,子系統會返回到可執行提交或回退操作的狀態。
設計起步
MAXQ微控制器的EEPROM存儲系統功能完備。系統設計者可以根據需要來增強該系統的功能。但需要注意以下幾點:- C封裝程序:在多數C語言標準中,與匯編語言子程序雙向傳送數據時都有一套標準的方法。例如在IAR開發環境下,參數在低編號的累加器中傳入和傳出。由于參數已經傳入A[0]和A[1],為這些程序建立一個C封裝器,就像寫函數原型一樣容易。在其它C環境下,參數傳遞是通過數據棧進行的,需要一個簡單的封裝子程序。
- 并發處理:首先要保證寫周期的完整性,并且提供一套能夠保證完整性的機制,對于整個平臺的成敗至關重要。但很多應用都需要這樣一種機制,即可以讓一系列寫周期排隊并一次執行完畢,從而保證全都執行或全都不執行。但本文討論的機制不能工作在這種方式下。如果一個系統存有跨越多個頁面的信息記錄,則可以中斷一個寫操作,這使得恢復之后的記錄涵蓋了包含部分新數據的頁面和包含部分舊數據的頁面。有一種方法可避免該問題,即在執行提交操作之前允許多重寫操作。這種方法并不像聽上去那么簡單,因為一個部分提交的事務,可能同時包括新紀錄片斷、舊記錄片斷和損壞的頁面。
- 平均讀寫機制:平均讀寫作為閃存文件系統的一個特點,是指虛擬化頁面地址,使得被頻繁寫入的頁面會出現在存儲器的任何物理位置。但是很難找到實現這一目標的最佳方法。這是因為,最直接的解決方法(活動存儲塊的目錄處于固定的位置,并且每次寫操作后都要對它進行更新)會導致存儲目錄的頁面迅速損耗。所以,就像處理數據頁面那樣,還必須虛擬化和離散化目錄本身。
- 其它頁面尺寸:這里給出的系統假定采用一個16kB、每頁32字節的存儲器件。如果所選擇的器件具有更大的頁面尺寸(64字節或128字節),這些函數仍可工作,只是會伴隨一些額外的寫入損耗。(更新128字節頁面中的32字節區段時,會對整個128字節頁面執行寫操作)。但這些函數無法對具有更小頁面尺寸的器件進行操作??梢詷嫿ㄒ粋€能夠在線確定EEPROM器件特性的系統,并可根據實際特性配置系統的參數。
- 增強的安全性:本系統對以下類型的錯誤提供保護:由于電源失效或不可預期的系統復位而造成的EEPROM操作中斷。但EEPROM器件偶爾也會因為其它原因出錯。例如,由于電路噪聲或致電離輻射導致的軟件錯誤?;蛘哂捎谝粋€或多個存儲單元損耗而導致硬件錯誤。
一種解決方法是計算并維護校正子(syndrome),而不是采用簡單的CRC校驗字。校正子和校驗字類似,但是包含了足夠的信息以修復簡單的位錯誤。最簡單的校正子系統可以用log2n + 1個校驗位來檢驗n個數據位。因此,對于一個32字節(256位)的頁面來說,一個僅包含9位的校正子就可以修正任何1位錯誤。對數據完整性的要求更加嚴格時,可以采用更加復雜的系統來解決類似問題。
評論
查看更多