1. 一块Flash塞进多个应用问题到底出在哪ESP32这颗芯片玩的人越来越多很多人手里都不止一个项目。我自己的习惯是拿一块ESP32模组当通用实验平台今天跑个温湿度采集明天烧个蓝牙控制LED后天又想塞个Web服务器进去。时间一长就遇到一个很现实的问题多个小应用共用同一块Flash数据开始互相打架。最典型的现象是这样的你写了个WiFi配网的小工具把SSID和密码存进了NVSNon-Volatile Storage非易失性存储。过几天你烧了另一个项目进去那个项目也用了NVS结果把之前的配网信息覆盖了。再烧回第一个项目发现连不上WiFi了因为NVS里的数据已经被第二个项目改得面目全非。更隐蔽的情况是两个应用都往Flash的某个固定偏移地址写数据你以为各写各的实际上它们在同一个物理扇区里互相踩踏读出来的数据全是乱的。这个问题的本质是ESP32的Flash是一块共享的物理资源但多个应用之间没有天然的隔离机制。不像Linux系统有文件系统权限、有进程地址空间隔离裸机或RTOS环境下Flash就是一块可以随意读写的存储介质。你不划定边界数据就会串门。那怎么解决核心思路有三条分区隔离、命名空间隔离、访问层封装。下面我会把这三条思路拆开讲透从ESP32的分区表原理讲到NVS命名空间的实际操作再给出一套可以直接抄作业的多应用Flash共享方案。注意本文讨论的是同一块ESP32模组上多个应用固件轮流烧录或共存时的Flash数据管理问题不涉及多芯片之间的通信。2. 先搞懂ESP32的Flash分区机制2.1 Flash物理结构与分区表的角色ESP32外挂的SPI Flash通常有4MB、8MB、16MB几种容量。这块Flash的地址空间从0x000000开始一直延伸到容量上限。芯片上电后Bootloader会从Flash的固定位置读取分区表Partition Table然后根据分区表把各个固件、文件系统、NVS区域加载到对应的位置。分区表默认放在偏移0x8000处长度0xC00字节最多可以定义95个分区。每个分区在分区表里用一条记录描述包含这几个关键字段字段含义示例值Name分区名称最长16字符nvsType分区类型dataSubType子类型nvsOffset在Flash中的起始地址0x9000Size分区大小0x5000Flags加密等标志空这张表就是Flash空间的“地契”。谁的地盘写在哪全凭这张表说了算。如果你不手动指定分区表ESP-IDF会用默认的分区表里面通常只有一个nvs分区、一个phy_init分区、一个factory分区存放主固件。默认分区表长这样# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, phy_init, data, phy, 0xe000, 0x1000, factory, app, factory, 0x10000, 1M,问题就出在这里默认只有一个nvs分区所有应用都往这一个NVS里写数据不串门才怪。2.2 多应用场景下的分区规划策略假设你手上有三个小应用要轮流烧录到同一块ESP32上应用A温湿度采集需要存传感器校准参数应用B蓝牙LED控制器需要存灯效配置应用CWiFi配网工具需要存SSID和密码如果三个应用共用一个NVS分区数据必然混乱。正确的做法是在分区表里给每个应用划一块独立的NVS分区# Name, Type, SubType, Offset, Size, Flags nvs_a, data, nvs, 0x9000, 0x4000, nvs_b, data, nvs, 0xD000, 0x4000, nvs_c, data, nvs, 0x11000, 0x4000, phy_init, data, phy, 0x15000, 0x1000, factory, app, factory, 0x20000, 1M,这样每个应用只操作自己的NVS分区物理上就隔离了。应用A的校准参数写在0x9000开始的16KB里应用B的灯效配置写在0xD000开始的16KB里互不干扰。但这里有个实际问题分区表是烧录时确定的如果你三个应用是分别独立编译烧录的每个应用都需要知道自己的NVS分区名。ESP-IDF提供了nvs_open_from_partition()接口可以指定分区名打开NVSnvs_handle_t my_handle; esp_err_t err nvs_open_from_partition(nvs_a, calibration, NVS_READWRITE, my_handle); if (err ! ESP_OK) { printf(打开NVS分区失败: %s\n, esp_err_to_name(err)); }这段代码里第一个参数是分区名第二个参数是命名空间名。分区名对应分区表里的Name字段命名空间名是NVS内部的逻辑分组。两层隔离叠加数据串门的概率就极低了。2.3 分区大小怎么算才够用NVS分区的大小不是随便定的。NVS内部以页Page为单位管理每页4096字节。一个NVS分区至少需要3个页12KB才能正常工作因为NVS需要空间做垃圾回收和磨损均衡。实际项目中我建议每个应用的NVS分区至少给16KB4个页留出足够的余量。如果你要存的数据量比较大比如日志、图片缓存那NVS就不合适了应该用SPIFFS或LittleFS文件系统分区。分区表里可以这样写# Name, Type, SubType, Offset, Size, Flags nvs_a, data, nvs, 0x9000, 0x4000, nvs_b, data, nvs, 0xD000, 0x4000, spiffs_a, data, spiffs, 0x11000, 0x20000, factory, app, factory, 0x31000, 1M,SPIFFS分区给128KB足够存一些配置文件和小的日志文件了。实操心得分区表的Offset字段必须按0x1000对齐否则烧录时会报错。Size字段也建议按0x1000对齐方便计算。我一般用Excel拉个表把每个分区的起止地址算清楚再写进去避免手算出错。3. NVS命名空间逻辑隔离的第一道防线3.1 NVS的工作原理与键值对模型NVS是ESP32上最常用的非易失存储方案本质是一个键值对数据库。你可以把它理解成一个简化版的Redis只不过数据存在Flash里而不是内存里。每个NVS条目由三部分组成命名空间Namespace、键Key、值Value。命名空间是NVS内部的逻辑分组不同命名空间下的同名键互不影响。比如命名空间wifi_config下有个键叫ssid命名空间led_config下也有个键叫ssid这两个ssid是完全独立的两个数据。NVS的读写接口很简洁// 写入 nvs_handle_t handle; nvs_open(wifi_config, NVS_READWRITE, handle); nvs_set_str(handle, ssid, MyHomeWiFi); nvs_set_str(handle, password, 12345678); nvs_commit(handle); nvs_close(handle); // 读取 nvs_open(wifi_config, NVS_READONLY, handle); size_t len 0; nvs_get_str(handle, ssid, NULL, len); // 先获取长度 char *ssid malloc(len); nvs_get_str(handle, ssid, ssid, len); // 再读取内容 nvs_close(handle);注意nvs_commit()这一步不能省。NVS的写入是先写到缓存调用nvs_commit()才会真正落盘到Flash。如果你写完不commit就重启数据就丢了。3.2 命名空间命名规范与冲突避免命名空间的名字最长15个字符建议用“应用名_用途”的格式比如appA_calib、appB_led、appC_wifi。这样即使多个应用共用同一个NVS分区只要命名空间不同数据就不会冲突。但这里有个坑NVS命名空间的数量是有限的。每个NVS分区最多支持254个命名空间每个命名空间最多支持126个键。对于小应用来说完全够用但如果你在代码里动态生成命名空间名比如用时间戳当命名空间那很快就会耗尽。我见过一个项目开发者用nvs_open()时传入了随机生成的命名空间名结果跑了两天NVS就写不进去了。排查了半天才发现是命名空间数量超限。所以命名空间一定要用固定的、有意义的字符串。3.3 跨应用数据共享的正确姿势有时候多个应用之间确实需要共享一些数据比如都需要的WiFi配置。这时候有两种方案方案一共享NVS分区共享命名空间。两个应用约定好使用同一个分区和同一个命名空间键名也约定好。这种方案简单但风险是任何一个应用写错了键名或值类型另一个应用就会读到脏数据。方案二共享NVS分区独立命名空间数据同步。每个应用有自己的命名空间但通过一个约定的“共享命名空间”来交换数据。比如应用A把WiFi配置写到shared_wifi命名空间应用B从同一个命名空间读取。这种方案隔离性更好但需要额外的同步逻辑。我一般推荐方案二因为它的边界更清晰。具体实现时可以定义一个公共头文件把共享的命名空间名和键名都宏定义好// shared_config.h #define NVS_PARTITION_SHARED nvs_shared #define NVS_NS_WIFI shared_wifi #define NVS_KEY_SSID ssid #define NVS_KEY_PASS password所有应用都include这个头文件就不会出现键名拼写不一致的问题。注意事项NVS的键名区分大小写SSID和ssid是两个不同的键。我踩过这个坑调试了半天才发现是大小写不一致导致读不到数据。4. 分区隔离实战从分区表到代码的完整落地4.1 自定义分区表的创建与烧录ESP-IDF默认使用partitions_singleapp.csv要使用自定义分区表需要在项目根目录创建partitions.csv然后在menuconfig里指定Partition Table --- (partitions.csv) Custom partition CSV file (0x8000) Partition table offset或者直接在CMakeLists.txt里设置set(PARTITION_TABLE_CUSTOM partitions.csv) set(PARTITION_TABLE_CUSTOM_FILENAME partitions.csv)分区表文件写好后编译时会自动生成partition_table.bin烧录时和固件一起写入Flash。这里有个细节分区表的偏移地址默认是0x8000但如果你用了OTA功能Bootloader会占用更多空间分区表可能需要后移。不过对于本文讨论的多应用共用Flash场景0x8000足够了。4.2 代码中指定NVS分区的两种方式方式一使用nvs_open_from_partition()显式指定分区名。这是最灵活的方式适合多应用场景。esp_err_t init_nvs_for_app(const char *partition_name, const char *namespace_name, nvs_handle_t *out_handle) { esp_err_t err nvs_flash_init_partition(partition_name); if (err ESP_ERR_NVS_NO_FREE_PAGES || err ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase_partition(partition_name)); err nvs_flash_init_partition(partition_name); } if (err ! ESP_OK) { return err; } return nvs_open_from_partition(partition_name, namespace_name, NVS_READWRITE, out_handle); }注意nvs_flash_init_partition()这一步。默认的nvs_flash_init()只会初始化分区表里第一个subtype为nvs的分区。如果你有多个NVS分区必须用nvs_flash_init_partition()逐个初始化。方式二修改默认NVS分区名。在menuconfig里可以设置默认NVS分区名但这种方式不够灵活不推荐在多应用场景使用。4.3 分区表版本管理与多应用协同多个应用共用一块Flash时分区表必须统一。如果应用A烧录时用了分区表版本1应用B烧录时用了分区表版本2两个版本的分区布局不一致数据就会错位。我的做法是把分区表文件放在一个公共的Git仓库里所有应用都从这个仓库拉取最新的分区表。每次修改分区表都要走代码评审确保所有应用同步更新。另外分区表的Offset字段一旦确定就不要轻易改动。如果你把nvs_a的起始地址从0x9000改到0xA000之前存在0x9000的数据就全部作废了。如果确实需要调整必须提供数据迁移方案。5. 当NVS不够用SPIFFS与LittleFS的隔离方案5.1 文件系统分区的划分原则NVS适合存小块的键值对数据一般单个值不超过4000字节。如果你要存配置文件、日志、HTML页面这类较大的数据就得用文件系统。ESP32上常用的有SPIFFS和LittleFS后者更推荐因为LittleFS支持目录结构、磨损均衡更好、掉电安全性更高。分区表里给每个应用划独立的文件系统分区# Name, Type, SubType, Offset, Size, Flags nvs_a, data, nvs, 0x9000, 0x4000, nvs_b, data, nvs, 0xD000, 0x4000, spiffs_a, data, spiffs, 0x11000, 0x30000, spiffs_b, data, spiffs, 0x41000, 0x30000, factory, app, factory, 0x71000, 1M,应用A挂载spiffs_a应用B挂载spiffs_b各自读写自己的文件系统互不干扰。5.2 挂载点与路径隔离SPIFFS的挂载点可以自定义比如应用A挂载到/appA应用B挂载到/appBesp_vfs_spiffs_conf_t conf_a { .base_path /appA, .partition_label spiffs_a, .max_files 5, .format_if_mount_failed true }; esp_vfs_spiffs_register(conf_a);这样应用A读写文件时用/appA/config.json应用B用/appB/config.json路径上就隔离了。即使两个应用同时运行比如通过FreeRTOS任务也不会互相覆盖文件。5.3 文件系统与NVS的选型对比对比项NVSSPIFFSLittleFS数据模型键值对文件文件目录单条数据上限约4000字节受分区大小限制受分区大小限制磨损均衡有有更好掉电安全好一般好目录支持无无有适用场景配置参数、校准值小文件、日志配置文件、网页资源选型建议配置参数用NVS小文件用LittleFS日志用LittleFS或SPIFFS。如果数据量特别大考虑外挂SD卡。6. 常见问题与排查技巧实录6.1 NVS初始化失败的那些坑问题一ESP_ERR_NVS_NO_FREE_PAGES。这个错误表示NVS分区里没有空闲页了。原因通常是分区太小或者写入次数太多导致页耗尽。解决办法是增大分区或者在初始化时调用nvs_flash_erase_partition()擦除重建。问题二ESP_ERR_NVS_NEW_VERSION_FOUND。这个错误表示NVS分区的版本号不匹配通常发生在固件升级后。解决办法同样是擦除重建但要注意擦除会丢失所有数据升级前要做好备份。问题三ESP_ERR_NVS_NOT_INITIALIZED。忘了调用nvs_flash_init()或nvs_flash_init_partition()。这个错误最容易被忽略因为代码编译没问题运行时才报错。6.2 数据串门的典型症状与定位方法症状一读出来的数据是乱码或旧值。定位方法是用nvs_get_str()读取时先获取长度如果长度异常比如特别大说明数据被破坏了。症状二写入成功但重启后读不到。检查是否调用了nvs_commit()。另外NVS的写入是异步的commit之后数据才真正落盘。症状三两个应用交替烧录后数据混乱。检查分区表是否一致NVS分区名是否写对。我一般会在代码启动时打印当前使用的分区名和命名空间名方便排查。6.3 多应用共用Flash的避坑清单分区表统一管理所有应用使用同一份分区表文件每个应用使用独立的NVS分区或独立的命名空间NVS分区大小至少16KB文件系统分区至少64KB写入数据后必须调用nvs_commit()键名和命名空间名用宏定义避免拼写错误固件升级时注意NVS版本兼容性必要时做数据迁移定期检查NVS分区的剩余空间避免写满最后分享一个小技巧在应用启动时打印分区表信息可以快速确认当前Flash布局。ESP-IDF提供了esp_partition_table_verify()和esp_partition_find_first()等接口几行代码就能把分区信息全部打出来。我习惯在每个项目的启动日志里加上这段调试时省事很多。这个内容后续还可以这样扩展如果你用的是Arduino IDE而不是ESP-IDF分区表的配置方式略有不同需要在boards.txt里指定分区方案或者手动创建partitions.csv并修改编译选项。另外如果多个应用需要同时运行而不是轮流烧录那就需要考虑OTA双分区方案把不同应用烧到不同的app分区通过Bootloader切换启动。
企业数字化 ERP 产品动态
相关推荐
Origin科研绘图模板体系:图形模板、工程模板与系统默认模板协同实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:26:40
银行不良贷款归因:因子分析+多元线性回归实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:26:40
IAR 9.5 升级后 J-Link 驱动冲突解决指南:替换与兼容性处理 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:26:34
MTK Camera7启动流程全解析:从上层调用到底层Sensor出帧 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 6:32:53
建一个视频网站要多少钱?源码下载避坑指南 建一个视频网站要多少钱?源码下载避坑指南 上周刚帮一个客户救火,网站后台被挂马,首页弹赌博广告。客户急得团团转,问我咋回事。我一看源码,发现是开源程序没打补丁,数据库密码还是123456。这种事儿太常见了,很多人以为 源码下载… · 2026/9/27 6:32:53
KEIL MDK寄存器级调试实战:实时查看外设与内核寄存器 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 6:32:53
treg OAuth自动刷新机制深度解析:single-flight refresh如何保持令牌新鲜 treg OAuth自动刷新机制深度解析:single-flight refresh如何保持令牌新鲜 【免费下载链接】treg OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn 项目地址: https://gitcode.com/GitHub_Trending/treg/treg
在 treg&#… · 2026/9/27 6:32:46
K230与STM32串口通信实战:自定义协议实现稳定数据传输 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 6:32:46
PSO-BP神经网络混凝土抗压强度预测:原理、代码与实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 6:32:46
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01