目录

概念

实现原理

Flash特性

NVS实现原理

概念

特性

扇区

记录分配表

垃圾回收

ATE结构

重复写入

NVS恢复

NVS初始化

NVS API

nvs_init

函数原型

nvs_clear

函数原型

nvs_write

函数原型

nvs_read

函数原型

nvs_delete

函数原型

nvs_read_hist

函数原型

nvs_calc_free_space

函数原型

struct nvs_fs fs

结构体原型:

成员介绍

使用前准备

实战


概念

Zephyr NVS是建立在Flash存储器之上的一个简易文件系统,它的目的是为了解决Flash擦除的寿命,因为Flash每次覆盖写入时都需要擦除扇区区域,NVS的作用是让用户每次覆盖写入时不会去擦除块区域,有效降低Flash的擦写寿命

实现原理

Flash特性

Flash在每次写入数据时需要将Flash按扇区(不同的Flash类型划分单位不一样)擦除,也就是将所有的存储单元置为1(0xFFFF),因为Flash的特性它只能将bit从1变为0,而不能将0变为1,所以它不能对同一块内存区域进行重复写入,每次写入之前需要对块区域进行擦除然后在写入。

这里需要提到为什么Flash不能将0变为1,首先是电路上的设计,Flash是EEPROM,注意它是ROM改良而来的,而ROM本身就是设计为不可写的,只可读的,所以它里面的总线以及电子架构都是按只读方式来的,Flash是在ROM架构之上增加的写的方式,它在内部设计的时候对每个电容增加了放电的电路,每个电容都有一个单独的电路控制,一个电容在内存单元里对应一个BIT位,所以电容越多也就意味着电路越多,放电与充电的电路是分开的。

有的人肯定会问,为什么不能设计成一个?在放电的时候不给电容充电不就可以了?

这个原因是因为电容充完电之后如果此时断开VCC电源充电,那么电容也有一定的真空期,而且Flash里面使用的是一些类似磁信号的电容是不丢失的,如果要放电是需要电容的负极产生电能来推动电容里的电子向外流通,将电容里存储的电子导出,这是需要单独设计电路的,而充电只需要正负极接好,从正极给正向电流使电子流入电容,电容是一个闭环设计,正极可以流向负极而负极也可以流向正极,当正极流如足够电子时也就是电容无法存储的情况下额外的电子就从负极流出。

如果只是接了充电的电路,没有放电的电路,如果此时想要放电,那么不充电的情况下即便是动态电容它也会隔一段时间也能让把电放完,它是有一个真空期的,不是瞬间放电,如果要瞬间放电需要从负极产生能量把里面的电子导出。

Flash里的电路设计可以看成这样:

从上图可以看到放电电路在芯片控制器里面有单独的线接在上面,而充电电路是一根线分接在所有的电容上面,也就是说如果我要充电,只需要给充电这根线一个正向电流就可以了,如果要对一个bit放电则根据解析地址总线里的地址然后找到接在电容放电端的那根线开始放电就可以了,读根放电一样都是单独的电路,只有充电不是,这一点和我们主机PC上面的DDR这一类内存不一样,这一类的比较贵,并且DDR内部还有内存寻址算法这些,相较于Flash就高端了很多,Flash的优点就是廉价,所以它基本上是用在单片机这一类微型MCU上的。

NVS实现原理

概念

nvs巧妙的利用的Flash不能往重复地址写的特性,NVS每次写入时会规定一个ID,这个ID是唯一的,当写入数据时NVS会往FLASH里写一组数据并在开头字节里做好ID标记,当再次写入时NVS会往后面的地址插入数据,它不会覆盖原始ID,也就是说第二次写入时Flash里其实有两组ID相同的数据,但是当使用Read函数去读的时候读取记录分配表里的ID信息找到有效的数据位置,NVS实则就是利用了空间来换取擦写寿命,当某个扇区写满了以后在想往里面写NVS就需要擦除这个扇区了。

特性

  1. 使用ID作为数据索引
  2. ID使用16bit(2字节)存储,所以最大只能有655336条数据
  3. 屏蔽Flash写入时的对齐,NVS会自动对齐
  4. 不需要每次重复写入时都擦除

扇区

Flash一般管理内存是会将内存划分为块、扇区、页,如:W25Q16会将2MB内存划分为:32个块、每块为16个扇区,每个扇区的大小是4096,最后每个扇区划分为16页,每页大小为256字节

而NVS则会在此基础上建立一层自己的文件系统映射:

NVS以扇区为单位,一个扇区对应Flash里至少两个以上的页,这个取决于你Flash页的大小,同时擦除写入等动作都由Zephyr调用Flash提供的功能完成。

