/*

  • ESP32 OTA (Over-The-Air) 固件升级完整示例
  • 功能:通过HTTP服务器下载新固件并更新ESP32
  • 作者:示例代码
  • 日期:2025
    */

#include <WiFi.h> // WiFi库,用于连接无线网络
#include <HTTPClient.h> // HTTP客户端库,用于下载固件
#include <Update.h> // ESP32的OTA更新库
#include <WiFiClientSecure.h> // HTTPS安全连接库(如果需要)

// ==================== 配置参数 ====================
// WiFi配置
const char* ssid = “你的WiFi名称”; // 替换为你的WiFi SSID
const char* password = “你的WiFi密码”; // 替换为你的WiFi密码

// OTA固件服务器配置
const char* firmwareUrl = “http://192.168.1.100:8080/firmware.bin”; // 固件下载地址
const char* firmwareVersion = “1.0.0”; // 当前固件版本号

// OTA升级检查间隔(毫秒)
const unsigned long checkInterval = 60000; // 每60秒检查一次更新
unsigned long lastCheck = 0; // 上次检查时间戳

// ==================== 函数声明 ====================
void connectWiFi(); // 连接WiFi函数
void performOTAUpdate(); // 执行OTA升级函数
bool checkForUpdate(); // 检查是否有新版本
void printProgress(size_t progress, size_t total); // 打印升级进度

// ==================== 初始化设置 ====================
void setup() {
// 初始化串口通信,波特率115200
Serial.begin(115200);

// 等待串口就绪
delay(1000);

// 打印启动信息
Serial.println();
Serial.println(“=“);
Serial.println(“ESP32 OTA 固件升级系统”);
Serial.printf(“当前固件版本: %s\n”, firmwareVersion);
Serial.println(”
=”);

// 连接到WiFi网络
connectWiFi();

// 打印ESP32的一些基本信息
Serial.println(“\n— 设备信息 —”);
Serial.printf(“芯片型号: %s\n”, ESP.getChipModel());
Serial.printf(“芯片核心数: %d\n”, ESP.getChipCores());
Serial.printf(“CPU频率: %d MHz\n”, ESP.getCpuFreqMHz());
Serial.printf(“Flash大小: %d MB\n”, ESP.getFlashChipSize() / (1024 * 1024));
Serial.printf(“可用堆内存: %d bytes\n”, ESP.getFreeHeap());
Serial.printf(“固件大小: %d bytes\n”, ESP.getSketchSize());
Serial.printf(“剩余OTA空间: %d bytes\n”, ESP.getFreeSketchSpace());
Serial.println(“-------------------\n”);
}

// ==================== 主循环 ====================
void loop() {
// 检查WiFi连接状态
if (WiFi.status() != WL_CONNECTED) {
Serial.println(“WiFi断开,尝试重新连接…”);
connectWiFi();
}

// 定时检查OTA更新
// 使用millis()避免delay()阻塞
if (millis() - lastCheck >= checkInterval) {
lastCheck = millis(); // 更新检查时间戳

Serial.println("\n[OTA] 检查固件更新...");

// 检查是否有新版本可用
if (checkForUpdate()) {
  Serial.println("[OTA] 发现新版本,开始更新...");
  performOTAUpdate();  // 执行OTA升级
} else {
  Serial.println("[OTA] 当前已是最新版本");
}

}

// 这里可以添加你的主要业务逻辑
// 例如:读取传感器、控制设备等

delay(1000); // 延迟1秒,避免过快循环
}

// ==================== WiFi连接函数 ====================
void connectWiFi() {
Serial.println(“\n[WiFi] 开始连接WiFi…”);
Serial.printf(“[WiFi] SSID: %s\n”, ssid);

// 设置WiFi为Station模式(客户端模式)
WiFi.mode(WIFI_STA);

// 开始连接WiFi
WiFi.begin(ssid, password);

// 等待连接,最多等待20秒
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 20) {
delay(1000); // 每秒检查一次
Serial.print(“.”); // 打印连接进度点
timeout++;
}

