概述
NVS是非易失性存储的简写, 而Zephyr中的NVS单只是对Flash功能进行包装,并提供一个“识别号-数据”对的访问接口,用于存储系统中经常改变的数据。例如播放器的音量,当前的环境噪声,设备重启的次数。我们知道在Flash上写入数据是需要先擦除,而Flash的擦除次数是有限制的,引入NVS的目的是通过Flash的空间换取Flash不被频繁的擦除,并均衡使用Flash的磨损。
Zephyr的基本构成如下:
NVS以扇区(sector)为单位管理Flash,一个扇区包含一个或以上的Flash页,扇区必须按照Flash的页大小对齐。NVS至少要2个扇区,NVS永远维持一个空扇区做记录回收用,NVS写入记录的数据小于一个扇区。
NVS数据以记录为单位,一条记录就是一个id-data对。在NVS中以唯一id来标识记录。id的长度为16bit,最大0xFFFF被作为特殊用途,也就是说NVS可以存储的数据条目不能超过65535条。
NVS写入记录时不会擦除原来的记录,而是先检查NVS上已有记录,如果有相同的id-data对就不进行写入,如果没有则进行写入。所以NVS内可能会存在多条id相同的记录。
NVS读出记录时,会读出指定id最近的一条写入记录。
接口
基于include/fs/nvs.h
进行注释分析1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86/**
* @brief nvs_init
*
* 在Flash上初始化NVS文件系统.
*
* @param fs 指向nvs
* @param dev_name Flash设备名
* @retval 0 表示成功
* @retval 负数为失败返回的errno
*/
int nvs_init(struct nvs_fs *fs, const char *dev_name);
/**
* @brief nvs_clear
*
* 清除NVS文件系统
* @param fs 指向nvs
* @retval 0 表示成功
* @retval 负数为失败返回的errno
*/
int nvs_clear(struct nvs_fs *fs);
/**
* @brief nvs_write
*
* 向nvs写入一条记录.
*
* @param fs 指向nvs
* @param id 记录的ID
* @param data 要被写入记录的数据
* @param len 被写入的长度
* @return 返回实际的写入数据长度,应该和len相等,当返回0时表示找到相同的id-data,无需再次写入。负数为失败返回的errno。
*/
ssize_t nvs_write(struct nvs_fs *fs, uint16_t id, const void *data, size_t len);
/**
* @brief nvs_delete
*
* 从nvs中删除一条记录
*
* @param fs 指向nvs
* @param id 删除记录的ID
* @retval 0 表示成功
* @retval 负数为失败返回的errno
*/
int nvs_delete(struct nvs_fs *fs, uint16_t id);
/**
* @brief nvs_read
*
* 从nvs中读出一条记录.
*
* @param fs 指向nvs
* @param id 要读出记录的ID
* @param data 被读出的数据
* @param len 读出数据的长度
*
* @return 返回实际的读出数据长度,应该和len想等。当返回值大于len时表示该条记录还有数据没有读完。负数为失败返回的errno。
*/
ssize_t nvs_read(struct nvs_fs *fs, uint16_t id, void *data, size_t len);
/**
* @brief nvs_read_hist
*
* 读NVS内的历史记录
*
* @param fs 指向nvs
* @param id 要读出记录的ID
* @param data 被读出的数据
* @param len 读出数据的长度
* @param cnt 历史计数器,0表示最近的一个
*
* @return 返回实际的读出数据长度,应该和len想等。当返回值大于len时表示该条记录还有数据没有读完。负数为失败返回的errno。
*/
ssize_t nvs_read_hist(struct nvs_fs *fs, uint16_t id, void *data, size_t len, uint16_t cnt);
/**
* @brief nvs_calc_free_space
*
* 计算NVS剩余可用空间.
*
* @param fs 指向nvs
*
* @return 返回NVS上可用空间的大小.如果返回负数则为错误的errno
*/
ssize_t nvs_calc_free_space(struct nvs_fs *fs);
使用
默认情况下NVS是被禁用的,通过下面配置项可以启用NVS1
2
3
4
5
6
7
8
9# 启用NVS
CONFIG_NVS=y
# NVS依赖Flash,需要启用Flash驱动
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
# 当使用的flash地址空间被MPU保护时,需要配置允许对应地址空间被写入
CONFIG_MPU_ALLOW_FLASH_WRITE=y
以下示例代码展示了如何使用nvs记录系统启动的次数和检查系统是否正常关机。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
struct nvs_fs fs;
struct flash_pages_info info;
uint16_t reboot_cnt = 0;
bool power_normal = false;
const struct device *flash_dev = device_get_binding(FLASH_LABLE);
flash_get_page_info_by_offs(flash_dev, fs.offset, &info);
// nvs放到flash的0x250000处,每个扇区使用一个flash页面,nvs总计3个扇区
fs.offset = 0x250000
fs.sector_size = info.size;
fs.sector_count = 3U;
// 初始化nvs
rc = nvs_init(&fs, FLASH_LABLE);
if (rc) {
printk("Flash Init failed\n");
return;
}
// 读出上一次reboot count,如果存在做一次加1
rc= 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);
}
if(power_normal == true){
// 正常关机处理
}else{
// 异常关机处理
}
// 处理
switch(status){
case DO_SOMTING:
break;
case POWER_OFF:
//关机流程,写入正常关机标记
nvs_write(&fs, POWER_NORMAL_ID, &power_normal, sizeof(power_normal));
//进行关机处理
break;
case NVS_FORMAT:
//清除NVS文件系统
nvs_clear(&fs);
break;
default:
break;
}
参考
https://docs.zephyrproject.org/latest/reference/storage/nvs/nvs.html