Nordic--nrf52832--FDS(二)基本使用_fds_register_liefyuan的博客-程序员宅基地

注册FDS

ret_code_t  fds_register(fds_cb_t cb); 

 该函数注册 fds的事件处理函数,fds提供了写/更新/删除等api,不过这些api都是异步的,即调用后函数函数会立刻返回,但是实际的flash操作可能不会立刻执行。协议栈内部会在合适的时候去执行实际操作。并最终返回给上层事件,fds模块内部处理后再返回 fds的事件,并调用fds_register函数注册的这个回调函数。


初始化FDS

ret_code_t fds_init(void);

 初始化FDS模块, FDS模块首先应该调用 fds_register 之后才调用fds_init 来初始化fds模块。因为fds的初始化完成后也会返回 init 完成事件并调用回调函数。


FDS写操作

ret_code_t  fds_record_write(fds_record_desc_t * const p_desc,
                fds_record_t      const * const p_record);

 写记录,第一次写入一个记录时需要用该函数,p_record 参数中需要给出该记录的file id,record key以及记录的内容指针。FDS内部不会缓存需要写入flash的内容,所以开发者需要自己保证传入的数据指针指向的内容在flash操作完成回调执行之前一直都是有效的

 第一次写入其实也同时就是创建过程。所以fds模块会返回这个创建的记录的现相关信息。

 P_desc即为fds返回的该记录的描述符。其中的内容为fds模块内部使用。比如其中的record id字段,因为file id和record key不要求唯一性,所以fds模块内部定义了一个record id来保证每个记录块的唯一性,即使他们fiel id,record key相同fds模块内部也可以同过record id来区分。


FDS其他操作

ret_code_t fds_reserve(fds_reserve_token_t * const p_token, uint16_t length_words);

ret_code_t fds_reserve_cancel(fds_reserve_token_t * const p_token);

ret_code_t fds_record_write_reserved(fds_record_desc_t * const p_desc,

                        fds_record_t const * const p_record,

                        fds_reserve_token_t const * const p_token);

 这三个函数放在一起说明,这三个函数的作用类似延迟写的作用。有些应用场景,你明确知道你需要存某个内容,但是这个内容可能需要一定时间后你才能知道其真正内容。但是如果等到那个时间再存,可能flash中已经没有空间来存储你的内容了。所以fds提供了预定写相关的操作。

 即可以通过fds_reserve 函数来直接申请预留一部分flash空间将来用。该函数返回的参数p_token即记录了申请的空间。

 等将来需要实际写入数据到之前申请预留的flash空间时,调用fds_record_write_reserved 函数并传入 之前获取到的p_token即可。 P_desc和P_record参数同fds_record_write函数

 如果后面不想写了,也可以通过fds_reserve_cancel 释放之前申请的预留空间。


FDS更新操作

ret_code_t  fds_record_update(fds_record_desc_t * const  p_desc,

                fds_record_t const * const p_record);

 更新记录内容,FDS的更新并不是真的直接修改原记录的内容,而是直接创建一个新的记录,内容即为新内容,即p_record参数指定的新内容。从FDS的更新角度来看,其实更新一个记录等于就是创建一个记录,只是多了一个无效旧记录的操作。所以p_record参数中的file id,record key可以和旧记录一样也可以不一样。如果和旧记录不一样那怎么找到旧记录去无效它? 其实就算和旧记录一样,FDS内部也不是依靠file id和record key去找这个旧记录的,因为fds不需要保证file id 和record key的唯一性,所以不能以这两个参数去找。 FDS模块内部是通过 record id这个内部使用的参数去找的。这个参数即在 p_desc 参数中。那个怎么知道传入的p_desc参数中的record id应该设置成什么值?即怎么知道旧记录的record id? 其实不需要设置,因为是更新操作,所以前面一定调用过fds_record_write 函数第一次去写这个记录。而fds_record_write 的第一个就是FDS模块内部返回的关于这个记录的内部信息,其中就有record id这个字段。

 所以综上,更新一个旧记录的内容,首先需要知道其旧记录的描述符,该描述符就是fds_record_write第一次写时返回的。 之后设置新记录的内容即可。 FDS内部直接创建一个新记录填写新内容。并利用旧记录的描述符找到旧记录然后去无效它。

 同时因为update会直接创建一个新的记录填写新内容,所以以前fds_record_write返回的描述符就没用了,因为旧记录已经无效了。所以update函数也同时会返回新记录的描述符存在在p_desc 变量中。


