news 2026/10/7 11:42:14

ESP32免刷机改WiFi配置:NVS+浏览器方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32免刷机改WiFi配置:NVS+浏览器方案

1. 从一个让人抓狂的场景说起

如果你玩过 ESP32,大概率经历过这个场景:设备已经焊好、装进外壳、挂在墙上跑了三个月,突然要换个 WiFi 密码。你翻出数据线,拆外壳,插 USB,打开 Arduino IDE 或者 ESP-IDF,改一行ssid和password,编译,烧录,等两分钟,再装回去。整个过程十分钟起步,如果设备装在不好够到的地方,那更是灾难。

更别提有些项目是批量部署的,几十台设备散落在不同位置,每换一次密码就得挨个刷固件,这活儿谁干谁知道。

问题的根源在于:大多数人把 WiFi 配置写死在代码里,编译进固件。固件是只读的,改配置就得重新编译烧录。但 ESP32 其实自带了一块NVS(Non-Volatile Storage,非易失性存储)分区,专门用来存键值对数据,掉电不丢。WiFi 的 SSID 和密码完全可以存在 NVS 里,代码启动时从 NVS 读取,而不是写死。

那怎么改 NVS 里的值?传统做法是写一段串口命令解析代码,通过串口发指令修改。但这需要设备在线、需要串口连接、需要你自己实现一套协议。有没有更省事的办法?

有。用浏览器直接改。ESP32 跑一个轻量 HTTP 服务,浏览器打开一个页面,表单里填新的 SSID 和密码,点提交,NVS 就更新了,重启后自动连上新 WiFi。整个过程不需要数据线,不需要编译环境,手机浏览器就能操作。

这篇文章就把这套方案的完整实现思路、核心代码、踩坑经验全部拆开讲清楚。适合已经会用 Arduino IDE 或 ESP-IDF 开发 ESP32、想给项目加一个"免刷机改配置"能力的开发者。哪怕你只做过点灯和连 WiFi 的基础项目,跟着思路也能落地。

2. 整体方案设计与技术选型

2.1 为什么选 NVS 而不是其他存储方式

ESP32 上能存数据的地方有好几个,选哪个得看场景。

Flash 直接读写:ESP32 的 Flash 可以通过esp_partitionAPI 直接读写,但 Flash 有擦除块限制(通常 4KB),写之前要先擦除整个扇区,操作不当容易把固件区域搞坏。而且没有磨损均衡,频繁写会缩短寿命。

SPIFFS/LittleFS 文件系统:适合存大文件、日志、网页资源,但存几个键值对属于杀鸡用牛刀,挂载文件系统本身也要占资源和时间。

NVS:ESP-IDF 原生支持的键值存储,专门为小数据设计。支持磨损均衡、掉电保护、类型区分(整数、字符串、二进制块)。API 简单,nvs_set_str、nvs_get_str两行搞定。Arduino 环境下通过Preferences库封装,更好用。

所以存 WiFi 配置这种"几个字符串"的需求,NVS 是最合适的。它就像一个微型数据库,你给它一个命名空间(namespace)和一个键(key),它帮你持久化。

2.2 为什么用浏览器而不是串口或蓝牙

串口改配置需要物理连接,设备装进盒子后就很难够到。蓝牙需要配对、需要写 App 或者用通用蓝牙调试工具,操作门槛高,而且蓝牙和 WiFi 共存时射频资源紧张。

浏览器方案的优势在于零客户端依赖。ESP32 自己开一个 HTTP 服务,任何有浏览器的设备(手机、电脑、平板)连上就能访问。不需要装 App,不需要驱动,不需要数据线。配置页面就是一个 HTML 表单,提交后 ESP32 解析请求参数,写入 NVS,返回结果。

这里有个关键设计点:配置模式和运行模式要分开。设备正常运行时连的是目标 WiFi,你没法通过那个网络访问它(IP 不确定,而且可能不在同一网段)。所以需要一个"配置模式"——设备启动时如果检测到某个条件(比如按键按下、或者 NVS 里没有有效配置),就切换成 AP 模式,自己发一个热点,你连上这个热点访问配置页面。

