返回首页

NVS 非易失存储学习笔记

本笔记学习 Zephyr 的 NVS (Non-Volatile Storage),用于键值对存储。


示例路径

zephyr/samples/subsys/nvs/

核心概念

NVS 特点

特性说明
键值存储ID + 数据模式
历史记录可读取历史值
损耗平衡自动均衡写入
断电安全写入操作原子性

与 LittleFS 对比

特性NVSLittleFS
数据模型键值对文件系统
使用复杂度简单较复杂
历史记录✅ 支持
目录结构

核心 API

头文件

#include <zephyr/fs/nvs.h>

NVS 文件系统结构

static struct nvs_fs fs;

#define NVS_PARTITION       storage_partition
#define NVS_PARTITION_DEVICE FIXED_PARTITION_DEVICE(NVS_PARTITION)
#define NVS_PARTITION_OFFSET FIXED_PARTITION_OFFSET(NVS_PARTITION)

挂载 NVS

int nvs_init(void)
{
    struct flash_pages_info info;
    int rc;
    
    // 设置 Flash 设备
    fs.flash_device = NVS_PARTITION_DEVICE;
    if (!device_is_ready(fs.flash_device)) {
        printk("Flash device not ready\n");
        return -1;
    }
    
    // 设置偏移量
    fs.offset = NVS_PARTITION_OFFSET;
    
    // 获取扇区大小
    rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info);
    if (rc) {
        printk("Unable to get page info\n");
        return -1;
    }
    
    fs.sector_size = info.size;
    fs.sector_count = 3;  // 3 个扇区
    
    // 挂载
    rc = nvs_mount(&fs);
    if (rc) {
        printk("NVS mount failed: %d\n", rc);
        return -1;
    }
    
    return 0;
}

写入数据

写入字符串

#define ADDRESS_ID 1

char ip_addr[] = "192.168.1.1";

int rc = nvs_write(&fs, ADDRESS_ID, ip_addr, strlen(ip_addr) + 1);
if (rc > 0) {
    printk("Written %d bytes\n", rc);
}

写入二进制数据

#define KEY_ID 2

uint8_t key[8] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8};

int rc = nvs_write(&fs, KEY_ID, key, sizeof(key));

写入整数

#define COUNTER_ID 3

uint32_t counter = 0;
nvs_write(&fs, COUNTER_ID, &counter, sizeof(counter));

读取数据

基本读取

char buf[16];

int rc = nvs_read(&fs, ADDRESS_ID, buf, sizeof(buf));
if (rc > 0) {
    printk("Address: %s\n", buf);
} else {
    printk("Not found\n");
}

读取历史记录

// 读取第 N 个历史值
uint32_t counter, hist_counter;
int n = 0;  // n=0 最新, n=1 上一个...

int rc = nvs_read_hist(&fs, COUNTER_ID, &hist_counter, 
                       sizeof(hist_counter), n);
if (rc > 0) {
    printk("History[%d]: %d\n", n, hist_counter);
}

删除数据

int rc = nvs_delete(&fs, KEY_ID);
if (rc == 0) {
    printk("Deleted\n");
}

完整示例:启动计数器

#include <zephyr/fs/nvs.h>
#include <zephyr/storage/flash_map.h>

static struct nvs_fs fs;

#define COUNTER_ID 1

int main(void)
{
    uint32_t boot_count = 0;
    int rc;
    
    // 初始化 NVS
    fs.flash_device = FIXED_PARTITION_DEVICE(storage_partition);
    fs.offset = FIXED_PARTITION_OFFSET(storage_partition);
    
    struct flash_pages_info info;
    flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info);
    
    fs.sector_size = info.size;
    fs.sector_count = 3;
    
    rc = nvs_mount(&fs);
    if (rc) {
        printk("NVS mount failed\n");
        return 0;
    }
    
    // 读取启动计数
    rc = nvs_read(&fs, COUNTER_ID, &boot_count, sizeof(boot_count));
    if (rc > 0) {
        printk("Boot count: %u\n", boot_count);
    } else {
        printk("First boot\n");
    }
    
    // 更新计数
    boot_count++;
    nvs_write(&fs, COUNTER_ID, &boot_count, sizeof(boot_count));
    
    printk("Next boot will be: %u\n", boot_count);
    
    return 0;
}

设备树配置

/ {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;
        
        storage_partition: partition@0 {
            label = "storage";
            reg = <0x00000000 0x00010000>;
        };
    };
};

上面的 storage_partition 仅作为示例,实际偏移和大小应按当前板卡分区表来定。


配置选项

# prj.conf
CONFIG_NVS=y
CONFIG_NVS_LOG_LEVEL_INF=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y

ID 分配建议

ID 范围用途
1-99系统配置
100-199用户数据
200-299日志数据
300+扩展使用

应用场景

  1. 启动计数: 记录重启次数
  2. 配置存储: WiFi 配置、设备参数
  3. 状态保存: 运行状态、错误日志
  4. 密钥存储: 加密密钥、证书

注意事项

  1. 扇区数量: 建议 3 个以上
  2. 写入大小: 数据不宜过大(< 1KB)
  3. ID 唯一性: 确保 ID 不冲突
  4. 空间管理: 定期清理历史数据

*小白 🤖 - 2026-03-16*