记录分配表

NVS在存储时会向ate(记录分配表)里写入一组数据,记录了这组数据的信息,结构如下:

struct nvs_ate {uint16_t id;    /* 数据的ID */uint16_t offset;    /* 数据在扇区内的偏移 */uint16_t len;   /* 数据的长度 */uint8_t part;   /*  */uint8_t crc8;   /* crc8 check of the entry */
} __packed;

ate在扇区中从后向前增长,而数据在扇区的开头,向后增长

当当前扇区被写满后会切换到下一个扇区继续开始写,每次读取时NVS会首先去ate里寻找最后的一个ID,即有效ID,然后取出它指向的地址与数据,NVS就是以这种方式,将新数据向后插入而不覆盖数据,这样就能避免擦除,但浪费时间与空间。

垃圾回收

同时为了避免多余的ID数据浪费,因为nvs里永远是最后一条ID是有效的,这就意味在进行多次写入的情况下会有大量的重复ID,nvs引入了垃圾回收:

触发垃圾回收的条件是最后一个C扇区里的剩余空间大于A扇区里的ID数据(不包含重复ID),当达到这一条件的时候C扇区会去A扇区里遍历ate,比如寻找到了ID1,它会去A扇区寻找,发现ID1最新一条记录就在A扇区,那么搬运到C扇区,如果A扇区没有,那么在B扇区遍历到了,则丢弃A扇区里所有ID为1的数据,遍历完成之后把A扇区清空,当C扇区写满以后开始往A扇区里写,当A扇区剩余空间大于B扇区里的ID数据(不包含重复ID)时开始重复刚刚的垃圾回收步骤,遍历B扇区里的eta,有效数据搬运到A扇区,无效则清空,当A扇区写满了以后则开始往B扇区里写,反复进行这个过程,达到清理多余的ID的目的,每次完成垃圾回收的扇区都会写入一条ate,id=gc

同样地,如果删除了一个ID,nvs会在ate里插入一条id=delete的记录信息,当nvs找到这条记录时就知道这个id是被删除的了

ATE结构

  • last ate

    • 最近一次写入的ate
  • close ate

    • id=0xFFFF

    • len=0

    • offset指向该扇区最后写入的ate

    • close ate固定位于扇区的最末尾,不指向任何数据,用于标识扇区已被关闭

  • gc ate

    • id=0xFFFF

    • len=0

    • offset指向gc ate自己

    • gc ate固定位于close ate前,不指向任何数据,用于标识该扇区的数据是被垃圾回收过的

  • delete ate

    • id为被删除数据的id

    • len=0

    • offset=0

    • delete ate用于标识某个id的数据被删除

通过如上状态,可以分为如下状态:

  • open sector 不含有有效close ate的扇区,表示开放扇区

  • close sector 含有有效close ate的扇区

  • write sector 当前可写入的扇区

  • empty sector 被擦除后,内容为全FF的扇区

重复写入

在写入时nvs会首先去寻找这个id最新数据,看下数据是否重复,如果重复则不写,否则则写入

NVS恢复

如果Flash之前已经被NVS初始化过一次之后,NVS会对它进行一次恢复,恢复过程如下:

  1. 遍历每个扇区的ate,找到open sector标识(即没有close sector eta)的扇区,并将其设置为write sector(活动写入扇区)
  2. 如果全部都是open sector,那么第一个扇区作为写入write sector
  3. 检查write sector是否为empty sector:
    1. 如果不是则检查是否有gc ate,如果没有gc ate则对当前扇区进行擦除,并进行垃圾回收
    2. 如果已经有gc ate,则往后扇区去遍历

NVS初始化

初始化就更简单了,NVS初始化时只需要告知NVS Flash页与扇区对应关系以及NVS应该放入Flash的哪个位置,即从哪个位置开始,一般首地址,注意开发者需要为这个Flash实现驱动模型

NVS API

nvs_init

函数原型

int nvs_init(struct nvs_fs *fs, const char *dev_name);

函数作用

在Flash上初始化NVS

参数介绍

fs:

nvs描述结构体

dev_name:

Flash驱动名称

返回值:

0成功,errno失败

nvs_clear

函数原型

int nvs_clear(struct nvs_fs *fs);

函数作用

清楚Flash上的NVS文件系统

参数介绍

fs:

nvs描述结构体

返回值:

0成功,errno失败

nvs_write

函数原型

ssize_t nvs_write(struct nvs_fs *fs, uint16_t id, const void *data, size_t len);

函数作用

写入数据

参数介绍

fs:

nvs描述结构体

id:

记录的ID

data:

要写入的数据

len:

数据长度

返回值:

返回写入数据长度,如果返回为0则找到了重复数据,无需写入,否则返回errno

