拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP32多应用Flash数据隔离:分区表与NVS命名空间实践

ESP32多应用Flash数据隔离:分区表与NVS命名空间实践

1. 先说清楚:多个应用共用 Flash,到底“串门”在哪一层?

前几年我有一阵特别喜欢在 ESP32 上一板多用:今天挂个温湿度采集,明天再加个网页配网,后天还要记日志。直到有一回两个模块同时往 Flash 里写数据,把对方的参数整个覆盖掉,重启之后设备不仅没进正常模式,还疯狂报校验失败。那一刻我才意识到,ESP32 这种“一块 Flash 多个应用”的做法,不提前把数据隔离想清楚,后面所有功能都会在非常隐蔽的 bug 里来回折腾。这个问题看起来玄乎,但本质上就是一件事:你的数据到底写进了 Flash 的哪一块区域,以及你的应用能不能“点到为止”地只操作自己那一块。

1.1 两种截然不同的“多应用”场景

先别急着撸代码。得先搞清楚“多个小应用”到底指哪种形态,因为两者的隔离思路完全不同。

第一种形态是同一个固件里塞了好几个逻辑功能模块。比如一个设备既有传感器采集模块,又有 Wi-Fi 配网模块,还有日志上报模块。这些模块在编译时会被揉进同一个 app 固件里,跑在同一个 CPU 上,但它们各自需要保存不同的配置、状态和业务数据。这种场景下,“串门”通常不是 Flash 地址冲突,而是键名冲突、文件路径冲突、存储区域写串。隔离的关键是给每个模块划好命名空间或独立数据分区。

第二种形态是同一块 Flash 里放了不止一份固件映像。典型做法是留一个 bootloader,由它来决定启动哪一份固件,或者通过 OTA 方式把新固件写到备用分区再切换过去。这种场景下,除了每份固件自己的运行空间要隔开,连它们各自的数据区也得跟着隔开。否则 A 固件把自己运行时产生的配置写到 Flash 地址 0x300000,B 固件以为这个地址装的是它的代码,启动后轻则功能异常,重则直接变砖。

这两类场景会同时出现在同一个项目中。所以下文讨论的“隔离”,既要解决代码映像的分区,也要解决数据存储的分区,还不能把 NVS、OTA 这类系统分区搅在一起。

1.2 数据串门的四个典型现场

我在实际项目里见过的“串门”,翻来覆去就是下面四种。

第一种,键名撞车。ESP32 原生的 NVS 是非易失存储,以键值对形式保存数据。如果“温度采集”模块用了一个叫status的键,“配网”模块也用了同一个键,或者两个模块使用同一个 NVS 命名空间,那后写的就会把先写的覆盖掉。这是最隐蔽的一类,因为代码看起来完全正常,读写函数都执行成功,但数据就是莫名其妙地变掉。

第二种,地址重叠。自己手动往 Flash 写数据的时候,地址算差了。比如模块 A 的分区是 0x9000 到 0xF000,模块 B 却从 0xE000 开始写,两个区域有一部分叠在一起。一旦 B 写进去,A 的数据就被破坏。这类问题往往不是立即爆发的,要做很多次读写之后才偶发一次,排查起来极其痛苦。

第三种,擦除越界。Flash 的特性是“写之前必须先擦”,而且擦除的最小单位通常是 4KB。很多人只记得自己要写 2KB 的数据,没意识到往前擦了一个 block,结果把隔壁分区一起擦掉了。相比地址重叠,擦除越界对数据的破坏是一次性的、大面积的,基本没有回旋余地。

第四种,OTA 覆盖。做 OTA 升级的时候,bootloader 会把新固件放到备用 app 分区,然后切换启动分区。如果分区表设计得不合理,比如把一个数据分区放在了 app 分区后面,而 app 固件的大小又超过了预期容量,那升级包在解压写入时就可能把数据分区吃掉。升级完成之后数据“消失”,实际上是被新固件覆盖了。

把这四种现场对上号之后,你会发现一个规律:绝大多数串门,根源都在于“没有一套清晰可控的分区规划”。所以下面的正戏,是从分区表开始讲。

2. 先把“地契”签好:分区表是隔离的第一道防线

2.1 分区表里到底写了什么

