文章目录
前言
在嵌入式系统开发中,稳定可靠的非易失性存储方案是系统健壮性的关键保障。近期在项目芯片驱动开发中,先后实现了内部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。
具体问题路径: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 物理特性
- 内存对齐要求
通过系统性的问题分析和逐步调试,最终成功解决了所有编译和运行时错误。