做嵌入式最烦的一件事,就是明明在帮客户做方案,最后却被一句“几个小应用共用一块 ESP32 Flash,数据会不会串门”问住。这个问题看起来基础,实际上踩过坑的人都知道,它牵扯到分区表、NVS 命名空间、OTA 升级方式,甚至应用之间的启动切换逻辑。你如果只是把两个应用随便塞进 Flash,跑起来可能没问题,但只要有一次升级、一次异常重启、一次日志写满,数据就会莫名其妙对不上,那种排查过程真的让人头秃。
我这两年用 ESP32 做了不少多应用共存的设备,从刚开始的“反正 Flash 够大,随便放”到后来一上来先画分区表,中间交的学费不少。这篇就把我实际沉淀下来的方案、踩坑经验、以及可复用的分区模板写出来。适合三类人看:一是刚把 ESP32 产品原型做出来、准备扩展功能的开发者;二是要用一块 Flash 同时跑多套逻辑、还要各自保住数据的工程师;三是正在做OTA升级但这块还没理清楚,怕升级把数据带飞的新手。我会从最底层的分区表讲起,讲到 NVS 和文件系统,最后给一份完整的分区规划案例,希望能让你少踩几个坑。
1. 先把问题说清楚:什么场景下会“共用一块 Flash”
1.1 不是只有“多固件”才叫共用
很多人听到“多个小应用共用 Flash”,第一反应是“那我直接给每个应用烧一个固件不就好了”。但实际碰到的场景远比这个复杂。最常见的是单固件里有多个业务模块,比如一个传感器节点里既跑温湿度采集,又跑配网 Web 服务,还跑 OTA 升级逻辑,它们虽然编译在一个固件里,却各自要用 Flash 保存数据。另一种场景才更像标题描述的那样:一套板子上通过分区规划放了几个独立的固件,比如一个负责数据采集,一个负责本地策略,甚至还有出厂自检程序,通过跳转或 OTA 机制切换运行。
无论是哪种情况,它们共用的都是同一块物理 SPI Flash。平时大家只把它当成“存代码的地方”,但代码、参数、日志、OTA 临时数据、出厂校准数据全都在里头。一旦多个模块或固件使用同一套存储 API、同一个 NVS 命名空间、同一个文件系统根目录,就会出现“串门”现象。换句话说,数据串门不只是地址越界或者指针错误这种低级 bug,更多时候是逻辑设计上没把存储边界划清楚,导致 A 应用把 B 应用的配置覆盖了,或者 A 的日志把 B 的数据区写穿了。
1.2 “数据串门”到底串的是什么
我在实际调试中把“串门”归结成三类,方便对症下药。第一类是逻辑串门:两个应用不约而同使用了同一个 NVS key,比如都用sensor_cal保存校准值,一个写进去另一个读出来就是错的。这类问题最隐蔽,因为不报错,但数据就是不对。第二类是物理串门:分区表设计不合理,两个分区有重叠,或者一个应用通过 OTA 写入另一个应用所在的 flash 区间,直接覆盖了别人的代码或数据。这类问题通常在升级和异常重启后才暴露,排查时要看 flash 二进制,比较痛苦。
第三类是运行态串门:文件系统缓存没 flush,日志缓冲区频繁写入导致扇区提前老化,某个分区磨损严重后出现整块数据读出来都是0xFF。所以“保证数据不串门”真正要做的事,是在物理层面划好分区,在逻辑层面定好命名空间和 key 规则,在运行层面控制好写入频率与校验机制。这三件事缺一不可,顺序也不能乱。
2. 分区表:隔离的第一道物理边界
2.1 分区表是 Map,不是口号
ESP32 的 Flash 里不是只有代码,bootloader、分区表、应用固件、NVS 数据、日志文件系统都要塞进去。分区表就是一张地图,告诉 bootloader、应用代码以及烧录工具,每一块区域叫什么名字、是什么类型、从哪里开始、有多长。你用 ESP-IDF 编译时看到partitions.csv,就是这张地图的文本格式;编译后它被烧写到 Flash 的固定偏移地址,默认是0x8000附近。
很多新手以为分区表只是给烧录工具看的,这是误解。代码运行起来后,esp_partition_find_first、esp_ota_get_running_partition这些 API 全是读分区表来定位存储区域的。你如果用一个没有在分区表里定义的地址去读 Flash,等于在没有正常道路的地方开车,轻则读到别的分区残留数据,重则写入时破坏其他分区。所以分区表是 Flash 数据隔离的第一道物理边界,设计得越清楚,后续应用层隔离压力越小。
2.2 手写一份可落地的 partition.csv
ESP32 烧录工具支持自定义分区表,文件格式很简单:每一行是名称,类型,子类型,偏移量,大小,标志。偏移量可以留空让工具自动排,但我个人习惯显式写出来,方便核对上下边界。类型分为app和data,app 类型用于存放可执行固件,data 类型用于存数据。记住一点:分区表里分区大小必须是 4KB 的整数倍,偏移量也要以 4KB 对齐,因为 Flash 擦除最小单位就是 4KB sector。
下面是我经常用的一份 4MB Flash 分区模板,适合一个产品里跑两个独立 app 固件,同时还要给它们各自保存数据,外加一块公共日志区:
# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, otadata, data, ota, 0xf000, 0x2000, phy_init, data, phy, 0x11000, 0x1000, factory, app, factory, 0x12000, 0x180000, app_a, app, ota_0, 0x192000, 0x180000, app_b, app, ota_1, 0x312000, 0x180000, nvs_a, data, nvs, 0x492000, 0x10000, nvs_b, data, nvs, 0x4A2000, 0x10000, logs, data, spiffs, 0x4B2000, 0x14E000,看到这里你可能觉得奇怪,为什么 app 分区用ota_0和ota_1而不是随便起个名字。这是有意为之:ESP32 的标准 bootloader 只认factory和ota_x这些预定义子类型作为可启动分区。你如果自定义一个app_a_subtype,bootloader 根本不会管它,甚至可能导致无法启动。用ota_0和ota_1这类标准子类型,既能被 bootloader 识别,也能用 OTA 接口来切换启动目标,多应用切换就很自然了。
2.3 设计分区时的三条铁律
第一,数据区和代码区必须物理分离。你不能把应用放在 0x12000 到 0x200000,然后数据区从 0x1F0000 开始,这样代码区尾部就重叠了。可能暂时跑得起来,但只要应用体积增大、烧录时写满边界,数据区就被覆盖。第二,NVS 分区尽量给每个应用单独划一块。因为 NVS 是一个带版本管理和磨损均衡的 KV 存储,共享一个 NVS 分区并不是不行,但它的命名空间机制只能做到逻辑隔离,一旦某个应用的操作异常导致分区元数据损坏,其他应用也跟着遭殃。第三,OTA 升级时不会自动保护你的数据,你要在代码里确认升级目标分区是不是 app 类型。用esp_ota_begin之前最好调用esp_partition_find_first校验一下目标分区,不要直接拿了一个 data 分区就当 OTA 分区用。
3. 多应用隔离的三种常用架构
3.1 单固件多模块:逻辑命名空间隔离
如果几个小应用本质上是一个固件里的不同任务,最简单的做法就是“一个固件、多个模块”,物理上只占用一个 app 分区,数据隔离靠 NVS 命名空间和文件系统子目录实现。优点是开发简单、调试方便,所有代码都在同一个程序里,内存和 Flash 资源容易共享;缺点是隔离强度有限,一个模块写一个全局数组越界,可能踩到另一个模块的数据,而且任何模块的崩溃都可能牵连整个固件。
这个架构适合模块之间关系紧密、版本同步发布的场景。我在做智能家居网关时,配网服务、设备发现、本地规则引擎这三个模块就放在同一个固件里,通过不同的 NVS 命名空间保存参数。这里有个很容易踩的坑:NVS 命名空间名称限制 15 个字符,key 也限制 15 个字符,而且 key 不能包含一些特殊字符。你如果起名太随缘,比如a、b,一旦命名空间相同,key 还是会冲突。我的习惯是命名空间用app_加模块标识,key 全部带模块缩写前缀,比如app_wifi、app_rule,等于做了两重保险。
3.2 多固件独立分区:OTA 切换实现硬隔离
如果你的“多个小应用”是几套完全独立的固件,希望它们互不干扰,甚至能独立升级,那就要走多固件独立分区路线。每个固件占用一个独立的 app 分区,运行时通过 OTA API 把下一次启动的目标设置成另一个分区,重启后 bootloader 就会跳到对应固件。这在物理上做到了代码区不重叠、数据区也可以各自独立。
我建议把其中一个分区留作factory,把它当成不可覆盖的“救砖”入口。比如设备出厂时烧录一个最小的诊断固件,用户升级到 A 应用或 B 应用后,如果哪天 A/B 都坏了,bootloader 可以根据条件回退到 factory。不过要注意一点:如果你用esp_ota_set_boot_partition把启动目标从 A 切到 B,一定要先确保 B 分区里已经有完整可用的固件,否则切过去之后 bootloader 校验失败,可能陷入启动失败循环。最稳妥的做法是先在 B 分区写完固件并校验成功,再调用设置启动分区的接口。
3.3 数据区拆分:谁的数据就是谁的
独立 app 分区解决了代码隔离,数据隔离还要再单独设计。我之前做过一个设备,A 固件负责传感器采集,B 固件负责联网上报,两个固件各自运行,都要保存自己的配置和校准数据。当时我偷懒让两个固件都用系统默认的nvs分区,结果 A 固件在某个版本的代码里写入了一个 key 叫state,B 固件旧版本也用过这个 key,两边配置互相覆盖,设备行为变得非常诡异。后来我把分区表改成nvs_a和nvs_b两块独立分区,每个固件只访问自己的分区,问题才彻底消失。
这种物理拆分看起来很笨,但非常有效。Flash 分区的粒度是 4KB,一块 64KB 的 NVS 分区足够存放几千个 key-value,成本很低。如果你觉得每个应用一个 NVS 分区太碎,至少也要做到“一个数据域一个分区”,把强相关的数据放一起,和弱相关的数据隔开。这个原则和代码里模块化是一个逻辑:高内聚、低耦合。
3.4 三种方案怎么选
我做了个对比表格方便你快速决策:
| 方案 | 隔离强度 | 开发复杂度 | 适用场景 |
|---|---|---|---|
| 单固件多模块 | 逻辑隔离为主 | 低 | 模块关系紧密、同步发布 |
| 多固件独立分区 | 物理隔离 | 中 | 独立功能、独立升级 |
| 数据分区拆分 | 物理隔离 | 低 | 任何一个多应用方案都建议配合 |
在实际产品上,我经常是“多固件独立分区 + 数据分区拆分”一起上。代码上互相独立,数据上各自占坑,升级互不影响,排查问题也容易定位到具体模块。唯一要付出的代价是分区表规划和启动切换逻辑要花心思,但这些工作一次做好,后面能省很多事。
4. NVS 和文件系统:串门高发区
4.1 NVS 命名空间不是保险箱
NVS(Non-Volatile Storage)是 ESP-IDF 提供的 KV 存储组件,很多人以为开了不同的命名空间就万事大吉,其实不然。NVS 的命名空间更像是一个目录分隔符,它的作用是把 key 归到不同组里,避免 key 字符串直接冲突,但它不提供越界保护。你在代码里如果调用nvs_open("AppA", NVS_READWRITE, &handle),然后拿到 handle 后准备写入 1KB 的数据,而某个命名空间下已经存了大量数据,NVS 分区空间不足,写入就会失败;严重的还会出现分区元数据问题。
所以我的观点是:命名空间是逻辑隔离的第一道关,但你不能只靠它。关键数据要有版本号,每次读取后先核对版本再使用;写入nvs_set_*之后一定要调nvs_commit,否则数据只停留在内存缓存里,断电就丢。如果你发现 NVS 写入偶尔成功偶尔失败,优先检查分区大小和剩余空间,而不是怀疑 Flash 坏了。我踩过的坑是给日志计数这种高频变量设置了同步写入,三分钟就能把 NVS 分区写废,后来改成批次写入才解决。
4.2 给每个应用单独划一个 NVS 分区
如果你想做到“数据不串门”,我强烈建议在分区表里为每个独立应用划一个专属 NVS 分区。分区表里 NVS 类型有专门的子类型nvs,应用代码通过分区名称找到它。示例里nvs_a和nvs_b就是两块独立的 NVS 区域,A 固件访问nvs_a,B 固件访问nvs_b,从物理上杜绝了交叉写入的可能。
代码里查找自定义 NVS 分区需要两步。第一步调用esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs_a")拿到esp_partition_t*;第二步调用nvs_flash_init_partition("nvs_a")初始化。这里有一个细节:默认 NVS 分区可以直接用nvs_flash_init()初始化,但自定义分区必须用nvs_flash_init_partition(),很多初学者在这里写错,结果nvs_open一直返回错误。初始化之后,nvs_open的namespace参数仍然要保持每个模块独立,相当于“物理分区隔离 + 逻辑命名空间隔离”双保险。
4.3 共享文件系统的目录边界
除了 NVS,很多设备还会用 SPIFFS 或 LittleFS 存放文件,比如 Web 页面、配置文件、日志文本。多个应用如果共用同一个文件系统分区,问题比 NVS 更棘手,因为文件系统本身就有一个全局的根目录,你不用路径区分的话,文件就会互相覆盖。常见低级错误是 A 应用写一份config.json,B 应用也写一份config.json,文件系统分区只有一个,后者就会覆盖前者。
解决思路有三种。第一种是在分区表里给每个应用单独分配文件系统分区,这是最省心的,适合文件量大的场景。第二种是共用分区但强制使用子目录约定,比如 A 应用只允许写/app_a/,B 应用只允许写/app_b/,文件名以外层目录做隔离,代码里插入一层路径校验,防止误用根目录。第三种是做成只读共享资源区加可写数据区分离,把公共的 Web 页面放在一个只读分区,应用私有数据放在各自分区。复杂度依次递增,但隔离效果也是递增的。我自己的偏好是如果 Flash 空间够,能用独立分区就不用共享目录,因为你无法控制团队里每个人都严格遵守目录约定。
4.4 日志写入对 Flash 磨损比你想象的大
多应用共用一个 Flash 时,日志系统最容易变成“隐形杀手”。单独看几条日志数据量不大,但如果你每秒钟写一条日志到 SPIFFS,按一条 64 字节算,一天下来就是 5MB 左右的写入量,而普通 SPI Flash 的擦写寿命大都在 1 万到 10 万次之间,按 4KB 扇区擦除的粒度计算,一个扇区很快会被写废。更可怕的是,日志文件系统的磨损均衡算法如果覆盖不到整个分区,某些扇区会被反复擦写,最后整块数据区提前退休。
我建议日志不要直接写到文件系统,至少不要高频同步写。先在内存里做环形缓冲区,攒满 4KB 再落盘;或者把日志频率降到一个小时一批。如果确实需要高频日志,那就把日志单独放到一个分区,并选用带磨损均衡的文件系统。LittleFS 在这方面比 SPIFFS 做得好一些。总之,日志写入策略要和 Flash 寿命一起考虑,不能只看功能能不能跑通。
5. OTA 与分区表约束:升级时最容易把邻居家拆了
5.1 OTA 怎么读懂分区表
OTA 升级本质上就是“往某个 app 分区写入新固件,然后设置下次启动指向它”。ESP32 的 OTA API 运行时会读取分区表,找到目标分区,然后流式写入。你需要显式确定目标分区,不能用随机偏移。常用做法是esp_ota_get_next_update_partition(NULL)自动选择一个与当前运行分区不同的 OTA 分区,或者在代码里通过名称找到指定分区再写。
这里最需要注意的是 OTA 目标分区的大小必须能容纳整个固件镜像。如果固件编译出来 1.2MB,而你给 OTA 分区只留了 1MB,写入会在中途失败。分区表里的Size字段不只是一个建议值,它是一个硬边界,超过边界的写入会被 flash 驱动拦截或直接破坏相邻分区。我认识的工程师里至少有两个人因为压缩了 app 分区大小,导致新功能编译后 OTA 失败,排查半天才发现是分区不够装。
5.2 升级到底会不会清掉数据区
这个问题的答案是“取决于你写的代码”。OTA 本身只会写目标 app 分区,不会主动去擦除数据分区。但以下两种行为会让数据区遭殃:第一种是你在 OTA 流程里偷懒,直接对指定地址执行一整块擦除,结果把相邻的 NVS 或文件系统分区也擦了;第二种是应用为了“干净升级”,主动调用esp_partition_erase_range擦除整个 Flash,这种一刀切做法在单应用设备上可能没问题,但在多应用共存的设备上就是灾难。
我的建议是永远使用官方 OTA 接口,不要直接操作底层 flash 地址。如果你需要在升级后清掉某个数据区,比如恢复出厂设置,那也要明确指定你要擦除的分区对象,用分区名称从分区表查出来再擦,而不是凭记忆写偏移量。这个习惯能避免很多“升级后所有配置都没了”“升级后另一个应用起不来了”之类的疑难杂症。
5.3 多应用切换下的回滚边界
多应用使用独立分区后,切换启动目标相当于一次“人为 OTA 切换”。你从 A 切到 B,只是改了 otadata 里的启动索引,A 分区里的固件不会被删除。这既是好事也是坏事:好事是可以随时切回 A;坏事是两个应用之间如果状态不同步,数据版本可能对不上。比如 A 固件某次运行写入了 v2 格式的配置,B 固件还停留在 v1 逻辑,切回 B 后可能读不了配置,这时你就要设计好兼容策略。
我在实际产品里会在切换前做一个简单的数据握手:A 要切到 B 时,先把公共状态标记为“切换中”,B 启动后读取该标记,知道这是一个受控切换,可以做数据迁移;如果 B 连续启动失败,bootloader 会根据策略回退到 A。这些逻辑不算复杂,但要提前设计,不能等到切不过去再临时加。回滚边界本质上是状态管理问题,flash 分区只是给你提供了物理操作空间,逻辑上的过渡必须有明确规则。
6. 真实案例:三合一传感器节点的分区规划
6.1 需求与 Flash 预算
我这里用一个很常见的案例来讲透:一个三合一传感器节点,功能包括温湿度采集、配网服务、本地规则引擎,还希望能支持 OTA 升级,同时保存出厂校准数据、用户配置和运行日志。硬件用的是 4MB Flash 的 ESP32 模组。一开始我打算默认分区表直接跑,后来发现三个模块的数据互相打架,日志也没地方放,才下定决心重新规划分区。
需求拆解:需要两个可启动固件,一个是出厂自检固件,一个是正式运行固件;正式运行固件内部有三个模块,但数据要按模块隔离;还需要一块日志区,以及至少两块 NVS 分区保存校准值和用户配置。把 4MB 空间做预算,app 固件按 1.5MB 估算,NVS 各 64KB,日志区 320KB 左右,剩余空间留给分区表、ota 数据、phy init 数据。这个预算只多不少,实际编译后固件一般不到 800KB,余量留给自己心里踏实。
6.2 分区表这样写
基于上面的预算,分区表我写成下面这样,强调可读性和可维护性:
# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, otadata, data, ota, 0xf000, 0x2000, phy_init, data, phy, 0x11000, 0x1000, factory, app, factory, 0x12000, 0x160000, ota_0, app, ota_0, 0x172000, 0x160000, nvs_cal, data, nvs, 0x2D2000, 0x10000, nvs_user, data, nvs, 0x2E2000, 0x10000, logs, data, littlefs, 0x2F2000, 0x14E000,这里我没有划ota_1,因为产品只跑一个正式 app,再加一个出厂恢复分区已经完全够用。如果我有两个独立业务固件要互相切换,再增加ota_1并把factory作为一个很小的诊断入口。要点是:nvs_cal保存传感器校准参数,nvs_user保存用户配置,两个数据分区互相独立;logs使用 LittleFS 而不是 SPIFFS,因为它带更好的磨损均衡。校准数据和用户配置分开,是因为它们的写入频率差异很大:校准数据几乎只写一次,用户配置可能频繁修改。分开之后即使用户配置分区磨损严重,也不会连累校准数据。
6.3 代码里怎么固定分区
分区表定了,代码里就要“按名取地”。拿 NVS 举例,读取校准值的代码不要直接写死地址,而是先查分区表:
const esp_partition_t* cal_part = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs_cal" ); if (cal_part) { nvs_flash_init_partition("nvs_cal"); }文件系统的挂载也一样,我叫它logs,代码里就只挂载logs分区,不碰其他分区。另一个容易出现的问题是 flash 擦除操作,很多示例代码会教你spi_flash_erase_sector直接擦,但在多应用环境下我强烈不建议这么做。因为 Flash 分区表是唯一的,你手动擦除的 sector 不一定落在哪个分区里。我一般通过esp_partition_t指针来做擦除,因为它已经为你限制好了可操作范围,自带边界保护。
6.4 实测验证:有没有“串门”
分区规划完,我会做三个验证动作确认数据真的不会串门。第一个是最简单的:编译两版固件,A 版应用往nvs_cal写入一个有特征的数值,B 版应用启动后读取nvs_cal能原样读出来;同时往nvs_user写另一个值,读取nvs_cal时不受影响。第二个是压力测试:循环 1000 次读写用户配置,监视nvs_user分区剩余空间变化,确认没有数据溢出到相邻日志区。第三个是升级验证:用新固件 OTA 升级ota_0分区,升级完成后检查nvs_cal和logs里的历史数据仍然可读。
这三个动作写起来不复杂,但能把“串门”问题暴露得比较充分。尤其要注意验证时别只测当前固件自己写自己读,还要跨固件验证,因为串门往往发生在“别人写的,你来读”的场景。如果你能做到读数据前先校验版本,写数据前先确认分区归属,那基本可以放心地把这个分区规划交给后续开发了。
7. 踩坑记录与排查思路
7.1 改完分区表,旧固件打不开了
这是我遇到最多的问题。不少人图方便,直接用新的分区表覆盖烧录到一块已经有旧应用的 Flash 上,结果重启后 bootloader 找不到有效 app,串口打印invalid partition table或直接循环重启。原因是旧固件的代码区偏移量和大小是基于旧分区表编译的,新分区表改变了布局,bootloader 去新偏移找固件,找不到有效镜像头。
解决思路很简单:改分区表之后,不要再保留旧分区里的固件,直接擦除整块 Flash,重新烧 bootloader、分区表和新固件。如果你不想擦整块,至少要把旧 app 分区对应区域擦掉再烧新固件。这里提醒一点:擦除 Flash 是危险操作,执行前务必先通过分区表确认目标范围,不要把 NVS 数据区也顺手擦没了。我在开发板上用esptool.py erase_flash擦过太多次,每次都默念“这次之后重新烧”。不过生产线上千万别轻易整片擦除,很容易把出厂校准数据抹掉。
7.2 读出来的数据不对,先查缓存层
当你的应用读出某个配置值,发现和上次写入的不一样,第一反应别急着怪 Flash 坏掉。先想想有没有缓存层在搞鬼。ESP-IDF 的 flash 驱动默认有读写缓存,再加上 NVS 组件自己的缓存、文件系统驱动的缓存,数据在多个层级之间流转。如果你用两个不同模块同时访问同一份数据,一个模块改了底层数据,另一个模块还在读缓存里的旧值,就会出现“数据没串门,但读出来是串门”的假象。
我一般用三步排查:先调用nvs_flash_init_partition重新初始化目标分区,看读出的值是否变化;再直接调用esp_partition_read_raw绕过文件系统和 NVS,读取原始字节比对;最后再确认代码里有没有另一个模块悄悄改了同一个命名空间。大多数情况下都能定位到是缓存或多写者问题,而不是物理 Flash 出错。
7.3 擦除 Flash 之后分区表也丢了的误会
有朋友问我,为什么erase_flash后设备完全无法启动,是不是 Flash 坏了。其实不是,是擦除后连分区表也一起消失了。ESP32 上电第一步是运行 ROM 里的 bootloader,它要去 Flash 固定偏移读取分区表,你整片擦除后那里全是0xFF,bootloader 当然什么都找不到。这时候你必须先用烧录工具重新写入 bootloader、分区表和 app,设备才能再次正常启动。
这个“误会”之所以常发生,是因为很多人以为分区表存于 ROM 或芯片内部,但 ESP32 的 BootROM 只是第一级引导,分区表和应用都在外部 Flash 里。设计分区表时,也别忘了给 bootloader 和分区表本身留出固定区域。默认布局是 bootloader 在0x1000,分区表在0x8000附近,自定义分区表时尽量沿用这个基础布局,不要为了省空间把分区表挪到奇怪的位置,那样只会给自己增加不必要的麻烦。
7.4 最后补两句心里话
说实话,ESP32 的 Flash 分区设计,花一个下午认真规划,比以后花一周排查数据错乱要划算得多。我现在拿到一个新项目,第一件事不是写功能代码,而是根据业务模块、升级策略、数据写入频率把分区表画出来,再让团队按这个表去开发。你不用把每个分区弄得特别大,够用、清晰、有冗余就行。
如果你现在手里已经有设备在跑,而且遇到了类似的数据串门问题,我的建议是先别急着改代码。先把产品拆成“哪些数据是哪个业务写的”、“哪个业务可以启动”、“升级要不要切换固件”,然后重新设计分区表,一步一步迁移验证。这个内容后续还可以扩展出很多玩法,比如用安全启动保护分区、在多个 app 之间做安全的数据交换,但前提始终是:物理边界清楚、逻辑边界明确、写入过程可控。做好这三点,数据就真的不会串门了。