ESP32 的 Flash 布局并不是你写好地址就能保证的,真正说了算的是一张分区表。这片分区表位于 Flash 的固定位置(典型情况下是地址 0x8000,占用一个 4KB sector 的空间),Boot ROM 和二级 bootloader 启动时会读取它,应用运行时也会通过它来查找自己需要的存储区域。

分区表本质上是一个数组,每个条目 32 字节,包含这些字段:名称、类型、子类型、起始偏移、大小、标志位。CSV 文件里常见的写法是这样的:

# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x1F0000,

一行就是一个分区。Type决定这块区域是 app(存放固件)还是 data(存放数据);SubType进一步细化,比如 app 里区分 factory、ota_0、ota_1,data 里区分 nvs、phy、spiffs 等;Offset和Size决定这块区域在 Flash 里的具体位置和大小。

一个非常重要的细节是:这些偏移和大小并不是你随手写的,必须满足对齐要求。Flash 擦除的最小单位是 4KB,所以偏移至少要是 4KB 的整数倍;而 app 分区因为涉及 bootloader 跳转和安全启动,ESP-IDF 也倾向于强制使用 64KB 对齐。分区之间不能重叠,也不应该留出零碎到无法使用的空白。说白了,分区表就是把一块大存储“切成地块”,每个地块写清楚所有者,签好“地契”。

2.2 设计一张适合“多应用”的分区表

我之前在一个 16MB Flash 的项目里做过这样的布局:主固件支持 OTA,轮换运行两个 app 版本,同时还要给两个逻辑子应用分别提供独立的文件存储区。CSV 长这样:

# ESP-IDF Partition Table, 16MB Flash # 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, 0x300000, ota_0, app, ota_0, 0x312000, 0x300000, ota_1, app, ota_1, 0x612000, 0x300000, app1_data, data, spiffs, 0x912000, 0x180000, app2_data, data, spiffs, 0xA92000, 0x180000,

来验证一下这个布局的合理性。第一个 app 分区从 0x12000 开始,长度 0x300000,也就是 3MB,所以它结束在 0x312000。ota_0 接着从 0x312000 开始,同样 3MB,结束在 0x612000。ota_1 接着从 0x612000 开始,结束在 0x912000。两个数据分区各占 1.5MB,分别从 0x912000 和 0xA92000 开始,最后一个分区结束于 0xC12000,距离 16MB Flash 的末尾还有约 3.9MB 的空间,足够后续扩展或者加一个小日志分区。

为什么每个分区都刚好接在下一个分区的起始处,没有留空隙?因为对齐规则是“分区边界对齐到 4KB”或“app 分区对齐到 64KB”,在上面的布局里,所有地址都是 0x1000(4KB)的整数倍,app 分区也恰好落在 64KB 对齐的位置上。这样烧录时不会因为对齐问题报警,也能最大限度避免“两个分区互相咬合”的情况。

如果你的项目不大,用的还是最常见的 4MB Flash,那也没关系。一个默认分区表往往就够用:NVS 区域专门存键值对,factory 放一份固件。多个逻辑子应用如果数据量不大,直接在同一个 NVS 分区分命名空间即可,不用改表。这个我放到后面讲。

2.3 让自定义分区表生效

如果你用的是 ESP-IDF,操作并不复杂。先把上面这段 CSV 保存成项目根目录下的partitions.csv,然后执行:

idf.py menuconfig

找到Partition Table分类,把模式改成Custom partition table CSV,然后在弹出的输入框里填partitions.csv。保存退出后,正常编译烧录即可。

编译系统在生成 bin 文件时会自动处理这些偏移,你不用手工计算烧录地址。重点是你随时可以用一行命令验证当前分区表到底是什么样:

idf.py partition_table

这个命令会把当前构建产物中的分区表解析出来,打印成可读的表格。我在调试时几乎必用这个命令,因为它能直接看出分区偏移是不是预期值,是否发生了重叠。

如果用的是 Arduino IDE,事情会稍微繁琐一点。Arduino 的 ESP32 核心在“工具”菜单里有预置的Partition Scheme,比如Default、Huge APP、No OTA、Custom。选Custom时需要用户自己提供一个custom.csv,并且要把这个文件放到 Arduino 核心对应的 boards 目录里,再修改 boards.txt 才能生效。这个操作你得有一定耐心,所以如果你要做复杂的多应用隔离,我更建议直接用 ESP-IDF 来管理分区表。分区表这件事,谋定而后动,比后面出 bug 再返工省事得多。