FDS删除操作

ret_code_t fds_record_delete(fds_record_desc_t * const p_desc)

 该函数用来删除某个记录,这里也实际并不是真的删除,fds内部只是设置使这个记录无效。同样因为fds并不要求file id和record key唯一性。所以删除不能依据这两个数据。而是需要用 fds_record_write 或者fds_record_update 函数返回的描述符。这个描述符中有记录的唯一标识record id。Fds内部会依据这个值来查找记录。

 还是因为fds的file id和record key的不唯一性。所以当我们需要读某个记录时怎么办? 当然可以直接用write/update操作返回的变量p_desc,这个变量中除了有这个记录的record id这个fds内部使用的记录唯一标识。还有p_record,这个变量记载了这个记录的存储地址。

 上面的方法可以用,但是fds提供了读相关的接口。另外fds有自己的存储结构,如果直接用访问地址的方式读,你需要自己解析一下数据头。总之直接用fds提供的读api 更方便。

 另外应用场景中的确有多个记录有相同的record key和file id。怎么读取他们?

Fds提供了下面的接口


FDS查找操作

ret_code_t fds_record_find(uint16_tfile_id,
              uint16_t record_key, fds_record_desc_t * const p_desc,

              fds_find_token_t  * const p_token);

 该接口通过file id和record key找到flash中符合file id和record key的第一个记录,并通过p_desc返回这个记录的描述符(包含了记录的record id 和存储地址)

 那么如何找到剩下的相同file id和record key的记录? 这需要用到该函数返回的p_token。

 这个结构体变量保存的是找到的这个记录所在的页和地址。

 那么就明白了。找到了第一个记录,并且知道了这个记录所在的页和地址。那么下一个记录直接从这只有找就可以了。

 综上,只需设置p_token初始值为0,然后迭代调用这个函数,即可找到所有file id和record key相同的记录了。

    memset(&ftok, 0x00, sizeof(fds_find_token_t));

   while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)

    {
    

       // 找到了一个

    }

找到了记录,那么就可以通过返回的描述符来读取记录了。

   ret_code_t fds_record_open(fds_record_desc_t  * const p_desc,

                      fds_flash_record_t * const p_flash_record);

 

   ret_code_t fds_record_close(fds_record_desc_t * const p_desc);

 fds_record_open通过之前获取到的描述符去打开这个记录,并通过p_flash_record这个结构体返回这个记录数据,包括它的file id,record key,长度,实际内容,如果使能了crc校验还会有crc值。当对flash的访问结束后通过fds_record_close来结束访问。

 为什访问flash需要有一个open和colse操作?这与下面要说的fds的垃圾回收机制有关。上面提到过fds的删除操作只是无效了这个记录(update对于旧块也有使其无效的操作),其实并没有删除。这样的操作多了会导致flash中很多没有释放的无效记录,那么就没有空间来存储新的记录了。Fds的垃圾回收机制会将一页有这些无效记录的脏页中的有效数据写入到一个全新的交换页,之后这个交换页就作为数据页了。而之前有无效记录的数据页会被直接整页擦除然后作为新的数据页。

所以为了在访问flash内容时,flash的数据不会变动。所以fds提供的读操作会有open和close接口, open函数会打开这个记录,之后访问这个记录内容。访问结束后调用close来关闭访问。实际上Open操作会设置fds内部标记,从而阻止垃圾回收对该记录所在也做处理,保证在访问期间flash数据不会变动。所以访问完后需要调用close释放,从而可以是垃圾回收能正常工作。

 综上,如果想访问所有相同file id 和record key的记录块的内容。则可以通过如下方式:

    memset(&ftok, 0x00, sizeof(fds_find_token_t));
    // 迭代查找所有file id和record key相同的记录
    while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)
    {
    
        if (fds_record_open(&record_desc, &flash_record) != FDS_SUCCESS)
        {
    
            //打开成功,访问数据,flash_record保存了记录的相关信息和内容。
            
            //访问结束关闭记录
            if (fds_record_close(&record_desc) != FDS_SUCCESS)
            {
    
                //错误处理
            }
        }
    }

