一、背景
NRF52832 內部 Flash 的存儲官方提供了兩種方式,一種是 FStorage 方式,另一種是在 FStorage 基礎上的 FDS 方式。
1.1 FDS方式
Flash 數據存儲(FDS)模塊是芯片上閃存的最小化文件系統,它可以最小化數據損壞的風險,並簡化了持久存儲的交互。它通過在文件中組織數據來實現這一點,這些數據由一個或多個 記錄 組成。這些 記錄 包含實際的數據,可以被寫入、刪除、更新或檢索。
將數據作爲文件處理的概念,提供了一個高水平的抽象方案。您可以使用 FDS 模塊,而不需要詳細瞭解內部使用的實際數據格式。相反,您可以只處理文件和記錄,並將模塊用作 黑盒。
該 FDS 模塊化的設計方法提供了以下好處:
- 通過不斷驗證的方式來最小化訪問損壞數據的風險:在掉電後,數據可能沒有被完全寫入,這時認爲數據爲無效數據,通過驗證來確保 FDS 識別無效的數據,並且不將損壞的數據返回給用戶。
- 在打開記錄時提供(可選的)CRC校驗,以確保數據寫入以後沒有發生變化。
- 最小化 Flash 操作(更新和刪除):FDS 沒有刪除完整的頁面的操作,而是先存儲新數據的副本,然後通過一個寫操作來使舊的數據失效。
- 基本的損耗均衡:內部 Flash 的寫入次數雖然非常多,但是超過寫入次數限制也會損壞 Flash,FDS 模式通過順序寫和垃圾收集提供了一個相當平均的 Flash 使用級別,也就是說把讀寫均勻的分佈在 Flash 空間上,使得不會出現哪塊位置由於過度的讀寫造成損壞。
- 在不需要複製數據的情況下可以輕鬆訪問數據,使得訪問這些數據的影響與數據的大小無關。
- 通過靈活選擇數據塊的大小來最小化的使用內存,而不是需要一個固定長度。
- 對數據的內容不加限制(這意味着可以包含特殊字符)。
二、移植文件
注意:以下出現缺失common.h文件錯誤,去除即可。uint8改爲uint8_t或unsigned char或自己宏定義
鏈接:https://pan.baidu.com/s/1NeVo1GSFDjByWwdA8gkX8w 提取碼:mm86
將 board_flash_fds.c 和 board_flash_fds.h 兩個文件加入工程的Application文件夾下
2.1 board_flash_fds.c
/*********************************************************************
* INCLUDES
*/
#include "fds.h"
#include "nrf_delay.h"
#include "nrf_log.h"
#include "board_flash_fds.h"
#include "common.h"
static void fdsCallbackFunc(fds_evt_t const *pFdsEvent);
static bool readFdsData(uint16 fileId, uint16 key, uint8 *pData, uint8 dataLen);
static bool writeFdsData(uint16 fileId, uint16 key, uint8 *pData, uint8 dataLen);
static uint8 getWordLength(uint8 dataLen);
static uint32 flashUpdateWithWaiting(fds_record_desc_t *pDesc, fds_record_t *pRec);
static uint32 flashWriteWithWaiting(fds_record_desc_t *pDesc, fds_record_t *pRec);
static uint32 flashDelete(fds_record_desc_t *pDesc);
static void waitForFlashOperationToComplete(void);
/*********************************************************************
* LOCAL VARIABLES
*/
static bool volatile s_fdsIfInitialized; // FDS初始化完成標誌
static fds_record_desc_t s_recordDesc;
static uint8 s_dataBuffer[FDS_BUFFER_SIZE];
static uint16 s_currentId;
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/**
@brief FDS讀寫內存初始化
@param 無
@return 無
*/
bool Fds_FlashInit(void)
{
ret_code_t retCode;
fds_record_t record;
(void)fds_register(fdsCallbackFunc); // FDS註冊
retCode = fds_init(); // FDS初始化
APP_ERROR_CHECK(retCode);
while(!s_fdsIfInitialized) // 等待初始化完成
{
sd_app_evt_wait(); // 等待過程中待機
}
//第一次寫入其實也同時就是創建過程。所以fds模塊會返回這個創建的記錄的現相關信息
retCode = fds_record_write(&s_recordDesc, &record);
if(retCode == NRF_SUCCESS)
{
return true;
}
return false;
}
/**
@brief FDS讀寫內存操作
@param fileId -[in]
@param readWriteFlag -[in] 讀寫操作標誌
@param pData -[in&out] 指向需要操作的數據
@param dataLen -[in] 數據長度
@return 無
*/
void Fds_FlashContrl(uint16 fileId, uint8 readWriteFlag, uint8 *pData, uint8 dataLen)
{
if(readWriteFlag == FDS_READ) // 讀取數據
{
readFdsData(fileId, TEMPERATURE_FLASH_KEY, pData, dataLen);
}
else // 寫入數據
{
writeFdsData(fileId, TEMPERATURE_FLASH_KEY, pData, dataLen);
}
}
/*********************************************************************
* LOCAL FUNCTIONS
*/
/**
@brief FDS事件回調函數
@param pFdsEvent -[in] FDS事件
@return 無
*/
static void fdsCallbackFunc(fds_evt_t const *pFdsEvent)
{
switch(pFdsEvent->id)
{
case FDS_EVT_INIT:
{
if(pFdsEvent->result == FDS_SUCCESS)
{
s_fdsIfInitialized = true;
}
} break;
case FDS_EVT_WRITE:
{
if(pFdsEvent->result == FDS_SUCCESS)
{
NRF_LOG_INFO("Record IDw:\t0x%04x", pFdsEvent->write.record_id);
NRF_LOG_INFO("File ID:\t0x%04x", pFdsEvent->write.file_id);
NRF_LOG_INFO("Record key:\t0x%04x", pFdsEvent->write.record_key);
}
} break;
case FDS_EVT_UPDATE:
{
if(pFdsEvent->result == FDS_SUCCESS)
{
NRF_LOG_INFO("Record IDu:\t0x%04x", pFdsEvent->write.record_id);
NRF_LOG_INFO("File ID:\t0x%04x", pFdsEvent->write.file_id);
NRF_LOG_INFO("Record key:\t0x%04x", pFdsEvent->write.record_key);
}
} break;
case FDS_EVT_DEL_RECORD:
{
if(pFdsEvent->result == FDS_SUCCESS)
{
NRF_LOG_INFO("Record IDd:\t0x%04x", pFdsEvent->del.record_id);
NRF_LOG_INFO("File ID:\t0x%04x", pFdsEvent->del.file_id);
NRF_LOG_INFO("Record key:\t0x%04x", pFdsEvent->del.record_key);
}
} break;
default:
break;
}
}
static bool readFdsData(uint16 fileId, uint16 key, uint8 *pData, uint8 dataLen)
{
ret_code_t retCode;
fds_record_desc_t recordDesc = {0};
fds_find_token_t token = {0};
retCode = fds_record_find(fileId, key, &recordDesc, &token);
if(retCode == FDS_SUCCESS)
{
s_currentId = recordDesc.record_id;
fds_flash_record_t record = {0};
fds_record_open(&recordDesc, &record);
memcpy(pData, record.p_data, dataLen);
fds_record_close(&recordDesc);
return true;
}
return false;
}
static bool writeFdsData(uint16 fileId, uint16 key, uint8 *pData, uint8 dataLen)
{
ret_code_t retCode;
fds_record_desc_t recordDesc = {0};
fds_find_token_t token = {0};
fds_record_t record = {0};
retCode = fds_record_find(fileId, key, &recordDesc, &token);
record.file_id = fileId;
record.key = key;
memcpy(s_dataBuffer, pData, dataLen);
record.data.p_data = (uint8 *) s_dataBuffer;
record.data.length_words = getWordLength(dataLen);
if(retCode == FDS_SUCCESS) // 找到之前記錄,進行更新
{
retCode = flashUpdateWithWaiting(&recordDesc, &record);
}
else
{
flashDelete(&recordDesc);
retCode = flashWriteWithWaiting(&recordDesc, &record);
}
return true;
}
static uint8 getWordLength(uint8 dataLen)
{
uint8 word_length ;
if(FDS_BUFFER_SIZE < dataLen)
{
dataLen = FDS_BUFFER_SIZE;
}
word_length = dataLen / 4;
if(dataLen % 4)
{
word_length += 1;
}
return word_length;
}
static uint32 flashUpdateWithWaiting(fds_record_desc_t *pDesc, fds_record_t *pRec)
{
ret_code_t retCode;
retCode = fds_record_update(pDesc, pRec);
waitForFlashOperationToComplete();
return retCode;
}
static uint32 flashWriteWithWaiting(fds_record_desc_t *pDesc, fds_record_t *pRec)
{
ret_code_t retCode;
retCode = fds_record_write(pDesc, pRec);
waitForFlashOperationToComplete();
return retCode;
}
static uint32 flashDelete(fds_record_desc_t *pDesc)
{
ret_code_t retCode;
retCode = fds_record_delete(pDesc);
waitForFlashOperationToComplete();
return retCode;
}
static void waitForFlashOperationToComplete(void)
{
nrf_delay_ms(10);
}
/****************************************************END OF FILE****************************************************/
2.2 board_flash_fds.h
#ifndef _BOARD_FLASH_FDS_H_
#define _BOARD_FLASH_FDS_H_
/*********************************************************************
* INCLUDES
*/
#include "common.h"
/*********************************************************************
* DEFINITIONS
*/
#define FDS_READ 0x00
#define FDS_WRITE 0x01
#define TEMPERATURE_FLASH_ID 0x00A0
#define TEMPERATURE_FLASH_KEY 0x00B0
#define FDS_BUFFER_SIZE 200
/*********************************************************************
* API FUNCTIONS
*/
bool Fds_FlashInit(void);
void Fds_FlashContrl(uint16 fileId, uint8 readWriteFlag, uint8 *pData, uint8 dataLen);
#endif /* _BOARD_FLASH_FDS_H_ */
三、API調用
需包含頭文件 board_flash_fds.h
Fds_FlashInit
功能 | 初始化Flash讀寫模塊 |
---|---|
函數定義 | bool Fds_FlashInit(void) |
參數 | 無 |
返回 | 成功或失敗 |
Fds_FlashContrl
功能 | Flash讀寫操作 |
---|---|
函數定義 | void Fds_FlashContrl(uint16 fileId, uint8 readWriteFlag, uint32 *pData, uint8 dataLen) |
參數 | fileId:文件標識ID readWriteFlag:讀寫標記,0-讀,1-寫 pData:讀寫的數據 dataLen:寫入數據長度 |
返回 | 無 |
四、SDK配置
點擊 sdk_config.h 文件
選擇 Configuration Wizard
nRF_Libraries 中勾選FDS相關選項
define FDS_ENABLED 1
對 FDS 進行使能,在實現 FDS 庫函數之前,需要首先將其設置爲 1。
define FDS_VIRTUAL_PAGES 3
define FDS_VIRTUAL_PAGE_SIZE 1024
這兩個參數用於配置要使用的虛擬頁面數量及其大小。
FDS_VIRTUAL_PAGES:要使用的虛擬 Flash 頁面的數量。系統爲垃圾收集預留了一個虛擬頁面。因此,最少是兩個虛擬頁面:一個用於存儲數據的頁面和一個用於系統垃圾收集的頁面。FDS 使用的閃存總量爲 FDS_VIRTUAL_PAGES * FDS_VIRTUAL_PAGE_SIZE * 4 字節。
FDS_VIRTUAL_PAGE_SIZE:虛擬 Flash 頁面的大小。用 4 字節的倍數表示。默認情況下,虛擬頁面的大小與物理頁面相同。虛擬頁面的大小必須是物理頁面大小的倍數。
define FDS_BACKEND 2
配置 nrf_fstorage 後臺被 FDS 模式用於寫入 Flash。
參數選擇爲 NRF_FSTORAGE_NVMC 時,FDS_BACKEND 定義爲 1。沒有使用藍牙協議棧工程時,使用這個。
參數選擇爲 NRF_FSTORAGE_SD 時,FDS_BACKEND 定義爲 2。使用藍牙協議棧工程時,使用這個。
define FDS_OP_QUEUE_SIZE 4
內部隊列的大小。如果經常得到同步的 FDS_ERR_NO_SPACE_IN_QUEUES 錯誤,請增加這個值。
define FDS_CRC_CHECK_ON_READ 1
define FDS_CRC_CHECK_ON_WRITE 0
FDS_CRC_CHECK_ON_READ:使能 CRC 檢查。當記錄寫入閃存時保存記錄的 CRC,並在記錄打開時檢查它。使用 FDS 函數的用戶仍然可以“看到”不正確的 CRC 記錄,但是不能打開它們。此外,它們在被刪除之前不會被垃圾收集。
FDS_CRC_CHECK_ON_WRITE:對新記錄進行 CRC 檢查。此設置可用於確保記錄數據在寫入 Flash 時不會發生更改。
define FDS_MAX_USERS 4
可以註冊的回調的最大數量。
五、使用例子
1)添加頭文件
#include "board_flash_fds.h"
2)添加初始化代碼(SDK15.3 中 ble_peripheral 的 ble_app_template 工程 main() 函數中)
加入 Fstorage_FlashInit()
int main(void)
{
bool erase_bonds;
/*-------------------------- 外設驅動初始化 ---------------------------*/
// Initialize.
log_init(); // 日誌驅動初始化
timers_init(); // 定時器驅動初始化(在此加入自定義定時器)
Fds_FlashInit(); // 初始化Flash讀寫模塊
/*-------------------------- 藍牙協議棧初始化 ---------------------------*/
power_management_init();
ble_stack_init(); // 協議棧初始化
gap_params_init();
gatt_init();
advertising_init(); // 廣播初始化
services_init(); // 服務初始化
conn_params_init(); // 連接參數初始化
peer_manager_init();
/*-------------------------- 開啓應用 ---------------------------*/
// Start execution.
NRF_LOG_INFO("Template example started.");
advertising_start(erase_bonds); // 開啓廣播
application_timers_start(); // 定時器應用開啓(在此開啓自定義定時器)
Fds_FlashContrl(CUSTOM_FSTORAGE_ADDR, FSTORAGE_READ, (uint32 *) &s_totalConfigData, sizeof(s_totalConfigData));
// Enter main loop.
for(;;)
{
idle_state_handle();
}
}
3)在開啓應用部分 讀取數據
/*-------------------------- 開啓應用 ---------------------------*/
// Start execution.
Fds_FlashContrl(CUSTOM_FSTORAGE_ADDR, FSTORAGE_READ, (uint32 *) &s_totalConfigData, sizeof(s_totalConfigData));
advertising_start(erase_bonds); // 開啓廣播
application_timers_start(); // 定時器應用開啓(在此開啓自定義定時器)
• 由 Leung 寫於 2020 年 3 月 19 日