kFlashFile
一、簡介
kFlashFile 是一個基于 NOR Flash 的輕量級文件數據存儲方案,用于需要斷電數據保存的項目。
kFlashFile 主要為 i.MXRT 系列設計,但其分層框架設計使其也可輕松移植到其他 MCU 平臺。
kFlashFile 從設計上分為三層:
最底層是 Driver 層:即 Low-level 驅動,這層是 MCU 相關的,對于 i.MXRT 來說,就是 FlexSPI 模塊的驅動。
中間是 Adapter 層:主要用于適配底層 Driver,不同 MCU 其 Driver 接口函數可能不同,因此會在這一層做到接口統一。
最頂層是 API 層:純軟件邏輯設計來實現文件數據存儲,提供了四個非常簡易的 API。
二、設計
2.1 API 定義
kFlashFile 是一個文件數據存儲的設計,file_read()、file_save()是兩個必備的 API,此外也提供業界通用 API 接口 file_init()、file_deinit()。
kflash_file_init(): 用于初次分配 Flash 空間來存儲文件數據,并且指定文件長度。如果當前指定的 Flash 空間里存在有效文件數據,那么繼續復用。kflash_file_read(): 用于獲取當前有效存儲的文件數據,文件數據可以部分讀取。
kflash_file_save(): 用于實時寫入最新的文件數據,文件數據可以部分更新。
kflash_file_deinit(): 用于清除當前分配的 Flash 空間里的文件數據,以便下次重新分配。
2.2 空間分配
kFlashFile 將分配的 Flash 空間分成兩個部分,前面是文件數據區(Data Sectors),后面是文件頭區(Header Sectors)。
文件數據區:從區內起始地址開始按序存放一份份文件數據,只要文件數據出現無法覆蓋的更新(即 Flash 無法改寫的特性),便會在下一個新地址重新存儲。如果數據區滿了,便擦除區內起始地址處的歷史文件數據,繼續循環存儲。
文件頭區:區內 Sector 起始地址放一個 Magic 值(4 字節),用于標識文件頭。然后開始按序記錄一份份文件數據在文件數據區里的位置信息(默認用 2byte 去記錄一份文件數據的位置)。如果當前 Header Sector 存儲滿了,便換到下一個 Header Sector 繼續記錄。
2.3 API 主參數
kFlashFile 設計上使用 kflash_file_t 型作為 API 主參數,這個參數原型定義如下:
managedStart:表示文件存儲區映射首地址,即 kflash_file_init() 調用時的 memStart 值加上 Flash 在內存里映射首地址,managedStart 需要以 Flash Sector 大小對齊。
managedSize:表示文件存儲區總大小,即 kflash_file_init() 調用時的 memSize 值,需要是 Flash Sector 大小的整數倍。
activedStart:表示當前有效文件數據存儲的映射首地址,需要以 Flash Page 大小對齊。
activedSize:表示當前有效文件數據長度,需要是 Flash Page 大小的整數倍。
recordedIdx:表示當前有效文件頭所在的 Header Sector 索引。
recordedPos:表示 Header Sector 中用于存儲當前有效文件數據位置信息的區域偏移。
buffer[]:當前有效的文件數據暫存區。
三、實現
3.1 Driver 層
在 i.MXRT 系列上,kFlashFile 的 Driver 層即 FlexSPI NOR 驅動,這個驅動既可以采用 MCU SDK 版本,也可以采用 BootROM 版本。
此處推薦 BootROM 版本的 FlexSPI NOR 驅動,因為這個驅動歷經多個 MCU ROM 的洗禮,已經相當成熟穩定。這里簡單講下其中 Flash 操作的函數:
因為 flexspi_nor_flash_page_program() 每次都要固定編程整個 Page 數據,不夠靈活,因此我新寫了一個 flexspi_nor_flash_program() 函數,這個函數支持編程用戶自定義長度的數據,并且支持跨物理 Page 去寫:
需要特別注意,對于 SDR 模式的 Flash,最小編程長度可以是 1Byte;而 DDR 模式的 Flash,最小編程長度應是 2Bytes(如果這 2Bytes 地址上有一個 Byte 內容是 0xFF,該 Byte 依舊可以被再次編程)。
此外 flexspi_nor_flash_program() 函數有一個限制,即傳入的 src 源數據首地址必須 4 字節對齊,哪怕你只想寫入 2 個字節,這是 FlexSPI 模塊底層對驅動的要求。
3.2 Adapter 層
kFlashFile 的 Adapter 層是對 Driver 層做了一層封裝,用于屏蔽硬件相關特性。該層與 MCU 以及板載 Flash 型號息息相關。下面的宏定義適用 i.MXRT1170 芯片以及連接在 FlexSPI1 上的 Octal Flash(MX25UM51345):
kFlashFile 的 Adapter 層接口函數如下,參數是硬件無關的,因此上層可以輕松基于這些接口函數做純軟件邏輯設計。
3.3 API 層
kFlashFile 的 API 功能設計思路前面介紹過了,這里介紹具體代碼實現,先來看幾個關鍵的宏定義:
3.3.1 init()
kflash_file_init() 函數處理流程如下:
如果是首次指定 Flash 空間,那么直接將全部空間擦除干凈,并在第一個 Header Sector 中寫入初始文件頭(Magic + 文件數據位置值 0),即最新有效文件數據在 Flash 空間文件數據區的首地址。
這里有一個特殊的設計,文件數據區其實并不是直接存儲用戶寫入的文件數據,而是將用戶文件數據全部按位取反之后再存儲進 Flash。這里假定用戶數據初始應該是全 0,然后更改主要是將 0 值改為其他值,取反之后,正好對應 Flash 里的 bit1 編程為 bit0(Flash 擦除后是全 0xFF),這樣可以充分利用 Flash 覆蓋操作以減少擦除次數。
函數中比較關鍵的步驟是找尋當前 Flash 空間中是否存在有效文件數據,方法是遍歷 Header Sector,發現存在 Magic 便繼續尋找最新文件數據位置信息存放的區域(默認 2 字節),按照前面的設計,只需要按序讀取區域內容,直到遇到 0xFFFF 為止。
3.3.2 read()
kflash_file_read() 函數最簡單了,直接從緩存區 buffer 里獲取數據即可,因為每次更新文件數據操作完成之后都會將最新文件數據放在 buffer 里。
3.3.3 save()
kflash_file_save() 函數是最核心的函數了,這里邏輯比較復雜,涉及文件數據區全部滿了之后的動作,以及文件頭區某個 Sector 滿了的動作。其處理流程如下:
當有一個新文件數據要求保存時,首先會判斷這個文件能不能在 Flash 中直接覆蓋存儲,如果能,那就直接覆蓋存儲,文件頭完全不需要更新,這種情況比較簡單。
如果新文件數據無法直接覆蓋存儲,那么首先判斷文件數據區是否滿了,如果上一個文件數據已經存在了文件數據區的最后位置,此時需要擦除數據區第一個 Sector 從頭開始存儲。如果沒有到最后位置,那就按序往下存儲。
新文件數據已經保存到數據區之后,此時需要處理文件頭,記錄這個新文件數據的位置。如果文件頭區已經記錄到當前 Sector 的最后位置,需要切換到下一個 Sector 開始存儲,切換存儲完新位置后,將之前 Sector 擦除。如果沒有,那就按序在當前 Sector 繼續記錄。
3.3.4 deinit()
kflash_file_deinit() 函數也比較簡單,就是將文件頭區域 Header Sectors 全部擦除即可,文件數據區內容可以不用管,下次重新分配 Flash 時會做擦除。
評論
查看更多