3. 键值数据:命名空间隔离 vs 分区隔离

3.1 NVS 内部是怎么隔离的

先讲最常用也是最便宜的隔离:NVS 命名空间。

NVS 是 ESP32 官方提供的一个轻量键值存储组件。它不会被编译进代码里,而是占用一个独立分区,负责把数据分成一页一页地管理,每页对应 Flash 的一个擦写块。它内部实现了磨损均衡、修改合并、写入失败回滚等机制,比你自己手动写 Flash 可靠得多。

理解 NVS 的命名空间,最好的类比是衣柜里的抽屉。整个 NVS 分区是一个大衣柜,命名空间则是一个个抽屉。你可以在抽屉 A 里放一个叫count的东西,也可以在抽屉 B 里放一个叫count的东西,彼此互不干扰。读取的时候必须写清楚“我去哪个抽屉找什么”,只给键名不给抽屉名,系统是找不到的。

在nvs.h的接口里,打开抽屉的函数就是nvs_open:

nvs_handle_t handle; esp_err_t err = nvs_open("app1", NVS_READWRITE, &handle);

第一个参数就是命名空间名。同一个命名空间下不允许重复键名,不同命名空间之间则可以放心使用相同键名。这是官方设计的硬隔离,不会出现“我明明只写了一个键,另一个应用的同名键却被覆盖”的尴尬。

3.2 用命名空间实现多应用隔离

假设你的固件里有两个逻辑子应用,一个负责温湿度采集,一个负责配网信息管理。代码如下:

// 温湿度模块 void sensor_save_state(uint32_t uptime) { nvs_handle_t h; nvs_open("sensor", NVS_READWRITE, &h); nvs_set_u32(h, "uptime", uptime); nvs_commit(h); nvs_close(h); } // 配网模块 void wifi_save_config(const char *ssid) { nvs_handle_t h; nvs_open("wifi_cfg", NVS_READWRITE, &h); nvs_set_str(h, "ssid", ssid); nvs_commit(h); nvs_close(h); }

即便两个模块都存了一个ssid,因为命名空间不同,读写互不影响。这样做的成本非常低:不需要自定义分区表,不需要烧录额外的数据分区,甚至连 NVS 初始化代码都不用改。我测试过,一个默认的 0x6000 大小的 NVS 分区,足够容纳几十个命名空间,前提是每个命名空间下的键不多、数据量不大。

需要提醒几个限制。命名空间名最长 15 个字符,键名同样有长度上限。别取那种一个单词恨不得排到 20 个字符的名字,编译不会报错,运行时会返回ESP_ERR_NVS_KEY_TOO_LONG。数据长度也要控制,单个字符串值的上限受 NVS 页大小影响,超过限制会直接写入失败。

3.3 何时必须升级为分区隔离

命名空间虽好,但有极限。当你的子应用要存大量数据时,NVS 会显得窘迫,比如保存几百 KB 的日志、批量采集曲线、离线图片资源,或者需要一个能随时“整个丢弃重来”的存储区域。NVS 的设计目标是小而快,不是让你拿它当文件系统用。

这时候就该上分区隔离了。所谓分区隔离,就是在分区表里为每个子应用分配一个独立的 data 分区,子应用拥有这片区域的绝对控制权。它可以自己决定用 SPIFFS、LittleFS、FAT,甚至直接裸读写。子系统 A 把分区格式化、擦空、重写,和子系统 B 一点关系都没有。

判断标准我可以给得很实在:如果你的数据是“结构化的小型键值对”,比如配置项、计数值、开关状态,用 NVS 命名空间;如果你要处理的是“大块文件或频繁批量擦写”,就单独划数据分区。把两种隔离方案混用来管理不同粒度的数据,才是完整的隔离策略。

4. 数据文件:给每个应用圈一块自己的地盘

4.1 用 SPIFFS / LittleFS 分区隔离文件

文件型数据的隔离,直观的做法就是给每个子应用挂载不同的分区。回到上面那个 16MB 分区表,app1_data和app2_data就是两个独立的文件系统分区。在 ESP-IDF 里,可以用esp_vfs_spiffs_register把它们分别挂载到不同路径下:

