1. 项目概述为什么ESP32的OTA如此重要搞嵌入式开发的朋友尤其是玩ESP32的应该都遇到过这个场景设备已经部署到现场了可能是挂在墙上的智能开关也可能是藏在角落里的环境传感器。这时候你发现固件里有个逻辑bug需要修复或者想增加一个新功能。难道要派人去现场把设备拆下来再用USB线接上电脑重新烧录这成本高得吓人用户体验也差。OTAOver-The-Air空中升级技术就是为了解决这个痛点而生的。它允许你通过网络远程、无线地更新设备上的固件就像给手机升级系统一样方便。ESP32作为一款集成了Wi-Fi和蓝牙的明星芯片其OTA功能是原生支持的官方ESP-IDF框架提供了非常完善的API。但很多新手甚至一些有经验的开发者在初次接触ESP32 OTA时往往会被官方文档里大量的配置选项和底层细节绕晕。是使用简单的esp_ota_ops.h接口还是配置复杂的HTTPS OTA分区表该怎么设计升级失败了如何回滚这些问题如果不搞清楚贸然上线OTA功能很可能导致设备“变砖”造成不可挽回的损失。我花了相当长的时间在多个实际量产项目中打磨ESP32的OTA方案从简单的HTTP服务器升级到需要安全签名的HTTPS OTA再到结合云平台的双备份无缝升级踩过的坑不计其数。这篇文章我就以ESP-IDF提供的原生OTA API为核心抛开Arduino等上层封装带你从底层原理到实战代码彻底搞懂如何在你的ESP32项目中实现一个稳定、可靠的OTA升级功能。无论你是想给DIY的智能家居设备增加远程更新能力还是在设计一款需要持续迭代的商用物联网产品这篇笔记都能给你提供直接的参考。2. OTA核心原理与ESP32的独特设计在撸起袖子写代码之前我们必须先理解OTA在ESP32上是如何工作的。这不同于在Linux服务器上替换一个可执行文件嵌入式设备的存储和启动机制有其特殊性。2.1 闪存分区表OTA的舞台ESP32的闪存Flash并不是一整块随便读写的内存。它被划分成多个具有特定功能的区域这个划分规则就记录在“分区表”中。理解分区表是理解OTA的基石。当你编译一个ESP-IDF项目时除了生成固件文件project.bin还会生成一个分区表partitions.csv。一个典型的支持OTA的分区表如下所示# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x4000, otadata, data, ota, 0xd000, 0x2000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M, ota_0, app, ota_0, 0x110000, 1M, ota_1, app, ota_1, 0x210000, 1M, storage, data, spiffs, 0x310000, 0xF0000,我们来拆解一下关键分区factory: 工厂分区。这是最初始的、出厂时烧录的固件。当OTA数据分区otadata无效或指定时会回退启动这个分区。在实际OTA应用中这个分区通常作为最后的救命稻草不建议用于常规升级。ota_0和ota_1: 这是OTA的双备份分区是ESP32 OTA设计的精髓。任何时候只有一个分区被标记为“正在运行”boot分区另一个则作为“接收更新”的分区update分区。升级过程就是把新的固件二进制文件写入到那个非运行的分区。otadata: OTA数据分区。它很小通常8KB但极其重要。它内部保存了一个结构体ota_ops_entry_t主要记录了两个信息当前正在运行的分区序号例如ota_0以及上次升级尝试的状态成功、失败、进行中。Bootloader引导程序在启动时就是读取这个分区来决定从哪个app分区启动。nvs: 非易失性存储。用于存储Wi-Fi密码、设备配置等键值对数据OTA过程中通常需要保持其内容不变。这种双备份分区机制带来了两大好处安全回滚和无缝升级。如果新写入ota_1的固件启动失败Bootloader可以根据otadata中的状态自动回滚到之前稳定运行的ota_0分区。同时升级过程不影响当前运行的系统直到下次重启才切换实现了业务无感知。2.2 OTA升级的完整工作流程一次完整的OTA升级是设备端、服务器端和用户端协同的结果。我们从设备端ESP32的视角看看一次标准的HTTP OTA流程引导阶段ESP32上电Bootloader首先读取otadata分区根据其中的信息判断应该从哪个应用分区factory,ota_0,ota_1启动。同时它会检查otadata中上次OTA操作的状态如果标记为“进行中”或“失败”可能会采取回滚策略。应用运行应用程序正常启动连接Wi-Fi初始化OTA功能模块。更新检查应用程序定期或由服务器推送访问一个预设的更新服务器URL。通常设备会发送当前固件版本号可以定义在idf_component.yml或version.h中。版本比对服务器比较设备发送的版本号和服务器上最新的版本号。如果有新版本则返回新固件的元信息如版本号、文件大小、MD5/SHA256校验和以及固件文件的下载URL。下载与写入这是核心步骤。ESP32应用程序会根据otadata信息确定当前哪个OTA分区是空闲的非启动分区。调用esp_ota_begin()传入目标分区句柄开始一个OTA更新会话。此函数会擦除目标分区的相应区域。循环从服务器下载固件文件流分片调用esp_ota_write()将数据写入目标分区。这里必须注意分片大小和内存管理。下载并写入完成后调用esp_ota_end()结束会话。此时固件数据已完全写入闪存但尚未生效。验证与设置可选的可以调用esp_ota_set_boot_partition()将目标分区设置为下次启动的分区。更常见的做法是在esp_ota_end()之后将otadata中的状态标记为“新镜像待验证”。重启与切换应用程序主动重启esp_restart()。Bootloader再次运行读取otadata发现新分区有待启动便会验证新镜像的签名如果启用安全启动和完整性然后跳转到新分区执行。如果启动成功otadata中的状态会被更新为“成功”如果失败如连续重启多次Bootloader会自动回滚到之前的分区。注意上述流程中esp_ota_set_boot_partition()并不是必须立即调用的。你可以先写入新固件在满足某些业务条件如用户确认、低峰期后再调用该函数并重启。这给了你控制升级时机的能力。2.3 原生API vs 简化接口ESP-IDF提供了不同抽象层次的OTA API原生API (esp_ota_ops.h)提供最基础、最灵活的操作如esp_ota_begin(),esp_ota_write(),esp_ota_end(),esp_ota_get_boot_partition()等。你需要自己处理网络下载、分片写入、错误处理等所有细节。本文重点就是讲解这一层。esp_https_ota组件在原生API之上封装了HTTPS下载流程。你只需要提供一个HTTPS URL它内部会处理TLS连接、下载、写入和验证。简化了HTTPS OTA的开发但灵活性稍逊。esp_http_client OTA常见组合。使用HTTP Client组件下载配合原生OTA API写入。适用于HTTP服务器或内网升级场景。选择原生API的理由很充分你拥有完全的控制权。你可以自定义下载协议不限于HTTP/HTTPS可以是MQTT、WebSocket甚至蓝牙、实现断点续传、精细控制写入缓存、集成自定义的加密解密流程。这对于构建复杂、定制化的OTA系统至关重要。3. 基于原生API的HTTP OTA实战代码拆解理论说得再多不如一行代码。我们来实现一个最经典的场景从一个简单的HTTP服务器下载固件firmware.bin并使用原生API完成升级。3.1 工程配置与分区表设计首先确保你的CMakeLists.txt或Makefile取决于IDF版本包含了必要的组件。OTA功能主要依赖app_update组件它通常已被默认包含。对于HTTP下载我们还需要esp_http_client组件。# 在 CMakeLists.txt 中 set(COMPONENTS app_update esp_http_client)分区表是关键。你可以使用上面示例中的分区表或者通过idf.py menuconfig工具来生成和修改。运行idf.py menuconfig。进入Partition Table菜单。选择Custom partition table CSV并输入你的partitions.csv文件路径。确保分区表中至少有两个app类型的OTA分区ota_0,ota_1并且大小足够容纳你的固件。固件大小必须严格小于OTA分区大小需要预留一些空间。3.2 核心代码实现分片下载与写入下面是一个高度精简但功能完整的HTTP OTA函数它展示了原生API的核心调用逻辑。在实际项目中你需要添加更完善的错误处理、状态上报和用户交互。#include esp_http_client.h #include esp_ota_ops.h #include esp_log.h static const char *TAG OTA_EXAMPLE; // 固件下载服务器的URL #define FIRMWARE_URL http://your-server.com/path/to/firmware.bin esp_err_t simple_http_ota_update(void) { esp_err_t err ESP_OK; esp_http_client_config_t config { .url FIRMWARE_URL, .timeout_ms 10000, // 下载超时时间 .keep_alive_enable true, }; esp_http_client_handle_t client esp_http_client_init(config); if (client NULL) { ESP_LOGE(TAG, Failed to initialize HTTP client); return ESP_FAIL; } // 发起HTTP GET请求 err esp_http_client_open(client, 0); if (err ! ESP_OK) { ESP_LOGE(TAG, Failed to open HTTP connection: %s, esp_err_to_name(err)); esp_http_client_cleanup(client); return err; } // 获取固件文件大小可选用于进度显示 int content_length esp_http_client_fetch_headers(client); if (content_length 0) { ESP_LOGW(TAG, Cannot get content length, proceeding anyway); } else { ESP_LOGI(TAG, Firmware size: %d bytes, content_length); } // --- 核心步骤1: 开始OTA会话获取目标分区 --- const esp_partition_t *update_partition esp_ota_get_next_update_partition(NULL); if (update_partition NULL) { ESP_LOGE(TAG, No OTA update partition found!); esp_http_client_close(client); esp_http_client_cleanup(client); return ESP_FAIL; } ESP_LOGI(TAG, Writing to partition subtype %d at offset 0x%x, update_partition-subtype, update_partition-address); esp_ota_handle_t update_handle 0; err esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, update_handle); if (err ! ESP_OK) { ESP_LOGE(TAG, esp_ota_begin failed: %s, esp_err_to_name(err)); esp_http_client_close(client); esp_http_client_cleanup(client); return err; } ESP_LOGI(TAG, esp_ota_begin succeeded); // --- 核心步骤2: 分片读取HTTP流并写入OTA分区 --- uint8_t ota_write_buffer[1024 * 4]; // 4KB的写入缓冲区 int total_bytes_written 0; int binary_file_length 0; while (1) { int data_read esp_http_client_read(client, (char *)ota_write_buffer, sizeof(ota_write_buffer)); if (data_read 0) { ESP_LOGE(TAG, Error reading HTTP data); err ESP_FAIL; break; } else if (data_read 0) { ESP_LOGI(TAG, Connection closed, all data read); break; // 正常读完 } // 将读取到的数据块写入OTA分区 err esp_ota_write(update_handle, (const void *)ota_write_buffer, data_read); if (err ! ESP_OK) { ESP_LOGE(TAG, esp_ota_write failed at byte %d: %s, total_bytes_written, esp_err_to_name(err)); break; } total_bytes_written data_read; binary_file_length data_read; // 可以在这里添加进度回调更新UI或打印日志 if (content_length 0) { ESP_LOGI(TAG, Written %d/%d bytes (%.1f%%), total_bytes_written, content_length, (total_bytes_written * 100.0) / content_length); } } // --- 核心步骤3: 结束OTA会话 --- if (err ESP_OK) { ESP_LOGI(TAG, Total write: %d bytes, binary_file_length); err esp_ota_end(update_handle); if (err ! ESP_OK) { if (err ESP_ERR_OTA_VALIDATE_FAILED) { ESP_LOGE(TAG, Image validation failed! The firmware file might be corrupted.); } else { ESP_LOGE(TAG, esp_ota_end failed: %s, esp_err_to_name(err)); } } else { ESP_LOGI(TAG, OTA image written successfully!); } } else { // 如果中途出错必须取消OTA会话否则会标记分区为损坏 esp_ota_abort(update_handle); ESP_LOGE(TAG, OTA update aborted due to error); } // --- 核心步骤4: 设置新分区为启动分区并重启 --- if (err ESP_OK) { err esp_ota_set_boot_partition(update_partition); if (err ! ESP_OK) { ESP_LOGE(TAG, esp_ota_set_boot_partition failed: %s, esp_err_to_name(err)); } else { ESP_LOGI(TAG, Prepare to restart system!); esp_restart(); // 重启生效 } } esp_http_client_close(client); esp_http_client_cleanup(client); return err; }3.3 代码关键点与避坑指南缓冲区大小ota_write_buffer这里设置了4KB。这个值需要权衡。太小会导致频繁的write系统调用降低效率且可能增加闪存磨损太大会消耗更多RAMESP32的RAM很宝贵。建议范围在1KB到16KB之间根据你的可用堆内存和网络包大小调整。可以通过heap_caps_get_free_size(MALLOC_CAP_DEFAULT)在运行时查看剩余内存。esp_ota_begin的第二个参数OTA_SIZE_UNKNOWN这意味着我们事先不知道固件文件的总大小。OTA子系统会为这次写入分配空间。如果你知道确切大小传入该值可以提高一点效率。更重要的场景是“防写满”假设你的OTA分区是1MB固件是900KB如果你传入OTA_SIZE_UNKNOWN写入901KB时esp_ota_write会返回错误。如果你传入了900KBesp_ota_begin会预先检查分区剩余空间是否足够不够则直接失败避免写到一半才发现空间不足。错误处理与esp_ota_abort在while循环中任何网络错误或写入错误都必须跳出循环并立即调用esp_ota_abort(update_handle)。这个调用至关重要它会告诉OTA子系统这次更新会话是无效的避免将未写完的、损坏的数据标记为有效镜像。如果不调用abort这个分区可能会被污染影响后续升级。esp_ota_end的验证esp_ota_end函数内部会验证写入镜像的完整性例如检查文件结束符。如果验证失败返回ESP_ERR_OTA_VALIDATE_FAILED说明下载的固件文件可能不完整或已损坏。在生产环境中强烈建议在服务器端计算固件的MD5或SHA256校验和设备端下载完成后先校验再调用esp_ota_end双保险。重启时机esp_ota_set_boot_partition只是修改了otadata中的标记真正的切换发生在下次重启。调用esp_restart()是立即重启。你可以根据业务逻辑延迟重启例如在esp_ota_end成功后先向服务器报告“升级包接收成功”然后等待用户操作或进入闲时再重启。4. 进阶话题让你的OTA系统更健壮基础功能跑通只是第一步要用于实际项目我们必须考虑更多。4.1 断点续传与失败重试网络不稳定是常态。实现断点续传能极大提升OTA成功率。思路如下在esp_ota_begin之前先读取目标分区已写入的数据长度。这可以通过读取分区开头的数据或维护一个外部状态如存储在NVS中来实现。假设已写入X字节。在HTTP请求头中加入Range: bytesX-告诉服务器从第X字节开始传输。调用esp_ota_begin时必须传入OTA_WITH_SEQUENTIAL_WRITES标志并且需要指定一个image_size。因为OTA写入必须是顺序的不能跳着写。将update_handle和已写入的偏移量关联起来继续调用esp_ota_write。实现起来代码会复杂不少你需要处理HTTP 206 Partial Content响应并妥善管理恢复点状态。对于可靠性要求极高的场景这个功能值得投入。4.2 安全升级HTTPS与签名验证HTTP是明文的中间人可以轻易篡改你下载的固件从而完全控制设备。对于任何面向公网的OTAHTTPS是必须的。你可以使用esp_https_ota组件它简化了流程。但如果想用原生API实现HTTPS OTA你需要配置esp_http_client使用HTTPS在配置中设置.cert_pem指向服务器的CA证书或跳过验证但极不安全。下载流程和HTTP类似但数据经由TLS加密。比HTTPS更重要的是固件签名验证。即使使用了HTTPS也要防止服务器被攻破后发布恶意固件。ESP-IDF支持基于RSA或ECDSA的安全启动v2。编译阶段在项目配置中启用安全启动并使用一个安全的签名密钥对编译生成的固件进行签名。Bootloader阶段设备Bootloader在启动任何应用分区前会使用预烧录在芯片中的公钥验证固件的签名。签名无效则拒绝启动。OTA写入阶段esp_ota_write和esp_ota_end本身不验证签名签名验证是由Bootloader完成的。这意味着即使你写入了未签名的恶意固件设备重启后也无法启动会自动回滚。这是最后一道也是最关键的安全防线。实操心得安全启动和签名密钥的管理是产品化的关键一步。务必在项目早期就搭建好签名流水线并将私钥妥善保管如使用硬件安全模块HSM。丢失私钥或泄露私钥会导致整个产品线无法更新。4.3 版本管理与升级策略你不能让设备盲目地下载和升级。需要一个简单的版本管理协议。设备端在代码中定义一个固件版本号例如#define FIRMWARE_VERSION 1.2.3。在检查更新时将该版本号通过HTTP请求参数如?current_ver1.2.3或自定义报文头发送给服务器。服务器端维护一个版本数据库或简单的JSON文件。当收到设备请求时比较设备版本和最新版本。可以设计多种策略强制升级低于某个临界版本的设备必须升级。可选升级有新版本可用但设备可以选择是否立即安装。灰度发布仅对特定比例如10%的设备推送新版本观察稳定性后再全量推送。服务器响应可以返回一个JSON包含新版本号、下载URL、文件大小、校验和、升级类型强制/可选、版本描述等信息。4.4 低电量与内存管理OTA过程耗电且需要大量连续内存用于下载缓冲区。低电量检测在启动OTA任务前检查电池电量。如果电量低于安全阈值如20%应提示用户并中止升级防止升级过程中断电导致设备变砖。内存管理使用heap_caps_get_largest_free_block()来动态判断当前能分配的最大缓冲区。如果内存紧张可以适当调小缓冲区或者先释放一些非关键功能占用的内存如断开非必要的网络连接、暂停传感器采样。5. 实战中遇到的典型问题与排查实录即使理解了所有原理实际调试时还是会遇到各种妖魔鬼怪。下面是我和同事们踩过的一些坑及解决办法。5.1 问题OTA升级后设备不断重启循环现象升级过程显示成功设备重启后在Bootloader或应用初始化早期就崩溃再次重启形成循环。串口日志可能显示“Invalid image length”或“Signature verification failed”。排查思路检查分区表首先确认编译时使用的分区表和你烧录到设备的分区表是否一致。特别是ota_0和ota_1的偏移地址和大小。如果编译的固件大小超过了分区定义的大小写入时不会报错因为OTA_SIZE_UNKNOWN但启动时一定会失败。使用idf.py partition-table查看编译出的分区表摘要。检查安全启动如果启用了安全启动但升级的固件没有用正确的密钥签名Bootloader就会拒绝启动。确认服务器上的固件文件是用产品对应的签名密钥签名的。检查固件文件完整性可能是下载的固件文件本身损坏。在服务器端计算固件的MD5值设备端下载完成后也计算一次比对是否一致。也可以在esp_ota_end后读取刚写入分区的数据计算哈希进行二次验证。查看崩溃日志尽可能捕获崩溃时的Backtrace。在menuconfig中打开Core dump功能或者检查panic handler输出的寄存器信息和堆栈。崩溃地址可能指向某个函数帮你定位问题。解决方案最有效的方法是在本地模拟OTA。将生成的新版firmware.bin放在本地HTTP服务器如Python的http.server让设备从本地下载升级。这样可以排除网络传输问题集中排查固件和分区配置问题。5.2 问题esp_ota_write失败返回ESP_ERR_INVALID_ARG现象在写入过程中esp_ota_write返回错误。排查思路缓冲区溢出检查你的写入缓冲区ota_write_buffer以及每次读取的data_read。确保esp_ota_write调用时传入的数据长度data_read是正数且没有超过缓冲区大小。一个常见的低级错误是缓冲区指针或长度计算错误。会话状态错误确保在esp_ota_begin成功之后esp_ota_end或esp_ota_abort之前调用esp_ota_write。不要在会话结束后再次写入。闪存磨损极端情况下如果闪存某个扇区损坏写入会失败。ESP32的闪存有写寿命但通常很难遇到。可以尝试擦除整个OTA分区再试。5.3 问题升级后设备的Wi-Fi配置或用户数据丢失了现象固件升级成功但设备之前保存的Wi-Fi密码、校准参数等不见了。原因与解决这通常是因为你的分区表设计或代码逻辑有误。用户数据应该存储在独立的数据分区如nvs分区而不是应用分区ota_0,ota_1里。应用分区在OTA时会被完全擦除和覆盖。确保你的分区表中有独立的nvs分区并且你的代码使用nvs_flash_init()和nvs_*系列API来读写数据。检查在OTA升级的代码中没有进行nvs_flash_erase()操作。OTA过程不应影响NVS分区。进阶对于更复杂的数据如文件系统可以使用spiffs或littlefs分区同样要确保OTA不会擦除它们。5.4 问题设备无法连接到OTA服务器现象HTTP客户端初始化或连接失败。排查思路DNS解析失败确保设备能正确解析服务器域名。可以尝试先用ping命令或简单的HTTP GET测试网络连通性。服务器可达性检查服务器防火墙是否开放了80HTTP或443HTTPS端口。客户端配置检查esp_http_client_config_t配置超时时间是否太短是否设置了正确的请求头。内存不足在创建HTTP客户端时如果堆内存不足可能会初始化失败。在OTA开始前打印一下空闲堆内存。为了系统化解决问题我将常见错误、可能原因和解决方法汇总成下表方便快速查阅现象/错误码可能原因排查步骤与解决方案ESP_ERR_OTA_VALIDATE_FAILED1. 固件文件下载不完整或损坏。2. 固件编译选项与设备不匹配如SPI Flash模式、频率。3. 分区表不匹配。1. 比对服务器和下载后文件的MD5。2. 检查menuconfig中Serial flasher config的Flash设置是否与硬件一致。3. 确认设备分区表和编译固件使用的分区表一致。ESP_FAILfromesp_http_client_open1. 网络未连接。2. URL错误或服务器无响应。3. DNS解析失败。1. 检查Wi-Fi连接状态。2. 用电脑浏览器或curl测试URL是否可达。3. 尝试使用IP地址代替域名。升级后不断重启1. 新固件有致命Bug如内存溢出。2. 安全启动验证失败。3. Flash设置错误。1. 回滚旧版本确认是否固件问题。2. 检查签名密钥。3. 确认Flash大小和模式qio,dio等。esp_ota_begin失败1. 找不到可用的OTA分区。2. 指定大小超过分区剩余空间。3. 分区已损坏。1. 检查分区表中ota_0和ota_1分区定义。2. 使用OTA_SIZE_UNKNOWN或传递正确的固件大小。3. 尝试擦除整个Flash重新烧录。升级后数据丢失用户数据存储在了OTA应用分区。将用户数据移至独立的NVS或SPIFFS分区并确保OTA流程不擦除它们。6. 从原型到产品OTA系统设计建议当你完成了单个设备的OTA功能验证后需要考虑如何将其扩展为一个支撑成千上万设备的产品级系统。状态上报与监控设备在OTA的每个关键步骤开始下载、下载完成、验证成功、设置启动分区、重启成功都应该向服务器上报状态。这让你能在后台监控升级成功率、失败原因及时发现问题。可以结合MQTT的Last Will特性在设备意外离线时感知。灰度发布与回滚自动化不要一次性推送给所有设备。设置一个灰度发布策略。当灰度设备群升级失败率超过阈值如5%时自动暂停全量推送并分析日志。服务器应支持向设备发送“回滚”指令命令设备调用esp_ota_set_boot_partition()回滚到旧版本。差分升级对于小版本更新每次都下载完整的固件可能1MB以上非常浪费流量和电量。可以考虑差分升级Delta Update。在服务器端通过工具比较新旧两个版本固件的二进制差异生成一个很小的“补丁”文件可能只有几十KB。设备只需下载这个补丁然后在本地与当前运行的固件进行合并生成新固件再写入。ESP-IDF官方提供了esp_ota_img_diff.py脚本作为起点但实现一个稳定可靠的差分升级需要深厚的功底。电源与看门狗OTA过程耗时可能较长下载1MB文件在一般网络下可能需要数十秒。确保你的硬件有稳定的电源供应。同时在OTA下载和写入循环中要适时喂看门狗esp_task_wdt_reset()防止任务阻塞导致看门狗复位。测试测试再测试建立完整的OTA测试流水线。包括单元测试模拟各种网络错误、断电、集成测试在真实网络环境下升级、压力测试连续多次升级回滚、异常测试在升级过程中断电、断网。OTA是设备“生命线”其稳定性必须通过严苛的测试来保障。我个人在多个项目中实践下来的体会是一个健壮的OTA系统其代码复杂度可能不亚于业务逻辑本身。它涉及网络、存储、安全、电源管理、错误恢复等多个领域。但一旦搭建完成它将为你产品的整个生命周期带来巨大的灵活性和价值。开始可能觉得麻烦但当你第一次在办公室轻轻点击一个按钮就为远在千里之外的设备更新了功能时你会觉得这一切都是值得的。最后一个小技巧在开发调试阶段务必保留一个可以通过串口强制回滚到factory分区的“后门”命令这是你在OTA代码写崩了之后救活开发板的最后手段。