关于查找记录fds还提供了另外两个接口,可以只通过file id或者record key来查找某个记录,或者迭代查找其值相同的所有记录。

    ret_code_t fds_record_find_by_key(uint16_t record_key,

                      fds_record_desc_t  *const p_desc,

                     fds_find_token_t  *const p_token);

     ret_code_t fds_record_find_in_file(uint16_t file_id,

                       fds_record_desc_t * const p_desc,

                     fds_find_token_t  * const p_token);

使用方法和上面介绍的fds_record_find 是一样的。


FDS垃圾回收机制

ret_code_t fds_gc(void);

 fds 的删除记录/更新记录相对以前的flash操作方式都比较快。因为不论是直接的flash删除记录操作,还是更新操作中的无效旧记录的操作。fds并不会真的去执行删除操作,只是设置一下无效标记即将记录的record key置位无效的0x0000表示该记录无效。从而实现删除的操作。

 这样操作避免了flash局部更新时的麻烦,否则你需要去读取整个flash的内容,修改局部后再写回。

 而fds的机制则避免了这类麻烦,但带来另一个问题,如果无效记录累计越来越多,最终导致要写一个新记录时返回空间不足。这个时候就需要做一次总的回收,回收所有无效记录占用的flash 的空间。以便新纪录写入。

 Fds的回收机制需要一个额外的 交换页,该页是一个全新页。在执行垃圾回收时,fds模块会找到有无效记录的脏页,然后将该页中的有效的记录都写到这个交换页中,这个交换页作为新的数据页。最后整页擦除之前的脏页,并将擦除后的脏页作为新的交换页。并且继续查找有无效数据的脏页做相同处理。直到所有数据页都没有无效数据。

 PS:前面提到过fds_record_open函数会阻止垃圾回收的处理,如果一个页中有无效数据,但是其中某个或多个有效数据被打开了并且还未释放。那么垃圾回收会跳过该页不做处理。

 垃圾回收机制会将所有没有打开记录的有无效记录的脏页都做一次回收处理,释放其中的无效记录所占的flash空间,所以fds的垃圾回收比较耗时,因此fds不会主动做垃圾回收的处理。因此需要开发这自己在必要的时候才去调用。比如写操作返回 FDS_ERR_NO_SPACE_IN_FLASH 错误时调用ret_code_t fds_gc(void); 进行无效记录的空间释放。并在收到垃圾回收完成的事件之后再进行写操作。


FDS状态获取操作

    ret_code_t fds_stat(fds_stat_t * const p_stat);

 最后介绍一下fds的状态获取函数。该函数可以获取当前fds模块的总状态。该函数返回的p_stat 记录了fds管理的flash存储空间的状态。例如有多少个文件打开了,flash使用了多少,有多少个有效记录,有多少个无效记录,有多少空间可回收等信息。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28877125/article/details/93071476

智能推荐

ODI工作资料库底层数据结构研究(一)_qingqingxuelang的博客-程序员宅基地

为了准备ODI测试环境往正式环境的迁移,特地研究了ODI工作资料库的底层数据库结构。 对于ODI的模型层面,如下图所示: 从图上可以看出,模型大概分了4层: 模型文件夹 --模型 --关系图 --表 对应的数据库底层则为:snp_mod_folder --snp_model

Ardupilot使用Nra24毫米波雷达仿地飞行_nra24 can通信__azureg润的博客-程序员宅基地

引子仿地飞行能缓解植保多旋翼在农业植保过程中产生的雾滴漂移问题,增加雾滴有效沉积率,提升施药效果。传统植保无人机使用超声波作为高度计,实际应用过程中由于超声波传感器的频段处在40khz-45khz之间,容易穿透植被,不容易获得植被真实高度信息。此外,超声波传感器还容易受到雾滴及风场影响。毫米波雷达不容易穿透植被,对雾滴及粉尘穿透性强,仿地高度信息稳定可靠。这里选用Nra24型号毫米波雷达接..._nra24 can通信

计算机技术的应用现状,计算机技术的应用现状分析及其发展趋势探究_俞林鑫的博客-程序员宅基地

摘 要:科学技术的发展促进了计算机技术的广泛应用,人们的生活、工作、学习都与计算机联系紧密,随着人们生活水平的提升,对计算机的需求更加广泛,计算机的服务水平也得到了快速的提升,在互联网、多媒体以其通信技术领域中的应用水平不断提升。目前人们使用的计算机主要以数字计算机为基础,一定程度上限制了计算机的储存和运行速度,本文主要就计算机技术的应用现状进行分析,进一步探讨计算机技术未来的发展趋势。关键词:计..._计算机技术的应用情况

