当前位置: 首页 > news >正文

SOC-ESP32S3部分:27-设备OTA

飞书文档https://x509p6c8to.feishu.cn/wiki/Hd9TwkuZ3iEQiUkjaoic5p7Knuh

ESO32S3应用程序可以在运行时通过网络从服务器下载新的固件,然后将其存储到某个分区中,从而实现固件的升级功能。

在ESP-IDF中有两种方式可以进行空中(OTA)升级:

使用 app_update 组件提供的原生API

使用 esp_https_ota 组件提供的简化API,它在原生OTA API上添加了一个抽象层,以便使用HTTPS协议进行升级。

分别在 esp-idf/examples/system/ota下的native_ota_example 和 simple_ota_example 下的OTA示例中演示了这两种方法。

在实现OTA功能时,要求我们要重新设计分区表,添加两个OTA分区,例如下方所示,具体设置方法在后面代码中会有讲解。

# Name,   Type, SubType, Offset,   Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     ,        0x4000,
otadata,  data, ota,     ,        0x2000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        1M,
ota_0,    app,  ota_0,   ,        1M,
ota_1,    app,  ota_1,   ,        1M,

与升级相关的(至少)四个分区:OTA data、Factory App、OTA_0、OTA_1。

升级流程如下:

  1. 其中 FactoryApp 内存有出厂时的默认固件。
  2. 首次进行 OTA 升级时,应用向 OTA_0 分区烧录目标固件,并在烧录完成后,更新 OTA data 分区数据并重启。
  3. 系统重启时获取 OTA data 分区数据进行计算,决定此后加载 OTA_0 分区的固件执行(而不是默认的 Factory App 分区内的固件),从而实现升级。
  4. 同理,若某次升级后已经在执行 OTA_0 内的固件,此时再升级时应用就会向 OTA_1 分区写入目标固件。再次启动后,执行 OTA_1 分区实现升级。以此类推,升级的目标固件始终在 OTA_0、OTA_1 两个分区之间交互烧录,不会影响到出厂时的 Factory App 固件。

本地服务器搭建

在升级前,我们可以先搭建一个本地HTTPS文件服务器,由于第三方服务器可能不可靠,随时有关停可能,所以本地服务器可以方便我们快速验证,由于走的是HTTPS升级,所以我们还需要生成一个证书,这个证书同时放在服务器端和设备中,升级时设备会验证服务器端的证书是否合法,这也是企业级解决方案必须具备的功能。

生成证书

我们可以使用openssl生成一个自己的证书,这里面需要自行安装openssl工具,如果不想自己安装,也可以直接使用文档提供的证书,在实际开发中,这部分证书是由服务器同事进行管理的。

openssl req -x509 -newkey rsa:2048 -keyout xiaozhi_key.pem -out xiaozhi_cert.pem -days 3650 -nodes
依次输入:
(国家)、
(洲/省)、
(城/镇)、
(组织名)、
(单位名)、
(httpd-ssl.conf中的ServerName 名称)、
(邮箱)
这里其实可以随意填写任意字符,不影响后续操作。

以上指令会生成一个密钥xiaozhi_key.pem,一个证书xiaozhi_cert.pem文件

拿到证书后,我们就可以搭建本地HTTP服务器

本地HTTP服务器搭建

我们直接使用开源的hfs进行搭建,链接如下,需要魔法访问

https://github.com/rejetto/hfs/releases

根据对应平台下载

解压后,双击hfs.exe打开

这里填写https的端口为8088,其它值也是可以的,只要你电脑没占用此端口即可,把这个端口记录下来,因为后续设备需要用到

然后上传上面生成的证书和密钥,上传完成一定要点击底部的保存

然后查看本机ip,在windos的命令行窗口,输入ipconfig,例如我的ip是192.168.3.24,把这个ip记录下来

然后添加需要升级的文件到服务器中,这里同时要配置链接,根据上面的ip和端口输入https://192.168.3.24:8088

添加完成后点击底部的ADD,找到需要升级的固件,例如这里使用最简单的工程固件hello_world.bin

固件在工程编译成功后的build文件内,例如:hello_world/build/hello_world.bin

上传成功后,记得点击SAVE,到这里配置就完成了,你在浏览器中访问https://192.168.3.24:8088/

看到上述文件代表配置成功,如果打不开此页面,估计了漏了步骤,可以重复多做几次,一般可能的原因:

  • 端口占用
  • IP错误
  • 有页面配置未保存

可以百度下”hfs服务器搭建“,结合其它文章多搭建几次即可。

设备端实现

代码部分先添加上述的证书,把xiaozhi_cert.pem文件放到工程main内

然后修改CMakeLists.txt

demo/main/CMakeLists.txt

idf_component_register(SRCS "main.c"INCLUDE_DIRS "."EMBED_TXTFILES xiaozhi_cert.pem)

