尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
ESP32多应用Flash数据隔离:分区表与NVS namespace实战
我一直觉得ESP32 上最难控制的东西不是代码而是那颗看起来不起眼的 SPI Flash。我手里这颗 4MB 的 Flash 上同时跑着三个小应用一个采集温湿度上传云端一个控制 LED 灯带还有一个提供本地的 Web 配置页面。每个应用都想在 Flash 里存东西传感器要存校准值和掉电待传数据LED 要存上次的颜色和亮度Web 页面要存 html、js、字体图片。问题很快就来了所有应用都住在同一块 Flash 里怎么保证数据不会串门这篇文章就把这个场景拆开讲透。核心就三件事分区表怎么规划NVS 的 namespace 和独立分区各自解决什么问题以及万一已经串门了怎么排查。全程用我实际调试时的视角来说不整虚的适合你正在用一块 ESP32 同时跑多个功能模块、又想数据互不干扰的场景。1. 先把“串门”问题拆开Flash 不是一整块饼1.1 Flash 是什么一块按扇区管理的大容量数组ESP32 用的 Flash 一般是 SPI NOR Flash常见容量 4MB、8MB、16MB地址从 0 开始编址。4MB 就是 0x000000 到 0x3FFFFFCPU 可以通过地址映射直接“读”里面的内容甚至能直接在 Flash 里执行代码。但“写”和“读”完全不是一个量级的事。NOR Flash 的写规则很别扭要写之前必须先把目标区域擦除成全 0xFF擦除的最小单位通常是 4KB 一个 sector而且擦写寿命有限常见的 W25Q32 这类芯片标称也就十万次擦除实际保守估计按几万次用更稳。也就是说如果两个应用还在同一个 sector 里抢着写互相覆盖是必然的而且频繁擦写还会加速整片 Flash 老化。所以想让多个应用互不串门第一步不是写代码而是把 Flash 这一整块“饼”按用途切成不同的“房间”。这个切法就是分区表。1.2 分区表给 Flash 做“房产证”ESP-IDF 和 Arduino-esp32 默认都靠一张分区表Partition Table来管理 Flash 划分。分区表本身会烧录在 Flash 的 0x8000 偏移处占固定 4KB 空间。你要把 Flash 分成几个区、每个区多大、放在哪个偏移地址、用来放固件还是放数据都在这张表里写死。如果不改分区表默认的 4MB 配置里只有三个分区nvs、phy_init、factory。factory 是整个 app 固件。这种布局下你所有业务数据都只能塞进 nvs 那个分区里多个模块挤在一个 NVS 中key-value 一多不串门才叫见鬼。我自己写分区表有一句口头禅分区表就是 Flash 的房产证没有房产证之前不要谈数据隔离。所有后续的隔离方案本质上都是在这张表上划房间。1.3 分区表里那些字段到底什么意思以 ESP-IDF 的 CSV 格式为例# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x4000, phy_init, data, phy, 0xd000, 0x1000, factory, app, factory, 0x10000, 0xC0000,Name 是分区标签代码里用字符串来定位分区比如esp_partition_find_first的第三个参数填的就是它。Type 分两大类app 表示存放可执行固件data 表示存放数据。app 只能被 bootloader 和 OTA 机制认领data 可以自己任意读写。SubType 是子类型进一步限定用途。app 下面有 factory、ota_0、ota_1 等data 下面有 nvs、phy、spiffs、littlefs 等。Offset 是该分区在 Flash 上的起始地址。Offset 和 Size 决定了房间的位置和面积。有一点要特别注意Offset 默认要按 0x1000064KB对齐除了紧跟分区表的那几个系统分区比如 0x9000 的 nvs 是历史惯例。乱写偏移地址烧录不一定报错但运行时可能因为地址映射对齐问题出各种莫名其妙的 crash。这个我在第 5 章会专门讲。2. 数据隔离的两条路逻辑命名隔离与物理分区隔离2.1 NVS namespace不同模块各开一个抽屉NVSNon-Volatile Storage是 ESP-IDF 内置的键值存储把数据以 key-value 形式存进 Flash。它天生支持 namespace 概念可以理解为在同一个 NVS 分区里再开几个“抽屉”。写法很简单#include nvs.h nvs_handle_t sensor_handle; nvs_open(sensor, NVS_READWRITE, sensor_handle); nvs_set_i32(sensor_handle, calib, 1234); nvs_commit(sensor_handle); nvs_handle_t led_handle; nvs_open(led, NVS_READWRITE, led_handle); nvs_set_i32(led_handle, brightness, 80); nvs_commit(led_handle);sensor和led是两个 namespace里面用同一个 key 都不会互相干扰。这个方案胜在改动极小不用动分区表代码里把原来单一的 nvs 改成“namespace 名 key”的组合就完成了一半隔离。但 namespace 隔离有个明显盲区所有 namespace 还是躺在同一个 NVS 分区里。只要这个分区被整体擦除比如有人调用了nvs_flash_erase()或者分区表重新规划导致 NVS 分区位置变化所有 namespace 一起清零。还有一点namespace 和 key 都有命名长度限制官方默认 namespace 名最长 15 个字符key 最长 15 个字符起名太随意最后苦的是自己。2.2 独立 data 分区每个模块一间房如果 namespace 是同一间房里的几个抽屉那独立 data 分区就是给每个模块单独砌一间房。每个模块有自己专属的分区专属的起始地址、大小和属性谁也不会碰到谁。自定义分区的读写要走esp_partitionAPI比如我手里有个 LED 模块要保存灯带参数我给它单独划一块led_store分区代码可以这样写#include esp_partition.h const esp_partition_t* part esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, led_store ); if (part NULL) { ESP_LOGE(APP, led_store partition not found); return; } uint8_t buf[64]; esp_partition_read(part, 0, buf, sizeof(buf)); // 修改 buf 后写回 esp_partition_erase_range(part, 0, part-size); esp_partition_write(part, 0, buf, sizeof(buf));这种隔离是物理级的。我想擦led_store就擦led_store跟传感器模块的数据一点关系都没有。对于需要频繁擦写、数据量大、或者以后可能单独导出单独备份的模块独立分区是最稳妥的路线。2.3 怎么选先看你的隔离需求到哪一层两种隔离适合不同场景我一般这么判断如果只是“不想让两个应用的 key 撞车”namespace 够用如果存在“一个应用崩了不能把所有应用数据连坐”“日志需要周期性擦除”“某个模块的数据格式跟 NVS 不兼容”这类要求独立分区才是正解。方案隔离粒度实现成本数据容量效率典型场景NVS 多 namespace逻辑命名低改几行代码高分区复用多个小功能模块共用一个小 NVS 分区独立 data 分区物理分区中要改分区表读写 API低可能有碎片日志、文件系统、频繁擦写数据3. 实操做一张多房间分区表并让代码认账3.1 容量估计与对齐动手前先把账算清规划分区表第一个问题就是每个房间分多少面积。我的做法是先编译一把看每个小应用的 bin 有多大。多个应用放在多个 app 分区时每个 app 分区至少留出当前 bin 大小 30%~50% 的余量因为后续加功能、开优化、加 OTA 缓存都会膨胀。数据分区的容量不用贪大。NVS 分区一般给 0x400016KB到 0x1000064KB就足够存很多条 key-value 了。文件系统分区按目标文件总大小的两倍预估留出文件系统元数据和磨损均衡的开销。分区大小最好按 4KB 的整数倍来offset 尽量按 0x10000 对齐。一个经验法则是先把所有分区按从低到高的顺序排好计算每个分区的结束地址下一个分区从结束地址向上对齐到 0x10000 的整数倍开始。3.2 一份可以直接抄的 4MB 分区表下面是我在一个四合一项目里实际用过的分区表4MB Flash三个应用逻辑主固件占 factory传感器配置单独用一个 NVS 分区LED 数据单独用一个 NVS 分区Web 文件系统单独一个 SPIFFS 分区日志单独一个 SPIFFS 分区。# Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x4000, phy_init, data, phy, 0xd000, 0x1000, factory, app, factory, 0x10000, 0xC0000, sensor_cfg, data, nvs, 0x100000, 0x10000, led_store, data, nvs, 0x110000, 0x10000, web_fs, data, spiffs, 0x120000, 0x100000, log_fs, data, spiffs, 0x220000, 0x100000,逐个解释一下。nvs是系统默认 NVS放 WiFi、蓝牙等系统配置这个小分区跟业务无关。phy_init放射频校准数据。factory是主固件区768KB。接着是sensor_cfg和led_store都是 data/nvs 类型但名字不同相当于两个独立的 NVS 分区。web_fs和log_fs是两个文件系统分区一个给 Web 静态文件一个给日志。注意一个关键点我用了两个 data/nvs 分区。ESP-IDF 的nvs_flash_init()默认只会初始化名字恰好叫nvs的那个分区其他 NVS 分区必须用nvs_flash_init_partition(sensor_cfg)或者nvs_open_from_partition(sensor_cfg, ...)来访问这正好给了业务数据分层的机会。3.3 编译、烧录与 Arduino 环境适配在 ESP-IDF 里把上面的 CSV 保存为partitions.csv然后idf.py set-target esp32 idf.py partition-table idf.py flashidf.py partition-table会把 CSV 编译成二进制并烧录到 0x8000。如果只想单独烧分区表esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 write_flash 0x8000 build/partition_table/partition-table.binArduino-esp32 环境稍微复杂一点。新版核心在“Tools - Partition Scheme”里有“Custom Partition Table”选项选中后它会在工程目录里找名为partitions.csv的自定义分区表。老版本则要手动改 boards.txt这里不展开记住一个大方向Arduino 环境也能用自定义分区表只是入口在工具菜单里不在代码里。烧录有个特别常见的坑只重新烧了app.bin忘了烧partition-table.bin。如果旧分区表和新分区表不一致app 启动后读到的数据偏移全是错的表现就是配置丢失、文件系统挂载失败误以为数据串门。我现在的习惯是只要改过分区表就整套重新烧最彻底的是先erase_flash再全部写一次。3.4 代码里如何访问“自己那间房”数据分区按我的经验分三种访问姿势。第一种访问默认 NVS 分区里的子 namespace最简单nvs_handle_t h; nvs_open(sensor, NVS_READWRITE, h); nvs_set_i32(h, calib, 100); nvs_commit(h); nvs_close(h);第二种访问非默认名字的 NVS 分区。比如我要把传感器校准数据存进sensor_cfg那就不能再用nvs_open要用指定分区的变体nvs_handle_t h; nvs_open_from_partition(sensor_cfg, sensor, NVS_READWRITE, h); nvs_set_i32(h, calib, 100); nvs_commit(h); nvs_close(h);使用前还要先初始化分区esp_err_t err nvs_flash_init_partition(sensor_cfg); if (err ESP_ERR_NVS_NO_FREE_PAGES || err ESP_ERR_NVS_NEW_VERSION_FOUND) { nvs_flash_erase_partition(sensor_cfg); err nvs_flash_init_partition(sensor_cfg); }这里有个很隐蔽的细节如果你在一个分区里反复改 key、删 keyNVS 的存储页面会因为碎片越用越少最后报ESP_ERR_NVS_NO_FREE_PAGES。所以初始化时的擦除兜底逻辑不是摆设是保命用的。第三种文件系统绑定指定分区。我把 Web 静态文件放web_fs在 Arduino-esp32 里可以这样挂载if (!SPIFFS.begin(true, /fs, 10, web_fs)) { Serial.println(SPIFFS mount failed); }SPIFFS.begin的第四个参数就是分区标签。这样你在/fs下写的所有文件都会落在web_fs这个独立分区里跟其他模块的文件完全不搭界。4. 三种多应用架构横向对比各有各的主场4.1 单固件多模块 NVS 多 namespace这是最省事的做法。所有小应用编译成一个固件运行在同一片内存里数据通过 NVS 的 namespace 做逻辑隔离。适合几个流程简单、数据量小、彼此调用关系紧密的小功能比如一个采集程序里同时处理温度、湿度、电量三个数据块。优点是代码改动少不用管分区表一个大 NVS 分区就够了。缺点是隔离靠“约定”如果后来有人又写了个裸nvs_open(default, ...)或者某个 bug 把 NVS 分区整片格式化大家一起遭殃。我一般只在快速原型验证阶段用它正式产品里数据都往外挪。4.2 单固件多模块 每模块一个独立分区这是我在量产项目里最推荐的架构。固件还是一个固件但每个业务模块有自己的 data 分区数据访问走esp_partitionAPI 或者独立文件系统分区。好处是模块之间谁也不能动谁的数据。日志模块天天擦写不会磨损到配置分区Web 升级想刷新页面文件也不会把传感器校准值冲掉。代价是分区表要花心思规划代码里要多写一层“分区查找”的逻辑。不过这套逻辑复用性很强写一次后面所有模块都按这个套路走。4.3 多固件共存 启动/OTA 切换如果架构升级到“每个小应用是独立固件”那就要在分区表里放多个 app 分区。ESP-IDF 的 app 分区除了 factory还有 ota_0、ota_1 等bootloader 会根据分区表启动指定的 app。用这种办法可以在同一块 Flash 里装两个甚至多个独立应用运行时通过代码切换启动项。切换的代码大致是这样const esp_partition_t* next esp_partition_find_first( ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL ); if (next ! NULL) { esp_ota_set_boot_partition(next); esp_restart(); }这个方案本质上是把“多应用”做成了“多系统”每个固件有自己独立的代码、堆栈、业务。但注意分区表还是全局的公共的nvs系统分区还是大家共用。所以每个独立固件内部仍然要使用不同的 NVS namespace或者干脆给每个固件配置一个专属的 NVS data 分区否则 WiFi 配置这种全局数据还是会串。4.4 横向对比一张表说清楚维度NVS 多 namespace独立 data 分区多固件 app 分区改动规模小只改代码中改代码分区表大要维护多个固件工程隔离强度逻辑隔离物理隔离物理隔离 进程隔离数据容量受限于单个 NVS 分区可按模块灵活划分受限于整体 Flash 容量更新策略整包升级整包升级分区数据独立维护可分别 OTA 各应用适合阶段原型、内部工具量产中小型产品大型多子系统产品5. 数据串门事故复盘与排查实录5.1 事故一NVS key 撞车两个模块互相读错数据曾经有个项目传感器模块和 UI 模块都用了同一个 keystate存标志位。传感器模块每次上报后写state1UI 模块读到自己想要的state2永远读不到画面卡在某个状态不刷新。查了两天最后把 NVS 内容 dump 出来才发现两个模块写在同一个 namespacedefault里同一个 key 互相覆盖。从那以后我立了三条规矩每个模块必须有独立 namespace名字用功能前缀比如sensor_cfg、ui_state、led_status。key 也要带模块缩写比如sensor_calib宁可长点不要裸奔。访问非默认分区时统一用nvs_open_from_partition指定分区名杜绝“顺手打开默认 nvs”的习惯。5.2 事故二分区表没跟着升级配置数据直接找错房间这个坑尤其隐蔽。产品已经跑了几百台设备某天我把固件升级到新版新版分区表把led_store从 0x110000 挪到了 0x100000但现场只烧了 app bin没烧分区表。结果旧分区的数据还在 0x110000新代码去 0x100000 找读取到的全是 0xFF 或者别人的数据。排查时用esptool.py read_flash把 0x100000 和 0x110000 两段内容拉出来对比一下就明白了。数据没丢但代码找不到之前的房间。修复方案有两个一是升级固件时强制同时更新分区表并做一次数据迁移二是分区表一旦发布就不再改变位置只加新分区不动老分区的 offset 和 size。5.3 事故三频繁擦写日志分区整片 Flash 老化快日志模块为了省事直接写一个 SPIFFS 分区每条日志都 fsync结果三个月后那个分区读取速度明显变慢部分扇区开始报损坏。数据不是串门是自残。后来我把日志分区单独划出去并且采用先写内存缓冲、攒够一页再落盘的策略磨损速度降了一个数量级。如果你也有高频写日志的需求分区表里一定给日志独立的房间别跟配置类数据挤在一起。5.4 排查看家本领把 Flash 内容读出来做“法医鉴定”怀疑数据串门的时候别瞎猜直接把 Flash 拉出来看。用 esptool 读取指定偏移的数据esptool.py --chip esp32 --port /dev/ttyUSB0 read_flash 0x110000 0x10000 led_store.bin读出来的 bin 用十六进制编辑器打开看文件头是不是你写的特征字段。我一般会在每个分区头部写一个 magic number比如0xA5A5表示 LED 模块0x5A5A表示传感器模块。读回来一比对立即能确定数据是不是落在预期位置。如果发现数据跑到了别的分区排查下一步就是确认分区表。把分区表二进制也读回来esptool.py --chip esp32 --port /dev/ttyUSB0 read_flash 0x8000 0x1000 partition_table.bin再跟工程里编译出的partition-table.bin做二进制对比两份一样就基本排除了分区表烧录问题。不一样说明有人烧过旧分区表擦掉重来。我在实际项目的最终方案是单固件 多独立 data 分区。每个模块数据都有自己的房间擦写隔离、备份隔离、升级隔离都做到位了。这条路线虽然要多写几个esp_partition_find_first但换来的是半年多没再处理过数据串门的售后单。最后再分享一个小习惯把partitions.csv当作代码一样提交进版本库每次改动分区表都在 commit 里写清楚“谁动了哪个房间”。别等到出问题才去翻聊天记录分区表这东西配一次可能只要十分钟但要是记不清改过哪里排查一次可能就得一整天。
RELATED

