ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESP32 NVS + 浏览器配置WiFi:彻底告别重编译烧录

ESP32 NVS + 浏览器配置WiFi:彻底告别重编译烧录 说实话搞 ESP32 开发的这两年我最烦的一件事就是家里路由器一换密码所有靠 WiFi 联网的设备都得重新编译一遍固件。改个字符串、点一下编译、再插线烧录来回少说十分钟多则半小时。如果设备已经装进外壳、焊在电路板上还得拆机。后来我换了个思路——把 WiFi 的 SSID 和密码存进 NVS再在 ESP32 上跑一个极简的浏览器配置页面。需要换网时直接手机连上设备的热点打开网页改一下键值就行完全不用重刷固件。这个方案我已经在几个项目里稳定跑了大半年今天把它拆开讲清楚包括原理、代码、避坑点给同样被改密码重刷折磨过的朋友一个可以直接抄作业的参考。适合正在做 WiFi 联网项目、又不想每次部署后都靠串口线或重编译维护设备的开发者。1. 痛点场景改个 WiFi 密码为什么这么麻烦1.1 传统改密码的三种姿势没一个省心的先说最常见的做法很多初学 ESP32 的朋友喜欢把 WiFi 信息直接硬编码在代码里。const char* ssid MyHome_WiFi; const char* password password123;这种方式开发阶段很爽连上就能跑。但真到设备装好之后问题就来了路由器改密码、加了个信号更好的热点、带去朋友家临时蹭个网……任何一次网络变动这行字符串就得变。于是流程变成改代码 - 打开编译 - 等待 - 插 USB 线 - 烧录 - 重启。如果设备放在天花板上、嵌在墙里、或者螺丝封死在外壳里每次都得拆机脾气再好也会崩溃。第二种方式是把 WiFi 信息存进 SPIFFS / LittleFS 里的配置文件。这样改密码不用重编译但需要把配置文件单独上传到 flash 文件系统。听起来不错实操起来还是要连数据线而且对小白要解释怎么用 Arduino IDE 的插件上传文件到 SPIFFS这一步就能劝退一大半人。第三种方式就是串口改。接一根 USB-TTL打开串口监视器发送指令比如set ssid MyHome、set pass pass123。这个方案给了我们一个新思路把配置数据从代码里剥离出来、放到一个可随时修改的存储区。但它仍然依赖物理连接而且用户需要记住一套命令。对开发者还行对使用设备的家人朋友来说完全不现实。所以真正方便的方案应该是不需要数据线、不需要额外 App、不需要让用户理解任何底层概念打开一个网页填两个框点保存完事。1.2 NVS 是什么为什么它正好解决这个问题NVS 的全称是 Non-Volatile Storage非易失性存储。它位于 ESP32 内部 flash 的一个分区里专门用来保存键值对数据。你可以把它理解成一个带标签的小抽屉柜每个抽屉有一个名字key抽屉里可以放整数、字符串、二进制数据等。断电之后数据不会丢下次上电照样能读出来。在 ESP-IDF 和 Arduino 框架里NVS 的读写 API 是现成的。它相比 SPIFFS / LittleFS 最大的优势是天生为保存配置项设计比如 WiFi 账号、设备名称、传感器校准值这一类量少、但需要频繁修改的数据。NVS 内部还做了磨损均衡和掉电保护你不用操心哪块 flash 被写坏了也不用担心写一半断电导致数据损坏。项目标题里说的浏览器工具能直接改 NVS 键值其本质就是ESP32 同时扮演两个角色——一个 WiFi 设备一个微型 Web 服务器。设备启动时从 NVS 读取 WiFi 配置去连接路由器。如果你需要改配置设备就进入 AP 热点模式你手机连上它的热点在浏览器里打开一个配置页面填上新的 WiFi 密码提交给 ESP32 的 Web 接口ESP32 再把数据写回 NVS 并重启。整个过程零数据线、零重编译、几分钟搞定。这也是我在项目里最终采用的方案。它没有引入任何复杂的框架就是 ESP32 自带的能力却能把改密码从一次开发流程变成一次日常操作。2. 方案选型为什么是浏览器 NVS而不是其他组合2.1 设备端跑 Web 服务器的思路ESP32 本身带 WiFi 硬件既支持 STA连接路由器模式也支持 AP开热点模式甚至两者可以共存。利用这一点我们可以在设备固件里内置一个极小的 HTTP 服务。硬件上唯一的额外要求是闪存空间几十 KB 的代码量对 ESP32 来说九牛一毛。整个交互逻辑非常简单设备上电尝试从 NVS 读取 WiFi 配置。如果读到有效的 SSID 和密码就 STA 模式连路由器连不上或者超时回退到 AP 模式。如果 NVS 里根本没有配置直接进入 AP 模式热点名比如ESP-Config-XXXX。手机或电脑连上这个热点后浏览器访问http://192.168.4.1得到网页表单填入新的 WiFi 信息。设备端收到 POST 请求后把内容写入 NVS、提交、重启然后以新的配置尝试连接路由器。这套逻辑在任何支持 WiFi 的嵌入式平台上都是通用的。ESP32 的优势在于它自带 WebServer 相关的库用 Arduino 框架时可以直接用WebServer.h不需要自己拼 HTTP 报文大大降低实现门槛。2.2 四种方案对比为什么浏览器方案胜出我自己把这几种方式放在一张表里对比过选型时很直观方案需要数据线需要安装软件对普通用户友好度开发成本部署后维护成本改代码重刷固件是是极差低极高每次改都要重编译串口命令修改是是差要懂指令中高现场维护麻烦手机 App 配网否是中要装 App高要改双端中浏览器 Web 配置否否好有手就会中低极低手机浏览器即可从表格能看出浏览器方案在部署后维护成本这个维度上几乎是最优的。它没有一个特定的客户端需要维护任何带浏览器的设备都能操作。开发成本比手机 App 低得多因为整个交互界面只是一个 HTML 页面加几个 HTTP 接口。另外一个隐性优点是排障方便。网页是你自己写的调试时可以直接用浏览器开发者工具看请求是否成功、POST 参数是否正常比调 App 和串口程序都要直观。我在开发过程中遇到问题基本是打开浏览器 F12 就能定位效率高很多。2.3 为什么不直接用现成的 WiFiManager 库我知道有人会说tzapu/WiFiManager 这个库不是早就实现了类似功能吗确实WiFiManager 是 ESP32/ESP8266 社区很成熟的配网方案它也支持把自定义参数保存到 NVS。但我在实际使用中发现它有两个让我不太舒服的地方。第一WiFiManager 的功能很全全到很多项目用不到代码体积和逻辑复杂度都比自己写一个小模块要高。如果项目本身很精简引入一整套 WiFiManager 反而有点重。第二它的配网页面和保存逻辑是封装好的我要想定制页面比如加一个设备名称输入框、加一个MQTT 服务器地址栏就得去读它的回调接口文档折腾一圈下来不如自己写一个三十行表单加十行处理函数来得直接。当然如果你不想写任何代码直接用 WiFiManager 也是一个非常靠谱的选择。我这里讲的是浏览器工具改 NVS 键值这一思路的最小实现顺便也能让你彻底理解它背后的机制以后不管是自己写还是用库心里都有底。3. 核心细节NVS 键值操作与 Web 配置页的配合3.1 NVS 键值读写中最容易踩的三个细节NVS 的 API 形态在 Arduino 和 ESP-IDF 下略有不同但核心逻辑都是围绕几个函数展开的nvs_open/nvs_get_str/nvs_set_str/nvs_commit/nvs_close。我在写代码时发现三个细节新手非常容易忽略。第一写入后一定要调用nvs_commit。很多人写完nvs_set_str就直接断电发现数据没存住。原因是nvs_set_str只是把数据写入缓存nvs_commit才真正落盘到 flash。虽然某些情况下系统会自动提交但这是不确定行为不能依赖。第二读取字符串前要先查询长度。NVS 读字符串不是直接给一个目标缓冲区就完事它需要你告诉它缓冲区多大。如果你不知道字符串多长正确做法是先调用nvs_get_str(handle, key, NULL, length)拿到实际长度后再用这个长度分配缓冲区然后第二次调用读取。我看到很多初学者固定一个 64 字节数组结果密码一长就溢出数据被截断WiFi 怎么都连不上。第三键名有限制。NVS 的 key 最长 15 个字符不能包含特殊符号命名空间最长也是 15 个字符。我在早期项目里用过Router_Password_2023这种键名结果写入直接返回错误查了半天文档才发现是长度超限。现在统一用简短风格比如wifi_ssid、wifi_pass。3.2 Web 页面到底该放哪嵌入固件还是放文件系统要让浏览器能打开配置页面页面本身有两种存放方式。一种是直接把 HTML 作为字符串常量放进固件比如 Arduino 里的const char index_html[] PROGMEM Rrawliteral(...)rawliteral;。好处是页面总是存在不依赖文件系统烧录用一种方式就完事坏处是如果你想把页面做得复杂一点比如加个 SVG 图标、加个 JS 脚本代码文件会变得很长阅读和维护都不舒服。另一种方式是放在 LittleFS / SPIFFS 分区里通过LittleFS.begin()和server.serveStatic()来提供访问。好处是页面和固件解耦改页面不用重新烧录固件坏处是首次烧录时要额外上传一次文件系统镜像对不熟悉这套流程的人来说多了一道手续。我自己目前偏好嵌入固件因为配置页本身很轻量一个表单加一小段说明文字就够了。让用户少一个操作步骤维护成本也更低。如果你的页面比较复杂、需要经常调整样式再考虑 LittleFS 方案不迟。3.3 表单提交GET 还是 POST浏览器页面提交数据到 ESP32有 GET 和 POST 两种方式。网上一搜一大把示例用的是 GETURL 长这样http://192.168.4.1/save?ssidMyHomepasspassword123。GET 的好处是代码少服务器端处理简单server.arg(ssid)就能拿到值。但它有两个明显的坑一是 URL 有长度限制二是 WiFi 密码会出现在浏览器历史记录、路由器日志里。虽然这只是局域网内的一次配置操作风险有限但养成用 POST 的习惯总是好的。POST 的处理同样不复杂在 Arduino 的 WebServer 库中你只需要在表单里写methodpost然后服务端用server.arg(ssid)照样能取到参数保存逻辑没有任何额外负担。所以我会建议直接上 POST别在局域网里也把密码挂在 URL 上。4. 手把手实操把NVS 浏览器配置工具集成进你的 ESP32 工程4.1 搭建最小工程结构我用的是 PlatformIO Arduino 框架因为它的platformio.ini管理依赖更干净编译速度的瓶颈主要体现在首次编译后续增量编译其实也挺快。工程整体结构如下esp32_nvs_web_config/ ├── platformio.ini ├── src/ │ └── main.cpp └── include/ └── config.hplatformio.ini里最核心的内容就是选择开发板和上传方式。我用的是esp32dev烧录速度默认 921600 波特率实测很稳不用特地去改。[env:esp32dev] platform espressif32 board esp32dev framework arduino monitor_speed 115200 board_upload.flash_size 4MB如果你用的是 Arduino IDE新建工程后记得在开发板管理器里选好对应芯片型号比如 ESP32-S3 要选ESP32S3 Dev Module。剩下的代码逻辑完全一样只是编译和烧录入口不同。4.2 核心代码NVS 读取 Web 服务直接上关键代码这些代码我在多个项目里复用实测稳定。先定义两个全局变量存 WiFi 配置再写 NVS 读写函数#include WiFi.h #include WebServer.h #include Preferences.h Preferences prefs; String wifi_ssid ; String wifi_pass ; void loadWiFiConfig() { prefs.begin(wifi, true); // 只读模式打开命名空间 wifi wifi_ssid prefs.getString(ssid, ); wifi_pass prefs.getString(pass, ); prefs.end(); } void saveWiFiConfig(String ssid, String pass) { prefs.begin(wifi, false); // 读写模式 prefs.putString(ssid, ssid); prefs.putString(pass, pass); prefs.end(); // Preferences 库在 end 时会自动 commit不需要手动调 }这里我用的是Preferences库它是 Arduino 对 NVS 的封装API 比原生 ESP-IDF 的nvs_*函数友好得多。prefs.end()内部会做提交操作所以写完后不用担心数据没落盘。接下来是 Web 服务部分。用WebServer对象监听 80 端口注册两个路径根路径/返回配置页表单/save接收 POST 并保存WebServer server(80); void handleRoot() { String html !DOCTYPE htmlhtmlhead meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1 titleESP32 WiFi配置/title/headbody h2WiFi 配置/h2 form methodpost action/save labelWiFi 名称/labelbr input typetext namessid value wifi_ssid brbr labelWiFi 密码/labelbr input typepassword namepass value wifi_pass brbr input typesubmit value保存并重启 /form/body/html; server.send(200, text/html, html); } void handleSave() { if (server.hasArg(ssid) server.hasArg(pass)) { String newSSID server.arg(ssid); String newPass server.arg(pass); saveWiFiConfig(newSSID, newPass); server.send(200, text/html, h3已保存设备正在重启.../h3); delay(1000); ESP.restart(); } else { server.send(400, text/html, h3缺少参数/h3); } }这段代码里的一个细节是密码框用了typepassword不至于让旁边的人一眼看到密码。另一个细节是保存成功后先server.send再delay再重启如果直接ESP.restart()浏览器可能还没收到响应页面就断了用户体验会差一些。4.3 启动逻辑没有配置就进入 AP 模式整个工具的灵魂是启动逻辑。设备上电后先读 NVS判断有没有可用的 WiFi 配置。如果没有或者连接路由器失败就打开 AP 模式并启动 Web 服务。void setup() { Serial.begin(115200); loadWiFiConfig(); if (wifi_ssid.length() 0) { WiFi.mode(WIFI_STA); WiFi.begin(wifi_ssid.c_str(), wifi_pass.c_str()); unsigned long start millis(); while (WiFi.status() ! WL_CONNECTED millis() - start 10000) { delay(500); Serial.print(.); } } if (WiFi.status() ! WL_CONNECTED) { // 没有配置或连接失败进入 AP 配置模式 WiFi.mode(WIFI_AP); WiFi.softAP(ESP-Config-1234); server.on(/, handleRoot); server.on(/save, HTTP_POST, handleSave); server.begin(); Serial.println(AP Mode: http://192.168.4.1); } else { Serial.print(Connected, IP: ); Serial.println(WiFi.localIP()); } } void loop() { server.handleClient(); }这段代码有一个细节值得展开讲连接超时时间我设的是 10 秒。如果设得太短比如 3 秒路由器握手慢一点就会误判连接失败导致设备频繁进入 AP 模式用户刚把手机连上热点设备又重启了体验很差。如果设得太长比如 30 秒又会让切换 WiFi这个动作变得很笨重。10 秒是我试下来比较平衡的值当然你如果知道自己的路由器握手很快也可以缩短到 5 秒。另一个容易忽略的点是设备连接成功后我故意没有启动 Web 服务。也就是说正常情况下设备是不会对外开放配置页面的——只有当你需要改密码、让设备进不了网络的时候它才会开启配置模式。这样既符合直觉也减少了一个可以被随意改动配置的暴露面。4.4 浏览器端实际操作完整流程演示假设你现在已经烧录好了这个固件接下来就是普通用户视角的操作了。路由器改了密码ESP32 连不上网络自动进入 AP 模式。此时设备上的 LED 如果是可编程的建议做成快闪提示方便用户感知它在等配置。手机打开 Wi-Fi 列表找到ESP-Config-1234这个热点连接。注意这个热点不需要密码因为它的作用只是让你进入配置页。打开手机浏览器输入http://192.168.4.1注意有些安卓手机会因为页面不满足 HSTS 强制跳转 HTTPS如果打不开手动换成http://就正常了。页面上有两个输入框WiFi 名称、WiFi 密码。填好新路由器的信息点击保存并重启。浏览器提示已保存设备正在重启...设备随即进入 STA 模式尝试连接新的 WiFi。回到手机 Wi-Fi 列表选回你家里的路由器整个改动就完成了。实测下来整个操作耗时不到 1 分钟。对比原来的重编译 找线 拆机流程这就是降维打击。5. 常见问题与排查技巧实录5.1 修改完密码设备还是连不上原来那个 WiFi这是我踩过最多次的坑。排查看似简单其实涉及好几层。首先确认设备是否真的进入了配置模式而不是正常连接模式。如果你用了串口监视器能看到打印信息比如Connected, IP: 192.168.x.x说明它已经连上了旧网络这时你在 AP 热点里根本找不到它。如果串口打印的是AP Mode那就说明它确实在等待配置。其次检查你是否写对了键名。Preferences库里的命名空间和键名是大小写敏感的ssid和SSID会被识别为完全不同的两个 key。我在项目里统一用小写避免混用。如果读取和保存用的键名不一致保存永远成功但启动时读到的还是空的。最后如果保存成功但连接还是失败大概率是密码本身输错了。这边建议在配置页上额外增加一个密码可见的复选框用 JavaScript 切换typepassword和typetext能显著减少手滑输错的概率。别小看这个小功能它能省掉你大量远程排障的时间。5.2 手机怎么都打不开 192.168.4.1这个问题有几个常见原因。第一个原因最普遍手机连上 ESP32 的热点后安卓系统检测到这个热点没有互联网会在系统层面拦截流量很多手机会弹出一个是否保持连接的提示。如果你点了断开或者没留意手机就会自动切回有网络的 WiFi导致配置页根本访问不了。正确做法是在系统提示弹出来时选择保持连接 / 不切换网络。第二个原因跟浏览器有关。有些手机浏览器默认走 HTTPS访问http://192.168.4.1时反而会报错或者一直转圈。解决办法是在地址栏完整输入http://192.168.4.1如果还不行换一个浏览器试试比如 Chrome 或 Safari 一般都没问题。第三个原因就比较冷门了如果你电脑上开着别的虚拟网卡比如 VMware、VirtualBox 的虚拟适配器访问 192.168.4.1 时流量可能走错网卡。这种场景下把虚拟网卡先禁用再访问一般就通了。我在 Windows 笔记本上遇到过几次禁用后秒开。5.3 关于安全和权限自己的设备也要留点心用浏览器工具改配置本质上是把一个 Web 服务暴露给了局域网。虽然设备只在 AP 模式下提供配置页但如果有别有用心的设备连着你的热点理论上也能打开这个页面把你的 WiFi 密码改掉。这种场景在城市里概率不高但我还是建议做两步加固。第一步给配置页加一个简易访问密码比如在 URL 上加一个 tokenhttp://192.168.4.1/?token1234或者直接让 AP 热点本身带密码。热点密码可以固定写进固件或者用设备唯一 ID 生成比如ESP-Config-1234本身就是一个可传播的标识热点密码同步显示在串口打印和外壳标签上即可。第二步不要在产品页面上明文回显密码。默认情况下我的示例代码是回显wifi_pass的这是为了方便调试。但在真正给别人用的产品里我会把密码输入框留空仅显示已配置如需修改请输入新密码。这一来防止旁观者偷看二来避免用户在服务器端日志里留下明文密码。还有一点是固件本身的安全NVS 里的数据默认不加密如果有人拿到设备、读出 flash仍然可以提取到 WiFi 密码。如果这个设备用在安全性要求较高的环境可以考虑启用 ESP32 的 flash 加密特性但这是另一个话题了普通家用场景不必过度担心。5.4 频繁重启、NVS 写入磨损问题你可能会担心万一用户手滑一天改 10 次 WiFi 密码NVS 会不会被写坏实际上 NVS 底层有磨损均衡机制会把写入操作分散到不同 flash 扇区。按照 ESP-IDF 的文档说明NVS 可以承受上万次甚至更多次写入。一天改 10 次的话理论上可以用好几年根本不用焦虑。真正需要留意的是不要用这个机制去存高频变化的数据比如传感器每秒钟上报一次就把当前值写入 NVS那才是加速损耗的用法。配置类数据本质上就是偶尔改一次、经常读一次和 NVS 的定位完美匹配。6. 写在最后的一些体会这套浏览器改 NVS 键值的方案我现在已经集成到了三个项目里一个是智能家居网关一个是阳台自动浇灌控制器还有一个是给朋友做的桌面温湿度计。每次他们换路由器只需要在微信上问我一句怎么改我把操作步骤发过去两分钟就搞定再也不用上门拆机了。我觉得它最核心的价值不在于代码本身有多复杂而在于它改变了设备维护的交互方式把配置从开发者的编译烧录流程中解放出来变成普通用户也能完成的日常操作。你仔细想想NVS 只是提供了一个可靠的存储池浏览器的价值在于它成为了一种跨平台的配置面板。你甚至不需要额外写 App不需要学习什么协议就用了互联网世界最通用的网页 表单这一套语言把嵌入式设备和普通用户连接了起来。最后分享一个小彩蛋这个思路不只是能改 WiFi 密码。你可以把 MQTT 服务器地址、设备名称、上报间隔、GPIO 引脚定义甚至温度校准值都可以用同样的方式存储到 NVS 并通过网页修改。等你想扩展功能的时候只需要在表单里加几个输入框、在保存函数里多写两行putString配置能力立刻翻倍。我现在的新项目默认就会做一个高级配置页把所有需要在现场调、但又不常见到的参数都塞进去开发调试时省了非常多事。
返回列表