// 检查连接结果
if (WiFi.status() == WL_CONNECTED) {
Serial.println(“\n[WiFi] 连接成功!”);
Serial.printf(“[WiFi] IP地址: %s\n”, WiFi.localIP().toString().c_str());
Serial.printf(“[WiFi] 信号强度: %d dBm\n”, WiFi.RSSI());
} else {
Serial.println(“\n[WiFi] 连接失败!”);
Serial.println(“[WiFi] 请检查WiFi名称和密码是否正确”);
}
}

// ==================== 检查更新函数 ====================
bool checkForUpdate() {
// 创建HTTP客户端对象
HTTPClient http;

// 构建版本检查URL(假设服务器有version.txt文件)
String versionUrl = String(firmwareUrl);
versionUrl.replace(“firmware.bin”, “version.txt”);

// 开始HTTP GET请求
http.begin(versionUrl);
int httpCode = http.GET(); // 发送GET请求

// 检查HTTP响应码
if (httpCode == HTTP_CODE_OK) {
// 读取服务器返回的版本号
String newVersion = http.getString();
newVersion.trim(); // 去除首尾空白字符

Serial.printf("[OTA] 服务器版本: %s\n", newVersion.c_str());
Serial.printf("[OTA] 当前版本: %s\n", firmwareVersion);

// 结束HTTP连接
http.end();

// 比较版本号(简单字符串比较)
// 实际应用中建议使用更严格的版本号比较逻辑
if (newVersion != firmwareVersion) {
  return true;  // 有新版本
}

} else {
Serial.printf(“[OTA] 版本检查失败,HTTP错误码: %d\n”, httpCode);
http.end();
}

return false; // 无新版本或检查失败
}

// ==================== OTA升级执行函数 ====================
void performOTAUpdate() {
Serial.println(“\n========== 开始OTA升级 ==========”);

// 创建WiFi客户端和HTTP客户端对象
WiFiClient client;
HTTPClient http;

// 设置HTTP超时时间(毫秒)
http.setTimeout(15000);

// 开始HTTP连接
Serial.printf(“[OTA] 连接到: %s\n”, firmwareUrl);
http.begin(client, firmwareUrl);

// 发送GET请求获取固件文件
int httpCode = http.GET();

// 检查HTTP响应码
if (httpCode == HTTP_CODE_OK) {
// 获取固件文件大小
int contentLength = http.getSize();
Serial.printf(“[OTA] 固件大小: %d bytes (%.2f KB)\n”,
contentLength, contentLength / 1024.0);

// 检查是否有足够的空间存储新固件
if (contentLength > 0) {
  // 检查剩余OTA分区空间
  if (contentLength > ESP.getFreeSketchSpace()) {
    Serial.println("[OTA] 错误: OTA分区空间不足!");
    http.end();
    return;
  }
  
  // 获取HTTP流对象
  WiFiClient* stream = http.getStreamPtr();
  
  // 开始OTA更新过程
  // Update.begin()参数:
  // - contentLength: 固件大小
  // - U_FLASH: 更新Flash区域(固件)
  // - LED_BUILTIN: 指定LED引脚,更新时会闪烁
  // - LOW: LED点亮的电平
  if (!Update.begin(contentLength, U_FLASH)) {
    Serial.println("[OTA] 错误: 无法开始OTA更新");
    Update.printError(Serial);  // 打印详细错误信息
    http.end();
    return;
  }
  
  Serial.println("[OTA] 开始下载固件...");
  
  // 创建缓冲区用于接收数据
  uint8_t buff[128] = { 0 };
  size_t written = 0;  // 已写入字节数
  
  // 循环读取并写入固件数据
  while (http.connected() && written < contentLength) {
    // 获取可用数据大小
    size_t available = stream->available();
    
    if (available) {
      // 读取数据到缓冲区
      // 读取大小为缓冲区大小和可用数据的较小值
      int c = stream->readBytes(buff, 
                                ((available > sizeof(buff)) ? sizeof(buff) : available));
      
      // 将数据写入Flash
      size_t bytesWritten = Update.write(buff, c);
      
      if (bytesWritten != c) {
        Serial.println("[OTA] 错误: 写入数据失败");
        break;
      }
      
      written += bytesWritten;  // 累加已写入字节数
      
      // 打印进度(每10KB打印一次)
      if (written % (1024 * 10) == 0 || written == contentLength) {
        printProgress(written, contentLength);
      }
    }
    delay(1);  // 小延迟,让系统稳定
  }
  
  Serial.println();  // 换行
  
  // 检查是否完整下载
  if (written == contentLength) {
    Serial.printf("[OTA] 下载完成,共 %d bytes\n", written);
  } else {
    Serial.printf("[OTA] 警告: 下载不完整 (%d / %d bytes)\n", 
                  written, contentLength);
  }
  
  // 完成OTA更新
  if (Update.end()) {
    // 验证更新是否成功
    if (Update.isFinished()) {
      Serial.println("[OTA] ✓ 更新成功!");
      Serial.println("[OTA] 3秒后重启设备...");
      delay(3000);
      
      // 重启ESP32以应用新固件
      ESP.restart();
    } else {
      Serial.println("[OTA] ✗ 更新未完成");
      Update.printError(Serial);
    }
  } else {
    Serial.println("[OTA] ✗ 更新失败");
    Update.printError(Serial);
  }
  
} else {
  Serial.println("[OTA] 错误: 固件大小无效");
}

} else if (httpCode == HTTP_CODE_NOT_MODIFIED) {
Serial.println(“[OTA] 固件未修改(304)”);
} else {
Serial.printf(“[OTA] HTTP错误码: %d\n”, httpCode);
}

// 关闭HTTP连接
http.end();

Serial.println(“========== OTA升级结束 ==========\n”);
}

