使用Arduino开发ESP32(17):固件更新演示

目的

很多时候我们会有因为bug修复、功能增加等情况需要对已投产使用的设备更新固件,这种情况下再使用工具通过串口烧录固件就不是那么方便了,比较常用的是通过网络或SD卡进行固件升级。
在Arduino core for the ESP32中有两个库可以实现固件升级功能: ArduinoOTAUpdate ,官方例程中用的最多的是 Update ,这篇文章也将以这个库作为说明。

基础说明

ESP32常见的模块都搭载了4M的Flash,但是固件真正可用的空间通常没有那么大,因为Flash往往被划分成了多个区域用作不同用途:
在这里插入图片描述
上图就是默认情况下Flash的划分,固件可用区域为1.2M,SPIFFS占用1.5M,还剩约1.3M未使用,这1.3M其实就是用来临时存放用于升级的固件的,参考下面分布:
在这里插入图片描述
正常使用时固件和SPIFFS中间是一片空白的区域,当需要升级固件时会将新固件拷贝到这个空白区域中,在下次系统启动时新固件会覆盖旧固件然后运行新固件。

使用演示

在演示固件更新功能前我们首先准备一个用作更新的固件,按下图方式生成固件:
在这里插入图片描述

通过SD卡更新固件

从SD卡进行固件升级算是最简单的了,将固件重命名为 update.bin(不是必须的,只要代码中读取的文件名能匹配上就行) ,然后复制到SD卡中,然后按照下文方式连接SD卡:
《使用Arduino开发ESP32(13):SD卡的使用》
完成电路连接后就可以使用下面代码测试了:

#include <SD_MMC.h>
#include <Update.h> // 引用相关库

void setup()
{
    Serial.begin(115200);
    Serial.println();

    if (!SD_MMC.begin()) //挂载SD卡
    {
        Serial.println("存储卡挂载失败");
        return;
    }

    File updateBin = SD_MMC.open("/update.bin"); // 读取update.bin文件
    if (!updateBin)
    {
        Serial.println("读取update.bin失败");
        return;
    }
    if (updateBin.isDirectory())
    {
        Serial.println("update.bin是一个文件夹");
        updateBin.close();
        return;
    }

    size_t updateSize = updateBin.size(); // 获取固件大小
    if (updateSize <= 0)
    {
        Serial.println("文件为空");
        return;
    }
    Serial.println("固件大小为" + String(updateSize) + "字节");

    Serial.println("开始固件升级");

    if (!Update.begin(updateSize)) // 开始固件升级,检查空可用间大小,如果正在升级或空间不够则返回false
    {
        Serial.println("升级不可用");
        return;
    }

    size_t written = Update.writeStream(updateBin); // 将数据写入到OTA区域 // 使用writeStream方法的话前面Update.begin()必须填写要更新的固件大小
    Serial.println("写入" + String(written) + "字节到OTA区域");

    if (!Update.end()) // 完成数据写入,设置在系统重启后自动将OTA区域固件移动到Sketch区域
    {
        Serial.println("升级失败 Error #: " + String(Update.getError()));
        return;
    }

    updateBin.close(); // 关闭update.bin文件
    //SD_MMC.remove("/update.bin"); // 移除update.bin文件

    Serial.println("升级操作完成,模块将在5秒后重启以完成固件升级");
    delay(5000); 
    ESP.restart(); // 重启设备
}

void loop()
{
}

在这里插入图片描述
上面演示中代码看着蛮多行,大多是都是文件读取、检测的代码,真正固件更新的操作就 Update.begin()Update.writeStream()Update.end() 这几步。

通过网页更新固件

通过网页更新也不复杂,和上面通过SD卡升级需要涉及SD卡使用相关内容一样,通过网页更新需要涉及网页和服务器相关内容,可以参考下面文章:
《使用Arduino开发ESP32(09):WebServer使用演示与说明》
使用下面代码可以测试通过网页进行更新固件的操作:

#include <WiFi.h>
#include <WebServer.h>
#include <Update.h> // 引用相关库

const char *ssid = "********";
const char *password = "********";

WebServer server(80);

// 以下为网页文件
String indexhtml = String("") +
                   "<!DOCTYPE html>\n" +
                   "<head>\n" +
                   "    <meta charset=\"UTF-8\">\n" +
                   "    <title>Update Test</title>\n" +
                   "</head>\n" +
                   "<body>\n" +
                   "    <form method=\'POST\' action=\'/update\' enctype=\'multipart/form-data\'>\n" +
                   "        <input type=\'file\' name=\'firmware\'>\n" +
                   "        <input type=\'submit\'>\n" +
                   "    </form>\n" +
                   "</body>\n";

bool shouldreboot = false; // 重启标志,固件升级操作完成后设置该标志准备重启设备

void handleResponse() //回调函数
{
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
}

void handleFileupload() //回调函数
{
    HTTPUpload &upload = server.upload();   // 文件上传对象
    if (upload.status == UPLOAD_FILE_START) // 文件上传开始
    {
        Serial.printf("开始上传文件: %s\n", upload.filename.c_str());
        if (!Update.begin()) // 开始固件升级,检查空可用间大小,如果正在升级或空间不够则返回false
        {
            Update.printError(Serial);
        }
    }
    else if (upload.status == UPLOAD_FILE_WRITE) // 文件读写中
    {
        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) // 将文件数据写入到OTA区域
        {
            Update.printError(Serial);
        }
    }
    else if (upload.status == UPLOAD_FILE_END) // 文件上传完成
    {
        Serial.println("写入" + String(upload.totalSize) + "字节到OTA区域");
        if (!Update.end(true)) // 完成数据写入,设置在系统重启后自动将OTA区域固件移动到Sketch区域 // Update.begin不指定大小时这里设置true
        {
            Update.printError(Serial);
        }
        Serial.println("升级操作完成,模块将在5秒后重启以完成固件升级");
        shouldreboot = true;
    }
    else
    {
        Serial.printf("固件上传失败: status=%d\n", upload.status);
    }
}

void setup()
{
    Serial.begin(115200);
    Serial.println();

    WiFi.mode(WIFI_STA);
    WiFi.setSleep(false);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
    Serial.println("Connected");
    Serial.print("IP Address:");
    Serial.println(WiFi.localIP());

    server.on("/", HTTP_GET, []() {
        server.sendHeader("Connection", "close");
        server.send(200, "text/html", indexhtml); // 发送网页
    });

    server.on("/update", HTTP_POST, handleResponse, handleFileupload); // 绑定回调函数

    server.begin(); //启动服务器

    Serial.println("Web server started");
}

void loop()
{
    server.handleClient(); //处理来自客户端的请求

    if (shouldreboot)
    {
        delay(5000);
        ESP.restart(); // 重启设备
    }
}

在这里插入图片描述
上面演示中代码看着也蛮多行,大多是都是网页文本、回调函数、网络连接等代码,真正固件更新的操作就 Update.begin()Update.write()Update.end() 这几步。和上一个例子不同的是这里使用Update.begin()时未指定需求空间的大小,所以在后面Update.end()中指定了true参数。

总结

固件更新相关内容的使用演示就是上面这些了,更多内容可以参考官方链接:
https://github.com/espressif/arduino-esp32/tree/master/libraries/Update
https://github.com/espressif/arduino-esp32/tree/master/libraries/ArduinoOTA
https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/WebUpdate/WebUpdate.ino

  • 8
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naisu Xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值