1. OTA 升级原理与分区表

ESP32 的 OTA 功能依赖于其 Flash 分区表。默认分区表通常包含两个 ota_0ota_1 应用分区,以及一个 otadata 分区。升级时,新固件被写入当前未运行的 OTA 分区(例如当前运行在 ota_0,则写入 ota_1),写入完成后,通过设置 otadata 中的引导计数器,使系统在下次重启时切换到新分区。若新固件启动失败,引导程序会自动回滚到旧分区,保证设备不“变砖”。

分区表示例(partitions.csv)

# 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,  0x200000,
ota_0,    app,  ota_0,   0x210000, 0x200000,
ota_1,    app,  ota_1,   0x410000, 0x200000,
  • 每个 OTA 分区大小需一致,且足够容纳固件(本例为 2MB)。
  • factory 分区可选,若存在,首次启动会运行它,之后 OTA 分区优先。

2. HTTP OTA 实现步骤

2.1 硬件与软件准备

  • 硬件:ESP32 开发板(如 ESP32-DevKitC)、USB 线。
  • 软件:ESP-IDF v5.x 或 Arduino ESP32 核心(本文以 ESP-IDF 为例)。
  • 服务器:任意 HTTP 服务器(如 Python http.server),存放编译好的 .bin 固件文件。

2.2 配置工程

menuconfig 中启用 OTA 支持:

  • Component config → ESP HTTPS OTA → 勾选 Allow HTTP for OTA(若用 HTTP 而非 HTTPS)。
  • 设置分区表为自定义 CSV(Partition Table → Custom partition CSV file)。

2.3 编写 OTA 代码

核心逻辑:连接 Wi-Fi → 发起 HTTP GET 请求 → 流式写入 OTA 分区 → 校验 → 重启。

#include <stdio.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"

static const char *TAG = "OTA_HTTP";

// Wi-Fi 配置
void wifi_init(void) {
    // 初始化 NVS、Wi-Fi 等(省略详细代码)
    // 连接你的 AP
}

// HTTP OTA 任务
void ota_task(void *pvParameter) {
    esp_http_client_config_t http_config = {
        .url = "http://192.168.1.100:8000/firmware.bin",
        .timeout_ms = 10000,
    };

    esp_https_ota_config_t ota_config = {
        .http_config = &http_config,
    };

    ESP_LOGI(TAG, "Starting OTA...");
    esp_err_t ret = esp_https_ota(&ota_config);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "OTA success, restarting...");
        esp_restart();
    } else {
        ESP_LOGE(TAG, "OTA failed: %s", esp_err_to_name(ret));
    }
    vTaskDelete(NULL);
}

void app_main(void) {
    wifi_init();
    xTaskCreate(&ota_task, "ota_task", 8192, NULL, 5, NULL);
}

2.4 编译与生成固件

  • 编译工程:idf.py build
  • 生成的固件位于 build/your_project.bin
  • 将该 .bin 文件复制到 HTTP 服务器目录,并启动服务器:
    python3 -m http.server 8000
    

3. 完整示例:带进度显示与错误处理

以下代码增加进度回调,并处理网络错误:

// 进度回调
void ota_progress_cb(int progress, int total) {
    static int last_percent = -1;
    int percent = (progress * 100) / total;
    if (percent != last_percent) {
        ESP_LOGI(TAG, "Progress: %d%%", percent);
        last_percent = percent;
    }
}

void ota_task(void *pvParameter) {
    esp_http_client_config_t http_config = {
        .url = "http://192.168.1.100:8000/firmware.bin",
        .timeout_ms = 15000,
        .keep_alive_enable = true,
    };

    esp_https_ota_config_t ota_config = {
        .http_config = &http_config,
        .progress_cb = ota_progress_cb,
    };

    ESP_LOGI(TAG, "Starting OTA...");
    esp_err_t ret = esp_https_ota(&ota_config);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "OTA success, restarting...");
        esp_restart();
    } else {
        ESP_LOGE(TAG, "OTA failed: %s", esp_err_to_name(ret));
        // 可在此处添加重试逻辑或进入安全模式
    }
    vTaskDelete(NULL);
}

4. 注意事项与最佳实践

  • 固件大小:确保固件 .bin 小于 OTA 分区大小,否则写入失败。
  • 网络稳定性:HTTP 下载中断会导致 OTA 失败,建议使用 HTTPS 或添加断点续传(ESP-IDF 支持 esp_http_client 的 Range 请求)。
  • 回滚机制:新固件启动后,建议调用 esp_ota_mark_app_valid_cancel_rollback() 标记固件有效,否则多次重启后会自动回滚。
  • 安全:生产环境务必使用 HTTPS,并验证服务器证书,防止固件被篡改。
  • 测试:在升级前,先备份当前固件(可通过 esptool.py read_flash 导出)。
  • 日志:OTA 期间避免大量日志输出,以免阻塞网络任务。

5. 总结

本文介绍了基于 HTTP 的 ESP32 OTA 升级方案,从分区表原理到代码实现,并提供了带进度显示的完整示例。通过 OTA,你可以远程修复 bug、推送新功能,极大提升设备维护效率。实际产品中,建议结合 HTTPS、签名验证和回滚策略,构建健壮的升级链路。