痞子衡嵌入式:kFlashFile v1.0 - 一個基於Flash的掉電數據存取方案

来源:https://www.cnblogs.com/henjay724/archive/2020/07/01/13218090.html
-Advertisement-
Play Games

痞子衡最近在參與一個基於 i.MXRT1170 的項目,項目有個需求,需要在 Flash 里實時保存一些關鍵數據(初步設 512 bytes),掉電能恢復。這些數據在訪問方式上要友好,最好是很簡單的 API 介面,上層無需操心關鍵這些數據在 Flash 里是如何存儲以及具體存儲在什麼位置,只需在意關... ...


大家好,我是痞子衡,是正經搞技術的痞子。今天給大家帶來的是痞子衡的個人小項目 - kFlashFile。

痞子衡最近在參與一個基於 i.MXRT1170 的項目,項目有個需求,需要在 Flash 里實時保存一些關鍵數據(初步設 512 bytes),掉電能恢復。這些數據在訪問方式上要友好,最好是很簡單的 API 介面,上層無需操心關鍵這些數據在 Flash 里是如何存儲以及具體存儲在什麼位置,只需在意關鍵數據保存和讀取的操作即可(就像在 RAM 里動態存取那樣)。

根據上述需求,痞子衡做了一個參考設計,命名為 kFlashFile,當前是 v1.0 版本。痞子衡寫了比較詳細的設計文檔,特地分享給大家,如果大家有更好的建議和想法,歡迎在文章下麵留言。

項目地址:https://github.com/JayHeng/kFlashFile

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空間里的文件數據,以便下次重新分配。
status_t kflash_file_init(kflash_file_t *flashFile, uint32_t memStart, uint32_t memSize, uint32_t fileSize);
status_t kflash_file_read(kflash_file_t *flashFile, uint32_t offset, uint8_t *data, uint32_t size);
status_t kflash_file_save(kflash_file_t *flashFile, uint32_t offset, uint8_t *data, uint32_t size);
status_t kflash_file_deinit(kflash_file_t *flashFile);

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 主參數,這個參數原型定義如下:

typedef struct {
    uint32_t managedStart;
    uint32_t managedSize;
    uint32_t activedStart;
    uint32_t activedSize;
    uint32_t recordedIdx;
    uint32_t recordedPos;
    uint8_t buffer[KFLASH_MAX_FILE_SIZE];
} kflash_file_t;
  • 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_erase(uint32_t instance, flexspi_nor_config_t *config, uint32_t start, uint32_t length):這個函數實現Flash擦除,雖然形參里是任意設定的start, address,但實際擦除還是以Sector對齊的,函數內部會對start和address做自動對齊。
  • flexspi_nor_flash_page_program(uint32_t instance, flexspi_nor_config_t *config, uint32_t dstAddr, const uint32_t *src):這個函數實現Flash編程,一次固定寫一整個Page大小的數據,即使dstAddr不是以Page對齊,實際寫入的Page數據也不會跨物理Page(會自動跳回同一個物理Page首地址,這是Flash自身特性)。

因為 flexspi_nor_flash_page_program() 每次都要固定編程整個 Page 數據,不夠靈活,因此我新寫了一個 flexspi_nor_flash_program() 函數,這個函數支持編程用戶自定義長度的數據,並且支持跨物理 Page 去寫:

  • flexspi_nor_flash_program(uint32_t instance, flexspi_nor_config_t *config, uint32_t dstAddr, const uint32_t *src, uint32_t length):

需要特別註意,對於 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):

// 表示 Flash 連接的是 FlexSPI1
#define KFLASH_INSTANCE          (1)
// BootROM FlexSPI 驅動對 Octal Flash 支持的簡易配置值
#define KFLASH_CONFIG_OPTION     (0xc0403007)

// FlexSPI1 在系統記憶體中的映射首地址
#define KFLASH_BASE_ADDRESS      (0x30000000)
// 預設的 Flash Sector/Page 大小(如果 Flash 里有 SFDP,則此處定義無效)
#define KFLASH_SECTOR_SIZE       (0x1000)
#define KFLASH_PAGE_SIZE         (256)

// FlexSPI 編程介面對傳入的 src 源數據首地址必須 4 位元組對齊
#define KFLASH_PROGRAM_ALIGNMENT (4)
// Flash SDR 模式為 1,DDR 模式為 2
#define KFLASH_PROGRAM_UNIT      (2)

