赞
踩
ESP32应用程序可以在运行时通过Wi-Fi或以太网从特定的服务器下载新镜像,然后将其闪存到某些分区中,从而进行升级。在ESP-IDF中本文采用native_ota_example
进行空中(OTA)升级:
为了简单起见,OTA示例通过在menuconfig中启用CONFIG_PARTITION_TABLE_TWO_OTA
选项来选择预定义的分区表,该选项支持三个应用程序分区:工厂分区、OTA_0分区和OTA_1分区。有关分区表的更多信息,请参阅分区表.
在第一次引导时,引导加载程序将加载工厂应用程序图像(即示例图像),然后触发OTA升级。它将从HTTPS服务器下载一个新映像并将其保存到OTA_0分区。它还会自动更新ota_data分区,以指示下一次重置时应该从哪个应用程序启动。引导加载程序将读取ota_data分区中的内容并运行所选的应用程序。
OTA工作流程如下图所示:
OTA 升级机制可以让设备在固件正常运行时根据接收数据更新(如通过 Wi-Fi 或蓝牙)。
要运行 OTA 机制,需配置设备的 :doc:分区表 <../../api-guides/partition-tables>
,该分区表至少包括两个 OTA 应用程序分区(即 ota_0 和 ota_1)和一个 OTA 数据分区。
OTA 功能启动后,向当前未用于启动的 OTA 应用分区写入新的应用固件镜像。镜像验证后,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 固件。
使用官方提供的 Windows (ESP-IDF 工具安装器) 的介绍,安装所有必需工具。安装完成后会生成如下可执行文件。点开后工具安装器会自动配置环境变量,无需额外配置。
在此工具中进入IDF安装目录下的\examples\system\ota\native_ota_example\main目录,此处存放工程代码。
执行idf.py menuconfig
查看项目配置(IDF中项目都是配置好的),其相关配置如下
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 0x14F000,
ota_0, app, ota_0, , 0x14F000,
ota_1, app, ota_1, , 0x14F000,
本文档只对native_ota_example.c中的OTA功能解析
/* OTA example This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_event.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_flash_partitions.h" #include "esp_partition.h" #include "nvs.h" #include "nvs_flash.h" #include "driver/gpio.h" #include "protocol_examples_common.h" #include "errno.h" #if CONFIG_EXAMPLE_CONNECT_WIFI #include "esp_wifi.h" #endif #define BUFFSIZE 1024 #define HASH_LEN 32 /* SHA-256 digest length */ static const char *TAG = "native_ota_example"; /*an ota data write buffer ready to write to the flash*/ static char ota_write_data[BUFFSIZE + 1] = { 0 }; extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); #define OTA_URL_SIZE 256 static void http_cleanup(esp_http_client_handle_t client) { esp_http_client_close(client); esp_http_client_cleanup(client); } static void __attribute__((noreturn)) task_fatal_error(void) { ESP_LOGE(TAG, "Exiting task due to fatal error..."); (void)vTaskDelete(NULL); while (1) { ; } } static void print_sha256 (const uint8_t *image_hash, const char *label) { char hash_print[HASH_LEN * 2 + 1]; hash_print[HASH_LEN * 2] = 0; for (int i = 0; i < HASH_LEN; ++i) { sprintf(&hash_print[i * 2], "%02x", image_hash[i]); } ESP_LOGI(TAG, "%s: %s", label, hash_print); } static void infinite_loop(void) { int i = 0; ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it"); while(1) { ESP_LOGI(TAG, "Waiting for a new firmware ... %d", ++i); vTaskDelay(2000 / portTICK_PERIOD_MS); } } 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"); const esp_partition_t *configured = esp_ota_get_boot_partition(); 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(); } 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); 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; 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(); } } 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(); } 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(); } 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 ; } static bool diagnostic(void) { gpio_config_t io_conf; io_conf.intr_type = GPIO_PIN_INTR_DISABLE; io_conf.mode = GPIO_MODE_INPUT; io_conf.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_GPIO_DIAGNOSTIC); io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; io_conf.pull_up_en = GPIO_PULLUP_ENABLE; gpio_config(&io_conf); ESP_LOGI(TAG, "Diagnostics (5 sec)..."); vTaskDelay(5000 / portTICK_PERIOD_MS); bool diagnostic_is_ok = gpio_get_level(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC); gpio_reset_pin(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC); return diagnostic_is_ok; } void app_main(void) { uint8_t sha_256[HASH_LEN] = { 0 }; esp_partition_t partition; // get sha256 digest for the partition table partition.address = ESP_PARTITION_TABLE_OFFSET; partition.size = ESP_PARTITION_TABLE_MAX_LEN; partition.type = ESP_PARTITION_TYPE_DATA; esp_partition_get_sha256(&partition, sha_256); print_sha256(sha_256, "SHA-256 for the partition table: "); // get sha256 digest for bootloader partition.address = ESP_BOOTLOADER_OFFSET; partition.size = ESP_PARTITION_TABLE_OFFSET; partition.type = ESP_PARTITION_TYPE_APP; esp_partition_get_sha256(&partition, sha_256); print_sha256(sha_256, "SHA-256 for bootloader: "); // get sha256 digest for running partition esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256); print_sha256(sha_256, "SHA-256 for current firmware: "); const esp_partition_t *running = esp_ota_get_running_partition(); esp_ota_img_states_t ota_state; if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) { if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) { // run diagnostic function ... bool diagnostic_is_ok = diagnostic(); if (diagnostic_is_ok) { ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution ..."); esp_ota_mark_app_valid_cancel_rollback(); } else { ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version ..."); esp_ota_mark_app_invalid_rollback_and_reboot(); } } } // Initialize NVS. esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { // OTA app partition table has a smaller NVS partition size than the non-OTA // partition table. This size mismatch may cause NVS initialization to fail. // If this happens, we erase NVS partition and initialize NVS again. ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK( err ); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. * Read "Establishing Wi-Fi or Ethernet Connection" section in * examples/protocols/README.md for more information about this function. */ ESP_ERROR_CHECK(example_connect()); #if CONFIG_EXAMPLE_CONNECT_WIFI /* Ensure to disable any WiFi power save mode, this allows best throughput * and hence timings for overall OTA operation. */ esp_wifi_set_ps(WIFI_PS_NONE); #endif // CONFIG_EXAMPLE_CONNECT_WIFI xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL); }
const esp_partition_t *running = esp_ota_get_running_partition(); esp_ota_img_states_t ota_state; if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) { if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) { // run diagnostic function ... bool diagnostic_is_ok = diagnostic(); if (diagnostic_is_ok) { ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution ..."); esp_ota_mark_app_valid_cancel_rollback(); } else { ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version ..."); esp_ota_mark_app_invalid_rollback_and_reboot(); } } }
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
事件循环库是esp提供的一种事件处理方法,而默认事件循环是用于系统事件(例如WiFi事件)的特殊循环类型,这里创建一个默认事件循环用以处理升级事件
ESP_ERROR_CHECK(esp_event_loop_create_default());
执行任务时,开启HTTP客户端后再服务器循环读取升级所需固件,读取完成后重启。
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL); 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"); const esp_partition_t *configured = esp_ota_get_boot_partition(); 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(); } 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); 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; 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(); } } 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(); } 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(); } 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 ; }
• 由 青梅煮久 写于 2020 年 12 月 03 日
• 参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。