SOC-ESP32S3部分:27-设备OTA

article/2025/6/7 15:30:20

飞书文档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和文件名

http://www.hkcw.cn/article/svJRvrFwjh.shtml

相关文章

Windows清理之后,资源管理器卡顿-解决方法

一、点击本地磁盘选择属性 二、选择工具 三、选择驱动器进行优化

VBA模拟进度条

在上一章中我跟大家介绍了ProgressBar控件的使用方法&#xff0c;但由于该控件无法在64位版本的Office中运行&#xff0c;为此我们可以采用Lable控件来模拟进度条的变化&#xff0c;以解决在64位版本的Office中无进度条控件的问题。 一、设计思路 添加两个重叠的Lable标签控件…

Linux(线程概念)

目录 一 虚拟地址到物理地址的转换 1. 操作系统如何管理物理内存&#xff1a; 2. 下面来谈谈虚拟地址如何转换到物理地址&#xff1a; 3. 补充字段&#xff1a; 二 Linux中的线程 1. 先来说说进程&#xff1a; 2. 线程&#xff1a; 3. 线程相比较于进程的优缺点&#x…

手把手教你用Appsmith打造企业级低代码平台:从部署到性能调优实战

文章目录 前言1.什么是Appsmith2.Docker部署3.Appsmith简单使用4.安装cpolar内网穿透5. 配置公网地址6. 配置固定公网地址总结 前言 在当今快速变化的商业环境中&#xff0c;企业正面临内部系统建设的双重挑战。传统开发模式不仅需要漫长的开发周期&#xff08;通常需要数月&a…

PyTorch 入门学习笔记(数字识别实战)

目录 一、关于 PyTorch 的一个重要概念——神经网络 二、PyTorch 是如何解决问题的&#xff08;解决案例&#xff09; 1 案例&#xff1a;手写一个数字&#xff0c;让计算机识别出是哪个数字。 2 PyThorch 解决问题大约需要以下几个步骤&#xff1a; 3 代码示例&#xff1…

OSCP备战-BSides-Vancouver-2018-Workshop靶机详细步骤

一、靶机介绍 靶机地址&#xff1a;https://www.vulnhub.com/entry/bsides-vancouver-2018-workshop%2C231/ 靶机难度&#xff1a;中级&#xff08;CTF&#xff09; 靶机发布日期&#xff1a;2018年3月21日 靶机描述&#xff1a; Boot2root挑战旨在创建一个安全的环境&…

CANopen转Profinet 全攻略:打通施耐德变频器与西门子 300PLC通讯链路

Profinet转CAN open西门子300PLC与施耐德变频器通讯 项目 福建某公司在国外的一个工业自动化项目中&#xff0c;控制中心系统通过监控变频器的不同状态发送不同的命令启动/停止变频器&#xff0c;设定变频器的运行速度进而控制变频器所连接的伺服电机。监控中心系统使用的是西…

Shell脚本编程

shell概述 什么是shell&#xff1f; 在Linux内核与用户之间的解释器程序 Linux默认解释器为/bin/bash负责向内核翻译及传达用户/程序指令相当于操作系统的“外壳” shell的使用方式 交互式-命令行 人工干预&#xff0c;智能化程度高逐条解释执行&#xff0c;效率低、 非交…

win11中使用grep

一、下载 https://nchc.dl.sourceforge.net/project/gnuwin32/grep/2.5.4/grep-2.5.4-setup.exe?viasf1 二、控制面板的环境变量 Path中增加 E:\software\GnuWin32\bin 三、测试使用

负载均衡相关基本概念

负载均衡在系统架构设计中至关重要&#xff0c;其核心目标是合理分配负载&#xff0c;提升系统整体性能和可靠性。本文简要介绍了负载均衡的基本概念&#xff0c;包括四层和七层负载均衡、负载均衡的使用场景和实现方式、负载均衡的常用算法以及一些配置相关知识。 1、负载均衡…

