FDS,全稱Flash Data Storage,用來訪問芯片內部Flash的。當你需要把數據存儲在Flash中,或者讀取Flash中的用戶數據,或者更新或者刪除Flash中的數據,那么FDS模塊是你最好的選擇。FDS采用文件和記錄方式來組織Flash數據,也就是說,真正的數據是放在一條記錄中,而多條記錄組成一個文件。根據應用的需要,整個系統可以只有一個文件,也可以包含多個文件。文件采用文件ID來標示,文件ID為2個字節(注:不能取值為0xFFFF)。一個文件下面可以放一條記錄,也可以放多條記錄,記錄是通過記錄key來標示的,記錄key也是2個字節長度(注:不能取值為0x0000)。這里需要注意的是,同一個文件下面的兩條或者多條記錄他們的key可以是一樣的,比如我們可以建立如下文件系統:文件1包含2條記錄,文件2包含3條記錄,文件2包含2條key為0x0003的記錄
注:如果你可以保證一個文件下面所有記錄的key都不一樣,那么文件系統會變得更簡潔一些,尤其在find記錄的時候,只會返回一條記錄,可以簡化很多應用邏輯。如前所述,這個不是強制要求:同一個文件下記錄key可以相同。
FDS用法
一般而言,按照如下步驟使用FDS模塊:
修改FDS的默認配置參數,比如總共分配多少Flash空間(默認只分配了8kB Flash空間給用戶使用),請到sdk_config.h文件中修改如下默認配置項:
通過fds_register注冊FDS事件回調函數及通過fds_init初始化FDS模塊。FDS模塊的初始化,寫記錄,更新記錄,刪除記錄以及垃圾回收,這些API都是異步的。也就是說調用這些FDS操作的API,只是把相應操作放入隊列然后立即返回(隊列大小由上述的FDS_OP_QUEUE_SIZE控制),真正的Flash操作結果是通過事件回調函數通知你的。注:現在的FDS模塊可以進行多次初始化。示例代碼如下所示:
// Simple event handler to handle errors during initialization. static void fds_evt_handler(fds_evt_t const * p_fds_evt) { switch (p_fds_evt->id) { case FDS_EVT_INIT: if (p_fds_evt->result != FDS_SUCCESS) { // Initialization failed. } break; default: break; } } ret_code_t ret = fds_register(fds_evt_handler); if (ret != FDS_SUCCESS) { // Registering of the FDS event handler has failed. } ret_code_t ret = fds_init(); if (ret != FDS_SUCCESS) { // Handle error. }
通過fds_record_write創建新的記錄,即寫記錄。 注意寫記錄的時候,必須保證輸入參數是全局變量或者static的局部變量,推薦使用全局變量! 由于record key可以重復,所以連續調用兩次相同的fds_record_write,將生成兩條同樣key的記錄。前面也提及過,fds_record_write是異步的,所以它的返回值為success只是表示操作入隊成功,真正的flash操作結果是通過前面注冊的fds_evt_handler來通知的。示例代碼如下所示:
#define FILE_ID 0x0001 /* The ID of the file to write the records into. */ #define RECORD_KEY_1 0x1111 /* A key for the first record. */ #define RECORD_KEY_2 0x2222 /* A key for the second record. */ static uint32_t const m_deadbeef = 0xDEADBEEF; static char const m_hello[] = "Hello, world!"; fds_record_t record; fds_record_desc_t record_desc; // Set up record. record.file_id = FILE_ID; record.key = RECORD_KEY_1; record.data.p_data = &m_deadbeef; record.data.length_words = 1; /* one word is four bytes. */ ret_code_t rc; rc = fds_record_write(&record_desc, &record); if (rc != FDS_SUCCESS) { /* Handle error. */ } // Set up record. record.file_id = FILE_ID; record.key = RECORD_KEY_2; record.data.p_data = &m_hello; /* The following calculation takes into account any eventual remainder of the division. */ record.data.length_words = (sizeof(m_hello) + 3) / 4; rc = fds_record_write(&record_desc, &record); if (rc != FDS_SUCCESS) { /* Handle error. */ }
通過fds_record_open來讀記錄。讀記錄之前必須先找到這條記錄,這個是通過fds_record_find來實現的,由于同一個文件可以包含多條key相同的記錄,所以通過多次調用同一個fds_record_find,可以找到所有相關記錄。示例代碼如下所示:
#define FILE_ID 0x1111 #define RECORD_KEY 0x2222 fds_flash_record_t flash_record; fds_record_desc_t record_desc; fds_find_token_t ftok; /* It is required to zero the token before first use. */ memset(&ftok, 0x00, sizeof(fds_find_token_t)); /* Loop until all records with the given key and file ID have been found. */ while (fds_record_find(FILE_ID, RECORD_KEY, &record_desc, &ftok) == FDS_SUCCESS) { if (fds_record_open(&record_desc, &flash_record) != FDS_SUCCESS) { /* Handle error. */ } /* Access the record through the flash_record structure. */ /* Close the record when done. */ if (fds_record_close(&record_desc) != FDS_SUCCESS) { /* Handle error. */ } }
操作記錄,比如fds_record_update,fds_record_delete等,update和delete操作,必須先找到相應記錄,然后才能去update或者delete。fds_record_delete不是真得把記錄刪除,而是將記錄標示為無效。而fds_record_update實際包含2步:先找到之前的記錄然后將其標記為無效(即delete操作),然后write一條新記錄。記住:delete并不會回收Flash空間,無效記錄仍然占據著Flash空間,這些無效記錄占據著的Flash空間只有經過垃圾回收(fds_gc)才能再次給新記錄使用。請注意fds_record_find只會去尋找有效記錄,而不會將無效記錄返回給用戶的。另外,fds_record_ update和fds_record_delete是異步的,所以它們的返回值為success只是表示操作入隊成功,真正的flash操作結果是通過前面注冊的fds_evt_handler來通知的。示例代碼如下所示:
fds_record_desc_t desc = {0}; fds_find_token_t tok = {0}; rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok); if (rc == FDS_SUCCESS) { /* A config file is in flash. Let's update it. */ fds_flash_record_t config = {0}; /* Open the record and read its contents. */ rc = fds_record_open(&desc, &config); APP_ERROR_CHECK(rc); /* Copy the configuration from flash into m_dummy_cfg. */ memcpy(&m_dummy_cfg, config.p_data, sizeof(configuration_t)); NRF_LOG_INFO("Config file found, updating boot count to %d.", m_dummy_cfg.boot_count); /* Update boot count. */ m_dummy_cfg.boot_count++; /* Close the record when done reading. */ rc = fds_record_close(&desc); APP_ERROR_CHECK(rc); /* Write the updated record to flash. */ rc = fds_record_update(&desc, &m_dummy_record);
if (rc == FDS_ERR_NO_SPACE_IN_FLASH) fds_gc(); else APP_ERROR_CHECK(rc); } ret_code_t ret = fds_record_delete(&desc); if (ret != FDS_SUCCESS) { /* Error. */ }
當Flash不夠用時,即FDS寫記錄或者更新記錄操作返回錯誤FDS_ERR_NO_SPACE_IN_FLASH,請調用垃圾回收函數:fds_gc進行垃圾回收。fds_gc是一個非常耗時的操作過程(請確保操作過程中不會掉電,否則Flash行為未知),它會一個page一個page操作,然后將該page中的有效記錄拷貝到swap page,然后擦除該page,并標記該page為swap page,而之前的swap page則變為data page,如此往復,直到把所有page都回收完。只有經過fds_gc后,之前無效記錄占據的Flash空間才會釋放,這個時候才會有多余的Flash空間給用戶去操作。
建議大家直接參考SDK里面自帶的fds例子來編寫自己的fds應用代碼,SDK自帶的fds例子所在目錄為:SDK安裝目錄examplesperipheralflash_fds (注:從SDK14之后才有fds例子)
理解FDS
FDS作為上層模塊,它是通過調用fstorage API來實現自己的功能,fstorage又是通過調用NVMC外設驅動或者softdevice Flash訪問API來達到操作Flash的目的,調用關系圖如下所示:
當softdevice存在的時候,建議使用nrf_fstorage_sd后端;沒有softdevice的時候,請使用nrf_fstorage_nvmc后端。
根據有無bootloader,FDS將操作不同的Flash空間,如下:
當你通過FDS把數據寫入Flash中,除了數據本身,FDS還會在這條記錄中加入額外的信息:記錄頭header,一條記錄在Flash中完整的格式如下所示:
字段 | 大小 | 描述 |
Record key | 16 bits | Key that can be used to find the record. The value FDS_RECORD_KEY_DIRTY (0x0000) is reserved by the system to flag records that have been invalidated. See Restrictions on keys and IDs for further restrictions. |
Data length | 16 bits | Length of the data that is stored in the record (in 4-byte words). |
File ID | 16 bits | ID of the file that the record is associated with. The value FDS_FILE_ID_INVALID (0xFFFF) is used by the system to identify records that have not been written correctly. See Restrictions on keys and IDs for further restrictions. |
CRC value | 16 bits | CRC value of the whole record (checks can be enabled by setting the FDS_CRC_ENABLED compile flag, see Configuration). |
Record ID | 32 bits | Unique identifier of the record. 注:對用戶不可見 |
所以,在計算記錄總共占用多少Flash空間的時候,記得一定要把每條記錄的header(3個word)也加上。
FDS使用常見問題
大家在使用FDS模塊時,經常碰到的問題有如下幾種:
FDS不支持掉電保護,所以在Flash操作過程中出現了掉電,FDS行為將未知
OTA的時候,新固件的FDS page數目一定要等于老固件的FDS page數,否則將出現不可知行為
fds_record_write或者fds_record_update后,強烈建議回讀該記錄,以確保記錄的確write或者update成功
忘了給參數清0。Nordic提供的API輸入參數很多都是結構體變量,這些變量使用之前,記得一定要通過memset先清0。如果忘了清0,就會出現一些匪夷所思的現象。
fds_record_desc_t desc; //= {0}; //錯誤,忘了清0 fds_find_token_t tok; //= {0}; //錯誤,忘了清0
忘了使用全局變量或者靜態局部變量。因為write和update操作都是異步的,所以record.data.p_data必須指向全局變量或者靜態局部變量,以保證Flash操作過程中p_data指向的內容不會更改。
變量起始地址必須字對齊。Flash操作是以word為單位的,所以要求write和update操作的p_data指向的變量的起始地址必須word對齊,大家可以使用偽匯編指令“__ALIGN(sizeof(uint32_t))”來保證該變量起始地址是word對齊的。
Update或者delete之前必須先find。fds_record_update或者fds_record_delete會用到參數descriptor,這個descriptor必須是通過fds_record_find返回的。
忘了使用fds_gc導致Flash fatal error或者其他奇奇怪怪的問題。當write或者update報FDS_ERR_NO_SPACE_IN_FLASH錯誤時,記得一定要調用fds_gc。或者當delete record或者update record達到一定次數后,主動調用fds_gc。或者通過查看fds_stat得到dirty record數目達到某個值后,主動調用fds_gc。
SDK已知問題。每個版本SDK都有或多或少的問題,這些問題都可以在Nordic devzone上查到。比如SDK12.2.0 fds_gc在某些情況下,就會有問題,請參考:https://devzone.nordicsemi.com/question/93241/what-are-sdk-12x0-known-issues/,所以,一般建議大家使用最新版SDK,最新版SDK會把之前發現的問題都修復掉,它的穩定性和可靠性都是最高的。
最后再次強調一遍:FDS不支持掉電保護,所以在FDS操作過程中,尤其是垃圾回收過程中,發生了掉電,那么Flash內容將變得不可靠。所以強烈建議大家:在每一次write或者update之后,都把相應記錄讀出來,跟原始內容進行比對,以確保記錄真的寫成功或者更新成功了
審核編輯 黃宇
-
SDK
+關注
關注
3文章
1061瀏覽量
47479 -
Nordic
+關注
關注
9文章
195瀏覽量
47856
發布評論請先 登錄
定時模塊app_timer用法及常見問題—nRF5 SDK模塊系列二

nRF Connect SDK(NCS)/Zephyr固件升級詳解 – 重點講述MCUboot和藍牙空中升級

如何調試nRF5 SDK
藍牙模塊PTR5618性能、開發與應用解析
處理能力提高一倍,支持邊緣ML應用:基于nRF54L的ME54BS系列BLE模塊,性能全面升級!

40G光模塊介紹及常見問題探討
NRF21540—低功耗藍牙,藍牙mesh、Thread和Zigbee和2.4 GHz私有協議范圍擴展射頻前端模塊

評論