avatar

松果工作室

欢迎光临

  • 首页
  • ESP
  • LVGL
  • freeRTOS
  • 快速笔记
  • 考察日志
  • 个人收藏
  • 我的服务
Home (ESP-IDF)OTA
文章

(ESP-IDF)OTA

Posted 2025-07-29 Updated 2025-12- 27
By YCP
31~40 min read

双分区

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
# app offset must 64k 0x10000
nvs      ,data ,nvs     ,0x9000        ,0x4000,
otadata  ,data ,ota     ,0xd000        ,0x2000,
phy_init ,data ,phy     ,0xf000        ,0x1000,
ota_0    ,app  ,ota_0   ,0x10000       ,0xf0000,
ota_1    ,app  ,ota_1   ,0x100000      ,0xf0000,

ESP32 OTA 步骤

// 1. 预先需要的变量
//    esp_ota_handle_t update_handle = 0;
//    update_partition = esp_ota_get_next_update_partition(NULL);
// 2. esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
// 3. esp_ota_write(update_handle, download_static_buffer, data_read);
// 4. esp_ota_end(update_handle);
// 5. esp_ota_set_boot_partition(update_partition);

实际案例 HTTP OTA

#include "module_ota.h"
#include <esp_err.h>
#include <esp_ota_ops.h>
#include <esp_http_client.h>
#include <esp_crt_bundle.h>
#include "esp_log.h"
#include "stdbool.h"
#include "event_bus.h"

static const char *TAG = "module_ota";

#define DOWNLOAD_CHUNK_SIZE 2048
static uint8_t download_static_buffer[DOWNLOAD_CHUNK_SIZE];

