
1. 项目缘起一个让人抓狂的日常痛点但凡玩过 ESP32 的人大概率都经历过这样一个场景设备已经焊好、装进壳子、挂在墙上或者塞在某个角落跑了大半年突然要换 WiFi——可能是路由器换了、密码改了、或者要把设备从实验室挪到客厅。这时候你打开 Arduino IDE 或者 ESP-IDF改一行ssid和password重新编译插上 USB 线按住 BOOT 键重新烧录。如果设备装在够不着的地方那画面就更美了。这个项目要解决的就是这件事不重刷固件直接在浏览器里改 ESP32 的 WiFi 配置。核心思路是把 WiFi 凭据从编译期硬编码的常量挪到 ESP32 的NVSNon-Volatile Storage非易失性存储里然后在设备上跑一个轻量 Web 服务用浏览器访问就能读写这些键值。改完重启设备用新配置连网固件一行都不用动。适合谁来参考三类人最受益一是做 IoT 小产品、设备已经部署出去的开发者二是玩智能家居、懒得每次拆壳重烧的 DIY 玩家三是想搞明白 NVS 到底怎么用、Web 配置页怎么做的嵌入式初学者。哪怕你只会用 Arduino 框架跟着走也能落地。我先把结论摆在这整套方案的核心不是Web 服务器那部分而是配置读取的优先级设计和NVS 命名空间的规划。很多人第一次做会踩的坑几乎都出在这两块。下面我按实际做项目的顺序把设计思路、关键细节、完整实操和踩坑记录一层层拆开讲。2. 整体设计思路为什么是 NVS Web而不是别的方案2.1 三种常见方案对比与选型逻辑在动手之前先想清楚配置存哪、怎么改这两个问题。我实际试过或见过的主流方案有三种各自的取舍差别很大。方案配置存储位置修改方式优点致命缺点硬编码 重烧固件源码改代码重新烧录简单直接每次改都要接线、编译、烧录SD 卡 / 外部文件外部存储拔卡改文件不占内部 Flash需要额外硬件卡易松动NVS Web 配置页片内 Flash 的 NVS 分区浏览器访问无需接线、无需重烧需要设备先能连上首次配置需 AP 模式选 NVS Web 的理由很实在ESP32 片内 Flash 本来就有一块专门给 NVS 用的分区不额外花钱NVS 是键值数据库读写 API 成熟稳定Web 配置页对用户来说零门槛手机浏览器就能操作。SD 卡方案看着优雅但多一个硬件就多一个故障点设备震动、受潮都可能让卡接触不良反而更麻烦。注意NVS 不是万能的。它适合存小块的配置数据字符串、整数、布尔不适合存大文件或频繁写入的数据。NVS 的擦写寿命有限别把它当日志存储用。2.2 配置读取的优先级设计这是整个项目的灵魂真正让这个方案好用的不是能改而是改坏了还能救回来。我的设计是三级优先级NVS 中已保存的配置最高优先级——用户通过 Web 页改过的值。编译期默认值兜底——代码里写的DEFAULT_SSID/DEFAULT_PASSWORD。AP 配网模式最后防线——如果前两级都连不上设备自己开热点让用户连上来重新配。为什么这么设计因为实际部署中改错密码导致设备失联是最高频的事故。如果没有第三级兜底用户改错一个字符设备就变成砖只能拆下来重烧——那这个项目就白做了。有了 AP 兜底最坏情况也就是连上设备热点重新填一次。这个优先级逻辑用伪代码表达就是String ssid nvs_get_string(wifi, ssid, DEFAULT_SSID); String pass nvs_get_string(wifi, pass, DEFAULT_PASSWORD); if (tryConnect(ssid, pass, TIMEOUT_MS)) { startWebServer(); // 连上了正常跑业务 开配置页 } else { startAPMode(); // 连不上开热点等用户来配 startWebServer(); // AP 模式下同样开配置页 }2.3 NVS 命名空间规划别把所有东西塞一个筐NVS 的结构是命名空间namespace→ 键key→ 值value。命名空间相当于数据库里的表键相当于字段。我见过不少人图省事所有配置全塞进一个叫config的命名空间结果键名冲突、维护混乱。我的划分习惯是按功能域拆wifissid、pass、static_ip、gatewaydevicename、location、report_intervalmqttbroker、port、user、pass这样每个模块只管自己的命名空间代码解耦后期加功能也不会互相干扰。键名统一用小写加下划线长度控制在 15 字符以内——NVS 的键名上限是 15 个字符不含结尾符超了会直接报错这个坑我踩过。3. 核心细节解析NVS 操作与 Web 服务的实操要点3.1 NVS 读写 API 的正确打开方式Arduino 框架下用Preferences库操作 NVS 最省事它是对底层nvs_flash的封装。基本流程是打开命名空间 → 读/写 → 关闭。#include Preferences.h Preferences prefs; // 写入 prefs.begin(wifi, false); // false 读写模式 prefs.putString(ssid, newSsid); prefs.putString(pass, newPass); prefs.end(); // 必须关闭否则占用句柄 // 读取 prefs.begin(wifi, true); // true 只读模式 String ssid prefs.getString(ssid, DEFAULT_SSID); String pass prefs.getString(pass, DEFAULT_PASSWORD); prefs.end();几个必须记住的点begin()的第二个参数是只读标志。只读模式打开更快读配置时优先用只读。每次begin()后必须end()否则句柄泄漏多次操作后会失败。getString(key, default)的第二个参数是默认值键不存在时返回它——这正是我们做兜底逻辑的基础。写入操作会触发 Flash 擦写别在循环里高频写。配置类数据一次写入就够。提示如果你用 ESP-IDF 原生开发对应的是nvs_open/nvs_set_str/nvs_get_str/nvs_commit。记得nvs_set_str之后要nvs_commit才真正落盘Arduino 的Preferences帮你自动做了这一步。3.2 Web 配置页的最小实现Web 服务用WebServer库ESP32 自带就够了不需要上异步库。核心是两个路由一个返回配置页 HTML一个接收表单提交。#include WebServer.h WebServer server(80); void handleRoot() { String html form action/save methodPOST SSID:input namessidbr PASS:input namepass typepasswordbr buttonSave/button/form; server.send(200, text/html, html); } void handleSave() { String s server.arg(ssid); String p server.arg(pass); prefs.begin(wifi, false); prefs.putString(ssid, s); prefs.putString(pass, p); prefs.end(); server.send(200, text/html, Saved. Rebooting...); delay(1000); ESP.restart(); }这里有个细节值得说保存后直接重启。为什么不热切换 WiFi因为 ESP32 在已连接状态下切换 STA 配置行为不稳定容易卡死。重启一次几百毫秒换来的是确定性这笔账划算。3.3 表单安全与输入校验别让用户把设备搞死用户输入是不可信的。SSID 里可能有空格、中文、特殊符号密码可能超长。我一般做三层校验长度校验SSID 1~32 字节密码 8~63 字节WPA2 规范。空值校验SSID 不能为空密码允许为空开放网络。字符过滤HTML 表单提交的值做基本转义避免注入到页面里。if (s.length() 0 || s.length() 32) { server.send(400, text/plain, Invalid SSID length); return; }注意ESP32 的 WiFi SSID 按字节算一个中文字符占 3 字节UTF-8。如果你的 SSID 是中文32 字节其实只能放 10 个汉字左右。这个限制很多人不知道配中文热点名时容易翻车。4. 完整实操流程从零到能用的配置页4.1 环境准备与分区确认先确认你的开发环境。Arduino IDE 装好 ESP32 支持包或者用 PlatformIO。分区表用默认的default.csv就行它已经包含 NVS 分区。如果你想确认打开分区表看一眼# Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x50000x5000是 20KB存几十个配置键绰绰有余。如果你的项目 NVS 用量大可以调大这个值但要注意别和其他分区重叠。4.2 核心代码骨架把前面几块拼起来一个完整的setup()逻辑长这样void setup() { Serial.begin(115200); // 1. 读配置 prefs.begin(wifi, true); String ssid prefs.getString(ssid, DEFAULT_SSID); String pass prefs.getString(pass, DEFAULT_PASSWORD); prefs.end(); // 2. 尝试连接 WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); unsigned long start millis(); while (WiFi.status() ! WL_CONNECTED millis() - start 15000) { delay(500); Serial.print(.); } // 3. 分支处理 if (WiFi.status() WL_CONNECTED) { Serial.println(\nConnected: WiFi.localIP().toString()); } else { Serial.println(\nFailed, starting AP mode); WiFi.mode(WIFI_AP); WiFi.softAP(ESP32-Config, 12345678); Serial.println(AP IP: WiFi.softAPIP().toString()); } // 4. 启动 Web 服务 server.on(/, handleRoot); server.on(/save, HTTP_POST, handleSave); server.begin(); } void loop() { server.handleClient(); }15 秒的超时是我反复调出来的经验值。太短路由器 DHCP 慢的时候会误判太长用户等得心焦。15 秒是个平衡点。4.3 参数选择背后的计算超时时间怎么定我算过一笔账ESP32 连接 WiFi 的典型耗时是 2~5 秒路由器 DHCP 分配 IP 通常再加 1~3 秒网络拥堵或信号弱时可能到 10 秒。取 15 秒覆盖了 95% 以上的正常情况同时不至于让用户等太久。如果你部署环境信号特别差可以调到 20 秒但别超过 30 秒——超过这个数用户会以为设备死机了。AP 模式的密码12345678是临时用的用户连上配完就走。你也可以设成开放热点但那样附近任何人都能连进来改你的配置安全性差。8 位数字密码是个折中够用且好记。4.4 实测记录我在一块 ESP32-WROOM-32 上实测首次烧录后设备用默认配置连上家里路由器浏览器访问它的 IP改 SSID 和密码点保存设备重启约 8 秒后连上新网络。整个过程不用碰 USB 线。第二次故意填错密码设备 15 秒后自动进入 AP 模式手机连上ESP32-Config热点访问192.168.4.1重新配置恢复正常。兜底逻辑生效。5. 常见问题与排查技巧实录5.1 高频问题速查表现象可能原因排查方向保存后连不上密码错误 / SSID 含特殊字符进 AP 模式重配检查字节长度Web 页打不开设备没连上 / IP 变了串口打印 IP或用 mDNSNVS 写入失败命名空间未关闭 / 分区满检查end()调用看分区大小重启后配置丢失没调commit/ 写到了只读句柄确认begin用读写模式AP 模式连不上热点名冲突 / 密码错换热点名确认 8 位密码5.2 几个只有踩过才知道的坑坑一Preferences句柄没关第二次begin直接失败。这个错误很隐蔽因为第一次读写都正常第二次才出问题。养成begin 和 end 成对出现的习惯或者用 RAII 思路封装。坑二SSID 里的空格和中文。表单提交时URL 编码会把空格变成或%20WebServer库的arg()会自动解码但如果你手动拼 URL 就容易出错。中文 SSID 更要小心字节长度。坑三AP 模式和 STA 模式切换不干净。从 STA 切到 AP 前先调WiFi.disconnect(true)彻底断开否则可能残留状态导致 AP 起不来。我一开始没加这句AP 模式十次有三次失败。坑四mDNS 让访问更省心。与其记 IP不如起个 mDNS 名字浏览器访问esp32.local就行。加两行代码#include ESPmDNS.h MDNS.begin(esp32);这样用户不用去串口看 IP体验直接上一个台阶。5.3 安全加固的几点建议配置页默认没有鉴权局域网内谁都能访问。如果设备部署在公共网络建议加一层简单认证在handleSave里检查一个预设的 token或者用 HTTP Basic Auth。另外配置页只在需要时开启——正常运行时可以关掉 Web 服务省电也省心。我的做法是设备启动后开 5 分钟配置窗口超时自动关闭需要时通过物理按键重新唤起。6. 后续可扩展的方向这套骨架搭好之后能扩展的地方很多。比如把配置页做成带扫描功能的——列出周围 WiFi点一下自动填 SSID省得手输。再比如加个恢复出厂设置按钮一键清空 NVS 回到默认值。还可以把配置项做成动态的从 JSON 描述文件生成表单加新配置项不用改前端代码。我自己在实际项目里还加了一个小功能配置页显示当前连接状态和信号强度用户一眼就能看出设备连得稳不稳。这个信息对排查设备时好时坏的问题特别有用比翻串口日志直观多了。最后分享一个我踩过好几次才总结出的经验NVS 的键名和命名空间一旦定下来尽量别改。因为设备部署出去之后老固件写进去的键名和新固件读的键名对不上配置就丢了。如果非要改记得在新固件里做一次迁移逻辑把老键的值读出来写到新键再删掉老键。这个迁移代码写起来不复杂但能省掉大量现场返工的麻烦。