#include "esp_spiffs.h" void mount_app1_storage(void) { esp_vfs_spiffs_conf_t conf = { .base_path = "/app1", .partition_label = "app1_data", .max_files = 8, .format_if_mount_failed = true, }; esp_vfs_spiffs_register(&conf); } void mount_app2_storage(void) { esp_vfs_spiffs_conf_t conf = { .base_path = "/app2", .partition_label = "app2_data", .max_files = 8, .format_if_mount_failed = true, }; esp_vfs_spiffs_register(&conf); }

之后,模块 A 往/app1/config.ini写文件,模块 B 往/app2/config.ini写文件。虽然都是config.ini,但底层对应的是两块完全独立的分区。文件系统层根本看不见对方的东西,连“决定要不要互相覆盖”的机会都没有。

我在这里踩过一个坑:.base_path千万不要设成根路径/,否则文件系统会试图接管整个 VFS 根,和日志输出、控制台之类的子系统撞在一起。一般用一个有辨识度的子目录路径,比如/app1、/logs。

另外,ESP-IDF 新版本对文件系统有新的偏好。老的esp_spiffs组件一直可用,但社区和官方都在往 LittleFS 迁移。LittleFS 的掉电恢复能力和目录操作体验都比 SPIFFS 好,在 ESP-IDF 里可以用esp_littlefs组件,接口几乎一样。建议新项目直接上 LittleFS。

4.2 直接操作分区

如果你不想要文件系统的开销,也可以直接用分区 API 做裸读写。流程分三步:先找到分区,再擦除,再读写。

#include "esp_partition.h" const esp_partition_t *part = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "app1_data"); if (part == NULL) { ESP_LOGE(TAG, "partition not found"); return ESP_FAIL; } // 先擦后写 ESP_ERROR_CHECK(esp_partition_erase_range(part, 0, part->size)); ESP_ERROR_CHECK(esp_partition_write(part, 0, payload, payload_len));

这里最关键的一点是:esp_partition_write的偏移参数是分区内部的偏移,不是 Flash 的绝对地址。比如app1_data分区起始于 Flash 地址 0x912000,你从分区内偏移 0 开始写,实际擦写的是绝对地址 0x912000;但你完全不需要关心这个绝对地址,直接在 API 层面传相对偏移就行了。这个抽象是刻意的,因为分区表一旦调整,分区起始地址会变,但应用代码的逻辑不需要跟着变。如果有人习惯拿绝对地址直接操作 Flash,那么一旦分区表改动或者引入 OTA 切换,代码就很容易把数据写飞到其他分区去。

esp_partition_erase_range也有同样的特性,它的size参数是分区内连续擦除的长度。千万要保证start + size不超过part->size,否则会返回错误。ESP-IDF 内部有校验,但你不是每次都会检查返回值,所以最好在调用前自己做一个边界断言。

4.3 错误做法:裸读写跨分区

不少初学者会犯一个经典错误:不去调用分区 API,而是手动计算 Flash 绝对地址,直接操作 MMU 映射或者底层 flash 驱动。在“刚好只有一个应用、分区表万年不变”的小 demo 里,这种做法偶尔能跑通。可一旦多做几个应用、加一个 OTA,绝对地址就会变成大坑。

举个例子。你以为 0x912000 是app1_data的起点,但如果哪天新增了一个log分区把app1_data的偏移推到了 0xA12000,而你的代码里还写死着 0x912000,那写入就会直接砸到别的分区上,可能把 app 分区的一部分覆盖掉。这类问题在常规功能测试里几乎发现不了,直到用户升级固件或者特定条件下触发,才以“随机重启”“配置丢失”的形式爆发出来。

所以铁律只有一条:在 ESP32 上访问 Flash,永远不会直接写死绝对地址,要么通过esp_partition_find_first按名称找分区,要么通过esp_ota_get_running_partition找当前运行固件所在分区。一切访问都带上分区上下文,绝不裸奔。

5. 实操:从 CSV 到可运行的隔离工程

5.1 准备工作

直接套用上面 16MB 分区表,我会把一个双逻辑应用 + OTA 的验证工程跑通。你需要准备一块 Flash 不小于 16MB 的 ESP32 开发板,以及安装了 ESP-IDF 的开发环境。如果你是第一次配置,建议先跑一遍idf.py set-target esp32,把 toolchain 和依赖拉齐。