2.3 双模式架构:AP 配置 + STA 运行

整体架构是这样的:

  • 首次启动或配置无效:进入 AP 模式,SSID 类似ESP32-Config,开 HTTP 服务,等待用户提交配置。
  • 配置有效:进入 STA 模式,从 NVS 读取 SSID 和密码,连接目标 WiFi,正常运行。
  • 需要重新配置:可以通过按键触发(长按 GPIO0 若干秒),或者访问运行模式下的某个特定 URL 触发重启进入 AP 模式。

这个架构的好处是:正常运行时不影响业务逻辑,需要改配置时又能随时切换。而且配置页面可以做得比较友好,显示当前配置、扫描周围 WiFi 列表、表单验证等等。

2.4 技术栈选择:Arduino 还是 ESP-IDF

两种都能做,各有取舍。

Arduino 框架:Preferences库封装了 NVS,WebServer库处理 HTTP,代码量少,上手快。适合中小项目和快速原型。缺点是WebServer是同步阻塞的,处理请求时会卡住主循环,如果同时有业务逻辑在跑需要注意。

ESP-IDF:nvs_flash和esp_http_server是原生组件,性能更好,支持异步。但代码量大,配置项多,对新手不友好。

考虑到这篇文章面向的是"想快速给项目加功能"的开发者,我用 Arduino 框架来演示,核心逻辑在 ESP-IDF 下是相通的,只是 API 名字不同。后面会给出关键 API 的对照。

3. 核心细节解析与实操要点

3.1 NVS 的命名空间与键值设计

NVS 的数据组织是"命名空间 → 键 → 值"三层结构。命名空间相当于一个文件夹,键是文件名,值是内容。不同命名空间之间的键互不干扰。

对于 WiFi 配置,我建议这样设计:

命名空间键名类型说明
wifi_cfgssid字符串目标 WiFi 名称
wifi_cfgpass字符串目标 WiFi 密码
wifi_cfgvalid整数配置是否有效(0/1)
wifi_cfgmode整数0=STA,1=AP

为什么要加一个valid标志?因为 NVS 刚擦除时是空的,nvs_get_str会返回ESP_ERR_NVS_NOT_FOUND。你可以靠这个错误码判断,但显式存一个标志位更清晰,也方便做"配置版本升级"——比如以后加了新字段,可以用版本号判断是否需要迁移。

注意:NVS 的键名最长 15 个字符,命名空间最长 15 个字符。超了会报错。别用太长的名字。

3.2 Preferences 库的读写封装

Arduino 环境下用Preferences库操作 NVS,比原生 API 简洁很多。核心代码:

#include <Preferences.h> Preferences prefs; // 写入配置 void saveWifiConfig(const String& ssid, const String& pass) { prefs.begin("wifi_cfg", false); // false = 读写模式 prefs.putString("ssid", ssid); prefs.putString("pass", pass); prefs.putInt("valid", 1); prefs.end(); } // 读取配置 bool loadWifiConfig(String& ssid, String& pass) { prefs.begin("wifi_cfg", true); // true = 只读模式 if (prefs.getInt("valid", 0) != 1) { prefs.end(); return false; } ssid = prefs.getString("ssid", ""); pass = prefs.getString("pass", ""); prefs.end(); return ssid.length() > 0; }

这里有几个细节值得说:

prefs.begin()的第二个参数是只读标志。读的时候用true,性能更好,也不会意外写入。写的时候用false。每次操作完必须prefs.end(),否则命名空间一直打开着,下次begin会失败。

getString的第二个参数是默认值,当键不存在时返回这个值。这个设计很贴心,省去了判断错误码的麻烦。

实操心得:Preferences对象不要做成全局的然后一直begin不end。我见过有人这么写,结果第二次begin返回 false,数据写不进去,排查了半天。每次操作独立begin/end是最稳的。

3.3 HTTP 服务端点的设计

配置页面需要几个端点:

  • GET /:返回配置页面的 HTML
  • GET /scan:返回周围 WiFi 列表(JSON 格式)
  • POST /save:接收表单提交,写入 NVS,返回结果
  • POST /reset:清除配置,重启进入 AP 模式

