58 ESP32 OTA升级(双OTA分区无factory APP)
Posted Chasing_Chasing
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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; //启动http
extern 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");
//启动启动http
xEventGroupSetBits(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));
}
//写flash
err = 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 within
the 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);
}
}
}
//不会走到这里,走到这里需要删除task
vTaskDelete(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)的主要内容,如果未能解决你的问题,请参考以下文章
STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)
STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)
STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)
ESP32学习笔记(25)——OTA(空中升级)接口使用(简化API)