资讯动态

ESP32分区表完全指南:从partitions.csv到OTA与Flash规划

发布时间:2026/10/18 7:30:02 来源:尧图企业网站定制
第一次打开ESP32工程目录看到partitions.csv这个文件时我压根没当回事以为就是个自动生成的临时配置。直到有一次编译报错提醒我“app partition is too small”我才被迫去搞懂它。搞懂之后回头看分区表几乎决定了你整个项目的寿命——OTA能不能回滚、掉电数据会不会丢、文件系统能有多大全都由这几行不起眼的CSV说了算。这篇内容主要写给三类人刚接触ESP32、看到partitions.csv一脸懵的新手想把固件和文件系统分开管理、却不敢乱动分区表的进阶开发者以及已经被编译/烧录/OTA报错反复折腾的实战派。看完你应该能自己读懂分区表、按需求自定义并且少踩几个我当年踩过的坑。1. 一个常被忽略却决定项目生死的文件1.1 分区表就是Flash的一张规划图ESP32内部有一颗Flash用来存放固件、配置、文件系统数据。这颗Flash的容量通常是4MB、8MB或16MB看起来空间不大但里面要装的东西可不少bootloader、分区表、应用固件、NVS参数、OTA备份区域、文件系统……如果大家随意往Flash里写轻则互相覆盖重则开机直接起不来。partitions.csv就是用来给这颗Flash做“分区规划”的文件。它用纯文本描述每个区域的名字、类型、起始地址和大小编译时会被转换成二进制的分区表烧录到Flash的固定位置。芯片上电后bootloader会先读取这张表根据表里的信息找到应用固件并跳转执行。这里得先明确一个概念CSV并不是分区表本身它只是分区表的“源代码”。类似于你用文本描述硬盘怎么分区真正的分区动作发生在编译和烧录阶段。这也是很多人容易搞混的地方——改完CSV后必须重新编译烧录分区表才会真正生效而不是像改配置文件那样重启一下就行。1.2 什么时候你会绕不开它如果你只是拿ESP32跑个点灯、串口打印的小Demo用默认配置就够了确实可以不关心partitions.csv。但只要你开始做正经项目下面这几个场景迟早会遇到想给文件系统腾出更多空间比如存网页、存音频、存日志要做OTA远程升级并且希望升级失败后能自动回滚到旧版本想把设备参数存在Flash里断电重启后不丢失编译时提示“app partition is too small”或“partition table is too large”想给产品做加密启动、限制读写权限等高级功能。我在实际项目中感受最深的是OTA场景。没做分区规划前我以为OTA就是下载新固件、写入Flash、重启完事。结果发现如果只有一个App分区升级失败基本等于变砖连后悔药都没得吃。后来老老实实按分区表做了双App分区才算真正解决了问题。1.3 一个快速建立直观印象的比喻你可以把ESP32的Flash想成一个小的出租屋。bootloader是物业管理员分区表是户型图App是住在这里的租客NVS和文件系统则是储物间和车库。没有户型图管理员不知道哪个房间能住人、哪个房间是仓库所有东西只能乱堆最后连门都打不开。partitions.csv就是这张户型图——它决定了谁住哪、谁有多大面积、哪些区域是共享的公共空间。2. 逐字段拆解看懂一行配置背后的门道2.1 六列格式逐一解读一个标准的partitions.csv文件表头固定是六列# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M,先解释每一列的含义Name分区名称字符串类型编译时会转成标识符不能和别的分区重名。Type分区主类型最常用的是app应用固件和data数据还有一些特殊类型在生产环境中才会用到。SubType主类型下的子分类用来进一步区分用途。比如data类型下可以声明nvs、spiffs、fat等子类型app类型下可以声明factory、ota_0、ota_1。Offset分区起始地址即该分区在Flash中的物理偏移量。留空时编译工具会自动按顺序排列但第一个app分区偏移默认是0x10000这是有讲究的后面细说。Size分区大小支持K和M后缀比如1M等于0x100000字节。所有分区的大小必须是4KB的整数倍因为Flash的最小擦除单位就是4KB。Flags标志位用于声明分区的读写权限、加密属性等。普通项目这列可以留空但涉及到安全启动或Flash加密时就得认真对待。上面这个示例里0x9000到0x10000之间没有分区覆盖看起来像“闲置空间”其实是给分区表二进制和bootloader预留的位置不能乱动。Flash的完整布局大致是0x1000开始放bootloader0x8000开始放分区表0x10000之后才开始放App和其他数据区。2.2 主类型与子类型的核心规则这一节是全文最关键的地方因为很多人改分区表出错往往是对Type和SubType的匹配规则理解不透彻。如果把Type比作“大类”那SubType就是“小类”两者有严格的枚举值不能自己发明。比如data类型下如果写了spiffs编译工具就知道这个区域要挂载SPIFFS文件系统写成fat就是挂载FAT文件系统。app类型下最常用的子类型是factory表示唯一的出厂固件分区还有ota_0、ota_1用于OTA升级时的双区分区。一个有意思的点是如果分区表里出现了ota_0和ota_1bootloader会自动进入OTA管理模式如果只有factorybootloader就直接从factory启动。data类型下最常用的是nvs、phy_init、spiffs、fat。NVS分区用于存储WiFi校准数据、设备密钥等信息在绝大多数官方示例里都必须存在否则部分功能会异常。分区名称大小写有讲究系统默认约定用小写。我见过有人把factory写成Factory结果bootloader认不出来编译不报错但启动失败排查了一天。2.3 偏移地址和大小背后的数学逻辑先看地址。Flash地址从0x0000开始编址但0x0000到0x0FFF这段区域通常不分配给用户分区而是用来存放二级引导程序Second Stage Bootloader。分区表本体一般烧写在0x8000附近这个区域的长度是0x1000字节能容纳的分区项数量足够日常使用。所以第一个用户分区从0x10000开始是官方默认的安全约定。你手动指定偏移时如果小于0x10000极大概率会和引导程序/分区表区域重叠导致板子不开机。再说大小。Flash的擦除操作是按扇区进行的典型的扇区大小是4KB。因此每个分区的大小必须是4096的整数倍否则可能会出现擦写越界的诡异问题——表面上看烧录成功实际运行一段时间后数据错乱。分区表里写1M、512K这类带后缀的值时编译工具会自动换算成字节并做对齐检查但我建议养成写十六进制绝对地址的习惯比如0x100000这样可读性更好也方便和Flash容量对比。另外要记住所有分区大小的总和不能超过Flash物理容量。4MB Flash的容量是0x400000如果你把各个分区加起来接近甚至超过这个值编译系统会直接报错或者烧录后启动异常。3. 常见分区表方案从最小到全家桶3.1 最小可用方案省空间但别把命根子砍了如果你只是想跑个最简单的程序可以只保留一个App分区加NVS、PHY初始化这两个数据分区。下面是我在实际项目里用过的最小可用配置# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x200000,这里App分区给了2MB。为什么我不建议再往下砍因为ESP-IDF环境下WiFi协议栈、蓝牙协议栈这些部分本身就占空间如果App分区小于800KB编译大型工程时大概率会触发“app partition is too small”。另外NVS分区默认给24KB0x6000除非你有更精确的需求否则不要改小因为NVS底层有损耗均衡和提交日志机制空间太小容易频繁擦写缩短Flash寿命。3.2 常用方案App加SPIFFS数据和固件彻底分离绝大多数IoT项目都需要存储用户配置、网页资源或日志。把数据和固件放在同一个分区里一升级就可能把旧数据覆盖掉这是新手最常见的翻车点。改成独立的SPIFFS分区是最稳妥的做法。# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x1E0000, spiffs, data, spiffs, 0x200000, 0x1F0000,这个方案适合4MB FlashApp占约1.8MBSPIFFS占约1.9MB。为什么我习惯给SPIFFS留得比较大方因为SPIFFS的擦写也是按块进行的空间越大写日志、存小文件时越不容易触发垃圾回收整机运行越流畅。SPIFFS的挂载代码里分区名建议保持spiffs不动否则你需要额外修改挂载配置。3.3 OTA双分区方案能回滚才敢升级OTA升级的本质是把新固件写入另一个App分区然后切换启动标记。为了实现“升级失败自动回滚”必须要有两个App分区ota_0和ota_1外加一个存储OTA状态的小分区otadata。# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, otadata, data, ota, 0xf000, 0x2000, phy_init, data, phy, 0x11000, 0x1000, ota_0, app, ota_0, 0x20000, 0x1C0000, ota_1, app, ota_1, 0x1E0000, 0x1C0000, spiffs, data, spiffs, 0x3A0000, 0x50000,要注意的点otadata分区大小一般是0x2000用官方默认值就好不建议扩大或缩小。ota_0和ota_1的偏移要连续而且两者大小建议完全一致。如果不一致新固件体积超过较小分区时升级程序会拒绝写入表现就是OTA总是失败。加了otadata后编译系统会自动识别OTA模式。但如果你想同时保留factory分区做“出厂恢复”逻辑上会复杂不少因为bootloader发现factory存在时会优先走factory启动OTA要额外处理回退判断新手不建议两个混用。3.4 全功能布局把NVS、OTA、文件系统都安排上如果你的产品既要做OTA又要存大量文件可以参考下面这个4MB Flash的分配方案分区名类型子类型偏移地址大小用途nvsdatanvs0x90000x6000存储WiFi校准、用户参数otadatadataota0xf0000x2000OTA状态标记phy_initdataphy0x110000x1000PHY校准数据ota_0appota_00x200000x1C0000固件Aota_1appota_10x1E00000x1C0000固件Bspiffsdataspiffs0x3A00000x50000文件系统你可能会好奇0x3A0000到0x400000之间还剩了大约300多KB为什么不用满这是我自己的习惯Flash容量不是越塞满越好留一点余量将来想加个小功能分区、调大NVS或者做分区表偏移微调时不用推倒重来。做产品规划时余量就是灵活性。4. 让你的项目真正用上自定义分区表4.1 三种方式推荐用第一种要让你的工程使用自定义分区表有三种常见做法方式A在CMakeLists.txt里显式声明。在项目根目录的CMakeLists.txt中加入一行partitions.csv其中partitions.csv是你放在项目根目录下的自定义分区表文件。这种方式的优点是每个工程独立管理自己的分区表互不干扰团队协作时不容易出问题。方式B使用menuconfig配置。在终端执行idf.py menuconfig进入“Partition Table”选项选择“Custom partition table CSV”然后填写分区表文件的路径。适合不想动CMakeLists.txt、或需要临时切换不同分区表的情况。方式C直接替换官方默认文件。IDF在运行时有个默认分区表文件直接替换它也能达到目的但强烈不推荐。因为升级IDF版本或者同时维护多个项目时你会弄混到底谁在用谁的分区表排查问题浪费时间。我实际项目里一直用方式A配合代码管理工具可以把分区表变更记录看得清清楚楚。比如某次需求调整SPIFFS大小你可以在提交记录里看到CSV文件的改动比在menuconfig里操作更可追溯。4.2 编译系统是怎么处理CSV的这部分对排错特别有帮助。当你执行idf.py build时编译系统会用IDF自带的一个Python脚本把CSV转换成二进制文件并生成一个分区表二进制镜像。构建流程的各个环节里分区表会被嵌入App固件的头部信息中。bootloader启动时会比对固件头里的分区表哈希和Flash上实际烧录的分区表是否一致如果不一致会拒绝启动或提示错误。也就是说修改分区表后如果只烧App固件而不烧分区表就会出现“固件头与分区表不匹配”的怪问题。正确做法是重新编译后把整个Flash一起烧录或者确保分区表bin也被刷进去。IDF的flash命令默认会帮你在烧App前先烧分区表所以只要别手动单独烧App基本不会掉链子。另外每次修改CSV后我强烈建议执行一次idf.py fullclean再重新编译。因为分区表相关的一些中间文件带有缓存不清理干净容易出现“改了半天不对劲”的幻觉。4.3 烧录与验证让改动安全落地自定义分区表后常规烧录命令是idf.py -p /dev/ttyUSB0 flash monitor如果只想烧分区表而不动App可以用idf.py -p /dev/ttyUSB0 partition-table-flash烧录完成后如何确认分区表真的按预期生效我总结了一个三步验证法查看启动日志。串口监视器启动后bootloader通常会在日志里打印当前引导的分区信息比如“Loading app from partition at offset 0x20000”或者“OTA slot 0”。看到这些信息就说明分区表已经被正确读取并引导了对应的App。反过来验证把板子上的分区表bin读回来解析看内容和本地的CSV是否一致。这里用官方工具esptool.py读取即可不过这一步通常只在怀疑硬件烧录异常时才需要。对于OTA项目可以实际升级一次升级后确认App版本号和启动分区都发生变化。只有真正跑过一次升级流程才算验证通过。5. 我踩过的坑错误排查与经验速查5.1 七种常见问题与排查思路问题现象可能原因解决方法编译报错“app partition is too small”App分区小于固件实际体积用idf.py size查看固件大小适当调大App分区或裁剪组件编译报错“partition table is too large”分区项太多单个表超长精简分区数量默认分区表容量足够几十项一般不用改报错“No OTA partition”使用了OTA相关功能但分区表没有声明otadata/ota_0/ota_1添加相应的OTA分区定义启动日志提示找不到App分区Type或SubType拼写错误或者偏移地址与已有区域重叠仔细核对CSV每个字段尤其是app的子类型只能填factory或ota_xOTA升级后启动的还是旧版本otadata分区缺失或两个OTA分区大小不一致补上otadata让两个ota分区大小完全一致掉电后数据全部丢失数据写在RAM里或没有挂载可持久化存储建立NVS、SPIFFS或FATFS分区程序里显式读写分区烧录成功但无法启动App分区偏移小于0x10000覆盖了引导区域把所有用户分区偏移安排在0x10000之后5.2 三个必须避开的细节第一个细节是不要同时保留factory和ota_0分区。很多人在网上看到两种方案后直接把两套都拼在一起结果bootloader的启动逻辑被搞糊涂了——遇到factory它会优先从factory启动结果OTA切换分区压根不起效。如果你要做OTA请把所有App分区都声明为ota_0、ota_1这种形式并保留otadata不要混用。第二个细节是改分区表前先看固件体积。我记得有一次给一个项目从单分区改成双OTA分区App空间直接砍半结果编译时才发现固件体积已经超过了每个单独分区的大小只能重新调整整个Flash方案。后来我养成了一个习惯每次改分区表前跑一次编译用idf.py size看看固件体积再反推各分区大小而不是拍脑袋写数字。第三个细节是SPIFFS和FATFS的子类型区别。文件系统分区如果写错子类型程序挂载时会一直失败。比如你在data类型下写了fat却在代码里用SPIFFS的API去挂载就会反复报错。官方推荐的文件系统到子类型的映射是固定的spiffs挂SPIFFSfat挂FATFSlittlefs需要额外的组件支持不能用一个子类型去适配所有文件系统。5.3 我的个人建议让分区表成为项目设计的一部分我从第一次吃分区表的亏到现在养成了一个习惯拿到一个项目需求先列清楚需要哪些能力要不要OTA、要不要文件系统、要不要断电保存配置再倒推分区表设计最后才开始写业务代码。分区表不是给编译工具交差的配置文件而是整个项目里最底层的地基地基歪了后面写得再好也白搭。最后分享一个小技巧每次改完分区表后别急着烧录先运行一下分区表验证相关的命令再通过启动日志或系统工具确认每个分区都真正指向了预期地址。一次确认的时间永远比救砖找回数据的时间短得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