js存储数据cookie,localhost,sessionstorage_app中localhost 可以保存多久_快乐的二进制鸭的博客-程序员宅基地

存储数据1.cookie。设置 属性不会被覆盖特点:内存小只有4kb,可以设置过期时间。而且有些域名会限制网站里面找cookie:1.检查 2.Application 3.cookies​ 或者:点击网页最上面小锁。点击使用了多少条cookie//一条一条的设置document.cookie = "name=xinxin"; //设置document.cookie //查看document.cookie = "sex=_app中localhost 可以保存多久

R语言绘制布林带通道_bollen_plot.r_superdont的博客-程序员宅基地

使用R语言绘制布林带通道。_bollen_plot.r

7.10黄金白银TD走势分析,黄金原油操作指南及解套在线_zj678590的博客-程序员宅基地

​黄金消息面与基本面解析黄金先涨后跌,围绕1800美元重要关口展开争夺。市场出现了美元和黄金同跌的情况,隔夜美国股汇双杀,黄金被抛售换取流动性,黄金最大回撤幅度超过20美元。7月8日美国三大股指皆绿,道琼斯最大跌幅一度达到1.52%,纳斯达克跌幅一度逼近2%,这就造成市场抛售黄金换取流动性,再加上近期黄金上涨幅度较大,获利盘较多,造成黄金持有者的跟风抛售,黄金多头情绪需要时间来修复。投资者需注意短期黄金可能盛极而衰,短期市场持有黄金的力度在减弱。数据显示,截止7月8日全球最大的黄金ETF-SPDR G

随便推点

httpd_httpd bin_掌灯夜行的博客-程序员宅基地

第1章 Httpd安装与配置1.1 简介 Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性[1]被广泛使用,是最流行的Web服务器端软件之一。它快速、可靠并且可通过简单的API扩展,将Perl/Python等解释器编译到服务器中。功能包_httpd bin

【学习笔记】Redis的geohash数据结构介绍_geohash redis_march of Time的博客-程序员宅基地

geohash介绍⾃Redis 3.2开始,Redis基于geohash和有序集合提供了地理位置相关功能。Redis Geo模块包含了以下6个命令:GEOADD: 将给定的位置对象(纬度、经度、名字)添加到指定的key;GEOPOS: 从key⾥⾯返回所有给定位置对象的位置(经度和纬度);GEODIST: 返回两个给定位置之间的距离;GEOHASH: 返回⼀个或多个位置对象的Geohash表⽰;GEORADIUS: 以给定的经纬度为中⼼,返回⽬标集合中与中⼼的距离不超过给定最⼤距离的所_geohash redis

gtk 简单时钟_innotech的博客-程序员宅基地

#include #include gboolean draw_the_specail_area(GtkWidget * area, cairo_t * cr, gpointer user_data){ GDateTime * now; static int init = 0; static gdouble second = 0; static int min = 0; stat

linux常用命令_sunFlower_zxf的博客-程序员宅基地

cd :进入命令 cd /home,cd …:返回上一层目录 cd ./当前目录pwd显示当前所在的目录。mkdir创建目录,用法mkdir test ,命令后接目录的名称。rmdir 删除空目录rm 删除文件或者目录,用法 rm –rf test.txt (-r表示递归,-f表示强制)。cp 拷贝文件,用法,cp old.txt /tmp/new.txt ,常用...

idea创建Spring Boot项目_雷震子&Seven的博客-程序员宅基地

1、在idea中下载spring initalizr插件File->Setting-->Plugins-->Marketplace中搜索Spring boot,没有的话,可以搜索Spring Initalizr/Spring Boot Assistant2、创建Spring Boot项目事先下载jdkFile-->new-->Project--》Spring Initializr填写项目名称、选择jdk版本号3、创建好的项目结构,在目录下新建Sp

解决 openresty Nginx 重启报错问题 nginx: [error] open() “/usr/local/openresty/nginx/logs/nginx.pid“ fa_openresty初始化 挂载错误 nginx.conf_李子捌的博客-程序员宅基地

解决 openresty Nginx 重启报错问题nginx: [error] open() “/usr/local/openresty/nginx/logs/nginx.pid” failed (2: No such file or directory)解决方法:使用nginx -c的参数指定nginx.conf文件的位置/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf..._openresty初始化 挂载错误 nginx.conf

推荐文章

热门文章

相关标签