《ESP32 物联网全栈实战-08》OTA 远程升级
·
第 8 篇:OTA 远程升级——不用拆机更新固件
设备已经部署到客户家里了,Bug 修好了新功能写完了,怎么办——让他寄回来串口烧录?这就离谱了。OTA(Over-The-Air)远程升级是 IoT 产品的标配。
1. ESP32 OTA 的原理
Flash 布局(4MB):
┌──────────┬──────────┬──────────┬──────────┬────────┐
│ Bootloader│ OTA Data │ OTA_0 │ OTA_1 │ NVS │
│ 固定 │ 记录哪个 │ 当前固件 │ 新固件 │ 用户数据│
│ │ 分区启动 │ (2MB) │ (2MB) │ │
└──────────┴──────────┴──────────┴──────────┴────────┘
升级流程:
1. 当前运行在 OTA_0
2. 下载新固件 → 写入 OTA_1
3. 校验通过 → 修改 OTA Data → "下次从 OTA_1 启动"
4. 重启 → Bootloader 读 OTA Data → 从 OTA_1 启动
5. 如果 OTA_1 启动失败 → Bootloader 自动回滚到 OTA_0
关键设计:两个分区交替用,升级成功了才切换。新固件启动失败自动回滚,永远不会变砖。
2. 分区表配置
partitions.csv:
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000
otadata, data, ota, 0xf000, 0x2000
phy_init, data, phy, 0x11000, 0x1000
ota_0, app, ota_0, 0x20000, 0x1E0000
ota_1, app, ota_1, , 0x1E0000
nvs_user, data, nvs, , 0x4000
重要:ota_1 不写 Offset——让工具自动计算,避免手写出错。
在 sdkconfig 里指定分区表路径,或在 menuconfig 中:
Partition Table → Partition Table → Custom partition table CSV
→ Custom partition CSV file → "partitions.csv"
3. HTTP OTA——完整实现
// ota_manager.c
#include "esp_https_ota.h"
#include "esp_ota_ops.h"
// MQTT 消息回调:收到 OTA 指令
// topic: device/ota/cmd
// payload: {"url":"http://ota.example.com/firmware_v1.2.bin","version":"1.2","md5":"abc123..."}
static void on_ota_cmd(const char *json_str) {
cJSON *root = cJSON_Parse(json_str);
const char *url = cJSON_GetObjectItem(root, "url")->valuestring;
const char *md5 = cJSON_GetObjectItem(root, "md5")->valuestring;
const char *ver = cJSON_GetObjectItem(root, "version")->valuestring;
ESP_LOGI("OTA", "收到升级指令 → 版本 %s, URL=%s", ver, url);
// 上报状态:正在下载
mqtt_publish("device/ota/status", "{\"status\":\"downloading\"}");
// 开始 OTA
esp_http_client_config_t http_cfg = {
.url = url,
.timeout_ms = 30000, // 大固件需要更长时间
.keep_alive_enable = true,
};
esp_https_ota_config_t ota_cfg = {
.http_config = &http_cfg,
};
esp_err_t ret = esp_https_ota(&ota_cfg);
if (ret == ESP_OK) {
ESP_LOGI("OTA", "固件下载完成,校验通过,3 秒后重启...");
mqtt_publish("device/ota/status", "{\"status\":\"success\",\"version\":\"%s\"}", ver);
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
} else {
ESP_LOGE("OTA", "OTA 失败: %s", esp_err_to_name(ret));
mqtt_publish("device/ota/status", "{\"status\":\"failed\",\"error\":\"%s\"}",
esp_err_to_name(ret));
}
cJSON_Delete(root);
}
4. OTA 安全——不能谁都能给你设备刷固件
方案 A:HTTPS + 服务器认证
esp_https_ota 默认验证服务器证书(如果 URL 是 https://)。确保服务端证书有效即可。
方案 B:固件签名校验
在 HTTP OTA 下载完成后,用 RSA/ECDSA 校验固件签名:
// 在 esp_https_ota 下载完成后的回调中
esp_ota_handle_t update_handle;
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
// ... 写入固件 ...
// 校验签名
esp_image_metadata_t data;
esp_err_t err = esp_image_verify(ESP_IMAGE_VERIFY, &update_partition, &data);
if (err != ESP_OK) {
ESP_LOGE("OTA", "固件签名校验失败!可能是恶意固件。");
esp_ota_abort(update_handle);
return;
}
方案 C:最简单的——私有 URL + 短有效期 Token
服务器端:生成带过期时间的签名 URL
固件 URL: http://ota.example.com/fw.bin?token=xxx&expire=1710000000
设备端:不校验(小产品够用了)
5. MQTT 触发 OTA 的完整流程
┌──────┐ ┌────────┐ ┌──────────┐
│ 管理 │ │ MQTT │ │ ESP32 │
│ 后台 │ │ Broker │ │ │
└──┬───┘ └───┬────┘ └────┬─────┘
│ │ │
│ 1. 上传固件到 OSS/CDN │
│──────────────────────────────────→│
│ │ │
│ 2. 下发升级指令 │
│──────────────→│ │
│ │ 3. device/ota/cmd│
│ │───────────────→ │
│ │ │ 4. 下载固件
│ │ │ 5. 校验、切换分区
│ │ 6. 上报成功 │
│ │←─────────────── │
│ │ │ 7. 重启
6. 常见 OTA 翻车现场
| 问题 | 原因 | 对策 |
|---|---|---|
| 下载到一半断电 | 没做断点续传 | 小固件重试即可,大固件用 Range 请求 |
| 新固件启动后不停重启 | 新固件有 Bug | 自动回滚机制(ESP-IDF 自带) |
| OTA 后配置丢失 | NVS 被覆盖 | NVS 单独分区,不要和 app 混 |
| 固件太大 OTA 分区放不下 | 分区太小 | 检查 idf.py size-components,压缩或扩分区 |
最坑的坑:OTA 后 WiFi 凭据丢了。因为之前有人把 WiFi 凭据写在固件里(而不是 NVS 里)→ OTA 覆盖固件 → 凭据没了。永远把用户数据放 NVS,不要硬编码在固件里。
下一篇:低功耗设计——18650 电池如何撑 3 个月
更多推荐


所有评论(0)