【Zephyr开发实践系列】08_NVS文件系统调试记录

发布于:2025-07-19 ⋅ 阅读:(20) ⋅ 点赞:(0)


前言

在嵌入式系统开发中,稳定可靠的非易失性存储方案是系统健壮性的关键保障。近期在项目芯片驱动开发中,先后实现了内部Flash和外部NAND Flash的基础驱动验证。随即需要着手构建文件系统层->NVS与LittleFS对Flash进行统一文件管理。
选NVS,如果

  • 只需要存储少量配置参数(如Wi-Fi SSID、设备序列号)。
  • 数据需要频繁更新(如计数器、状态标志)。
  • 对RAM/Flash占用极其敏感。

选LittleFS,如果:

  • 需要存储日志、图片、固件等较大文件。
  • 需要目录管理(如/config/device.cfg)。
  • 设备可能频繁断电,需强数据一致性。

但一般可以互相搭配使用

  • NVS 存储关键配置(如蓝牙MAC、校准参数)。
  • LittleFS 管理日志、固件包等大文件。

本文记录使用内部Flash与外部NAND-Flash进行NVS文件系统操作时出现的BUG与调试情况。

一、NVS原理介绍:

请参考:NVS文件系统原理及应用

二、BUG-NO1:将NVS运用在NAND-Flash类大容量存储设备

2.1 情况描述:

想在外部NAND-Flash上构建类似EEPROM专门存储小数据的掉电记忆系统,于是碰见了NVS。

2.2 BUG复现:

文件系统设备树构建


		boot_partition: partition@0 {
			label = "mcuboot";
			reg = <0x00000000 DT_SIZE_M(1)>;
		};

		lfs1_partition: partition@100000 {
			reg = <0x00100000 DT_SIZE_M(510)>;
		};

		slot0_partition: partition@1FF00000 {
			label = "image-0";
			reg = <0x1FF00000 DT_SIZE_K(128)>;
		};

		slot1_partition: partition@1FF20000 {
			label = "image-1";
			reg = <0x1FF20000 DT_SIZE_K(128)>;
		};

		nvs_partition: partition@1FF40000 {
			label = "nvs";
			reg = <0x1FF40000 DT_SIZE_K(256)>;
		};

说明将512M NAND-Flash划分为四个分区:

  • boot_partition:存放mcu-boot
  • lfs1_partition:LittleFS文件系统管理处
  • slot0_partition:迭代版本程序0
  • slot1_partition:迭代版本程序1
  • nvs_partition:NVS文件系统管理处
    1M+510M+128K+128K+256K<512M,使用该分配是合理的。

测试应用编写(导致错误部分):

/* 最小擦除单位是128KB*/
#define NVS_SECTOR_SIZE  128*1024
#define NVS_SECTOR_COUNT (DT_SIZE_K(256K) / NVS_SECTOR_SIZE)  // 256KB / 128KB = 2 

static struct nvs_fs fs = {
    .sector_size = NVS_SECTOR_SIZE, //最小擦除单位*n
    .sector_count = NVS_SECTOR_COUNT, //大于等于2
    .offset = NVS_PARTITION_OFFSET, //起始地址,必须是可擦除区域的边界:1FF40000/128K无余数
};

说明:

  • sector_size:最小擦除单位,针对NOR-Flash叫扇区,一般是1-2K。而NAND-Flash,无扇区概念,额定最小擦除单位(块):128K
  • sector_count:扇区个数。sector_size*sector_count小于等于nvs_partition大小即可。
  • offset:可擦除边界地址

问题呈现:

编译后报错:NVS_SECTOR_SIZE Over sector_size(0~65536)

2.3 问题简述:

问题定位:

sector_size的数据格式是uint16(0~65535)=64K,若输入范围大于uint16将会返回错误。
定义文件路径:settings_env.c

	if (nvs_sector_size > UINT16_MAX) {
		return -EDOM;
	}

我们NAND-Flash最小擦除范围是128K,大于uint16。

强制实现:

尝试强制将NAND-Flash改为sector_size的范围(64K),同时增加sector_count数。
经尝试编译通过:但毫无疑问出现边界未对齐问题,芯片直接跑飞:

[19:57:48.802][00:00:01.387,000] <err> os: ***** USAGE FAULT *****
[19:57:48.804][00:00:01.387,000] <err> os:   Unaligned memory access
[19:57:48.805][00:00:01.387,000] <err> os: r0/a1:  0x00000073  r1/a2:  0x39c00000  r2/a3:  0x00000073
[19:57:48.806][00:00:01.387,000] <err> os: r3/a4:  0x39c00000 r12/ip:  0x0000019b r14/lr:  0x0c0cf2fb
[19:57:48.807][00:00:01.387,000] <err> os:  xpsr:  0x01100000
[19:57:48.808][00:00:01.387,000] <err> os: s[ 0]:  0x00000000  s[ 1]:  0x00000000  s[ 2]:  0x00000000  s[ 3]:  0x00000000
[19:57:48.810][00:00:01.387,000] <err> os: s[ 4]:  0x616c7f25  s[ 5]:  0x3fe55555  s[ 6]:  0xf8198216  s[ 7]:  0x3f326636
[19:57:48.811][00:00:01.387,000] <err> os: s[ 8]:  0x5f00cb5a  s[ 9]:  0xbf912862  s[10]:  0x35793c76  s[11]:  0x3dea39ef
[19:57:48.812][00:00:01.387,000] <err> os: s[12]:  0x40400000  s[13]:  0x40600000  s[14]:  0x3bb54000  s[15]:  0x44160000
[19:57:48.814][00:00:01.387,000] <err> os: fpscr:  0x20040010

