ESP32 OTA 踩坑实录

一、背景

ESP32 智能家居项目涉及多个传感器的数据采集和云端通信,代码迭代非常频繁。每次更新固件都要用 USB 线连接开发板,拔插、选端口、烧录,实在太麻烦了。

于是决定把 OTA(Over-The-Air,空中升级)功能加上,实现无线固件更新。

按理说 Arduino 的 OTA 功能很成熟,几行代码就能搞定。但实际上,我在这条路上踩了好几个坑,花了两天才彻底打通。这篇文章记录整个排查过程,希望帮你省点时间。

二、环境说明

项目 配置
开发板 TinkerNode NB-IoT
核心库版本 1.0.8(基于 ESP32 2.0.x)
IDE Arduino 1.8.19
OTA 库 ArduinoOTA(核心库自带)
电脑系统 Windows 11

三、具体问题

使用IDE自带的示例程序进行验证

cpp 复制代码
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

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

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

第一步: USB烧录OTA示例代码,一切正常。

第二步: 拔掉USB,Arduino IDE切换为网络端口(xxx.xxx.x.xxx)。

第三步: 修改代码(比如加个版本号打印),点击上传。

预期结果: 上传成功

实际结果:

就卡在这里,反复试了十几次,同一行报错。能看到设备,能认证通过,但就是传不上去。

四、问题排查

把常规方向都排查了一遍:

  • 代码没问题(用的官方示例)

  • 网络没问题(能 ping 通,能发现设备)

  • 防火墙关了

  • 端口没被占用

都没问题,开始抓包用 Wireshark 监听 OTA 通信过程:

红色方框为电脑端,绿色方框为开发板端。从截图可以清楚看到:UDP发现阶段正常 ,ESP32成功广播了自己的存在;但进入TCP握手阶段后,电脑没有回复ESP32发来的SYN包

通俗点说:ESP32主动发起连接请求(三次握手),但电脑那边像没听见一样,完全不搭理。更诡异的是,我多次抓包后发现:TCP传输时ESP32用的是随机端口,而不是固定端口。这意味着无法判断电脑端是否在对应端口上正常监听,排查一度陷入僵局。

上ESP32 OTA官方文档查资料时发现,官方推荐用的工具是espota.py,而Arduino IDE默认调用的OTA上传工具是TinkerNode核心库自带的espota.exe。这两个工具名字相似,但前者是Python脚本,后者是独立可执行文件。尝试在命令行手动调用espota.py上传固件.bin文件------一次成功 。再去Wireshark里看,这次UDP发现和TCP握手都正常了。看来问题就出在espota.exe这个工具上。

五、密码认证失败

第一次调试时没设密码,直接用了默认无密码模式。项目正式用肯定要加密码,在代码里添加:

cpp 复制代码
ArduinoOTA.setPassword("123456");

然后重新上传,结果密码明明是对的,但就是验证不过去,一直显示密码错误。WiFi连接、IP地址也都检查过,没问题。同样的环境,不加密码就通,加了密码就失败------问题只能出在密码认证这个环节。查看 ArduinoOTA 源码(ArduinoOTA.cpp)发现:setPassword() 底层把明文密码转成了MD5哈希值存储------也就是说,开发板端存的是密码的MD5,而不是密码本身。而espota.py默认走的是新安全协议,与旧版核心库的MD5协议不兼容。两边对不上,自然验证失败。

cpp 复制代码
ArduinoOTAClass& ArduinoOTAClass::setPassword(const char * password) {
    if (!_initialized && !_password.length() && password) {
        MD5Builder passmd5;
        passmd5.begin();
        passmd5.add(password);
        passmd5.calculate();
        _password = passmd5.toString();  // 存的是 MD5 哈希值!
    }
    return *this;
}

解决方案: 查了下espota.py的参数说明,发现有一个-m参数可以强制走MD5协议,加了 -m 参数后密码认证通过,上传成功。

六、一劳永逸

每次都敲命令行太麻烦,直接修改 Arduino IDE 的配置文件。

找到 platform.txt:C:\Users\你的用户名\...\Arduino15\packages\TinkerNode_NB-IoT\hardware\esp32\1.0.8\platform.txt(不同安装路径可能不同,按实际情况来)

修改 upload.network_pattern 这一行,原本是:tools.esptool_py.upload.network_pattern={network_cmd} -i "{serial.port}" -p "{network.port}" "--auth={network.password}" -f "{build.path}/{build.project_name}.bin"

替换为:tools.esptool_py.upload.network_pattern=C:\Users\86136\AppData\Local\Programs\Python\Python312\python.exe "{runtime.platform.path}/tools/espota.py" -i "{serial.port}" -p "{network.port}" --auth="{network.password}" -f "{build.path}/{build.project_name}.bin" -m

保存后重启 Arduino IDE,点击上传,直接走网络端口,一键无线升级。

七、总结

踩坑清单

问题 原因 解决
OTA 上传无响应 espota.exe TCP 服务端启动失败 换用 espota.py
密码认证失败 新工具默认用新协议,核心库用 MD5 -m 强制 MD5
每次都敲命令行麻烦 IDE 配置未修改 修改 platform.txt

相关资源:

相关推荐
云半S一1 小时前
Python学习总结
python·学习·学习分享
DevHub1 小时前
probe-rs 教程:一行命令替代 OpenOCD 烧写调试 Cortex-M
arm开发·嵌入式硬件·mcu·rust
小僧景贤1 小时前
嵌入式ADC高阶深度解析:基于STM32底层原理、误差溯源、硬件调校与工业级落地
单片机·嵌入式硬件·stm32 adc·工业级高精度采集·adc 误差校准·量产固件开发
YCOSA20252 小时前
雨晨 Windows 10 轻装版 64位 六合一 VIP 19045.7663
windows·物联网
黑猫学长呀2 小时前
【驱动开发常见问题】编译出现rm: cannot remove `asm/arch‘: Is a directory
驱动开发·单片机·嵌入式硬件·bug·编译报错·kernrl
雨田言炎2 小时前
STM32专题之内部FLASH详解
笔记·stm32·单片机
科信集团2 小时前
政策科普|广东省绿色制造体系全解读
学习·制造·绿色
迪丽热爱3 小时前
多媒体应用9-823
学习
汽车网络安全爱好者3 小时前
AUTOSAR SecOC 深入解析(一):从 Freshness Value 到 MAC 认证的完整流
物联网·网络安全·汽车·密码学