专栏前言

上一篇我们掌握了Flash存储与文件系统,实现了参数、文件的可靠持久化,解决了设备掉电数据丢失的问题。
对于量产部署的物联网设备而言,另一个核心刚需就是固件远程升级——设备安装到现场后,不可能逐一拆壳烧录固件,OTA(Over-The-Air)空中升级是所有联网设备的标配功能。

很多新手对OTA的理解停留在“下载固件写到Flash里”,但实际工程化的OTA是一套完整的可靠性体系:双分区备份保证升级失败不砖机、断点续传适配弱网场景、签名校验保障固件安全、故障自动回滚兜底,每一个环节出问题都可能导致设备现场瘫痪。
ESP-IDF 提供了原生的OTA框架,从底层分区切换到高层网络升级都有成熟的组件支持,无需自己实现底层逻辑。

本篇一次性讲透ESP-IDF下的完整OTA方案:
双分区启动原理 → 标准升级流程 → HTTPS OTA基础实战 → 断点续传与签名校验 → 故障自动回滚机制 → 批量升级策略 → 最佳实践与踩坑汇总
全程配可编译源码+架构图解,零基础也能实现工业级可靠的远程固件升级。


一、OTA核心基础与双分区架构

1. 什么是OTA空中升级

OTA 是通过无线网络远程更新设备固件的技术,无需物理接触设备、无需现场烧录器,即可完成功能迭代、Bug修复、配置更新。
核心价值:大幅降低设备运维成本,支持产品快速迭代,是量产物联网设备的必备功能。

2. 双分区启动原理

ESP-IDF 的 OTA 采用双分区交替升级架构,是其可靠性的核心保障。Flash 中划分两个大小完全相同的应用分区(ota_0ota_1),两者地位对等,交替作为“运行分区”和“升级分区”。

工作机制:

  • 设备启动时,Bootloader 读取专门的 ota_data 数据分区,根据其中的启动标记,决定从 ota_0 还是 ota_1 加载固件
  • 执行升级时,新固件只写入当前未运行的空闲分区,全程不触碰正在运行的固件
  • 新固件写入并校验通过后,修改 ota_data 的启动标记,下次重启自动切换到新分区
  • 升级过程中任何环节失败(断电、下载失败、校验失败),都不影响当前运行的系统,设备永远保留一个可正常启动的固件

下面是双分区交替启动的完整流程:

ota_0

ota_1

校验通过

校验失败

设备上电启动

Bootloader 读取 ota_data 启动标记

当前运行分区

从 ota_0 加载固件

从 ota_1 加载固件

正常运行

收到新固件

写入空闲分区
(当前未运行的分区)

校验固件完整性

更新 ota_data 启动标记

保留原系统运行

重启切换到新分区

3. 完整OTA分区组成

一个标准的OTA分区表包含以下核心区域:

分区作用说明
Bootloader启动引导负责校验分区、选择启动分区、加载固件
ota_0应用分区1存放固件,可作为运行分区或升级分区
ota_1应用分区2与ota_0大小完全一致,对等交替使用
ota_data启动配置区存储启动选择标记、版本信息、升级状态
factory(可选)出厂固件分区出厂默认固件,作为最终保底回滚分区
NVS / SPIFFS数据分区参数、文件存储,升级时不改动,数据保留

在这里插入图片描述

【配图1:ESP32 OTA双分区启动架构图】
配图说明:展示Flash内各分区的分布,Bootloader、ota_data、两个OTA应用分区的关系,标注启动选择、升级写入的数据流方向,直观呈现双分区交替工作的原理。


二、标准升级流程与核心API

1. OTA标准五步流程

一次完整可靠的OTA升级,严格遵循以下流程,任何一步失败都终止并保留原系统:

  1. 版本检测:设备连接升级服务器,查询最新固件版本,对比自身版本判断是否需要升级
  2. 固件下载:通过HTTP/HTTPS通道接收固件数据,支持流式下载边下边写
  3. 分区写入:将接收的固件数据按顺序写入空闲的OTA分区
  4. 完整性校验:写入完成后,校验固件的CRC、SHA256或数字签名,确保完整合法
  5. 启动切换:校验通过后更新ota_data启动标记,重启设备从新固件启动

下面是 OTA 标准五步流程的完整链路:

校验通过

校验失败

版本检测

固件下载

分区写入

完整性校验

启动切换

终止升级
保留原系统

重启运行新固件

设备继续运行旧固件