kFlashFile 的 Adapter 層介面函數如下,參數是硬體無關的,因此上層可以輕鬆基於這些介面函數做純軟體邏輯設計。

status_t kflash_drv_init(void);
uint32_t kflash_drv_get_info(kflash_mem_info_t flashInfo);
status_t kflash_drv_erase_region(uint32_t start, uint32_t length);
status_t kflash_drv_program_region(uint32_t dstAddr, const uint32_t *src, uint32_t length);

3.3 API層

kFlashFile 的 API 功能設計思路前面介紹過了,這裡介紹具體代碼實現,先來看幾個關鍵的巨集定義:

// 設置 Header Sector 的個數,至少是 2 個
#define KFLASH_HDR_SECTORS     (2)
// 設置 Header Sector 中用於存儲當前有效文件數據位置信息的區域存儲類型
// uint16_t 最多可記錄 65536 個位置,最大可支持的 Data 區域大小為 65536 * 文件數據長度
#define KFLASH_HDR_POS_TYPE    uint16_t   /* uint16_t or uint32_t */
// 設置總分配的 Flash 長度(Data+Header Sector 的個數),至少是 4 個
#define KFLASH_MIN_SECTORS     (KFLASH_HDR_SECTORS + 2)
// 設置最大支持的文件數據長度,需是 Flash Page 的整數倍
#define KFLASH_MAX_FILE_SIZE   (KFLASH_PAGE_SIZE * 2)

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 時會做擦除。

歡迎訂閱

文章會同時發佈到我的 博客園主頁CSDN主頁微信公眾號 平臺上。

微信搜索"痞子衡嵌入式"或者掃描下麵二維碼,就可以在手機上第一時間看了哦。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 委托與事件在C#1.0的時候就有了,隨著C#版本的不斷更新,有些寫法和功能也在不斷改變。本文溫故一下這些改變,以及在NET Core中關於事件的一點改變。 一、C#1.0 從委托開始 1. 基本方式 什麼是委托,就不說概念了,用例子說話。 某HR說他需要招聘一個6年 .NET5 研發經驗的“高級”工 ...
  • 使用邏輯式編程找出凶手! Boddy 先生死於謀殺,現有六個嫌疑犯,每個人在不同的房間,每間房間各有一件可能的凶器,但不知道嫌疑犯、房間、凶器的對應關係。請根據條件和線索,找出誰是凶手。 ...
  • 演示地址:http://demo.ineuos.net (註:自己註冊) iNeuOS 自主可控工業互聯網操作系統,提供全新解決方案 核心組件包括:邊緣網關(iNeuLink)、設備容器(iNeuKernel)、Web組態視圖建模(iNeuView)、機器學習(iNeuAI)、分析大屏(iNeuDA ...
  • 在C#中將帶時區的字元串轉成DateTime類型需要用到DateTimeFormatInfo,這個類包含特定於區域性的信息。 例如,將Sun, 28 Jun 2020 03:40:22 GMT轉成DateTime該怎麼弄呢? DateTimeFormatInfo dtFormat = new Dat ...
  • 項目背景及需求說明 這是一個數據管理"工具類"的系統,計劃有三個核心功能: 1、通過界面配置相關連接字元串,查詢資料庫的表數據。 2、配置相關模板,生成資料庫表。 可以界面填報或通過Excel導入導出填報表數據。 3、通過界面配置導出資料庫表結構(數據字典)。 通過以上功能,在數據分析、可視化項目中 ...
  • Java工具類——數學相關的類 在上一篇文章中,我們系統學習了 Java 裡面的包裝類,那麼這篇文章,我們就來學習一下Java提供好的類——數學相關的類。 一、數學類介紹 在最早期學習 Java 基礎語法結構的時候,其實我們學習並瞭解了加減乘除這些算數運算符,有了這些運算符,我們就可以做一些簡單的運 ...
  • 配置無密碼登陸本機ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsacat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keyschmod 0600 ~/.ssh/authorized_keys然後將~/.ssh/id_rsa.pub即... ...
  • 常用變數命名規則建議 倍福虛擬學院(https://tr.beckhoff.com.cn/) 1. 常量 1 常量都用大寫字母來表示, 用下劃線 “_” 加強可讀性。 1 VAR CONSTANT 2 MAX_HEIGHT: REAL := 1234; (* [Pa]*) 3 END_VAR 2. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...