// ==================== 进度打印函数 ====================
void printProgress(size_t progress, size_t total) {
// 计算百分比
int percent = (progress * 100) / total;

// 打印进度条
Serial.printf(“[OTA] 进度: %d%% (%d / %d bytes)\r”,
percent, progress, total);

// 如果完成,打印换行
if (progress == total) {
Serial.println();
}
}

/*

  • ==================== 使用说明 ====================
    1. 准备工作:
    • 修改WiFi SSID和密码
    • 准备HTTP文件服务器(可以用Python: python -m http.server 8080)
    • 编译生成.bin固件文件(Arduino IDE: 草图 -> 导出已编译的二进制文件)
    1. 服务器文件结构:
    • firmware.bin (新固件文件)
    • version.txt (版本号文件,如: 1.0.1)
    1. 分区表要求:
  • ESP32必须使用支持OTA的分区表,Arduino IDE中选择:
  • 工具 -> Partition Scheme -> “Minimal SPIFFS (1.9MB APP with OTA)”
  • 或其他带OTA的分区方案
    1. 测试流程:
    • 上传此代码到ESP32(版本1.0.0)
    • 修改代码中的版本号为1.0.1
    • 重新编译生成新的firmware.bin
    • 将firmware.bin和version.txt(内容为1.0.1)放到HTTP服务器
    • ESP32会自动检测并下载更新
    1. 安全建议:
    • 生产环境使用HTTPS(需要WiFiClientSecure和证书)
    • 添加固件签名验证
    • 实现回滚机制
    • 添加固件MD5校验
    1. 常见问题:
    • “OTA分区空间不足”: 选择更大的APP分区或减小程序大小
    • “更新失败”: 检查固件文件是否完整、网络是否稳定
    • “无法开始OTA”: 检查分区表是否支持OTA
  • ==================== 高级功能扩展 ====================
  • 可以添加的功能:
    1. HTTPS支持(安全传输)
    1. 固件MD5校验(防篡改)
    1. 分段下载(支持大文件)
    1. 断点续传(网络中断恢复)
    1. 回滚功能(更新失败恢复旧版本)
    1. 远程触发更新(MQTT/HTTP API)
    1. 更新进度Web界面
    1. 批量设备管理
      */
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