ARTICLE DETAIL

资讯详情

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

ESP32固件刷坏会变砖吗?双分区与自动回滚机制详解

ESP32固件刷坏会变砖吗?双分区与自动回滚机制详解 1. 从一次刷废了的深夜事故说起很多人第一次给 ESP32 刷固件心里都悬着一根弦万一刷到一半断电、刷错分区表、或者新固件本身有 bug 起不来这块芯片是不是就彻底变砖了我当年也是这么想的直到有一次深夜调试OTA 推到一半路由器抽风设备直接失联我盯着串口一片空白脑子里只有一个念头——完了得拆机接线重刷了。结果呢断电重启之后设备自己回到了上一个能跑的固件版本串口日志里清清楚楚打着一行回滚提示。那一刻我才真正意识到ESP32 的变砖焦虑其实大部分是可以被工程手段化解的。这篇文章就把我踩过的坑、验证过的方案完整讲一遍核心就两件事双分区dual OTA partition和自动回滚rollback。搞懂这两个机制你就能理直气壮地回答那个经典问题——ESP32 固件刷坏了到底会不会变砖。先说结论免得你看到一半还在焦虑只要分区表设计得当、回滚机制开启ESP32 在绝大多数刷坏场景下都不会真变砖。真正会变砖的情况非常有限而且基本都能提前规避。下面我把原理、配置、实操、排错一条条拆开讲适合刚上手 ESP32 的新手也适合已经在做 OTA 但没系统梳理过回滚机制的老手。2. 先搞清楚变砖到底指什么别自己吓自己2.1 三种刷坏的本质区别很多人把刷坏和变砖混为一谈其实这是三个完全不同层级的问题处理难度天差地别。第一种是应用层刷坏固件本身能烧进去但跑起来就崩溃、重启循环、连不上 WiFi。这种情况最轻因为 bootloader 和分区表都还在芯片完全有能力自己救自己。第二种是分区表或 bootloader 刷坏这就麻烦一些了因为负责决定启动哪个固件的那段代码本身出问题了。但只要 bootloader 还能进下载模式用串口重新烧录就能救回来。第三种才是真正的变砖通常是 eFuse 被烧错比如误烧了安全启动密钥、flash 加密密钥、或者供电异常导致 flash 物理损坏。这种才是真的救不回来但说实话正常开发流程里几乎碰不到。我做了这么多年真正意义上物理变砖的 ESP32 只有两块都是电源设计有问题的板子跟固件本身没关系。所以你可以放心固件层面的刷坏99% 都是可恢复的。2.2 为什么 ESP32 天生比很多 MCU 抗造这里要讲一个很多人忽略的点ESP32 的启动流程是分层的。芯片上电后先跑 ROM 里的固化代码这段代码你永远改不掉也刷不坏然后由它去加载 flash 里的二级 bootloader再由 bootloader 去决定加载哪个应用分区。这个分层结构就是抗造的根本原因。ROM 代码是出厂固化的你刷固件根本碰不到它。只要 ROM 代码能正常跑它就能进入串口下载模式你就能重新烧录。换句话说ESP32 的最后一道防线是硬件级的固件刷坏动不了它。理解了这一点你再看双分区和回滚就会发现它们其实是在应用层和bootloader 层之间又加了一道保险让设备在无人值守的情况下也能自己恢复。3. 双分区机制给固件准备一个备胎3.1 分区表长什么样为什么要留两个 app 分区ESP32 的 flash 是被划分成一个个分区的每个分区有名字、类型、子类型、偏移地址和大小。一个典型的支持 OTA 的分区表大概长这样# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x140000, app1, app, ota_1, 0x150000,0x140000, spiffs, data, spiffs, 0x290000,0x160000,关键就是app0和app1这两个app类型的分区子类型分别是ota_0和ota_1。它们大小一样都能存放一份完整的应用固件。同一时刻只有一个是当前运行的另一个就是备胎。为什么要这么设计因为 OTA 升级的本质是把新固件写到另一个分区然后切换启动目标。如果你只有一个 app 分区那升级就得先擦掉正在运行的固件再写新的中间一旦断电两边都没了那才是真完蛋。双分区把这个风险彻底消除了。3.2 otadata 分区那个决定启动谁的小本本光有两个 app 分区还不够bootloader 怎么知道该启动哪一个答案就在otadata分区里。这个分区很小一般 0x2000 就够但作用极其关键它记录了两个 OTA 槽位的状态。每个槽位的状态用一个结构体表示核心字段包括字段含义ota_seq序列号越大越新seq_label可选的标签ota_state状态NEW / PENDING_VERIFY / VALID / INVALID / ABORTEDcrc校验值bootloader 启动时会读 otadata挑出状态合法、序列号最大的那个槽位来启动。如果某个槽位状态是INVALID或ABORTED它就会被跳过。这个状态机就是自动回滚的核心后面会详细讲。提示otadata分区一旦损坏或校验失败bootloader 会退回到默认启动ota_0。所以哪怕这个小本本丢了设备也不会彻底起不来只是会回到出厂那个槽位。3.3 双分区带来的空间代价值不值双分区的代价很直接你要牺牲一半的 app 空间。比如一块 4MB 的 flash如果单分区能放 2MB 的固件双分区之后每个槽位就只剩 1MB 左右。这个代价值不值我的判断标准是看你的使用场景如果是消费类产品、需要远程 OTA那必须双分区没有商量余地。用户不会拆机给你重刷回滚能力就是生命线。如果是自己玩的开发板、固件很小双分区几乎无感1MB 也够跑大部分逻辑。只有当你的固件确实大到单分区都紧张时才需要考虑压缩固件或者用更大 flash来解决而不是砍掉双分区。我个人的经验是能上双分区就上双分区省下来的那点空间远不如一次远程救砖带来的价值大。4. 自动回滚让设备自己判断新固件到底行不行4.1 回滚的触发逻辑其实是一个试用期机制双分区解决了写到哪的问题但没解决新固件能不能用的问题。如果新固件烧进去了、也能启动但跑起来就崩溃那设备岂不是一直卡在崩溃循环里这时候就轮到自动回滚登场了。它的核心思路特别像试用期新固件第一次启动时bootloader 把它标记为PENDING_VERIFY待验证。然后应用代码必须在规定时间内主动报到告诉系统我跑起来了没问题这个动作叫mark valid。如果应用在超时前没报到比如一直崩溃重启bootloader 就会认为这个固件不合格把状态改成ABORTED然后回滚到上一个VALID的固件。这个机制的精妙之处在于判断权交给了应用自己。你可以把报到放在 WiFi 连上之后、业务初始化完成之后甚至放在跟服务器握手成功之后。这样回滚的判据就不是能不能启动而是能不能真正干活。4.2 在 ESP-IDF 里怎么开启回滚如果你用的是 ESP-IDF开启回滚非常简单在menuconfig里配置即可idf.py menuconfig # 进入 Bootloader config # 勾选 Bootloader config - Enable app rollback support # 同时确认 Bootloader config - Number of app slots 至少为 2对应的配置项在sdkconfig里是CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLEy CONFIG_BOOTLOADER_APP_ANTI_ROLLBACKn然后在应用代码里启动稳定之后调用#include esp_ota_ops.h void app_main(void) { // ... 初始化 WiFi、业务逻辑 ... // 确认一切正常后标记当前固件为有效 const esp_partition_t *running esp_ota_get_running_partition(); esp_ota_img_states_t ota_state; if (esp_ota_get_state_partition(running, ota_state) ESP_OK) { if (ota_state ESP_OTA_IMG_PENDING_VERIFY) { // 这里可以加一些自检比如 ping 通服务器 if (self_test_passed()) { esp_ota_mark_app_valid_cancel_rollback(); } else { esp_ota_mark_app_invalid_rollback_and_reboot(); } } } }注意esp_ota_mark_app_valid_cancel_rollback()这个名字它同时做了两件事标记有效 取消回滚。而esp_ota_mark_app_invalid_rollback_and_reboot()则是主动放弃直接重启回滚。4.3 在 Arduino 环境下怎么处理用 Arduino IDE 或者 PlatformIO 玩 ESP32 的朋友可能会问Arduino 框架下有没有对应的 API答案是有的只是封装层级不同。Arduino-ESP32 底层也是基于 ESP-IDF所以esp_ota_ops.h里的函数可以直接调用。#include esp_ota_ops.h void setup() { Serial.begin(115200); // ... 连接 WiFi、初始化业务 ... const esp_partition_t *running esp_ota_get_running_partition(); esp_ota_img_states_t state; if (esp_ota_get_state_partition(running, state) ESP_OK) { if (state ESP_OTA_IMG_PENDING_VERIFY) { // 简单自检WiFi 是否连上 if (WiFi.status() WL_CONNECTED) { esp_ota_mark_app_valid_cancel_rollback(); Serial.println(固件验证通过已取消回滚); } else { esp_ota_mark_app_invalid_rollback_and_reboot(); } } } }这里有个坑要提醒Arduino 的setup()里如果放了阻塞式等待很容易超过回滚超时时间。默认超时是 5 秒左右由CONFIG_BOOTLOADER_APP_ROLLBACK_TIMEOUT控制如果你在setup()里delay(10000)等 WiFi那还没报到就被判定失败了。解决办法是把报到逻辑放到一个非阻塞的状态机里或者适当调大超时时间。5. 一次完整的 OTA 升级 回滚实测记录5.1 实验环境与固件设计为了把机制讲透我搭了一个最小可复现的实验。硬件是一块常见的 ESP32-WROOM-32 开发板4MB flash用 ESP-IDF v5.x。分区表就用前面那份双 app 的配置。我准备了两版固件v1正常固件启动后连 WiFi连上就 mark valid。v2故意写坏的固件启动后进入死循环崩溃永远不 mark valid。预期结果是v1 正常运行OTA 推 v2 之后v2 启动崩溃超时后自动回滚到 v1。5.2 推送坏固件观察回滚全过程OTA 推送我用的是最朴素的 HTTP 方式服务端放一个v2.bin设备端用esp_https_ota拉取。推送完成后设备重启串口日志大致是这样的I (xxx) boot: Loaded app from partition at offset 0x150000 I (xxx) boot: Set actual ota_seq2 for ota_0 I (xxx) esp_image: segment 0: paddr... vaddr... size... ... I (xxx) cpu_start: Starting scheduler on PRO CPU. // 然后就是崩溃循环 Guru Meditation Error: Core 0 paniced (LoadProhibited)连续崩溃几次之后bootloader 检测到PENDING_VERIFY超时日志变成I (xxx) boot: Rollback to previously working app I (xxx) boot: Loaded app from partition at offset 0x10000设备回到了 v1业务恢复正常。整个过程没有人工干预没有拆机没有接线。这就是自动回滚的威力。5.3 几个容易翻车的细节实测下来有几个点特别容易踩坑我一个个说。第一回滚超时时间要合理。默认值对大多数场景够用但如果你的设备启动要连 WiFi、要等传感器预热5 秒可能不够。可以在menuconfig里调CONFIG_BOOTLOADER_APP_ROLLBACK_TIMEOUT我一般设成 30 秒给足余量。第二mark valid 的位置很关键。千万别在app_main一进来就 mark valid那样等于没验证。要放在核心功能确认可用之后。我通常放在 WiFi 连上并且跟服务器完成一次心跳之后。第三回滚之后 otadata 的状态要确认。回滚完成后坏固件那个槽位会被标记为ABORTED下次 OTA 会优先写到那个槽位。如果你连续推坏固件它会反复回滚这是符合预期的但日志里要能看出来。第四别在回滚逻辑里再引入 bug。我见过有人在 mark valid 之前做了一堆复杂判断结果判断逻辑本身崩溃了导致永远 mark 不上设备反复回滚。验证逻辑要尽量简单、无依赖。6. 那些真正会变砖的场景以及怎么提前防6.1 eFuse 误操作才是头号杀手前面说了固件层面几乎不会真变砖。真正危险的是 eFuse。ESP32 的 eFuse 是一次性可编程的烧进去就改不了。如果你在没搞清楚的情况下烧了安全启动密钥、flash 加密密钥而对应的固件又没准备好那设备就真的起不来了。我的建议很直接在量产之前绝对不要在开发板上随便烧 eFuse。要玩安全启动和 flash 加密先用专门的测试板把流程完整跑通再上正式板。6.2 供电不稳导致的 flash 损坏另一个真实存在的风险是供电。ESP32 在 flash 写入时电流会有波动如果电源设计余量不足写入过程中掉压可能导致 flash 内容损坏甚至物理损伤。这种损坏有时候连串口下载模式都进不去。防范手段也简单OTA 升级时确保供电稳定电池供电的设备要在电量充足时才允许升级市电设备要保证电源质量。我在产品里会加一个判断电量低于 30% 直接拒绝 OTA。6.3 分区表刷错导致看起来像砖还有一种情况是分区表本身刷错了比如 app 分区偏移地址跟实际固件对不上设备启动后找不到有效固件表现就是一直重启。这种其实不是砖重新烧一份正确的分区表 固件就好了。判断方法看串口日志。如果能看到 bootloader 的输出说明二级 bootloader 是好的那就一定能救。如果连 bootloader 日志都没有才需要怀疑更底层的问题。7. 把回滚机制用好的几个进阶思路7.1 灰度发布配合回滚风险直接砍半自动回滚最大的价值是在灰度发布场景下。你可以先给 1% 的设备推新固件观察一段时间。如果这批设备都正常 mark valid再逐步扩大比例。万一新固件有问题那 1% 的设备会自己回滚用户几乎无感。这套组合拳打下来OTA 的风险从要么全好要么全坏变成了最坏也就影响一小批而且能自愈。7.2 回滚不是万能服务端也要有兜底设备端回滚解决的是固件起不来的问题但如果固件能起来、只是业务逻辑有 bug比如数据算错了回滚机制是发现不了的。所以服务端也要有监控设备上报的版本号、心跳、关键指标一旦发现异常版本占比升高要能主动停止推送甚至下发回滚指令。设备端 服务端双保险才是完整的 OTA 安全体系。7.3 版本号管理别偷懒我踩过的一个坑是版本号没管好导致设备分不清哪个是新哪个是旧回滚之后又自动升级回坏版本来回横跳。后来我强制要求每次 OTA 的固件版本号必须严格递增且服务端要记录每个设备当前版本。这样回滚之后服务端知道该设备处于哪个版本不会盲目再推。8. 关于会不会变砖的最终回答回到标题那个问题。我的实测结论是在双分区 自动回滚的配置下ESP32 因为固件问题变砖的概率极低。应用崩溃会回滚OTA 中断有备胎分区兜底bootloader 和 ROM 代码是硬件级的最后防线。真正会变砖的基本都跟固件无关而是 eFuse 误操作或硬件供电问题。所以与其担心刷坏了怎么办不如把精力花在把回滚机制配好、把验证逻辑写对、把供电和版本管理做扎实。这几点做到位你完全可以放心地给设备做远程 OTA哪怕推了个有问题的固件它也能自己爬回来。最后分享一个我自己的习惯每次做 OTA 相关改动我都会故意推一版必崩固件来验证回滚链路是否真的生效。这个测试花不了几分钟但能在关键时刻救你一命。毕竟回滚机制最怕的不是它不工作而是你以为它工作、结果真出事的时候才发现它根本没配上。
返回列表