一、概述
ESP32應用程式可以在運行時通過Wi-Fi或以太網從特定的服務器下載新映像,然后將其閃存到某些磁區中,從而進行升級,
在ESP-IDF中有兩種方式可以進行空中(OTA)升級:
- 使用
app_update組件提供的原生API- 使用
esp_https_ota組件提供的簡化API,它在原生OTA API上添加了一個抽象層,以便使用HTTPS協議進行升級,分別在
native_ota_example和simple_ota_example下的OTA示例中演示了這兩種方法,
1.1 OTA作業流程

1.2 OTA資料磁區
ESP32 SPI Flash 內有與升級相關的(至少)四個磁區:OTA data、Factory App、OTA_0、OTA_1,其中 FactoryApp 記憶體有出廠時的默認韌體,
首次進行 OTA 升級時,OTA Demo 向 OTA_0 磁區燒錄目標韌體,并在燒錄完成后,更新 OTA data 磁區資料并重啟,
系統重啟時獲取 OTA data 磁區資料進行計算,決定此后加載 OTA_0 磁區的韌體執行(而不是默認的 Factory App 磁區內的韌體),從而實作升級,
同理,若某次升級后 ESP32 已經在執行 OTA_0 內的韌體,此時再升級時 OTA Demo 就會向 OTA_1 磁區寫入目標韌體,再次啟動后,執行 OTA_1 磁區實作升級,以此類推,升級的目標韌體始終在 OTA_0、OTA_1 兩個磁區之間互動燒錄,不會影響到出廠時的 Factory App 韌體,

為了簡單起見,OTA示例通過在menuconfig中啟用CONFIG_PARTITION_TABLE_TWO_OTA選項來選擇預定義的磁區表,該選項支持三個應用程式磁區:工廠磁區、OTA_0磁區和OTA_1磁區,有關磁區表的更多資訊,請參閱磁區表.
二、API說明
以下 OTA 介面位于 app_update/include/esp_ota_ops.h,
2.1 esp_ota_begin

2.2 esp_ota_write

2.3 esp_ota_end

2.4 esp_ota_set_boot_partition

2.5 esp_ota_get_boot_partition

2.6 esp_ota_get_running_partition

2.7 esp_ota_get_next_update_partition

三、編程流程
3.1 OTA詳細程序邏輯

3.2 OTA磁區操作流程

節選自 esp-idf\examples\system\ota\native_ota_example 中的例程
static void ota_example_task(void *pvParameter)
{
esp_err_t err;
/* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
esp_ota_handle_t update_handle = 0 ;
const esp_partition_t *update_partition = NULL;
ESP_LOGI(TAG, "Starting OTA example");
//獲取OTA app存放的位置
const esp_partition_t *configured = esp_ota_get_boot_partition();
//獲取當前系統執行的韌體所在的Flash磁區
const esp_partition_t *running = esp_ota_get_running_partition();
if (configured != running) {
ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",
configured->address, running->address);
ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
}
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)",
running->type, running->subtype, running->address);
esp_http_client_config_t config = {
.url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
.cert_pem = (char *)server_cert_pem_start,
.timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
};
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
char url_buf[OTA_URL_SIZE];
if (strcmp(config.url, "FROM_STDIN") == 0) {
example_configure_stdin_stdout();
fgets(url_buf, OTA_URL_SIZE, stdin);
int len = strlen(url_buf);
url_buf[len - 1] = '\0';
config.url = url_buf;
} else {
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
abort();
}
#endif
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
esp_http_client_handle_t client = esp_http_client_init(&config);
if (client == NULL) {
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
task_fatal_error();
}
//連http服務器
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);
task_fatal_error();
}
esp_http_client_fetch_headers(client);
//獲取當前系統下一個(緊鄰當前使用的OTA_X磁區)可用于燒錄升級韌體的Flash磁區
update_partition = esp_ota_get_next_update_partition(NULL);
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
update_partition->subtype, update_partition->address);
assert(update_partition != NULL);
int binary_file_length = 0;
/*deal with all receive packet*/
bool image_header_was_checked = false;
while (1) {
int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
http_cleanup(client);
task_fatal_error();
} else if (data_read > 0) {
if (image_header_was_checked == false) {
esp_app_desc_t new_app_info;
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
// check current version with downloading
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
esp_app_desc_t running_app_info;
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
}
const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition();
esp_app_desc_t invalid_app_info;
if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
}
// check current version with last invalid partition
if (last_invalid_app != NULL) {
if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
ESP_LOGW(TAG, "New version is the same as invalid version.");
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
http_cleanup(client);
infinite_loop();
}
}
#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
http_cleanup(client);
infinite_loop();
}
#endif
image_header_was_checked = true;
//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));
http_cleanup(client);
task_fatal_error();
}
ESP_LOGI(TAG, "esp_ota_begin succeeded");
} else {
ESP_LOGE(TAG, "received package is not fit len");
http_cleanup(client);
task_fatal_error();
}
}
//寫flash
err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
if (err != ESP_OK) {
http_cleanup(client);
task_fatal_error();
}
binary_file_length += data_read;
ESP_LOGD(TAG, "Written image length %d", binary_file_length);
} else if (data_read == 0) {
/*
* As esp_http_client_read never returns negative error code, we rely on
* `errno` to check for underlying transport connectivity closure if any
*/
if (errno == ECONNRESET || errno == ENOTCONN) {
ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
break;
}
if (esp_http_client_is_complete_data_received(client) == true) {
ESP_LOGI(TAG, "Connection closed");
break;
}
}
}
ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
if (esp_http_client_is_complete_data_received(client) != true) {
ESP_LOGE(TAG, "Error in receiving complete file");
http_cleanup(client);
task_fatal_error();
}
//OTA寫結束
err = esp_ota_end(update_handle);
if (err != ESP_OK) {
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
}
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
http_cleanup(client);
task_fatal_error();
}
//升級完成更新OTA data區資料,重啟時根據OTA data區資料到Flash磁區加載執行目標(新)韌體
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));
http_cleanup(client);
task_fatal_error();
}
ESP_LOGI(TAG, "Prepare to restart system!");
esp_restart();
return ;
}
四、測驗流程
4.1 配置Flash大小
打開專案配置選單 idf.py menuconfig
選擇串行Flash配置 Serial flasher config --->