nvs_read

函数原型

ssize_t nvs_read(struct nvs_fs *fs, uint16_t id, void *data, size_t len);

函数作用

读取数据

参数介绍

fs:

nvs描述结构体

id:

记录的ID

data:

要存放的数据的空间地址

len:

数据长度

返回值:

返回读取的数据长度,如果长度大于len则意味着数据没有读完,失败返回errno

nvs_delete

函数原型

int nvs_delete(struct nvs_fs *fs, uint16_t id);

函数作用

清楚Flash上的NVS文件系统

参数介绍

fs:

nvs描述结构体

id:

记录的ID

返回值:

0表示删除成功,失败返回errno

nvs_read_hist

函数原型

ssize_t nvs_read_hist(struct nvs_fs *fs, uint16_t id, void *data, size_t len, uint16_t cnt);

函数作用

读取历史数据

参数介绍

fs:

nvs描述结构体

id:

记录的ID

data:

要存放的数据的空间地址

len:

数据长度

cnt:

0表示最近的一个数据,其它数值则表示重复次数,如这个值为3,则寻找ID三次后返回

返回值:

返回读取的数据长度,如果长度大于len则意味着数据没有读完,失败返回errno

nvs_calc_free_space

函数原型

ssize_t nvs_calc_free_space(struct nvs_fs *fs);

函数作用

计算NVS可用剩余空间

参数介绍

fs:

nvs描述结构体

返回值:

返回NVS可用空间,以字节为单位,失败返回errno

struct nvs_fs fs

结构体原型:

struct nvs_fs fs{
off_t   offset;
uint32_t    ate_wra;
uint32_t    data_wra;
uint16_t    sector_size;
uint16_t    sector_count;
bool    ready;
struct k_mutex  nvs_lock;
const struct device *   flash_device;
const struct flash_parameters *     flash_parameters;};

成员介绍

成员名

作用

offset FLASH中的文件系统偏移量
ate_wra 分配表条目写入地址。地址存储为uint32_t:高2字节对应扇区,低2字节对应扇区中的偏移量
data_wra 数据写入地址
sector_size Flash总共多少扇区,每个扇区的大小,必须是页面大小的倍数
sector_count 将Flash里的页划分为多少个扇区
ready 指示文件系统是否已初始化的标志
nvs_lock 互斥锁
flash_device Flash设备运行时结构
flash_parameters Flash参数结构

使用前准备

使用前需要在prj里开启它,默认nvs是关闭的

# 启用NVS
CONFIG_NVS=y# NVS依赖Flash,需要启用Flash驱动
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y# 当使用的flash地址空间被MPU保护时,需要配置允许对应地址空间被写入
CONFIG_MPU_ALLOW_FLASH_WRITE=y

实战

如下代码记录了每次重启与开机的记录

