ESP8266/ESP32 WiFi模块Web配网:从原理到代码实现与优化
1. 项目概述为什么WiFi模块的Web配置是“通用好使”的搞嵌入式开发或者物联网项目的朋友对WiFi模块肯定不陌生。无论是ESP8266、ESP32还是其他厂家的串口WiFi模块拿到手第一件事就是配置它联网。早年大家可能都经历过打开串口调试助手对着AT指令手册一条一条敲命令设置SSID、密码、工作模式。这个过程不仅繁琐而且一旦指令敲错一个字母或者波特率没对上就得从头再来调试效率很低。后来很多模块厂商提供了手机App进行配置这确实方便了一些。但App往往有平台限制比如只支持安卓或者需要下载特定软件在工业现场、批量生产或给最终用户部署时依然不够灵活。这时候基于Web的配置方式就显现出它的“通用”优势了。所谓Web配置就是让WiFi模块在启动后自身先作为一个WiFi接入点AP我们用手边任何一台设备手机、电脑、平板连接上这个AP然后在浏览器里打开一个特定的IP地址通常是192.168.4.1就能看到一个配置页面。在这个页面上我们可以像设置家用路由器一样通过表单选择或输入目标WiFi的网络名SSID、密码、工作模式STA/AP/STAAP等参数提交后模块会自动重启并尝试连接。这个方法为什么“绝对通用好使”第一它不依赖任何特定操作系统或额外软件只要设备有浏览器和WiFi功能就能用覆盖了从手机到电脑的所有智能设备。第二它对用户极其友好图形化界面避免了记忆和输入复杂AT指令的麻烦。第三对于开发者而言一套Web配置代码可以固化在模块的固件中适配各种不同的主控芯片STM32、Arduino、树莓派等只要它们通过串口与WiFi模块通信即可实现了跨平台的“一次开发到处配置”。今天我就结合自己多次在项目中实现Web配网的经验把这套方案的里里外外、从原理到代码实现细节以及那些容易踩坑的地方给大家彻底讲透。2. 核心原理与方案选型从AT指令到Web服务器的跨越要理解Web配置得先知道没有它的时候我们是怎么做的。最原始的方式就是串口发送AT指令。模块上电后主控MCU通过串口发送像ATCWMODE1\r\n设置STA模式、ATCWJAPSSID,password\r\n连接AP这样的指令。这种方式直接、底层但所有逻辑和交互都压在MCU的程序里不灵活。Web配置的本质是在WiFi模块内部或与MCU配合运行了一个微型的Web服务器。这个服务器通常只处理少数几个页面和请求资源占用极小。整个流程可以拆解为以下几个核心阶段2.1 模块启动与SoftAP模式建立模块上电后首先执行初始化程序将自身设置为AP模式Access Point即无线接入点。此时它会广播一个特定的SSID比如“ESP8266_Config”或“Device_Setup_XXXX”。同时它会启动一个DHCP服务器为连接上来的设备如你的手机自动分配一个局域网IP地址如192.168.4.100它自己的IP通常固定为192.168.4.1。这个阶段模块与世界的关系是它自己创造了一个微型的、封闭的无线网络环境。2.2 嵌入式Web服务器响应在这个微型网络中模块内运行的Web服务器开始监听80端口HTTP默认端口。当你用手机连接上“ESP8266_Config”这个WiFi后在浏览器输入http://192.168.4.1浏览器就会向这个地址的80端口发起一个HTTP GET请求。模块的Web服务器收到请求后会返回一个HTML页面。这个页面就是我们看到的配置界面。它可能包含表单、下拉菜单、输入框等元素。2.3 配置信息的提交与处理你在网页上填写好目标WiFi的SSID、密码选择工作模式通常是STA模式即让模块作为客户端去连接家里的路由器点击“提交”或“连接”按钮。这时浏览器会向服务器模块发起一个HTTP POST请求请求体内就携带了你填写的所有表单数据格式通常是application/x-www-form-urlencoded。模块的Web服务器程序需要解析这个POST请求从中提取出SSID、密码等关键信息。然后它需要做两件最重要的事第一将这些网络配置参数保存到非易失性存储器中例如ESP系列常用的SPIFFS文件系统或Preferences库或者模块的EEPROM。确保掉电后信息不丢失。第二根据新的配置重启网络服务。这意味着模块需要先断开当前的AP模式然后根据保存的参数切换到STA模式并主动去连接你指定的那个路由器。2.4 连接反馈与模式切换连接尝试会有成功或失败两种结果。优秀的Web配置方案会提供反馈机制。一种常见做法是模块在尝试连接路由器时同时开启一个TCP服务器或者继续短暂提供Web服务让手机能在一定时间内查询连接状态。更通用的做法是模块连接成功后可以通过串口向主MCU发送状态信息或者如果模块自身有LED可以用不同的闪烁模式来指示状态例如快闪表示正在连接常亮表示连接成功慢闪表示连接失败。方案选型上对于像ESP8266/ESP32这类功能强大的芯片我们可以直接在其上开发完整的Web服务器逻辑使用Arduino框架的ESP8266WebServer或ESP32的WebServer库非常方便。对于更简单的、仅支持AT指令的串口WiFi模块如某些厂家的透传模块则需要主MCU来扮演Web服务器的角色MCU需要实现一个简单的TCP/IP协议栈可以使用轻量级库如lwIP并解析HTTP协议生成HTML页面。后一种方案对MCU的资源要求较高但优点是能适配几乎所有串口WiFi模块。本文讨论的重点是前一种方案即在ESP8266/ESP32这类片上系统SoC上实现这也是目前最主流、最高效的方式。3. 环境准备与核心库依赖动手之前我们需要把开发环境搭好。这里以最常见的Arduino IDE开发ESP8266为例ESP32的过程几乎完全一样。3.1 硬件准备WiFi模块NodeMCU ESP8266开发板、Wemos D1 mini或ESP32开发板一块。建议初学者从NodeMCU开始引脚丰富自带USB转串口。USB数据线用于供电和程序下载。一台无线路由器用于提供最终要连接的目标网络。电脑和智能手机电脑用于编程和串口监控手机用于测试Web配置界面。3.2 软件环境配置安装Arduino IDE从官网下载并安装最新版。添加开发板支持打开Arduino IDE进入“文件”-“首选项”在“附加开发板管理器网址”中输入http://arduino.esp8266.com/stable/package_esp8266com_index.json(对于ESP8266) 对于ESP32则需要添加https://espressif.github.io/arduino-esp32/package_esp32_index.json安装开发板打开“工具”-“开发板”-“开发板管理器”搜索“esp8266”或“esp32”找到由Espressif Systems提供的包点击安装。安装核心库我们主要依赖两个库它们通常随开发板包一起安装但最好确认一下。ESP8266WiFi(或 ESP32的WiFi库) 提供WiFi连接的核心功能。ESP8266WebServer(或 WebServer for ESP32) 这是我们实现Web配置服务器的核心。它允许我们定义处理特定URL路径如“/”、“/config”、“/save”的函数。注意在Arduino IDE的“库管理器”中搜索“ESP8266WebServer”或“WebServer”确认其已安装。有时网络问题可能导致安装不完整如果编译时报错找不到相关头文件可以尝试通过库管理器重新安装。3.3 项目代码结构规划在开始写代码前脑子里先有个清晰的蓝图。一个典型的Web配置项目其逻辑流程和代码结构大致如下初始化阶段上电后首先尝试从存储区如EEPROM或文件系统读取之前保存的WiFi配置。网络尝试阶段如果存在保存的配置则用这些配置尝试连接路由器STA模式。设置一个超时时间例如10-15秒。配网模式判断成功连接成功打印IP地址主Web服务器提供设备实际功能启动。失败/无配置连接失败或首次使用。则启动SoftAP模式创建配置网络并启动配网专用的Web服务器。配网服务器阶段配网Web服务器提供几个简单的页面和接口GET /: 返回配置主页HTML表单。GET /scan: 返回一个JSON包含当前可扫描到的所有WiFi网络列表用于前端页面动态填充SSID下拉框。POST /save: 接收前端提交的表单数据保存配置重启模块。配置保存与重启接收到新配置后将其存入非易失存储然后调用ESP.restart()重启模块让新配置生效。这个流程实现了“智能连接”有配置且有效则直连无效或没有则进入配网状态。用户体验非常顺畅。4. 代码实现详解从AP启动到配置保存接下来我们分步实现核心代码。我会先给出关键代码片段然后解释其作用和注意事项。4.1 定义全局变量与引入库#include ESP8266WiFi.h // ESP8266核心WiFi库 #include ESP8266WebServer.h // ESP8266 Web服务器库 #include EEPROM.h // 用于存储配置简单方案 // 定义Web服务器对象监听80端口 ESP8266WebServer server(80); // 定义存储WiFi配置的结构体 struct WiFiConfig { char ssid[32]; char password[64]; }; WiFiConfig config; // 软AP的配置 const char* apSSID ESP8266_Config; // 配网时的AP名称 const char* apPassword NULL; // 配网AP密码设为NULL则开放网络这里我们使用EEPROM来存储配置对于简单的SSID和密码足够用。对于更复杂的配置项建议使用Preferences库(ESP32)或SPIFFS/LittleFS文件系统。4.2 初始化与读取保存的配置在setup()函数开始部分void setup() { Serial.begin(115200); delay(1000); // 初始化EEPROM我们打算使用512字节空间 EEPROM.begin(512); loadConfig(); // 从EEPROM加载配置 // 尝试用保存的配置连接WiFi WiFi.mode(WIFI_STA); // 先设置为STA模式 WiFi.begin(config.ssid, config.password); Serial.print(Connecting to ); Serial.println(config.ssid); // 等待连接设置超时 unsigned long startAttemptTime millis(); const unsigned long timeout 15000; // 15秒超时 while (WiFi.status() ! WL_CONNECTED millis() - startAttemptTime timeout) { delay(500); Serial.print(.); } Serial.println(); // 判断连接结果 if (WiFi.status() WL_CONNECTED) { // 连接成功进入正常工作模式 Serial.println(WiFi connected!); Serial.print(IP address: ); Serial.println(WiFi.localIP()); startMainServer(); // 启动设备的主功能服务器 return; // 跳出不进入配网模式 } else { // 连接失败或超时进入配网模式 Serial.println(Failed to connect. Starting configuration AP...); startConfigAP(); } }loadConfig()函数负责从EEPROM的特定地址读取数据到config结构体。这里的关键是超时机制。不能让模块无限期地尝试连接一个可能不存在的网络15-30秒是一个合理的超时时间。连接成功后我们调用startMainServer()这里可以放置你设备真正的业务逻辑比如传感器数据上报、控制接口等。连接失败则进入startConfigAP()。4.3 启动配置AP和Web服务器void startConfigAP() { // 设置为APSTA模式有时需要AP模式提供Web服务的同时扫描网络 WiFi.mode(WIFI_AP_STA); // 启动软AP WiFi.softAP(apSSID, apPassword); Serial.print(Configuration AP started. SSID: ); Serial.println(apSSID); Serial.print(AP IP address: ); Serial.println(WiFi.softAPIP()); // 通常是 192.168.4.1 // 设置Web服务器路由 server.on(/, HTTP_GET, handleRoot); // 主页 server.on(/scan, HTTP_GET, handleScan); // 扫描WiFi server.on(/save, HTTP_POST, handleSave); // 保存配置 server.onNotFound(handleNotFound); // 404处理 // 启动Web服务器 server.begin(); Serial.println(HTTP server started); } void loop() { // 如果处于配网模式则需要处理客户端请求 server.handleClient(); // 这里可以添加配网状态下的其他逻辑如LED闪烁提示 }WiFi.mode(WIFI_AP_STA)是一个实用技巧。纯AP模式WIFI_AP下模块无法主动扫描周围的WiFi网络。而WIFI_AP_STA模式允许模块同时作为AP提供配置页面和STA客户端执行扫描任务这样我们才能在配置页面上动态列出可用的WiFi列表。4.4 实现Web请求处理器这是Web配置的核心我们实现三个主要的处理器。4.4.1 主页处理器 (handleRoot)这个函数返回一个HTML页面。为了代码简洁我们可以用C的原始字符串字面量R字符串来嵌入HTML。void handleRoot() { String html Rrawliteral( !DOCTYPE html html head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleWiFi Configuration/title style body { font-family: Arial; margin: 40px; } .container { max-width: 400px; margin: auto; padding: 20px; border: 1px solid #ccc; border-radius: 10px; } input, select, button { width: 100%; padding: 10px; margin: 8px 0; box-sizing: border-box; } button { background-color: #4CAF50; color: white; border: none; cursor: pointer; } button:hover { background-color: #45a049; } /style /head body div classcontainer h2WiFi Configuration/h2 form action/save methodpost label forssidNetwork SSID:/label select idssid namessid required option value-- Click Scan First --/option /select button typebutton onclickscanWiFi()Scan Networks/button br label forpasswordPassword:/label input typepassword idpassword namepassword placeholderWiFi password br button typesubmitSave Connect/button /form p idmessage/p /div script function scanWiFi() { fetch(/scan) .then(response response.json()) .then(data { const select document.getElementById(ssid); select.innerHTML option value-- Select a network --/option; data.networks.forEach(net { const option document.createElement(option); option.value net.ssid; option.textContent net.ssid ( net.rssi dBm); select.appendChild(option); }); document.getElementById(message).textContent Scan complete.; }) .catch(err { console.error(Scan error:, err); document.getElementById(message).textContent Scan failed.; }); } /script /body /html )rawliteral; server.send(200, text/html, html); }这个页面包含一个表单一个用于触发扫描的按钮以及一个显示扫描结果的下拉框。注意SSID列表是通过JavaScript调用/scan接口动态获取的这比写死在下拉框里要好得多因为用户环境中的WiFi是变化的。4.4.2 扫描处理器 (handleScan)这个接口返回一个JSON格式的WiFi列表。void handleScan() { Serial.println(Scanning networks...); int n WiFi.scanNetworks(false, true); // 参数asyncfalse, show_hiddenfalse String json {\networks\:[; for (int i 0; i n; i) { if(i) json ,; json {\ssid\:\ WiFi.SSID(i) \,; json \rssi\: String(WiFi.RSSI(i)) }; // 注意WiFi.encryptionType(i) 可以获取加密类型可根据需要添加 } json ]}; server.send(200, application/json, json); // 扫描完成后及时清理缓存防止内存占用 WiFi.scanDelete(); }重要提示WiFi.scanNetworks()是一个阻塞操作在网络环境复杂时可能需要几秒钟。在此期间Web服务器无法处理其他请求。因此在实际产品中可以考虑异步扫描或者缓存扫描结果一段时间避免频繁扫描。4.4.3 配置保存处理器 (handleSave)这是最后一步也是最关键的一步。void handleSave() { // 检查是否接收到了必要的参数 if (!server.hasArg(ssid) || server.arg(ssid) ) { server.send(400, text/plain, Error: SSID is required); return; } // 获取表单数据 String newSSID server.arg(ssid); String newPassword server.arg(password); // 密码可能为空开放网络 // 将新配置存入结构体 newSSID.toCharArray(config.ssid, sizeof(config.ssid)); newPassword.toCharArray(config.password, sizeof(config.password)); // 保存到EEPROM saveConfig(); // 返回成功页面提示设备将重启 String html Rrawliteral( !DOCTYPE html html headmeta charsetUTF-8titleConfiguration Saved/title/head body h2Configuration Saved!/h2 pSSID: )rawliteral newSSID Rrawliteral(/p pThe device will restart and try to connect to the new network. Please reconnect your device to your target WiFi and access the device via its new IP address./p pemThis page will close in 5 seconds.../em/p script setTimeout(function(){ window.close(); }, 5000); /script /body /html )rawliteral; server.send(200, text/html, html); // 延迟一段时间让响应发送完毕然后重启 delay(3000); ESP.restart(); }saveConfig()函数负责将config结构体写入EEPROM并调用EEPROM.commit()确保数据物理写入。这里有几个关键细节参数验证必须检查ssid参数是否存在且非空。密码处理密码字段可能为空如果目标网络是开放的。我们的代码需要能处理这种情况WiFi.begin()函数允许密码为空字符串。字符串拷贝安全使用toCharArray()并指定目标缓冲区大小防止缓冲区溢出这是嵌入式编程的安全基石。用户反馈与重启保存成功后必须给用户明确的反馈。然后延迟几秒确保HTTP响应已完全发送给客户端再执行重启。立即重启可能导致TCP连接中断浏览器收不到完整的成功页面。5. 进阶优化与生产级考量上面的代码实现了一个可用的基础版本。但要用于实际项目还需要考虑更多细节。5.1 配置存储的健壮性EEPROM有擦写次数限制通常10万次。频繁保存相同配置会导致不必要的磨损。可以在保存前先与已存储的配置比较只有发生变化时才写入。void saveConfigIfChanged() { WiFiConfig oldConfig; // 先读取当前存储的配置到oldConfig EEPROM.get(0, oldConfig); // 比较新旧配置 if (strcmp(oldConfig.ssid, config.ssid) ! 0 || strcmp(oldConfig.password, config.password) ! 0) { Serial.println(Configuration changed, saving to EEPROM...); saveConfig(); // 真正的保存函数 } else { Serial.println(Configuration unchanged, skip saving.); } }对于ESP32强烈推荐使用Preferences库它内部处理了磨损均衡和掉电保护比直接操作EEPROM更安全方便。5.2 连接状态反馈与超时重置设备连接路由器成功后如何让用户知道除了串口打印还可以LED指示在loop()中根据WiFi.status()控制LED。例如常亮表示已连AP慢闪表示正在连接快闪表示配网模式。智能超时重置如果设备在STA模式下长时间如30分钟无法连接到任何网络可能是路由器关机或移动了位置可以自动重启并进入配网模式。这需要记录连接失败的开始时间。unsigned long lastConnectedTime 0; void loop() { server.handleClient(); if (WiFi.status() WL_CONNECTED) { lastConnectedTime millis(); // 重置计时器 } else { // 如果断开连接超过30分钟 if (millis() - lastConnectedTime 30 * 60 * 1000UL) { Serial.println(Long time disconnect, reset to config mode...); // 可以清空配置或直接重启 ESP.restart(); } } }5.3 增强Web界面与用户体验显示信号强度在/scan返回的JSON中加入rssi并在前端页面上以图形化方式如信号条或文字显示帮助用户选择信号最好的网络。隐藏密码显示在密码输入框旁边增加一个“显示/隐藏”密码的小图标提升易用性。保存多个网络配置高级功能可以保存多个SSID和密码设备按优先级尝试连接。配置设备本身信息在Web页面上增加设备名称、静态IP设置等选项。5.4 安全性考虑基础层面对于大多数物联网设备以下基础安全措施是必要的AP密码为配网AP设置一个密码apPassword即使是一个简单的固定密码也能防止路人随意连接。防暴力提交对/save接口做简单的频率限制防止被恶意脚本连续提交。输入净化对接收到的SSID和密码进行长度检查防止超长字符串导致缓冲区溢出。SSID最长32字节WPA2密码最少8字节最长63字节。HTTPS对于高安全要求场景可以考虑使用HTTPS。但这需要向模块烧录证书并消耗更多资源实现复杂。多数家庭物联网设备仍使用HTTP因为配网网络本身是临时的、隔离的。6. 常见问题与深度排查指南在实际部署中你一定会遇到各种各样的问题。下面是我总结的“踩坑”实录和解决方法。6.1 手机/电脑搜不到配网AP现象模块启动了SoftAP但手机WiFi列表里看不到“ESP8266_Config”。排查步骤检查串口日志首先确认串口打印了Configuration AP started. SSID: ESP8266_Config和AP IP address: 192.168.4.1。如果没有说明AP启动失败。检查WiFi模式确保代码中设置了WiFi.mode(WIFI_AP)或WIFI_AP_STA。如果误设为WIFI_STA则不会开启AP。检查频道兼容性有些国家的路由器或设备对WiFi频道有限制。ESP8266的SoftAP默认可能在频道1。尝试在代码中指定一个常见频道如6WiFi.softAP(apSSID, apPassword, 6)。距离与干扰将手机靠近模块测试。2.4GHz信号穿墙能力弱。手机兼容性极少数旧手机可能不支持某些WiFi模式。用另一台设备测试。6.2 能连接AP但打不开192.168.4.1现象手机显示已连接“ESP8266_Config”但浏览器访问http://192.168.4.1时一直转圈或显示无法连接。排查步骤获取IP地址在手机WiFi设置里查看当前连接的详细信息确认获取到的IP地址确实是192.168.4.x网段网关是192.168.4.1。有时手机会因为之前连接过类似网段而使用静态IP导致冲突。关闭移动数据在测试时暂时关闭手机的移动数据功能防止手机因WiFi无互联网而自动切换到流量导致对192.168.4.1的请求被忽略。检查服务器启动确认串口打印了HTTP server started。检查端口占用Web服务器默认80端口。确保没有其他服务占用。尝试使用IP直接访问有些浏览器或系统对.1的地址有特殊处理。可以尝试在手机浏览器输入完整的http://192.168.4.1:80。清除浏览器缓存有时旧的缓存会导致问题。6.3 提交配置后设备重启但无法连接目标WiFi现象在网页提交配置后设备重启但串口显示一直在尝试连接最终失败并又回到了配网模式。排查步骤检查SSID和密码这是最常见的原因。确保密码正确区分大小写。特别注意是否有空格。可以在网页提交后立即从EEPROM读取并打印出来验证。检查路由器设置目标路由器是否开启了MAC地址过滤是否设置了最大连接数已满尝试用其他设备先连接一下该路由器确认其工作正常。信号强度模块距离路由器太远或隔墙太多信号太弱RSSI -80 dBm可能导致连接不稳定或失败。在handleScan接口中返回信号强度在网页上优先选择信号强的。加密方式不匹配虽然现代路由器基本都是WPA2-PSK但有些企业网络或旧路由器可能使用WEP或WPA。ESP8266的WiFi.begin()默认支持WPA/WPA2。如果遇到特殊加密可能需要调用更底层的连接函数。电源问题WiFi连接瞬间电流较大如果使用劣质USB线或电源可能导致电压跌落使模块重启或连接失败。使用可靠的5V/1A以上电源适配器。6.4 设备频繁断开重连现象设备能连上WiFi但运行一段时间后自动断开然后又重连。排查步骤电源稳定性同上这是首要怀疑对象。用示波器观察模块供电电压在WiFi发射时的波动。路由器DHCP租期检查路由器DHCP服务器设置的地址租期。租期过短可能导致IP续租失败。可以在代码中设置为静态IP或者处理WiFi断开事件WiFi.onStationModeDisconnected并尝试重连。WiFi睡眠模式ESP8266为了省电默认可能开启WiFi睡眠。在setup()中加入WiFi.setSleepMode(WIFI_NONE_SLEEP);可以禁用睡眠保持连接稳定。网络干扰2.4GHz频段拥挤微波炉、蓝牙设备都可能造成干扰。尝试修改路由器和ESP的WiFi频道。6.5 Web界面扫描不到任何网络现象点击网页上的“Scan Networks”按钮列表始终为空或提示失败。排查步骤检查WiFi模式必须使用WIFI_AP_STA或WIFI_STA模式才能扫描。纯AP模式WIFI_AP下无法扫描。扫描时机WiFi.scanNetworks()是阻塞的。如果在扫描完成前多次快速点击按钮可能导致资源冲突。可以在前端按钮点击后将其禁用直到收到响应。内存不足扫描结果会占用内存。如果设备剩余内存很少扫描可能失败。在扫描前后打印ESP.getFreeHeap()检查内存情况。检查JSON格式使用浏览器的开发者工具F12 - Network查看/scan请求的响应内容。确保返回的是有效的JSON格式。一个常见的错误是JSON字符串拼接时最后一个网络后面多了一个逗号。6.6 如何强制设备重新进入配网模式这是一个常见的运维需求。有几种方法硬件按键在loop()中检测一个GPIO引脚的电平如连接一个按键到GND。当检测到按键长按如5秒时清除保存的WiFi配置并重启。if(digitalRead(BUTTON_PIN) LOW) { delay(50); // 消抖 unsigned long pressStart millis(); while(digitalRead(BUTTON_PIN) LOW) { if(millis() - pressStart 5000) { // 长按5秒 clearConfig(); ESP.restart(); break; } } }软件指令在设备连接网络后提供一个隐藏的管理页面或UDP广播指令接收特定命令后执行重置。超时重置如前所述长时间连接失败后自动重置。实现一个稳定可靠的Web配网功能是物联网设备“开箱即用”体验的关键。它隐藏了技术的复杂性将友好的交互界面呈现给最终用户。从简单的AT指令到构建一个微型Web服务器这个演进过程体现了嵌入式开发中“用户体验至上”的思路。我个人的经验是在项目初期就规划并调试好配网逻辑能为后续的开发和测试节省大量时间。毕竟谁也不想每次修改代码后都拿着串口线去重新配置网络。把上述代码和思路吃透根据你的具体硬件和需求稍作调整就能打造出一个“绝对通用好使”的WiFi模块配置方案。