把之前写好的partitions.csv放进项目根目录,然后在sdkconfig或者menuconfig里指定分区表为 Custom。我比较推荐直接用menuconfig改,因为它会校验文件路径,避免你手动改写sdkconfig时把变量拼错。

5.2 编译和烧录

idf.py build

构建完成后,先别急着烧。先验证分区表的解析结果:

idf.py partition_table

如果输出里能看到nvs、otadata、factory、ota_0、ota_1、app1_data、app2_data这些名字,并且偏移跟你 CSV 里写的完全一致,说明分区表已经正确编入固件包。接着烧录:

idf.py -p /dev/ttyUSB0 flash monitor

如果你的闪存容量和分区表不匹配,或者 Flash 实际小于 16MB,烧录工具会在写 Flash 时报警。别忽视这种报警,我碰到过一例:开发板标称 16MB 但实际只焊了 8MB Flash,烧录时看似全部成功,结果系统一访问超出 8MB 地址的区域就 panic。升级固件时这种问题更致命,所以每次换板子,第一件事就是确认 Flash 容量。

5.3 运行验证

验证思路是“写 A、读 B、互不可见”。在 main 函数里依次挂载两个文件分区,然后让模块 A 往/app1/hello.txt写入一段文本,模块 B 尝试读取同一个路径。如果按分区表配置正确执行,模块 B 在/app1下找不到文件,因为它挂载的根路径是/app2。

NVS 的验证也如前面代码所示:两个子应用各开一个命名空间,写入同名同值的键,重启后互相读取对方的值,必然得到ESP_ERR_NVS_NOT_FOUND。这个现象恰恰说明隔离生效了——两个抽屉里的东西互不串门。

为了更直观,我习惯在启动日志里加上分区信息打印:

const esp_partition_t *part = esp_partition_find_first( ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL);

打印出当前 app 分区的 label 和 offset,以及正在使用的数据分区的 offset。这样即使固件后来发生了 OTA 切换,我也能从日志里一眼看出当前到底是哪份固件在跑,它的数据该落在哪块区域。日志逻辑哪怕写得简单点,这个打印别省,排查问题时能省一个小时。

6. 常见问题与排查实录:数据“串门”第一现场

6.1 NVS 键读取时好时坏

症状:同一个键有时能读到,有时返回ESP_ERR_NVS_NOT_FOUND,断电重启后数据仿佛随机丢失。

我见到最常见的两个原因。其一是两个模块打开了同一个命名空间,且键名相同,后写的覆盖了先写的,导致逻辑上“数据丢了”。这种情况不是 NVS 故障,是应用层命名空间没规划好。修复很简单:给每个模块一个独立命名空间,命名规则要稳定,不要今天叫app1明天叫app2_config_v2。

其二是 NVS 分区太小,运行过程中空间用尽,新增写入失败。NVS 的机制是页内没有空间时会合并、淘汰无效条目,但如果数据量超过分区容量,写入就会报ESP_ERR_NVS_NO_FREE_PAGES。默认 NVS 分区一般是 0x6000,也就是 24KB。如果你的子应用多、键也多,建议在分区表里把 NVS 扩大到 0x10000 或者更大,甚至可以为不同模块划分不同的 NVS 分区。

6.2 OTA 后数据全部消失

症状:OTA 升级成功,新固件跑起来了,但老固件时代写入的配置、文件全部丢了。

有三个常见原因。第一,新固件使用的分区表和老固件不一致。如果你的升级包里的分区表被改成 app 区域更大,导致后续数据分区偏移发生了变化,那么老固件的数据已经不在新固件所认知的位置上,自然就读不到。这就是我反复强调“分区表一旦发布,就不要随意改 offset”的原因。

第二,升级前你擦除了 app 分区,但顺手把相邻数据分区也擦掉了。部分 OTA 工具或者脚本在烧录前会执行全片擦除,如果命令行里写的起始地址和长度覆盖了其他区域,数据就没了。写脚本时先打印一遍将要擦除的地址范围,再动手执行。

第三,新固件代码里没有正确调用nvs_flash_init,或者挂载文件系统时没有打开正确的分区 label。检查日志,看part->size有没有打印为 0。

