一、OTA简介

1.1 概述

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工作流程如下图所示:

1.2 OTA 流程概览

OTA 升级机制可以让设备在固件正常运行时根据接收数据更新(如通过 Wi-Fi 或蓝牙)。

要运行 OTA 机制,需配置设备的 :doc:分区表 <../../api-guides/partition-tables>,该分区表至少包括两个 OTA 应用程序分区(即 ota_0 和 ota_1)和一个 OTA 数据分区。

OTA 功能启动后,向当前未用于启动的 OTA 应用分区写入新的应用固件镜像。镜像验证后,OTA 数据分区更新,指定在下一次启动时使用该镜像。

1.3 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 固件。

二、官方示例应用

2.1 示例配置

使用官方提供的 Windows (ESP-IDF 工具安装器) 的介绍,安装所有必需工具。安装完成后会生成如下可执行文件。点开后工具安装器会自动配置环境变量,无需额外配置。

在此工具中进入IDF安装目录下的\examples\system\ota\native_ota_example\main目录,此处存放工程代码。

2.1.1 配置菜单

执行idf.py menuconfig

查看项目配置(IDF中项目都是配置好的),其相关配置如下

  1. 进入Serial flasher config->设置Flash size为4

  2. 进入Partition table->Partition table->选中Factory app,two OTA definitions



    如果使用用户自己定义得按如下配置


    user_partitions.csv如下
# 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,

  1. 进入Example Configuration设置服务器信息

  2. 进入Example Connection Configuration设置路由器信息

2.2 示例代码

本文档只对native_ota_example.c中的OTA功能解析

2.2.1 native_ota_example.c

/* OTA exampleThis example code is in the Public Domain (or CC0 licensed, at your option.)Unless required by applicable law or agreed to in writing, thissoftware is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES ORCONDITIONS 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 256static 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_STDINchar 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_CHECKconfig.skip_cert_common_name_check = true;
#endifesp_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 downloadingmemcpy(&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 partitionif (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_CHECKif (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();}
#endifimage_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 tablepartition.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 bootloaderpartition.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 partitionesp_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_WIFIxTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
}

三、流程说明

3.1 获取当前分区并诊断

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();}}
}

3.2 升级前准备

  1. 初始化非易失性存储库
ESP_ERROR_CHECK(nvs_flash_init());
  1. 初始化底层TCP/IP需要用到的堆栈
ESP_ERROR_CHECK(esp_netif_init());
  1. 事件初始化

事件循环库是esp提供的一种事件处理方法,而默认事件循环是用于系统事件(例如WiFi事件)的特殊循环类型,这里创建一个默认事件循环用以处理升级事件

ESP_ERROR_CHECK(esp_event_loop_create_default());

3.2 开启升级任务

执行任务时,开启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_STDINchar 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_CHECKconfig.skip_cert_common_name_check = true;
#endifesp_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 downloadingmemcpy(&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 partitionif (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_CHECKif (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();}
#endifimage_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 日

• 参考:

空中升级 (OTA) - ESP32 - — ESP-IDF 编程指南 latest 文档

ESP32 学习日志(4)——OTA升级(1)-示例解析相关推荐

  1. CSR8670学习笔记:OTA升级固件

    为了方便大家学习,现与我爱蓝牙网联合推出[QCC300x/CSR867x/QCC30xx/QCC51xx开发板]. 技术交流QQ群号:743434463 开发板会员QQ群号:725398389(凭订单 ...

  2. stm32f407单片机rt thread 片外spi flash OTA升级配置示例

    参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...

  3. ESP32 学习日志(5)——NVS

    一.概述 非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据.本文档将详细介绍 NVS 在ESP32中的使用. NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持 ...

  4. 58 ESP32 OTA升级(双OTA分区无factory APP)

    1 引言 产品功能实现后,就要对产品的维护进行考虑.产品出来后,卖了N台出去,如果突然发现自己一行代码写错了,怎么办,肯定不能去现场吧N台设备,免费出差旅游也累啊,所以一般需要有远程升级设备的功能,此 ...

  5. ESP32学习笔记(50)——ESP-WIFI-MESH接口使用

    一.ESP-WIFI-MESH简介 1.1 概述 ESP-WIFI-MESH是建立在Wi-Fi协议之上的网络协议.ESP-WIFI-MESH允许分布在大范围物理区域内(室内和室外)的许多设备(以下称为 ...

  6. ESP32学习笔记(23)——NVS(非易失性存储)接口使用

    一.简介 非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据. NVS适合存储一些小数据,如果对象占用空间比较大,使用负载均衡的FAT文件系统. 如果NVS分区被截断,比如更改分 ...

  7. ESP32 OTA升级之HTTP OTA

    ESP32 OTA升级之 HTTP OTA 文章目录 ESP32 OTA升级之 HTTP OTA 1. 前言 2. 搭建http本地服务器 2. HTTP OTA 3. 补充学习 1. 前言 在所有电 ...

  8. python学习-日志(logging的定义、参数、format、示例代码、创建logging对象、设置Handler)

    文章目录 logging介绍 logging.basicConfig定义 logging.basicConfig参数说明 logging.basicConfig日志等级说明 logging.basic ...

  9. Android OTA 升级专栏文章导读

    Android OTA 升级专栏文章导读 文章目录 Android OTA 升级专栏文章导读 1. 快速入口 2. 简要介绍 1. 基础入门:<Android A/B 系统>系列 2. 核 ...

最新文章

  1. lodash 工具库
  2. Spring Boot 到底是怎么做到自动配置的?
  3. linux系统中的目录讲解
  4. 7-57 又来一个上三角数字三角形 (10 分)
  5. (HttpURLConnection)强制转化
  6. springboot+mybatis+thymeleaf项目搭建及前后端交互
  7. Mybatis-Plus基本
  8. 滑动窗口限流 java_Spring Boot 的接口限流算法优缺点深度分析
  9. vue项目中如何引入ElementUI
  10. php语法介绍,PHP语法介绍
  11. java随机加法题_Java简单随机加法算式
  12. C# 文件上传 默认最大为4M的解决方法
  13. 「需求广场」需求词更新明细(三)
  14. 如何在mysql中创建学生信息表_数据库怎么创建学生信息表
  15. 怎么用手机测量CAD图纸中的立面面积?
  16. MATLAB struct函数(结构体数组)
  17. 图像特征(一)——颜色特征(颜色直方图,颜色矩,颜色集,颜色聚合向量和颜色相关图)
  18. 护眼灯真的可以保护眼睛吗?推荐五款达到护眼级别的灯
  19. android 视频画面拼接,Android实现视频剪切、视频拼接以及音视频合并
  20. Bag标签之轻开B2C电子商务网站登录校验实例

热门文章

  1. 新闻与传播c刊_新闻专业有什么核心期刊
  2. 国家药监局:强化电子签名应用,助推疫苗生产检验电子化
  3. 我们工作是为了什么!
  4. 我读过的最好的epoll讲解--转自知乎
  5. 2019联想小新pro13.3 Intel i7 10710U+MX250 liinux双系统安装及美化修改(ubuntu19.10 / ubuntu18.04.4 / ubuntu20.04)
  6. 安装maven的eclipse插件出现Cannot complete the install because one or more required items could not be found
  7. Java顶级大神的面试经验,竟如此超越常理
  8. sedona-技术框架
  9. python--元组tuple
  10. 想给孩子买保险?这些掏心窝的建议,你一定要听 | 简保君