2. 核心API

ESP-IDF 提供了两层OTA接口:底层原生OTA API和高层封装组件。

  • 底层原生API:灵活可控,适合自定义升级流程
// 开始OTA,获取目标空闲分区
esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle);

// 写入固件数据
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size);

// 结束OTA,校验固件
esp_err_t esp_ota_end(esp_ota_handle_t handle);

// 设置该分区为下次启动分区
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition);
  • 高层HTTPS OTA组件:封装了网络下载、证书校验、分区写入全流程,一行调用即可完成升级,是绝大多数场景的首选。

三、实战1:HTTPS OTA最简实现

这是最常用的入门方案,使用ESP-IDF原生的esp_https_ota组件,自动完成网络下载、分区写入、校验全流程,代码简洁、可靠性高。

1. 实现功能

  • 连接WiFi网络
  • 连接指定HTTPS升级服务器,下载最新固件
  • 自动写入空闲OTA分区并校验
  • 校验通过自动切换启动分区,重启运行新固件

2. 完整可编译源码

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_https_ota.h"
#include "nvs_flash.h"

#define TAG "OTA_DEMO"

// WiFi配置
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PASS "你的WiFi密码"

// 升级固件URL
#define FIRMWARE_URL "https://你的服务器地址/firmware.bin"

// WiFi连接事件处理
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    }
}

// WiFi初始化
static void wifi_init(void)
{
    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASS,
        },
    };
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
    esp_wifi_start();

    // 等待连接成功
    uint8_t retry = 0;
    while(retry < 10) {
        vTaskDelay(pdMS_TO_TICKS(1000));
        if(esp_wifi_connect() == ESP_OK) break;
        retry++;
    }
    ESP_LOGI(TAG, "WiFi连接完成");
}

// OTA升级任务
void ota_task(void *pvParameters)
{
    ESP_LOGI(TAG, "开始OTA升级,固件地址:%s", FIRMWARE_URL);

    esp_http_client_config_t config = {
        .url = FIRMWARE_URL,
        .timeout_ms = 10000,
    };

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

    // 执行HTTPS OTA升级,阻塞直到完成或失败
    esp_err_t ret = esp_https_ota(&ota_config);

    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "OTA升级成功,准备重启");
        vTaskDelay(pdMS_TO_TICKS(2000));
        esp_restart();
    } else {
        ESP_LOGE(TAG, "OTA升级失败,错误码:%d", ret);
    }

    vTaskDelete(NULL);
}

void app_main(void)
{
    nvs_flash_init();
    wifi_init();

    // 创建OTA升级任务
    xTaskCreate(ota_task, "ota_task", 8192, NULL, 5, NULL);
}

3. 关键说明

  • 分区表需要选择包含双OTA分区的配置,如partition_table_double,或自定义分区表
  • 量产场景建议配置服务器证书校验,避免中间人攻击
  • 升级任务栈建议设大一些,网络协议栈需要一定的栈空间

在这里插入图片描述

【配图2:OTA标准升级流程图】
配图说明:按步骤展示版本检测→固件下载→分区写入→校验→切换启动→重启运行的完整流程,标注每个环节的失败处理路径,直观呈现升级的可靠性逻辑。


四、进阶工程化:断点续传、签名校验与故障回滚

基础OTA只能满足简单场景,量产工业级设备还需要解决弱网升级、安全校验、失败兜底三大核心问题。

1. 断点续传

针对大固件、低带宽、网络不稳定的场景,断点续传可以在下载中断后,下次升级从中断的位置继续写入,无需从头开始,大幅节省流量和时间。

  • 实现原理:每次写入固件后,将当前写入偏移量保存到NVS中
  • 升级中断后,下次启动先读取已写入偏移量,跳过已下载部分,从断点继续接收
  • 配合Flash写入校验,确保已写入部分数据正确
  • 适用场景:固件大小超过1MB、移动网络/低功耗广域网、网络信号不稳定的现场设备

2. 固件签名与完整性校验

这是量产设备的安全底线,防止恶意固件、篡改固件、损坏固件写入设备。

  • 完整性校验:使用SHA256算法校验固件整体哈希,确保下载过程中没有损坏、截断
  • 数字签名校验:服务器端用私钥对固件签名,设备端用公钥验证签名,只有官方发布的固件才能被升级
  • ESP-IDF 原生支持安全启动(Secure Boot)功能,硬件级校验固件签名,非法固件无法启动也无法写入