Houdini POP入门学习03

跟着教程学习降雪效果制作&#xff0c;这部分包含blast裁剪、外部引脚获取等。 阶段1 1.Geometry中创建grid&#xff0c;连接popnet。 2.双击进入popnet&#xff0c;在wire_pops_into_here前添加popforce&#xff0c;这一步并不是为了添加重力&#xff0c;而是增加一些乱流。 …

ULVAC DC-10-4P 400V input 10kW DC Pulse power supply 爱发科直流电源

ULVAC DC-10-4P 400V input 10kW DC Pulse power supply 爱发科直流电源

星野录(博客系统)测试报告

目录 一. 项目背景 二、项目功能 三、测试计划 1. 功能测试 1.1 测试用例 1.2 执行测试部分操作截图 2. 使用selenium进行自动化测试 2.1 添加相关依赖 2.2 登录页面测试 3.3 注册页面测试 3.4 博客列表页面测试 3.5 博客详情页测试 3.6 博客编辑页面测试 3.7 个人…

WPF技术体系与现代化样式

目录 ​​1 WPF技术架构解析​​ ​​1.1 技术演进与定位​​ ​​1.2 核心机制对比​​ ​​2 样式与资源系统​​ ​​2.1 资源(Resource)定义与作用域​​ ​​2.2 样式(Style)与触发器​​ ​​3 开发环境配置(.NET 8)​​ ​​3.1 安装流程​​ ​​3.2 项目结…

智能快递地址解析接口如何用PHP调用?

一、什么是智能快递地址解析接口 随着互联网技术的普及和电子商务的迅猛发展&#xff0c;网购已成为现代人日常生活的重要组成部分。然而&#xff0c;在这个便捷的背后&#xff0c;一个看似不起眼却影响深远的问题正悄然浮现——用户填写的快递地址格式混乱、信息不全甚至错漏…

Day11

1. HTTP常见状态码有哪些&#xff1f; 1xx 类状态码属于提示信息&#xff0c;是协议处理中的一种中间状态&#xff0c;实际用的比较少。2xx 类状态码表示服务器成功处理了客户端的请求。3xx 类状态码表示客户端请求的资源发生了变动&#xff0c;需要客户端用新的 URL 重新发送请…

Windows 下部署 SUNA 项目:虚拟环境尝试与最终方案

#工作记录 #回顾总结 本文记录了在 Windows 系统上&#xff0c;通过 PyCharm 图形界面&#xff08;尽量减少命令行操作&#xff09;部署 SUNA 项目时&#xff0c;针对不同虚拟环境方案的尝试过程、遇到的问题以及最终选择的可行方案&#xff0c;并补充了整体部署思路与推荐。…

Mycat的监控

参考资料&#xff1a; 参考视频 参考博客 Mysql分库分表&#xff08;基于Mycat&#xff09;的基本部署 MySQL垂直分库&#xff08;基于MyCat&#xff09; Mysql水平分表&#xff08;基于Mycat&#xff09;及常用分片规则 视频参考资料及安装包&#xff1a; https://pan.b…

安科电动机保护器通过ModbusRTU转profinet网关与PLC通讯

安科电动机保护器通过ModbusRTU转profinet网关与PLC通讯 在工业自动化领域&#xff0c;设备间的通信和数据交互至关重要。Modbus作为一种常用的通讯协议&#xff0c;广泛应用于各种工业现场&#xff1b;而Profinet则凭借其高效、实时性&#xff0c;在工业以太网通讯中占据重要…

CLion社区免费后,使用CLion开发STM32相关工具资源汇总与入门教程

Clion下载与配置 Clion推出社区免费&#xff0c;就是需要注册一个账号使用&#xff0c;大家就不用去找破解版版本了&#xff0c;jetbrains家的IDEA用过的都说好&#xff0c;这里嵌入式领域也推荐使用。 CLion官网下载地址 安装没有什么特别&#xff0c;下一步就好。 启动登录…