6.3 分区重叠导致“鬼魅串门”

症状:某一个子应用正常写入数据,另一个子应用的配置却隔三差五被改掉,查看代码逻辑完全没问题。

这是我最喜欢排查的一类问题,因为答案几乎永远在分区表里。打开idf.py partition_table,检查两个分区的 offset 和 size 是否首尾相接而没有重叠。比如 app1_data 从 0x912000 开始,大小 0x180000,那么它的合法结束地址是 0xA92000;如果 app2_data 从 0xA92000 开始就刚好,但如果手误写成 0x900000,就会覆盖 app1_data 的前 0x12000 个字节。这种重叠不会在编译时直接报错,只有运行时写入才会触发。

预防办法是我前面说的:每个分区起始地址 = 上一个分区起始 + 上一个分区大小。我在写 CSV 时会专门用一个现成的脚本计算,避免人工手算十六进制加法。你如果不写脚本,至少也在表格里留一列“本分区结束地址”,检查时一目了然。

6.4 排查工具与命令

想快速知道 Flash 里到底发生了什么,几个命令我说一下。

用 esptool 直接读回分区表区域:

python -m esptool -p /dev/ttyUSB0 read_flash 0x8000 0x2000 partition_table.bin

读回来后可以用 ESP-IDF 的parttool.py解析:

python -m espsecure --help

如果只是开发阶段,用idf.py partition_table就够了,它会直接打印解析结果。注意你读到的分区表能不能反映真实烧录情况,取决于烧录时 Flash 是否成功写入、烧录后有没有被其他程序覆盖。如果你觉得 Flash 里的表和代码里的 CSV 对不上,优先用 esptool 读原始字节,再手动比对字段,而不是盲目重烧。

运行时排查还有一个大招:打开CONFIG_LOG_DEFAULT_LEVEL_VERBOSE,把 bootloader 日志打开。bootloader 在启动时会把读到的分区表打印出来,你能在串口日志里直接看到 boot 使用的分区布局,和运行时应用层通过esp_partition_find_first拿到的结果做交叉验证。

7. 实际操作中的几点经验

最后分享几点我每次做 ESP32 多应用 Flash 隔离都会坚持的实践。

第一,默认 NVS 分区不要省。很多人图省事把 NVS 分区设成最小,结果新功能加了两三个命名空间就空间告急。在不影响 app 分区大小的前提下,NVS 给到 0x10000 是更安逸的选择,多出来的钱不过 64KB Flash,换来的是不再为NO_FREE_PAGES焦头烂额。

第二,能不改分区表就不改。分区表不是普通的代码文件,它是硬件布局的“宪法”。一旦设备已经出货或者 OTA 通道已经建立,老固件和服务器端的分区表必须保持一致。新版本固件想调整分区大小,只能靠先做一次迁移逻辑,把老分区数据搬过去,否则数据全丢。我自己维护过一个产品,因为升级时改了一次数据分区偏移,导致用户设备升级后全部配置被清空,后来花了整整一周写迁移脚本才救回来。

第三,文件系统分区优先选 LittleFS,别再用 SPIFFS 开新项目。SPIFFS 在目录多、文件频繁增删的情况下性能很一般,掉电恢复也不够健壮。现在的 ESP-IDF 组件仓库里 LittleFS 很好用,API 和 SPIFFS 基本兼容,切换成本很低。

第四,每个数据分区的用途要在代码里用宏或者枚举定义清楚,不要散落在各个模块里写find_first时随手传不同的 label 字符串。字符串拼错是小概率,但一旦云端配置下发了一个新 label,远端的模块却用旧 label 查找,分区查找失败后所有读写静默失败,你就得在设备现场抓日志了。

多应用共用一块 Flash 这件事,做起来并不复杂,核心就两个字:边界。分区表划定边界,命名空间细化边界,文件系统路径固化边界,OTA 机制守护边界。把边界想清楚,数据不会串门,设备也不会在深夜里莫名其妙重启。而在你底气十足地开始写第一个分区表 CSV 之前,不妨先把你项目里所有要存的数据列一张清单,看看它们分别属于哪个模块、大概多大、需要多长生命周期,然后再决定,哪一层隔离就够了。

返回列表