2.3 最后解决:

经询问,原因是NVS不支持需要大块擦除的存储设备,特别是NAND-Flash还需要ECC和坏块检测的存储设备,NAND-Flash只能使用LittleFS。
github
具体问题路径:NVS service sector_size limited range is (0~65535)

三、BUG-NO2:nvs_partition地址范围超出内存范围

3.1 情况描述:

幸亏Zephyr频繁在支持与更新,问题一很快得到了官方项目贡献者的回答。
得知NVS无法运用在NAND-Flash于是将NVS运用在内部NOR-Flash上,但由于nvs_partition地址范围超出内存范围,出现读取错误。

3.2 BUG复现:

文件系统设备树构建:

&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        /* Bootloader保留区 (16KB) */
        boot_partition: partition@0 {
            label = "bootloader";
            reg = <0x00000000 DT_SIZE_K(16)>;
            read-only;
        };

        /* 主应用程序区 (432KB) */
        app_partition: partition@4000 {
            label = "app";
            reg = <0x00004000 DT_SIZE_K(432)>;
        };

        /* NVS存储区 (48KB) - 严格对齐2KB边界 */
        storage_partition: partition@70000 {
            label = "storage";
            reg = <0x0007C000 DT_SIZE_K(48)>; 
        };
    };
};

内部Flash大小是512K,16K+532KB+48KB<512K

测试代码编写(无错代码):

/* 最小擦除单位是2KB*/
#define NVS_SECTOR_SIZE  2*1024
#define NVS_SECTOR_COUNT (DT_SIZE_K(256K) / NVS_SECTOR_SIZE)  // 64KB / 2KB = 32 

static struct nvs_fs fs = {
    .sector_size = NVS_SECTOR_SIZE, //最小擦除单位*n
    .sector_count = NVS_SECTOR_COUNT, //大于等于2
    .offset = NVS_PARTITION_OFFSET, //起始地址,必须是可擦除区域的边界
};

编译通过,但读取过程报错:

*** Booting Zephyr OS build v4.1.0-5510-g8d2010e1e179 ***[2025-07-18 12:48:39.376]
[00:00:00.005,000] [1;31m<err> flash_stm32: Read range invalid. Offset: 526328, len: 8[0m[2025-07-18 12:48:39.385]
[00:00:00.013,000] [1;31m<err> flash_stm32: Read range invalid. Offset: 528376, len: 8[0m[2025-07-18 12:48:39.392]
[00:00:00.021,000] [1;31m<err> flash_stm32: Read range invalid. Offset: 528376, len: 8[0m[2025-07-18 12:48:39.405]
[00:00:00.029,000] [1;31m<err> flash_stm32: Read range invalid. Offset: 530424, len: 8[0m[2025-07-18 12:48:39.412]
[00:00:00.037,000] [1;31m<err> flash_stm32: Read range invalid. Offset: 530424, len: 8[0m[2025-07-18 12:48:39.420]
[00:00:00.045,000] [1;31m<err> flash_stm32: Read range invalid. Offset: 532472, len: 8[0m[2025-07-18 12:48:39.428]

问题定位:

看似合理,但是512KB = 0x00000000 ~ 0x0007FFFF
地址计算错误:
storage_partition分区的起始地址 0x7C000 + 大小 0xC000 = 0x88000>512K,但实际Flash结束于 0x7FFFF。

根据NVS算法原理:NVS在存储时会向ate(记录分配表)里写入一组数据,ate在扇区中从后向前增长。所以说明部分ID数据的ATE表会保存在最后一块扇区的最后几位即0x88000附近>512K。这就是导致问题的直接原因。

3.3 最后解决:

&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        /* Bootloader保留区 (16KB) */
        boot_partition: partition@0 {
            label = "bootloader";
            reg = <0x00000000 DT_SIZE_K(16)>;
            read-only;
        };

        /* 主应用程序区 (432KB) */
        app_partition: partition@4000 {
            label = "app";
            reg = <0x00004000 DT_SIZE_K(432)>;
        };

        /* NVS存储区 (48KB) - 严格对齐2KB边界 */
        storage_partition: partition@70000 {
            label = "storage";
            reg = <0x00070000 DT_SIZE_K(48)>; 
        };
    };
};

0x00070000 +48K<512K,符合要求,测试顺利!!!!

总结

在 Zephyr 上配置 NVS 时,需要综合考虑:

  • 设备树分区布局
  • Flash 物理特性
  • 内存对齐要求

通过系统性的问题分析和逐步调试,最终成功解决了所有编译和运行时错误。


网站公告

今日签到

点亮在社区的每一天
去签到