3. 故障自动回滚机制

这是OTA的最后一道保障,确保即使新固件有Bug、启动失败,设备也能自动恢复,不会变砖。

回滚工作流程
  1. 升级完成后,新固件标记为待确认状态
  2. 设备重启运行新固件,业务逻辑正常运行后,调用API确认升级成功
  3. 如果指定时间内没有确认(新固件死机、崩溃、业务异常),Bootloader 在下一次启动时自动回滚到上一个正常的旧固件
  4. 极端情况下,两个OTA分区都损坏,自动回滚到factory出厂分区保底

下面是故障自动回滚的完整状态流转:

业务正常,调用确认API

死机/崩溃/业务异常

取消回滚

Bootloader 检测超时

双分区均损坏

升级完成

待确认状态

运行新固件

确认成功

超时未确认

正常运行

自动回滚

运行旧固件

出厂保底

运行出厂固件

核心API
// 确认当前运行的固件正常,取消回滚
esp_err_t esp_ota_mark_app_valid_cancel_rollback(void);

// 标记当前固件无效,立即回滚
esp_err_t esp_ota_mark_app_invalid_rollback_and_reboot(void);

工程建议:不要一启动就确认成功,要等核心业务都运行正常、关键外设都初始化完成后再确认,确保异常能触发回滚。

在这里插入图片描述

【配图3:OTA故障自动回滚机制示意图】
配图说明:展示升级→待确认→运行验证→确认成功/超时回滚的完整状态流转,标注不同状态下的启动分区选择,直观呈现回滚兜底的工作逻辑。


五、工程最佳实践

1. 版本与升级策略

  • 语义化版本号:采用主版本.次版本.修订号的规范,明确兼容关系,禁止跨大版本强制升级
  • 灰度升级:先升级小批量设备验证稳定性,没问题再逐步全量推送,避免批量故障
  • 差分升级:大固件场景使用差分升级,只传输新旧固件的差异部分,流量可减少90%以上
  • 定时升级:避开业务高峰期、网络高峰期,选择闲时自动升级,降低对业务的影响

2. 可靠性设计

  • 升级前检查:检查Flash剩余空间、电源电压、WiFi信号强度,条件不满足不发起升级
  • 状态持久化:升级进度、版本信息存入NVS,断电重启后可恢复
  • 多重保底:保留factory出厂分区,双OTA分区+出厂分区三重备份
  • 日志记录:完整记录升级过程、失败原因、版本切换记录,便于现场排查

3. 安全设计

  • 必须使用HTTPS加密传输,禁止明文HTTP升级,防止固件泄露和篡改
  • 量产设备开启安全启动与固件签名校验,硬件级拒绝非法固件
  • 升级服务器增加设备身份认证,只有授权设备才能获取固件
  • 敏感设备增加升级白名单,指定设备才能升级特定版本

六、新手高频踩坑汇总

  1. 升级后设备变砖,无法启动
    • OTA分区大小设置不足,固件大小超出分区容量
    • 编译固件的分区表和设备实际Flash分区不匹配
    • 升级中途意外断电,分区数据损坏,且未开启自动回滚
    • 新固件本身有致命Bug,启动直接死机,没有确认回滚机制
  2. OTA升级频繁失败
    • 网络不稳定,大固件下载中途断开,没有断点续传
    • HTTPS服务器证书过期、域名不匹配,连接校验失败
    • Flash擦写异常,或剩余空间不足
    • 固件校验不通过,下载的文件不完整
  3. 提示升级成功,但重启还是旧版本
    • 写入完成后忘记调用esp_ota_set_boot_partition,启动标记未更新
    • ota_data分区异常,Bootloader仍读取旧的启动配置
    • 新固件启动失败,自动回滚到旧版本,误以为升级未生效
    • 固件版本号判断逻辑错误,重复升级同一个版本
  4. 量产批量升级出事故
    • 全量设备同时升级,服务器带宽不足,大量设备下载失败
    • 没有灰度验证,新固件有Bug导致大面积设备异常
    • 没有回滚机制,升级失败后设备全部现场瘫痪
    • 未开启签名校验,存在恶意固件攻击风险

下一篇预告(22篇)

ESP-IDF保姆级入门22|WiFi联网与网络编程全解:STA/AP双模式、TCP/UDP Socket编程、HTTP客户端服务端、网络调试与优化,掌握物联网设备的网络通信基础,实现设备与云端的可靠数据交互!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