最终代码如下

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_eap_client.h"
#include "esp_netif.h"
#include "esp_smartconfig.h"
#include "esp_mac.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"static const char *TAG = "simple_ota_example";static EventGroupHandle_t s_wifi_event_group;static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static void smartconfig_example_task(void *parm);
static bool is_connect_wifi = false;extern const uint8_t server_cert_pem_start[] asm("_binary_xiaozhi_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_xiaozhi_cert_pem_end");#define OTA_URL_SIZE 256esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{switch (evt->event_id) {case HTTP_EVENT_ERROR:ESP_LOGD(TAG, "HTTP_EVENT_ERROR");break;case HTTP_EVENT_ON_CONNECTED:ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");break;case HTTP_EVENT_HEADER_SENT:ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");break;case HTTP_EVENT_ON_HEADER:ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);break;case HTTP_EVENT_ON_DATA:ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);break;case HTTP_EVENT_ON_FINISH:ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");break;case HTTP_EVENT_DISCONNECTED:ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");break;case HTTP_EVENT_REDIRECT:ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");break;}return ESP_OK;
}void simple_ota_run()
{ESP_LOGI(TAG, "Starting OTA example task");esp_http_client_config_t config = {.url = "https://192.168.3.24:8088/hello_world.bin",.cert_pem = (char *)server_cert_pem_start,.event_handler = _http_event_handler,.skip_cert_common_name_check = true, //自己生成的测试证书中没有域名信息,所以不检查};esp_https_ota_config_t ota_config = {.http_config = &config,};ESP_LOGI(TAG, "Attempting to download update from %s", config.url);esp_err_t ret = esp_https_ota(&ota_config);if (ret == ESP_OK) {ESP_LOGI(TAG, "OTA Succeed, Rebooting...");esp_restart();} else {ESP_LOGE(TAG, "Firmware upgrade failed");}while (1) {vTaskDelay(1000 / portTICK_PERIOD_MS);}
}static void http_get_task(void *pvParameters)
{while (1){if (is_connect_wifi){simple_ota_run();}vTaskDelay(1000 / portTICK_PERIOD_MS);}
}static void 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){// WiFi 站点模式启动后,创建 SmartConfig 任务xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);}else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED){is_connect_wifi = false;// WiFi 断开连接时,重新连接并清除连接标志位esp_wifi_connect();xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);}else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP){// 获取到 IP 地址后,设置连接标志位xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);is_connect_wifi = true;}else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE){// SmartConfig 扫描完成事件ESP_LOGI(TAG, "Scan done");}else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL){// SmartConfig 找到信道事件ESP_LOGI(TAG, "Found channel");}else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD){// SmartConfig 获取到 SSID 和密码事件ESP_LOGI(TAG, "Got SSID and password");smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;wifi_config_t wifi_config;uint8_t ssid[33] = {0};uint8_t password[65] = {0};uint8_t rvd_data[33] = {0};bzero(&wifi_config, sizeof(wifi_config_t));memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));memcpy(ssid, evt->ssid, sizeof(evt->ssid));memcpy(password, evt->password, sizeof(evt->password));ESP_LOGI(TAG, "SSID:%s", ssid);ESP_LOGI(TAG, "PASSWORD:%s", password);if (evt->type == SC_TYPE_ESPTOUCH_V2){// 如果使用的是 ESPTouch V2,获取额外的数据ESP_ERROR_CHECK(esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data)));ESP_LOGI(TAG, "RVD_DATA:");for (int i = 0; i < 33; i++){printf("%02x ", rvd_data[i]);}printf("\n");}// 断开当前 WiFi 连接,设置新的 WiFi 配置并重新连接ESP_ERROR_CHECK(esp_wifi_disconnect());ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));esp_wifi_connect();}else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE){// SmartConfig 发送 ACK 完成事件,设置 SmartConfig 完成标志位xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);}
}static void smartconfig_example_task(void *parm)
{EventBits_t uxBits;wifi_config_t myconfig = {0};ESP_LOGI(TAG, "creat smartconfig_example_task");// 获取wifi配置信息esp_wifi_get_config(ESP_IF_WIFI_STA, &myconfig);if (strlen((char *)myconfig.sta.ssid) > 0){// 如果配置过,就直接连接wifiESP_LOGI(TAG, "alrealy set, SSID is :%s,start connect", myconfig.sta.ssid);esp_wifi_connect();}else{// 如果没有配置过,就进行配网操作ESP_LOGI(TAG, "have no set, start to config");ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS)); // 支持APP ESPTOUCH和微信AIRKISSsmartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));}while (1){// 等待连接标志位或 SmartConfig 完成标志位uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);if (uxBits & CONNECTED_BIT){// 连接到 AP 后的日志ESP_LOGI(TAG, "WiFi Connected to ap");// 联网成功后,可以关闭线程vTaskDelete(NULL);}if (uxBits & ESPTOUCH_DONE_BIT){// SmartConfig 完成后的日志ESP_LOGI(TAG, "smartconfig over");// 停止 SmartConfigesp_smartconfig_stop();// 删除 SmartConfig 任务vTaskDelete(NULL);}}
}void app_main(void)
{// 初始化 NVS 闪存ESP_LOGI(TAG, "OTA example app_main start");esp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// 1.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.// 2.NVS partition contains data in new format and cannot be recognized by this version of code.// 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());// 创建事件组s_wifi_event_group = xEventGroupCreate();// 创建默认事件循环ESP_ERROR_CHECK(esp_event_loop_create_default());// 创建默认的 WiFi 站点模式网络接口esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();assert(sta_netif);// 初始化 WiFi 配置wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));// 注册事件处理函数ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));// 设置 WiFi 模式为站点模式并启动 WiFiESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_start());xTaskCreate(&http_get_task, "http_get_task", 8192, NULL, 5, NULL);
}