void module_ota_task(void *pt) {
    char *ota_url = (char *) pt;

    ota_url[256] = '\0';
    if (strlen(ota_url) == 0) {
        ESP_LOGE(TAG, "Invalid ota_url\r\n");
        return;
    }

    esp_err_t err = ESP_OK;
    esp_ota_handle_t update_handle = 0;
    const esp_partition_t *update_partition = NULL;
    esp_http_client_handle_t client = NULL;

    // 重试机制相关变量
    int retry_count = 0;
    const int max_retries = 3; // 最大重试次数
    const int retry_delay_ms = 2000; // 重试延迟2秒

    // 1. 获取OTA分区
    update_partition = esp_ota_get_next_update_partition(NULL);
    if (update_partition == NULL) {
        ESP_LOGE(TAG, "Failed to get next update partition");
        err = ESP_FAIL;
        goto cleanup;
    }

    ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32,
             update_partition->subtype, update_partition->address);

    // 2. 配置HTTP客户端(优化配置)
    esp_http_client_config_t config = {
            .url = ota_url,
            .transport_type = HTTP_TRANSPORT_OVER_SSL,
            .timeout_ms = 60000, // 增加到60秒超时
            .keep_alive_enable = true,
            .crt_bundle_attach = esp_crt_bundle_attach,
            .buffer_size = DOWNLOAD_CHUNK_SIZE, // 设置缓冲区大小
            .buffer_size_tx = DOWNLOAD_CHUNK_SIZE, // 发送缓冲区大小
    };

    // 3. 初始化HTTP客户端
    client = esp_http_client_init(&config);
    if (client == NULL) {
        ESP_LOGE(TAG, "Failed to initialize HTTP client");
        err = ESP_FAIL;
        goto cleanup;
    }

    esp_http_client_set_method(client, HTTP_METHOD_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));
        goto cleanup;
    }
    esp_http_client_fetch_headers(client);

    // 4. 开始OTA操作
    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));
        goto cleanup;
    }

    // 5. 循环下载数据(使用静态缓冲区,包含重试机制)
    bool download_complete = false;
    uint32_t start_ms = pdTICKS_TO_MS(xTaskGetTickCount());

    while (!download_complete) {
        // 读取数据到静态缓冲区
        int data_read = esp_http_client_read(client, (char *) download_static_buffer, DOWNLOAD_CHUNK_SIZE);

        if (data_read < 0) {
            ESP_LOGE(TAG, "Error reading data: %d, retry count: %d/%d", data_read, retry_count, max_retries);

            // 检查是否是连接断开错误
            if (esp_http_client_is_complete_data_received(client) == false) {
                retry_count++;
                if (retry_count <= max_retries) {
                    ESP_LOGW(TAG, "Retrying connection...");
                    vTaskDelay(retry_delay_ms / portTICK_PERIOD_MS); // 等待后重试

                    // 重新建立连接
                    esp_http_client_close(client);
                    err = esp_http_client_open(client, 0);
                    if (err != ESP_OK) {
                        ESP_LOGE(TAG, "Failed to reopen HTTP connection: %s", esp_err_to_name(err));
                        continue; // 继续重试
                    }
                    esp_http_client_fetch_headers(client);

                    // 修复:重新开始OTA操作,重置写入位置
                    if (update_handle != 0) {
                        esp_ota_abort(update_handle);
                        update_handle = 0;
                    }
                    err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
                    if (err != ESP_OK) {
                        ESP_LOGE(TAG, "Failed to restart OTA operation: %s", esp_err_to_name(err));
                        continue; // 继续重试
                    }

                    continue; // 重新开始读取
                } else {
                    ESP_LOGE(TAG, "Max retries exceeded, aborting download");
                    err = ESP_FAIL;
                    goto cleanup;
                }
            } else {
                // 数据传输已完成但读取错误,可能是正常结束
                download_complete = true;
                break;
            }
        } else if (data_read > 0) {
            // 重置重试计数器
            retry_count = 0;

            // 写入OTA分区
            err = esp_ota_write(update_handle, download_static_buffer, data_read);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "esp_ota_write failed: %s", esp_err_to_name(err));
                goto cleanup;
            }

        } else if (data_read == 0) {
            // 数据传输完成
            if (esp_http_client_is_complete_data_received(client) == true) {
                ESP_LOGI(TAG, "Download completed successfully");
                download_complete = true;
                break;
            }
        }

        // 超时检查(10分钟)
        if (pdTICKS_TO_MS(xTaskGetTickCount()) - start_ms > 60000 * 10) {
            ESP_LOGE(TAG, "Download timeout");
            err = ESP_FAIL;
            goto cleanup;
        }
    }

    // 8. 结束OTA操作
    err = esp_ota_end(update_handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
        goto cleanup;
    }

    // 9. 设置启动分区
    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));
        goto cleanup;
    }

    ESP_LOGI(TAG, "Firmware download and update completed successfully");

    cleanup:
    // 统一的资源清理
    if (client) {
        esp_http_client_cleanup(client);
    }

    if (update_handle != 0 && err != ESP_OK) {
        esp_ota_abort(update_handle);
    }

    esp_restart();
}

TaskHandle_t module_ota_task_handle = NULL;

void module_ota_init(char *ota_url) {
    if (module_ota_task_handle == NULL) {
        xTaskCreate(module_ota_task, "module_ota_task", 1024*4, ota_url, 10, &module_ota_task_handle);
    }
}


// OTA 步骤
// 1. 预先需要的变量
//    esp_ota_handle_t update_handle = 0;
//    update_partition = esp_ota_get_next_update_partition(NULL);
// 2. esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
// 3. esp_ota_write(update_handle, download_static_buffer, data_read);
// 4. esp_ota_end(update_handle);
// 5. esp_ota_set_boot_partition(update_partition);

ESP
License:  CC BY 4.0
Share

Further Reading

OLDER

(LIB)一个好用的Modbus解析函数

NEWER

(ESP-IDF)BLE GATTS

Recently Updated

  • (ESP-IDF)LVGL 模拟器
  • (ESP-IDF)LVGL 自定义对象加入编码器组
  • (ESP-IDF)vscode配置文件
  • (Elec)来复再生式晶体管单管收音机
  • (ESP-IDF)ESPNOW

Trending Tags

LVGL WCH Linux Elec ThatProject freeRTOS STM ESP Flutter Others

Contents

©2026 松果工作室. Some rights reserved.

Using the Halo theme Chirpy