用WebServer库实现:

#include <WebServer.h> WebServer server(80); void setupRoutes() { server.on("/", HTTP_GET, handleRoot); server.on("/scan", HTTP_GET, handleScan); server.on("/save", HTTP_POST, handleSave); server.on("/reset", HTTP_POST, handleReset); server.begin(); }

handleRoot返回一段 HTML 字符串,里面包含表单。表单的action指向/save,method是POST。

handleScan调用WiFi.scanNetworks(),把结果拼成 JSON 返回。注意扫描是阻塞操作,大概需要 2-3 秒,这期间 HTTP 请求会等着,浏览器可能超时。解决办法是扫描前先返回一个"扫描中"的响应,或者用异步扫描 API。简单起见,可以在页面加载时用 JavaScript 异步请求/scan,这样不阻塞页面渲染。

handleSave从server.arg("ssid")和server.arg("pass")取表单数据,调用saveWifiConfig,然后返回一个成功页面,延迟几秒后重启。

3.4 AP 模式与 STA 模式的切换逻辑

启动流程:

void setup() { Serial.begin(115200); String ssid, pass; if (loadWifiConfig(ssid, pass)) { // 有有效配置,尝试 STA 模式 WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); unsigned long start = millis(); while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { delay(500); } if (WiFi.status() == WL_CONNECTED) { // 连接成功,正常运行 startNormalMode(); return; } // 连接失败,降级到 AP 模式 } // 无配置或连接失败,进入 AP 模式 startConfigMode(); }

这里有个关键点:连接超时要设置。如果不设超时,WiFi 连不上时WiFi.begin会一直阻塞(取决于底层实现),设备就卡死了。设 10 秒超时,超时后进入 AP 模式,用户还能救回来。

startConfigMode里:

void startConfigMode() { WiFi.mode(WIFI_AP); WiFi.softAP("ESP32-Config", "12345678"); setupRoutes(); // 进入循环,处理 HTTP 请求 }

AP 的密码建议设一个简单的,或者干脆不设(softAP第二参数传NULL),方便连接。但要注意,不设密码的话任何人都能连上改配置,如果设备部署在公共场所,还是设一个。

注意:AP 模式和 STA 模式不能同时用同一个射频。ESP32 支持 AP+STA 共存,但同时工作时吞吐量会下降,而且信道要一致。如果只是配置用,建议纯 AP 模式,配置完重启切 STA。

4. 实操过程与核心环节实现

4.1 完整代码框架

把上面的片段拼起来,一个最小可用的实现大概长这样:

#include <WiFi.h> #include <WebServer.h> #include <Preferences.h> Preferences prefs; WebServer server(80); const char* AP_SSID = "ESP32-Config"; const char* AP_PASS = "12345678"; String cfgSsid, cfgPass; bool loadWifiConfig() { prefs.begin("wifi_cfg", true); bool valid = prefs.getInt("valid", 0) == 1; if (valid) { cfgSsid = prefs.getString("ssid", ""); cfgPass = prefs.getString("pass", ""); } prefs.end(); return valid && cfgSsid.length() > 0; } void saveWifiConfig(const String& s, const String& p) { prefs.begin("wifi_cfg", false); prefs.putString("ssid", s); prefs.putString("pass", p); prefs.putInt("valid", 1); prefs.end(); } void clearWifiConfig() { prefs.begin("wifi_cfg", false); prefs.clear(); prefs.end(); } const char CONFIG_PAGE[] PROGMEM = R"HTML( <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>ESP32 配置</title> <style> body{font-family:sans-serif;max-width:400px;margin:40px auto;padding:0 16px} input,button{width:100%;padding:10px;margin:8px 0;box-sizing:border-box;font-size:16px} button{background:#007bff;color:#fff;border:none;border-radius:4px} </style> </head> <body> <h2>WiFi 配置</h2> <form action="/save" method="post"> <input name="ssid" placeholder="WiFi 名称" required> <input name="pass" type="password" placeholder="WiFi 密码"> <button type="submit">保存并重启</button> </form> </body> </html> )HTML"; void handleRoot() { server.send(200, "text/html", CONFIG_PAGE); } void handleSave() { String s = server.arg("ssid"); String p = server.arg("pass"); if (s.length() == 0) { server.send(400, "text/plain", "SSID 不能为空"); return; } saveWifiConfig(s, p); server.send(200, "text/html", "<meta charset='utf-8'><h3>保存成功,设备将在 3 秒后重启</h3>"); delay(3000); ESP.restart(); } void startConfigMode() { WiFi.mode(WIFI_AP); WiFi.softAP(AP_SSID, AP_PASS); server.on("/", HTTP_GET, handleRoot); server.on("/save", HTTP_POST, handleSave); server.begin(); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); } void startNormalMode() { Serial.print("Connected, IP: "); Serial.println(WiFi.localIP()); // 这里放你的业务逻辑 } void setup() { Serial.begin(115200); delay(100); if (loadWifiConfig()) { WiFi.mode(WIFI_STA); WiFi.begin(cfgSsid.c_str(), cfgPass.c_str()); unsigned long t = millis(); while (WiFi.status() != WL_CONNECTED && millis() - t < 10000) { delay(500); Serial.print("."); } if (WiFi.status() == WL_CONNECTED) { startNormalMode(); return; } Serial.println("\n连接失败,进入配置模式"); } startConfigMode(); } void loop() { server.handleClient(); // 业务逻辑 }

这段代码烧进去,首次启动会开热点ESP32-Config,密码12345678。手机连上后浏览器访问192.168.4.1,填 WiFi 信息,保存,设备重启后连上目标 WiFi。

4.2 加入 WiFi 扫描功能

上面的页面需要手动输入 SSID,容易打错。加一个扫描功能,列出周围 WiFi,点击自动填充。

在 HTML 里加一个按钮和列表:

<button type="button" onclick="scan()">扫描 WiFi</button> <div id="list"></div> <script> function scan(){ document.getElementById('list').innerHTML='扫描中...'; fetch('/scan').then(r=>r.json()).then(d=>{ let h=''; d.forEach(n=>{ h+='<div onclick="document.querySelector(\'[name=ssid]\').value=\''+n.ssid+'\'">'+n.ssid+' ('+n.rssi+'dBm)</div>'; }); document.getElementById('list').innerHTML=h||'未发现网络'; }); } </script>

服务端/scan:

void handleScan() { int n = WiFi.scanNetworks(); String json = "["; for (int i = 0; i < n; i++) { if (i > 0) json += ","; json += "{\"ssid\":\"" + WiFi.SSID(i) + "\",\"rssi\":" + String(WiFi.RSSI(i)) + "}"; } json += "]"; WiFi.scanDelete(); server.send(200, "application/json", json); }

实操心得:WiFi.scanNetworks()是阻塞的,扫描期间整个程序卡住。如果设备同时有别的任务(比如读传感器),扫描会导致任务延迟。解决办法是用WiFi.scanNetworks(true)开启异步扫描,然后轮询WiFi.scanComplete()。不过配置模式下一般没别的任务,同步扫描也能接受。

4.3 运行模式下的重新配置入口

设备正常运行后,如果想改配置,不可能每次都拆机。可以在运行模式下也开一个 HTTP 服务,提供一个/config端点,访问后触发重启进入 AP 模式。

void handleEnterConfig() { server.send(200, "text/plain", "即将进入配置模式"); delay(1000); clearWifiConfig(); // 清除配置,下次启动进 AP ESP.restart(); }

在startNormalMode里注册这个路由:

server.on("/config", HTTP_GET, handleEnterConfig); server.begin();

这样设备正常运行时,浏览器访问http://设备IP/config就能触发重新配置。前提是你知道设备 IP,可以通过路由器后台查,或者在设备上显示(如果有屏幕)。

另一种触发方式是物理按键。长按 GPIO0(很多开发板上的 BOOT 键)5 秒,清除配置并重启:

void checkConfigButton() { if (digitalRead(0) == LOW) { unsigned long t = millis(); while (digitalRead(0) == LOW) { if (millis() - t > 5000) { clearWifiConfig(); ESP.restart(); } delay(50); } } }

在loop里调用这个函数。这样即使设备连不上、IP 也不知道,按一下按键就能救回来。

4.4 ESP-IDF 下的等价实现

如果你用 ESP-IDF 而不是 Arduino,核心逻辑一样,API 换一下:

功能ArduinoESP-IDF
初始化 NVSprefs.begin()nvs_flash_init()
写字符串prefs.putString()nvs_set_str()
读字符串prefs.getString()nvs_get_str()
提交更改自动nvs_commit()
HTTP 服务WebServeresp_http_server
启动 APWiFi.softAP()esp_wifi_set_mode(WIFI_MODE_AP)

ESP-IDF 下nvs_set_str之后必须调nvs_commit,否则数据可能还在缓存里没落盘。Arduino 的Preferences在end()时会自动 commit,所以不用手动调。

HTTP 服务端部分,esp_http_server的注册方式是:

httpd_uri_t save_uri = { .uri = "/save", .method = HTTP_POST, .handler = save_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &save_uri);

handler 里用httpd_req_recv读 POST 数据,解析表单。比 Arduino 的server.arg()麻烦一些,但更灵活。

5. 常见问题与排查技巧实录

5.1 配置保存后连不上 WiFi

这是最常见的问题。排查顺序:

第一,检查 SSID 和密码是否真的写进去了。在loadWifiConfig里加串口打印,把读出来的值打出来看。有时候是表单提交时字段名对不上,server.arg("ssid")取到空字符串。

第二,检查 WiFi 频段。ESP32 只支持 2.4GHz,不支持 5GHz。如果路由器是双频合一,SSID 相同但设备连的是 5G 频段,ESP32 可能连不上。解决办法是在路由器里把 2.4G 和 5G 的 SSID 分开。

第三,检查密码里的特殊字符。如果密码包含&、=、+等 URL 特殊字符,表单提交时可能被转义。WebServer库会自动 URL 解码,但如果你自己拼 JSON 或者用其他方式传输,要注意编码问题。

第四,检查 WiFi 认证方式。ESP32 支持 WPA/WPA2/WPA3,但某些企业级认证(WPA2-Enterprise)需要额外配置,简单的WiFi.begin(ssid, pass)连不上。

5.2 AP 模式连不上或页面打不开

AP 模式启动后,手机搜不到热点,或者连上了但访问192.168.4.1没反应。

搜不到热点:检查WiFi.softAP()是否成功。可以打印WiFi.softAPIP(),如果返回0.0.0.0说明启动失败。常见原因是 WiFi 模式没设对,必须先WiFi.mode(WIFI_AP)再softAP。

连上了但打不开页面:检查 HTTP 服务是否启动。server.begin()之后,loop里必须调server.handleClient(),否则请求不会被处理。另外检查手机是否自动切换到了移动数据,有些手机在 WiFi 无外网时会自动用蜂窝网络,导致访问不到局域网 IP。解决办法是关闭移动数据,或者在手机 WiFi 设置里关掉"自动切换"。

IP 地址不对:ESP32 的 AP 默认 IP 是192.168.4.1,但如果之前设置过,可能变了。用WiFi.softAPIP()打印确认。

5.3 NVS 写入失败或数据丢失

prefs.putString()返回 0 表示成功,非 0 表示失败。失败常见原因:

命名空间没打开:prefs.begin()返回 false 时后续操作都会失败。检查是否在begin之前就调用了put。

NVS 分区满了:NVS 默认分区大小有限(通常 20KB 左右),存太多数据会满。WiFi 配置这种小数据不会满,但如果你还存了别的东西,要注意。可以用nvs_get_stats查看使用情况。

Flash 操作冲突:如果同时有别的任务在写 Flash(比如 OTA 升级、文件系统写入),NVS 操作可能失败。解决办法是加互斥锁,或者错开操作时间。

避坑技巧:NVS 的写入有次数限制,虽然 ESP32 的 Flash 有磨损均衡,但频繁写同一个键仍然会加速老化。WiFi 配置这种低频写入没问题,但不要把它当日志用,每秒写一次。

5.4 常见问题速查表

现象可能原因排查方法解决
保存后连不上SSID/密码错误串口打印读出的值检查表单字段名
保存后连不上5G 频段路由器查看分开 2.4G/5G SSID
AP 搜不到模式未设置打印 softAPIP先 mode(WIFI_AP)
页面打不开handleClient 未调用检查 loop加 server.handleClient()
页面打不开手机用蜂窝网关闭移动数据手动切 WiFi
NVS 写入失败命名空间未打开检查 begin 返回值确保 begin 成功
数据丢失未 commitESP-IDF 下检查调 nvs_commit
扫描卡顿同步扫描观察响应时间用异步扫描

5.5 安全性考虑

这个方案有个明显的安全弱点:AP 模式下的配置页面没有认证。任何人连上热点都能改配置。如果设备部署在公共场所,这是个风险。

几个加固思路:

给 AP 设密码:WiFi.softAP(ssid, password),密码至少 8 位。这样只有知道密码的人才能连上。

配置页面加简单认证:HTTP Basic Auth,或者表单里加一个 PIN 码字段,服务端校验。PIN 码可以硬编码在固件里,或者首次启动时随机生成并显示在串口。

配置完成后关闭 AP:保存配置后立即重启,AP 只存在几秒钟,减少暴露窗口。

运行模式下的/config端点加认证:不要裸奔,至少加个 token 校验。

实操心得:我在一个户外项目里用过这个方案,AP 密码设了,但配置页面还是被人扫到过。后来加了一个逻辑:AP 模式只维持 5 分钟,超时自动重启回 STA 模式。这样即使有人连上,窗口期也很短。

6. 进阶扩展与实战建议

6.1 配置页面做成单页应用

上面的 HTML 是内嵌在固件里的字符串,简单但不好维护。如果配置项多了(比如还要配 MQTT 服务器、传感器阈值),可以做成一个单页应用,用fetch调 API,页面资源存在 SPIFFS/LittleFS 里。

这样固件里只放 API,页面用 HTML/CSS/JS 写,改页面不用重新编译固件,直接上传文件系统就行。开发效率高很多。

6.2 配置版本迁移

如果以后配置结构变了(比如加了新字段),旧设备升级固件后读不到新字段会出问题。解决办法是在 NVS 里存一个版本号:

int cfgVersion = prefs.getInt("ver", 0); if (cfgVersion < CURRENT_VERSION) { // 执行迁移逻辑 prefs.putInt("ver", CURRENT_VERSION); }

迁移逻辑可以是给新字段设默认值,或者从旧字段转换。这样固件升级不会导致配置丢失。

6.3 多套配置切换

有些场景需要在多个 WiFi 之间切换(比如设备在办公室和家里都用)。可以存多套配置,用数组或者多个命名空间。启动时依次尝试,连上哪个用哪个。

for (int i = 0; i < MAX_PROFILES; i++) { String ns = "wifi_" + String(i); prefs.begin(ns.c_str(), true); String s = prefs.getString("ssid", ""); String p = prefs.getString("pass", ""); prefs.end(); if (s.length() == 0) continue; WiFi.begin(s.c_str(), p.c_str()); // 等待连接... }

这个方案在移动设备上很实用,比如车载 ESP32 或者便携传感器。

6.4 与 OTA 升级配合

既然已经能通过浏览器改配置了,顺便把 OTA 升级也加上。ESP32 的ArduinoOTA库或者Update库支持通过 HTTP 上传固件。配置页面加一个文件上传表单,选择.bin文件,上传后自动升级重启。

这样设备部署后完全不需要物理接触,配置和固件都能远程更新。对于批量部署的项目,这是刚需。

注意:OTA 升级有风险,升级失败可能导致设备变砖。建议保留一个"恢复模式"——如果启动时检测到连续多次启动失败,自动进入 AP 模式等待救援。ESP32 的 RTC 内存可以在重启间保持数据,可以用来记录启动次数。

6.5 实际部署中的经验

我在几个项目里用过这套方案,总结几条经验:

AP 的 SSID 加上设备标识。如果周围有多台同型号设备,都用ESP32-Config会分不清。可以在 SSID 里加 MAC 地址后几位,比如ESP32-Config-A1B2。

配置页面适配手机。大部分时候是用手机配置的,页面要做响应式,输入框要大,按钮要好点。上面示例里的 CSS 已经做了基本适配。

保存后给明确反馈。用户点了保存,页面要显示"保存成功,设备重启中",然后倒计时。不要直接白屏或者无响应,用户会以为坏了。

串口日志保留。配置过程中的关键步骤都打日志,出问题时能通过串口看到。量产时可以关掉,但开发阶段一定要有。

测试不同路由器。有些路由器对 ESP32 兼容性不好,比如某些老款或者企业级设备。部署前在目标环境测试一下,避免现场翻车。

这套方案的核心价值在于:把配置从固件里解耦出来。固件负责逻辑,NVS 负责数据,浏览器负责交互。三者独立,各司其职。一旦你习惯了这种模式,以后做任何 ESP32 项目都会默认加上这个能力——毕竟谁也不想为了改个密码去拆机刷固件。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 11:42:11

为Claude装上长期记忆:claude-mem方案从原理到代码落地

写AI应用的人都有个绕不开的坎&#xff1a;对话一长&#xff0c;模型就“失忆”。前面聊的用户偏好、项目背景、技术选型&#xff0c;过了几轮之后它全忘光了&#xff0c;你只能一遍又一遍把上下文塞回提示词里&#xff0c;token成本越来越高&#xff0c;效果却越来越差。我在给…

作者头像 李华
网站建设 2026/10/7 11:41:56

CAN总线接口EMC防护设计实战:从器件选型到PCB布局整改

做车载电子和设备控制这些年&#xff0c;我吃过最多的亏不在算法和逻辑&#xff0c;而在最不起眼的接口电路上。CAN总线协议本身设计得相当皮实&#xff0c;差分信号抗共模干扰的能力比UART强出一大截&#xff0c;可一旦放到真实环境里&#xff0c;踩油门时的点火脉冲、继电器吸…

作者头像 李华
网站建设 2026/10/7 11:40:37

汇川IS620P伺服参数备份恢复指南:OpenPNP贴片机调试实用教程

前阵子给一台 OpenPNP 贴片机做维护&#xff0c;差点因为伺服参数丢失导致整台设备趴窝。OpenPNP 这种开源视觉贴片机&#xff0c;一旦把轴调顺了&#xff0c;大家往往会把注意力放在视觉识别、取放位置、吸嘴配置这些地方&#xff0c;反而最容易忽略一个藏在电柜深处的环节&am…

作者头像 李华
网站建设 2026/10/7 11:39:27

Agent技能库设计实战:从提示词堆叠到结构化技能编排

前阵子做AI Agent落地项目&#xff0c;又遇到了一个典型问题&#xff1a;底层模型已经换到目前能用的最强版本&#xff0c;还是频繁在处理多步任务时"断片"。一会儿把昨天的日期算错&#xff0c;一会儿对着一个需要登录的后台页面反复尝试无效操作。团队成员都开始怀…

作者头像 李华
网站建设 2026/10/7 11:38:20

用开源扫地机拆透机器人工程全栈:从SLAM到PID的实战指南

我很少给人推荐那种几千块的成品教育机器人来入门机器人。真正让我把一套机器人工程知识学通的&#xff0c;其实是一台几百块的扫地机——外加开源社区的源码。你只要把一个开源扫地机器人项目全栈拆开看&#xff0c;从LDS激光雷达、编码器电机、FreeRTOS任务调度&#xff0c;到…

作者头像 李华
网站建设 2026/10/7 11:37:43

AEM水电解实时监测系统设计:以电源为中枢的高精度动态表征

1. 项目概述&#xff1a;为什么一个AEM水电解研究电池的监测&#xff0c;值得花整整三天搭一套专用系统&#xff1f;“Monitoring an AEM Water Electrolysis Research Cell”——这个标题看起来平平无奇&#xff0c;就是实验室里再普通不过的一句设备操作记录。但如果你真在电…

作者头像 李华