58 ESP32 OTA升级(双OTA分区无factory APP)
1 引言
产品功能实现后,就要对产品的维护进行考虑。产品出来后,卖了N台出去,如果突然发现自己一行代码写错了,怎么办,肯定不能去现场吧N台设备,免费出差旅游也累啊,所以一般需要有远程升级设备的功能,此篇为对ESP32 OTA实验经验总结。
2 问题
当前ESP32官方例程,以及网络上的经验例程,都是factory APP + 双OTA_x 方式。我选用的esp32是4M flash的,现在编译的固件大小是1.3M,按照上述的方式,需要用到的空间是:1.3M * 3 =3.9M。
只有4M大小flash的我,很明显,这看起来马上就不能用,也不够用了。所以,按照网上或官网那种分区的方式,肯定不行。
然后在继续查找经验过程中,发现有人没有用官方的factory APP+双OTA_X方式,而是直接双OTA_0和OTA_1,没有factory这个东东,省下了1倍固件大小的空间,所以只有4M flash的我,就采用这个方案了。
3 双OTA no factory APP实现
要做OTA升级,需要了解的内容是:
1.如何对flash划分分区?
2.升级机制是怎样的?
3.如何将固件从网络上download下来并写到flash中,以及标记相关参数?
对于问题1:如何对flash进行分区划分?
对这个问题,我一开始比较迷惑。因为esp32给出的ota升级的demo,是需要三个分区,即factory app+OTA1+OTA2,按照这算法,所需flash的大小是单个app的三倍。
然后我固件大小是1.3M,整块flash大小4M,这样算下来3*1.3 = 3.9M,显然不够,所以一开始迷惑了。官方怎么不给只有两个的,完全两个就搞定了,然后经过资料搜索,和尝试,最后确认两个OTA分区,不需要factory app也是可以的,所以就敲定了以下的分区划分。
对于问题2:升级机制是怎样的?
esp32 OTA的升级机制跟传统的机制是一样的,来回切换,就这样。。。。
对于问题3:如何将固件从网络上download下来并写到flash中,以及标记相关参数?
下载的话,我采用了httpget方式,将文件从http服务器下载下来。至于下载下来的文件写入flash和如何标记OTA_data相关参数,直接参考官方例程即可。整个搞下来其实不难,要花一点时间调试而已。
本实验采用的方式是模块化编程,所以下面给出的代码都是模块独立的,稍微移植就可以用。
#include <stdio.h>
#include <string.h>#include "esp_http_client.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "mqtt_client.h"#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_wpa2.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_ota_ops.h"
#include "ht_led.h"#include "ht_console.h"
#include "ht_main.h"
#include "HTMngr.h"
#include "ht_ota.h"/
uint32_t ghttpfileSize = 0;
uint32_t gProtofileSize = 0;xQueueHandle htOTA_evt_queue = NULL;
TimerHandle_t gExitOTATaskTimeID = NULL;EventGroupHandle_t ht_OTAEvt_group; //全局事件句柄
const int HT_OTAEvt_OTA_UPDATE_BIT = BIT0; //ota事件
const int HT_OTAEvt_RUN_HTTP_BIT = BIT1; //启动httpextern EventGroupHandle_t ht_MngrEvt_group; //全局事件句柄
extern const int HT_MngrEvt_RebootSys_BIT; //系统重启extern GlobalArgs g_SysParam;
//
esp_err_t htOTA_http_event_handle(esp_http_client_event_t *evt)
{HTOTA_queData_t tmpHTOTA_queData;//判断事件类型switch (evt->event_id){case HTTP_EVENT_ERROR:Console_Debug("HTTP_EVENT_ERROR");break;case HTTP_EVENT_ON_CONNECTED:Console_Debug("HTTP_EVENT_ON_CONNECTED");break;case HTTP_EVENT_HEADER_SENT:Console_Debug("HTTP_EVENT_HEADER_SENT");break;case HTTP_EVENT_ON_HEADER:Console_Debug("HTTP_EVENT_ON_HEADER");if(strstr((char *)evt->header_key, "Content-Length")){ghttpfileSize = atoi(evt->header_value);Console_Debug("update file %s:%d\r\n", evt->header_key, ghttpfileSize);}break;case HTTP_EVENT_ON_DATA://接收数据memset(&tmpHTOTA_queData , 0, sizeof(tmpHTOTA_queData));tmpHTOTA_queData.rcvLength = evt->data_len;memcpy(tmpHTOTA_queData.rcvBuffer ,evt->data, tmpHTOTA_queData.rcvLength);//发送数据if(xQueueSend(htOTA_evt_queue, &tmpHTOTA_queData, (1500 / portTICK_PERIOD_MS) ) != pdPASS) {Console_Error("xQueueSend failed\r\n");}break;case HTTP_EVENT_ON_FINISH: Console_Debug("HTTP_EVENT_ON_FINISH");break;case HTTP_EVENT_DISCONNECTED:Console_Debug("HTTP_EVENT_DISCONNECTED");break;default:break;}return ESP_OK;
}int htOTA_runHttpGet(void)
{esp_http_client_handle_t client;//参数校验if(strlen((char*)g_SysParam.ota_url)<5){Console_Error("ota_url %s error!", g_SysParam.ota_url);return -1;}//配置http参数esp_http_client_config_t config = {.url = (char*)g_SysParam.ota_url, //目标文件.event_handler = htOTA_http_event_handle, //http回调函数.buffer_size = (1024), //指定接收缓存大小};client = esp_http_client_init(&config);Console_Debug("Starting htOTA_runHttpGet...");//发起http连接esp_http_client_perform(client); //发起http连接esp_http_client_close(client);esp_http_client_cleanup(client);return 0;
}void htOTA_setTargetFileSize(uint32_t utargetSize)
{gProtofileSize = utargetSize;
}void htOTA_queueTask(void *pvParameters)
{uint32_t uCnt = 0;uint32_t uLedCnt = 0;uint32_t rcvBinarySize = 0;esp_err_t err = 0;esp_ota_handle_t update_handle = 0 ;const esp_partition_t *update_partition = NULL;HTOTA_queData_t tmpHTOTA_queData = {0};//这里等待1s,等待mqtt回复完再启动vTaskDelay((1000) / portTICK_PERIOD_MS);Console_Debug("Starting OTA updating...");//获取当前boot位置const esp_partition_t *configured = esp_ota_get_boot_partition();//获取当前系统执行的固件所在的Flash分区const esp_partition_t *running = esp_ota_get_running_partition();if (configured != running) {Console_Debug("Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",configured->address, running->address);Console_Debug("(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");}Console_Debug("Running partition type %d subtype %d (offset 0x%08x)",running->type, running->subtype, running->address);//获取当前系统下一个(紧邻当前使用的OTA_X分区)可用于烧录升级固件的Flash分区update_partition = esp_ota_get_next_update_partition(NULL);Console_Debug("Writing to partition subtype %d at offset 0x%x",update_partition->subtype, update_partition->address);if(update_partition == NULL){Console_Error("update_partition == NULL...");goto iExit;}//启动OTA模块要进行烧写err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);if (err != ESP_OK) {Console_Error("esp_ota_begin failed, error=%d", err);goto iExit;}Console_Debug("esp_ota_begin succeeded");//启动启动httpxEventGroupSetBits(ht_OTAEvt_group, HT_OTAEvt_RUN_HTTP_BIT);//启动监控定时器xTimerStop(gExitOTATaskTimeID, 0);xTimerStart(gExitOTATaskTimeID,0);//在这里循环等待接收数据while(1){memset(&tmpHTOTA_queData, 0, sizeof(tmpHTOTA_queData));if(xQueueReceive(htOTA_evt_queue, &tmpHTOTA_queData, portMAX_DELAY)) {//检测文件大小是否正确if(ghttpfileSize != gProtofileSize){Console_Error("ghttpfileSize%d, gProtofileSize:%d! error", ghttpfileSize, gProtofileSize);goto iExit;}//先判断是否数据为空if(tmpHTOTA_queData.rcvLength >0){uLedCnt++;htLed_SetRGBLight(RGBLED_MODE_Y, uLedCnt&0x1);uCnt++;if(uCnt%20==0){ Console_Debug("file total size: (%d Byte), download...(%d %%)\r\n",ghttpfileSize, (rcvBinarySize*100/ghttpfileSize));}//写flasherr = esp_ota_write( update_handle, (const void *)tmpHTOTA_queData.rcvBuffer, tmpHTOTA_queData.rcvLength);if (err != ESP_OK) {Console_Error("Error: esp_ota_write failed! err=0x%x", err);goto iExit;}rcvBinarySize += tmpHTOTA_queData.rcvLength;if(rcvBinarySize >= gProtofileSize){Console_Debug("file total size: (%d Byte), download...(100 %%)\r\n",ghttpfileSize);Console_Error("Connection closed, all packets received");htLed_SetRGBLight(RGBLED_MODE_Y, 0);break;}}//重置监控定时器xTimerStop(gExitOTATaskTimeID, 0);xTimerStart(gExitOTATaskTimeID,0);}}Console_Debug("Total Write binary data length : %d", rcvBinarySize);//OTA写结束if (esp_ota_end(update_handle) != ESP_OK) {Console_Error("esp_ota_end failed!");goto iExit;}//升级完成更新OTA data区数据,重启时根据OTA data区数据到Flash分区加载执行目标(新)固件err = esp_ota_set_boot_partition(update_partition);if (err != ESP_OK) {Console_Error("esp_ota_set_boot_partition failed! err=0x%x", err);goto iExit;}Console_Debug("Prepare to restart system!");iExit://触发系统重启xEventGroupSetBits(ht_MngrEvt_group, HT_MngrEvt_RebootSys_BIT);vTaskDelete(NULL);return;
}void htOTA_EvtTask(void *pvParameters)
{uint8_t isRunOTATaskFlag = 0;EventBits_t uxBits;while(1){/* Wait a maximum of 100ms for either bit 0 or bit 4 to be set withinthe event group. Clear the bits before exiting. */uxBits = xEventGroupWaitBits(ht_OTAEvt_group, /* The event group being tested. */HT_OTAEvt_RUN_HTTP_BIT | HT_OTAEvt_OTA_UPDATE_BIT, /* The bits within the event group to wait for. */pdTRUE, /* BIT_0 & BIT_4 should be cleared before returning. */pdFALSE, /* Don't wait for both bits, either bit will do. */portMAX_DELAY );/* Wait a maximum of 100ms for either bit to be set. *///触发ota升级if( ( uxBits & HT_OTAEvt_RUN_HTTP_BIT ) != 0 ){Console_Debug("HT_OTAEvt_RUN_HTTP_BIT\r\n");htOTA_runHttpGet();}//启动ota升级线程if( ( uxBits & HT_OTAEvt_OTA_UPDATE_BIT ) != 0 ){Console_Debug("HT_OTAEvt_OTA_UPDATE_BIT, isRunOTATaskFlag:%d\r\n", isRunOTATaskFlag);if(isRunOTATaskFlag == 0){isRunOTATaskFlag = 1;//OTA接收数据处理函数xTaskCreate(htOTA_queueTask, "htOTA_queTsk", (6*1024), NULL, HT_TASK_HTOTA_queue_Priority, NULL);}}}//不会走到这里,走到这里需要删除taskvTaskDelete(NULL);return;
}//监控ota升级任务运行正常,如果服务器卡住,接收数据不完全卡住,则超时重启
static void htOTA_autoExitOTA_callback(void *arg)
{//触发系统重启xEventGroupSetBits(ht_MngrEvt_group, HT_MngrEvt_RebootSys_BIT);
}int htOTA_Init(void)
{//创建ota等待事件集ht_OTAEvt_group = xEventGroupCreate();//创建消息队列用以接收http收到的数据htOTA_evt_queue = xQueueCreate(2, sizeof(HTOTA_queData_t));if(htOTA_evt_queue == NULL){Console_Error("xQueueCreate failed\r\n");return -1;}//创建业务处理线程xTaskCreate(htOTA_EvtTask, "htOTA_EvtTask", (4*1024), NULL, HT_TASK_HTOTA_EVT_Priority, NULL);gExitOTATaskTimeID = xTimerCreate("exitOTA", (15*1000 / portTICK_PERIOD_MS), pdFALSE, NULL, htOTA_autoExitOTA_callback);return 0;
}
本人支持开源精神,以供有需要的人学习,减少重复造轮子带来的浪费,希望各位码主多多支持开源,有好东西多分享,一同进步!
over!
58 ESP32 OTA升级(双OTA分区无factory APP)相关推荐
- android ota升级涉及的分区,Android OTA升级原理 - 实现流程(整理一)
Android OTA实现流程分析 OTA升级概述 制作升级包 自动生成update.zip升级包 手动生成update升级包 增量包目录结构 升级包写入设备分区 Android的三种工作模式 Rec ...
- android怎么ota升级,Android OTA升级过程
通过网络或直接本地获取到OTA升级包之后,通过程序就可开始Android的升级.本文描述这一过程. 在获取到OTA升级包之后,可以直接通过android.os.RecoverySystem.insta ...
- ESP32 学习日志(4)——OTA升级(1)-示例解析
一.OTA简介 1.1 概述 ESP32应用程序可以在运行时通过Wi-Fi或以太网从特定的服务器下载新镜像,然后将其闪存到某些分区中,从而进行升级.在ESP-IDF中本文采用native_ota_ex ...
- AUTOSAR OTA升级
一.OTA技术概念 随着高级辅助驾驶的发展和自动驾驶的引入,汽车变得越来越智能,这些智能汽车被软件控制,装有巨量的软件程序,当出现一个软件程序问题或者更新时,如果 按照传统的解决方式 ,那都将是一项很 ...
- Android-AB系统OTA升级介绍
什么是OTA升级? OTA是Over-the-Air的简称,OTA升级可以理解为用户正常使用过程中进行升级,OTA 升级旨在升级基础操作系统.系统分区上安装的只读应用和/或时区规则. 什么是Andro ...
- Android OTA升级(1):编译升级全包
Android原生系统中就已经支持OTA升级.所谓OTA升级就是通过空中接口获取升级包,然后更新系统固件.一般地,升级包无论如何获取,哪怕是直接TCard本地升级,也被称为OTA升级. ...
- android的A/B到底是什么?OTA升级又是什么?
最近在做AVB校验,学习的时候就看到AVB里面有A/B系统.就挺纳闷这个是什么? 这个有什么用?在学习的时候看到了这个和OTA升级有关系? 下面看看为什么需要A/B系统,A/B是什么? 在这里.感谢这 ...
- Android 7.0 OTA升级(高通)
文章目录 1. Full OTA 方式升级介绍 1.1 Full OTA 制作第一步:生成 msm89xx-target_files-eng.XXX.zip 1.2 Full OTA 制作第二步:Mo ...
- Android OTA升级原理 - 实现流程(整理一)
Android OTA实现流程分析 OTA升级概述 制作升级包 自动生成update.zip升级包 手动生成update升级包 增量包目录结构 升级包写入设备分区 Android的三种工作模式 Rec ...
最新文章
- 《Fabric 云存储的电子健康病历系统》(1)系统介绍
- what courses to choose at Cambridge?
- cad java_cad和java哪个工资高
- JSF基于事件的沟通:过时的方法
- [Android] 年年有鱼手机主题
- android中shape资源定义,Android中drawable使用Shape资源
- git rebase --skip_可冒充git大神的git tips
- 前置++与后置++的要点分析
- MySQL初始化安装部署
- JS 对象(Object)和字符串(String)互转方法
- Windows MongoDB下载 安装以及配置(一条龙到底)
- qq传输文件为什么服务器忙,qq传送离线 接收文件很慢怎么回事
- U盘格式化后容量变小了恢复教程
- Inkscape制作logo
- 解决Could not build wheels for pikepdf which use PEP 517 and cannot be installed directly
- 每一个圣人都有一个过去
- 基于webpack4搭建Vue服务端渲染(SSR)
- 计算机毕业设计Java某银行OA系统某银行OA系统演示2021(源代码+数据库+系统+lw文档)
- uni-app中自定义图表(canvas实现chart图表)开发篇(5)-圆环进度条添加动画效果
- android 自定义声音,如何在Android设备中添加自己的自定义声音
热门文章
- TELE 4662 PHOTONIC NETWORKS -Lecture 3: Light Sources and Detectors-半导体(Semiconductor)
- 【RCJ-2 AC220V 0.015A静态冲击继电器】
- elementUI 标记提示:图标显示待办业务数量
- 数据库的约束和设计(完整版)
- Linux fedora 19 x86_64 双显卡 关闭独显
- CAD看图软件如何才好用
- 圣诞礼物c语言代码大全,windows编程入门二:圣诞节的礼物——从“屏幕飘雪”程序说起...
- Excel清除隐藏的引号或空格
- LSM优化系列(五) -- 【SIGMOD‘19】X-engine 在电商场景下针对大规模事务处理的优化-- 强者恒强啊
- 基于ise14.7中的cmd设计