根據模塊Flash大小,更改 Flash size,我使用的是 ESP32-WROVER-B 模組,所以修改為 4 MB,

4.2 配置磁區表
選擇磁區表配置 Partition Table --->

選擇 Factory app, two OTA definitions

4.3 配置服務器資訊

看服務器型別修改 https 或 http

4.4 配置連接方式

選擇WIFI連接方式,并修改要連接路由器的SSID和密碼

4.5 配置APP版本號
Note:
native_ota_example中沒有版本號大小檢查,它看到不同的版本就會下載,當本地設備是比OTA服務器版本號更高的時候,也會下載OTA服務器的舊版進行更新,這個需要自行添加版本號大小檢查,
4.5.1 通過version.txt控制

4.5.2 通過menuconfig控制
選擇應用管理 Application manager --->

勾選 Get the project version from Kconfig 并在下面填寫版本號,

4.6 開啟HTTP服務器(可選)
編譯鏈內 Python 有一個內置的 HTTP 服務器,我們這里可以直接使用它,我們將會使用示例 get-started/hello_world 作為需要更新的韌體,
4.6.1 生成目標韌體
使用示例 get-started/hello_world,此處執行idf.py build 會生成二進制檔案get-started\hello_world\build\hello-world.bin,
4.6.2 創建HTTP服務器
- 使用工具鏈python創建服務器
進入存放需要升級的韌體(hello-world.bin)目錄,在此處創建HTTP服務器,
此處執行python -m http.server --bind 192.168.61.67 8070會生成一個HTTP本地服務器,該服務器下對應\build檔案夾下所有內容,(有的文章執行的python命令不同,是由于python版本不同造成的,)

- 使用HFS創建服務器
鏈接:https://pan.baidu.com/s/1MIAI5m4WQJdAZpqRAdvsXg 提取碼:9m8y

4.7 開啟HTTPS服務器(可選)
Shirt + 滑鼠右鍵打開Linux shell- 創建一個新的自簽名證書和密鑰
openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes
依次輸入:(國家)、(洲/省)、(城/鎮)、(組織名)、(單位名)、(httpd-ssl.conf中的ServerName 名稱)、(郵箱)

- 將生成的證書
ca_cert.pem復制到OTA示例目錄中的server_certs目錄下,已存在則替換內容

- 創建HTTPS服務器
openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070
4.8 查看列印


? 由 Leung 寫于 2021 年 6 月 11 日
? 參考:ESP32 學習日志(4)——OTA升級(1)-示例決議
第二十章 ESP32的空中升級(OTA)
ESP32 OTA詳解-中文翻譯版
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286991.html
標籤:其他
下一篇:記錄我學習STM32踩的坑