然后修改分区表,选择支持OTA的分区表

(Top) → Partition Table → Partition Table
Espressif IoT Development Framework Configuration
( ) Single factory app, no OTA
( ) Single factory app (large), no OTA
(X) Factory app, two OTA definitions
( ) Two large size OTA partitions
( ) Custom partition table CSV

因为OTA分区表的分区如下,是大于默认工程设置的2M Flash大小的

*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,16K,
otadata,data,ota,0xd000,8K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,1M,
ota_0,app,ota_0,0x110000,1M,
ota_1,app,ota_1,0x210000,1M,
*******************************************************************************

所以还需要修改Flash大小,这个可以按ESP32S3的实际大小修改,例如我们改为4M

(Top) → Serial flasher config
Espressif IoT Development Framework Configuration
[ ] Disable download stub
[ ] Enable Octal Flash
[*] Choose flash mode automatically (please read help)Flash SPI mode (DIO)  --->Flash Sampling Mode (STR Mode)  --->Flash SPI speed (80 MHz)  --->Flash size (4 MB)  --->
[ ] Detect flash size when flashing bootloaderBefore flashing (Reset to bootloader)  --->After flashing (Reset after flashing)  --->

修改完成后,重新编译,代码会先进行配网操作,注意,配置的网络需要和你的电脑运行的服务器连接到同一个网络,保证网段是一样的,例如都是192.168.3.xx,配网完网络后,设备连接路由后会开启OTA,OTA结束后,重启日志如下

如果出现OTA失败,一般就三个原因,逐个排查即可。

  • IP网段不一样,设备连接的路由和电脑连接路由不一样
  • 证书没设置好
  • 电脑禁用了某个端口
  • 服务器端口没改过来
  • 服务器IP没改过来
  • 文件名没改过来,可以在浏览器上测试能否下载,https://192.168.3.24:8088/hello_world.bin改为你自己的IP和文件名

相关文章:

  • Ethernet/IP转DeviceNet网关:驱动大型矿山自动化升级的核心纽带
  • 【C++高级主题】多重继承下的类作用域
  • LeetCode第245题_最短单词距离III
  • 超临界二氧化碳再热再压缩布雷顿循环建模与先进控制
  • 704. 二分查找 (力扣)
  • 力扣HOT100之多维动态规划:1143. 最长公共子序列
  • 批量大数据并发处理中的内存安全与高效调度设计(以Qt为例)
  • 总览四级考试
  • Mac电脑_钥匙串操作选项变灰的情况下如何删除?
  • KEYSIGHT是德科技 E5063A 18G ENA系列网络分析仪
  • 电工基础【5】简单的电路设计接线实操
  • Python趣学篇:Pygame重现经典打砖块游戏
  • 微软Build 2025:Copilot Studio升级,解锁多智能体协作未来
  • Kotlin List 操作全面指南
  • 实现购物车微信小程序
  • Blocked aria-hidden on an element because its descendant retained focus.
  • 【Node.js 深度解析】npm install 遭遇:npm ERR! code CERT_HAS_EXPIRED 错误的终极解决方案
  • 美尔斯通携手北京康复辅具技术中心开展公益活动,科技赋能助力银龄健康管理
  • 三大中文wordpress原创主题汉主题
  • 【notepad++】如何设置notepad++背景颜色?
  • 有没有接单做加工的网站/广州网络推广外包
  • 自己怎么做视频收费网站/常见的系统优化软件
  • 鹤壁网站推广公司/总裁培训班
  • 文友胜做的网站/产品营销策划方案
  • 公司建设官方网站需要多少钱/网站搜什么关键词好
  • 上海松江做网站建设/一个新手怎么去运营淘宝店铺