ESP32-CAM OTA线上更新固件遇到的分区问题
对于之前写的文章 ESP32 通过HTTPS进行OTA更新固件(在platform上进行编码)中遇到的分区表的问题,我觉得有必要写一篇文章来解释一下到底怎么回事,我又是怎么解决的。
1、分区表
什么是分区表
分区表是 ESP32 划分内部 flash 闪存的清单,它将 flash 划分为多个不同功能的区域用于其他功能。
分区类型分为两种,分别为 “应用” 和 “数据”。如应用分为Factory程序,OTA程序等,又如数据分为校准数据、文件系统数据、参数存储数据等。
分区表的长度为 0xC00 字节(最多可以保存 95 条分区表条目)。分区表数据后还保存着该表的 MD5 校验和,用于验证分区表的完整性。此外,如果芯片使能了 “安全启动” 功能,则该分区表后还会保存签名信息。
2、OTA更新策略
首次进行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固件。
问题:分区表设置问题
我之前写的文章中提到我从AWS S3中OTA更新固件不能烧录到ESP32-CAM分区中,不能更新固件。
返回的错误信息:ESP_ERR_OTA_PARTITION_CONFLICT,原因是分区保持着正确的运行固件,不能更新到这个这个地方。
所以我在想能不能编写两个程序轮流进行OTA更新动作,这样就能解决不能烧录到分区的问题。
3、解决方法
1.设置分区表
为了解决分区表设置问题,我们自己设置一个分区表,再在配置文件中加上就能解决问题。
注意:我是通过PlatformIO进行编程的。
(1). 在项目文件夹中新建partition.csv文件。
(2). 在项目文件夹的platformio.ini文件中添加编译命令。(别忘了保存)
board_build.partitions = partition.csv
(3). 打开partition.csv,定义分区表。
根据自己需要定义分区表,比如我目前想进行OTA更新就需要两个分区(ota_0,ota_1),我就可以要定义连个分区,又比如我写的程序需要的存储空间大,我就要舍弃一个分区(ota_1)来增加另一个分区(ota_0)的大小。
开始定义分区表,我设置了两个分区。
2、编写两个用于OTA更新的程序
我编写的程序是两个闪灯程序,分别为闪白灯和闪红的(ESP32-CAM中有两个板载LED灯),这样就只要修改灯的引脚(红灯33,白灯4),就不用额外编写代码。
注意:红灯和白灯的 digitalWrite中的LOW和HIGH参数是相反的不要忘了更改。
架构图:
- 将闪红灯程序的.bin文档上传AWS S3。
- 将闪白灯程序烧录到ESP32-CAM中。
- 打开手机热点(设置ssid、password)
- 开始OTA更新,下载完成后会看到由白灯闪烁变为红灯闪烁。
特别的是:为了控制什么时候开始OTA更新,我添加了按钮,这样每当我按下按钮时就开始OTA更新。
3、代码
我就上传红灯代码,红灯代码修改一下灯的引脚就行了。
注意:需要准备一个外接按钮,需要在代码中输入yourssid,yourpassword,AWS S3中文件的url还有AWS网站证书。在这篇文章中有写怎么获取ESP32 通过HTTPS进行OTA更新固件(在platform上进行编码)
#include <Arduino.h>
#include "time.h"
#include <WiFi.h>
#include <ezButton.h>
#include "HttpsOTAUpdate.h"
#include "esp_ota_ops.h"
#define BUTTON_PIN 12 //将按钮引脚绑定到GPIO 12
#define FLASH_LED 4
#define RED_LED_BUILTIN 33
#define DEBOUNCE_TIME 50
#define uS_TO_S_FACTOR 1000000 //1000000=1s
#define TIME_TO_SLEEP 5 //5s
//setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;
static const char *ssid = "yourssid"; // your network SSID (name of wifi network)
static const char *password = "yourpassword"; // your network password
static const char *url = ""; //state url of your firmware image
static const char *server_certificate = "-----BEGIN CERTIFICATE-----\n" \
"-----END CERTIFICATE-----";
static HttpsOTAStatus_t otastatus;
ezButton button(BUTTON_PIN);
void HttpEvent(HttpEvent_t *event)
{
switch(event->event_id) {
case HTTP_EVENT_ERROR:
Serial.println("Http Event Error");
break;
case HTTP_EVENT_ON_CONNECTED:
Serial.println("Http Event On Connected");
break;
case HTTP_EVENT_HEADER_SENT:
Serial.println("Http Event Header Sent");
break;
case HTTP_EVENT_ON_HEADER:
Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value);
break;
case HTTP_EVENT_ON_DATA:
break;
case HTTP_EVENT_ON_FINISH:
Serial.println("Http Event On Finish");
break;
case HTTP_EVENT_DISCONNECTED:
Serial.println("Http Event Disconnected");
break;
}
}
void setup() {
// put your setup code here, to run once:
pinMode(RED_LED_BUILTIN, OUTPUT); //在使用引脚输入输出之前,先配置此函数,引脚做什么用,模式是什么。
//ezButton button(BUTTON_PIN);
button.setDebounceTime(DEBOUNCE_TIME);
Serial.begin(115200);
Serial.print("Attempting to connect to SSID: ");
WiFi.begin(ssid, password);
// attempt to connect to Wifi network:
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
digitalWrite(RED_LED_BUILTIN, LOW);
delay(1000);
digitalWrite(RED_LED_BUILTIN, HIGH);
}
Serial.print("Connected to ");
Serial.println(ssid);
}
unsigned int pressCount = 0;
void loop() {
// put your main code here, to run repeatedly:
/*
digitalWrite(RED_LED_BUILTIN, LOW); //让某个引脚输出高点品或者时低电平
delay(300);
digitalWrite(RED_LED_BUILTIN, HIGH);
delay(300);
*/
button.loop();
digitalWrite(RED_LED_BUILTIN, LOW);
if (button.isPressed()){
Serial.print("noise");
if (pressCount == 0){
pressCount = 1;
Serial.println("The button is pressed");
// attachClick();
//myClickFunction();
HttpsOTA.onHttpEvent(HttpEvent);
Serial.println("Starting OTA");
HttpsOTA.begin(url, server_certificate);
Serial.println("Please Wait it takes some time ...");
}
//attachClick();
}
if (pressCount == 1){
otastatus = HttpsOTA.status();
if(otastatus == HTTPS_OTA_SUCCESS) {
Serial.println("Firmware written successfully. To reboot device, call API ESP.restart() or PUSH restart button on device");
//esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); //设置计时器5s后解除深度睡眠
//gpio_deep_sleep_hold_dis();
//esp_deep_sleep_start(); //进入深度睡眠模式
ESP.restart();
} else if(otastatus == HTTPS_OTA_FAIL) {
Serial.println("Firmware Upgrade Fail");
}
delay(1000);
}
}
4、问题:OTA更新完成之后红灯和白灯一起闪烁
我猜测的原因是ESP.restart()没有成功引导正确的运行的程序。
细心的大家也看到了我设置了定时器,在ESP32-CAM完成OTA更新之后进入睡眠,在5s后醒来,但是效果是本该亮的灯不亮,不该亮的灯亮了,效果还没不设置好,我也不知道什么原因,如果有知道的请在评论区指出,谢谢!。
参考资料
ESP32-Flash分区,基于PlatfromIO-Arduino:
https://blog.csdn.net/liahfdsaf/article/details/119010732
ESP32之 ESP-IDF 教学(十三)—— 分区表:
https://blog.csdn.net/m0_50064262/article/details/122279800
esp32 Flash分区与OTA功能简析:
https://blog.csdn.net/abc517789065/article/details/79891568
ESP32 通过HTTPS进行OTA更新固件(在platform上进行编码)
https://blog.csdn.net/qq_62819897/article/details/129761069