相关推荐

LLC谐振变换器12V25A 300W开关电源设计与调试全攻略

LLC谐振变换器12V25A 300W开关电源设计与调试全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/11/6 1:02:57
GUI自动化测试中的ScreenLayout:从硬编码坐标到声明式画面地图

GUI自动化测试中的ScreenLayout:从硬编码坐标到声明式画面地图

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/11/6 1:02:57
EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

📅 2026/11/5 16:49:29
MORE NEWS

更多资讯

📰

Python编程基础:变量与类型

概述在这篇文章里面, 大家能够看到关于编程里面非常基础的变量还有类型这两个内容的详细介绍内容, 这包括了变量的命名规则怎么定之类的, 也包括了不同类型的数据应该怎么去操作之类的这些内容等等所有这些关键的步骤都会被分析得非常清楚明白的, 另外的话还会举出具体的实际例…

📰

为什么“总结这个PDF”根本不够用?ko-lesson:把吃灰课程PPT复活成可复习学习系统的完整指南

【免费下载链接】ko-lesson 一个帮助大学牲期末复习的skill,能将清朝老PPT复活为能看的资料 项目地址: https://gitcode.com/gh_mirrors/ko/ko-lesson 点击查看 免费下载 期末复习季一到,硬盘里吃灰的课程 PPT 反而成了最值钱的学习资料——…

