美文网首页物联网loT从业者物联网相关技术研究
NRF52832学习笔记(17)——Flash接口使用(FDS方

NRF52832学习笔记(17)——Flash接口使用(FDS方

作者: Leung_ManWah | 来源:发表于2020-03-19 16:03 被阅读0次

一、背景

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.cboard_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 日

• 参考:青风电子社区
    弄懂FDS
    FDS的GC操作
    FDS的掉电分析

相关文章

网友评论

    本文标题:NRF52832学习笔记(17)——Flash接口使用(FDS方

    本文链接:https://www.haomeiwen.com/subject/blekyhtx.html