#include <fs/nvs.h>
#include <drivers/flash.h>
//定义ID
#define REBOOT_COUNT_ID 0
#define POWER_NORMAL_ID 1//Flash驱动
#define FLASH_LABLE "FLASH_ESP32C3"//nvs结构体
struct nvs_fs fs;
struct flash_pages_info info;
uint16_t reboot_cnt = 0;
bool power_normal = false;int main(){//binbang驱动const struct device *flash_dev = device_get_binding(FLASH_LABLE);//获取flash信息flash_get_page_info_by_offs(flash_dev, fs.offset, &info);// nvs放到flash的0x250000处,nvs总计3个扇区fs.offset = 0x250000;fs.sector_size = info.size;fs.sector_count = 3U;// 初始化nvsint rc = nvs_init(&fs, FLASH_LABLE);if (rc) {printk("Flash Init failed\n");return -1;}// 读出上一次reboot count,如果存在做一次加1rc= nvs_read(&fs, REBOOT_COUNT_ID, &reboot_cnt, sizeof(reboot_cnt));if(rc == sizeof(reboot_cnt)){reboot_cnt++;}else{reboot_cnt = 0;}// 更新重启的次数nvs_write(&fs, REBOOT_COUNT_ID, &reboot_cnt, sizeof(reboot_cnt));//读出关机记录rc= nvs_read(&fs, POWER_NORMAL_ID, &power_normal, sizeof(power_normal));if(rc == sizeof(power_normal)){//删除关机记录nvs_delete(&fs, POWER_NORMAL_ID);}return 0;}

Zephyr NVS文件系统原理及应用相关推荐

  1. Linux 文件系统原理 / 虚拟文件系统VFS

    Linux 文件系统原理 / 虚拟文件系统VFS 虚拟文件系统 VFS VFS 定义 VFS 的对象演绎 超级块 super_block 索引节点 inode 目录项 dentry 文件 file 文 ...

  2. 硬盘FAT文件系统原理的详细分析——转载

    首先给大家分享一个巨牛巨牛的人工智能教程,是我无意中发现的.教程不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈-我正在学习中,觉得太牛了,所以分享给大家!点这里可以跳转 ...

  3. FAT文件系统原理的详细分析

    示例源代码下载: http://download.csdn.net/user/retty85 文本结构索引: 图片表格索引: 表1 分区表参数 表2 扩展分区表项的内容 一.硬盘的物理结构 图1 硬盘 ...

  4. 【收藏】FAT文件系统原理——MBR(主引导记录

    FAT文件系统原理--MBR(主引导记录) 一.硬盘的物理结构:          硬盘存储数据是根据电.磁转换原理实现的.硬盘由一个或几个表面镀有磁性物质的金属或玻璃等物质盘片以及盘片两面所安装的磁 ...

  5. FAT文件系统原理(转载)

    转自: http://blog.chinaunix.net/uid-24611346-id-3246892.html 一.硬盘的物理结构:     硬盘存储数据是根据电.磁转换原理实现的.硬盘由一个或 ...

  6. Docker 文件存储驱动:AUFS 文件系统原理及生产环境的最佳配置

    我们知道,Docker 主要是基于 Namespace.cgroups 和联合文件系统这三大核心技术实现的.那么你知道联合文件系统是什么吗?它的原理又是什么呢?首先我们来了解一下什么是联合文件系统. ...

  7. linux 文件系统原理 书,发个关于文件系统的书《Linux文件系统剖析》

    daqshan 于 2015-08-04 08:20:23发表: 看看 12345a 于 2014-12-22 09:38:06发表: 还有没有别的 12345a 于 2014-12-22 09:37 ...

  8. Hadoop HDFS分布式文件系统原理及应用介绍

    HDFS有着高容错性特点,且设计用来部署在低廉的硬件上,提供高吞吐量来访问应用程序的数据,适合那些有着超大数据集的应用程序.HDFS放宽了POSIX的要求,可以实现流的形式访问文件系统中的数据. Ha ...

  9. FAT文件系统原理(一)

    一.硬盘的物理结构: 硬盘存储数据是根据电.磁转换原理实现的.硬盘由一个或几个表面镀有磁性物质的金属或玻璃等物质盘片以及盘片两面所安装的磁头和相应的控制电路组成(图1),其中盘片和磁头密封在无尘的金属 ...

最新文章

  1. 静态NAT,动态NAT与NAPT区别
  2. 成都机场迎春运客流高峰 日均起降航班超1000架次
  3. 用python绘制柱状图标题-Python笔记:用Python绘制炫酷的柱形图
  4. springMVC请求发生重复路径
  5. STM32 端口复用与重映射及低功耗引脚配置
  6. 无需Grouping,中科大和微软亚研院提出Group-Free的三维物体检测方法,性能远超之前最好方法。...
  7. 浏览器渲染机制面试_面试官不讲码德,问我Chrome浏览器的渲染原理(6000字长文)...
  8. 【FFMPEG源码终极解析】 avformat_open_input (一)
  9. Ansible Privilege Escalation
  10. ubuntu14上安装ros教程
  11. 用3DMAX制作《滚动的小球》
  12. [1160]C语言实验——某年某月的天数
  13. Mac自定义触控栏 Touch Bar的显示教程
  14. window10关闭磁盘bitlocker加密
  15. linux的批处理文件怎么写,Linux下批处理文件编写
  16. php 正则筛选靓号如AABBCC(连对),abcdef(顺子)等QQ靓号保留
  17. 个人简历应聘优势怎么写? 个人优势让你的简历模板加分
  18. 程序员如何接私活、外包的秘技
  19. 基于python的网上订餐系统论文模板
  20. 五年,他们从应届生成为了滴滴的「技术扛把子」

热门文章

  1. mysql行级锁unique_MySQL行级锁,表级锁,页级锁详解
  2. Python灰度图像彩色化
  3. Android Studio实现通讯录项目
  4. 网络切片技术缺点_5G中网络切片研究的现状与挑战
  5. ae正在发生崩溃_adobe AE cc2015 打开后崩溃 然后就自动关闭 怎么解决
  6. spring freemarker 获取后端的值
  7. android多个网络请求如何依次执行,Android 并发和串行网络请求
  8. 恢复html的初始选定状态,jQuery实现点击旋转,再点击恢复初始状态动画效果示例...
  9. python 邮箱验证_在Django中进行用户注册和邮箱验证的方法
  10. 什么是pdi检测_为什么国人买车钟爱白色?这几点购车陷阱不要踩