📰

全栈开发用什么AI工具最好?6款工具横测,第一名出人意料

在进行全栈开发工作的时候, 开发人员需要独自承担前端界面、后端服务以及数据库管理等多项任务, 在这个过程中是否有人工智能辅助工具能够提供强有力的支持从而帮助大家减轻负担?我们对市面上六款具有代表性的主流人工智能编程工具进行了实际测试, 最终的测试结果让所有人意识…

📰

基于Python的Monkey自动化测试监控工具:支持日志采集与设备性能监控

-脚本.zip 所包含的 -- 项目方面, 它是一个对移动应用质量保障体系有着全面覆盖作用的综合性自动化测试与性能监测解决方案, 这个方案的核心技术基础依托于官方所提供的工具, 同时在功能方面进行深度的二次开发以及强化增强, 其实现手段是通过使用的编程语言来完成的。它是 SDK…

📰

开箱即用!开源纯Python后台管理系统

简介如今的市场上, 开源的后台管理系统项目越来越多, 这些项目所使用到的技术栈也是各种各样、不完全一样的。但是今天要跟大家分享的这个开源的后台管理系统, 虽然名字那里是空的, 但是它的部署过程是非常简单的。更加关键的一点是它有一个非常新颖的特点,那就是它…

📰

二极管电路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