1. 简介

很多物联网设备需要连接家庭 WiFi,但无法预知用户的网络名称和密码。本教程将教你如何编写一个 ESP32 程序,实现:

  • 首次开机:自动进入配网模式,创建一个名为 ESP32_Config 的热点,用户通过手机/电脑连接后打开网页输入 WiFi 信息。

  • 保存信息:将 WiFi 信息保存到 ESP32 的 NVS(非易失性存储)中,断电不丢失。

  • 后续开机:自动读取保存的 WiFi 信息并连接,如果连接失败则重新进入配网模式。

通过这种方式,用户只需配置一次,之后设备就能自动联网。

2. 准备材料

  • 硬件:ESP32 开发板、USB 数据线。

  • 软件:Arduino IDE,浏览器(用于配置页面)。

  • 可选:LED (验证连接状态,ESP32 板载 LED 通常已集成)。

3. 代码解析

以下是完整的代码(稍后解释各部分功能):

#include <WiFi.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <Preferences.h>

// 配置参数
const char* AP_SSID = "ESP32_Config";        // 配置模式下的热点名称
const char* AP_PASSWORD = "";                 // 热点密码(空表示开放)
const int DNS_PORT = 53;                       // DNS 服务器端口
const int WEB_PORT = 80;                       // Web 服务器端口
const unsigned long CONNECT_TIMEOUT = 30000;   // WiFi 连接超时时间(毫秒)
const unsigned long AP_TIMEOUT = 300000;       // AP 模式超时时间(毫秒,5分钟),超过无配置则重启

// 全局对象
WebServer server(WEB_PORT);
DNSServer dnsServer;
Preferences preferences;

// 标志位:是否处于配网模式
bool configMode = false;
unsigned long apStartTime = 0;

// 用户 LED 引脚(可根据需要修改)
const int ledPin = 2;   // 大多数 ESP32 开发板板载 LED 在 GPIO2

// 读取保存的 WiFi 凭据
bool loadWiFiCredentials(String &ssid, String &password) {
  preferences.begin("wifi", false);  // 命名空间 "wifi",读写模式
  ssid = preferences.getString("ssid", "");
  password = preferences.getString("password", "");
  preferences.end();
  return (ssid.length() > 0);
}

// 保存 WiFi 凭据
void saveWiFiCredentials(const String &ssid, const String &password) {
  preferences.begin("wifi", false);
  preferences.putString("ssid", ssid);
  preferences.putString("password", password);
  preferences.end();
}

// 尝试连接 WiFi
bool connectToWiFi(const String &ssid, const String &password) {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), password.c_str());
  Serial.print("Connecting to WiFi: ");
  Serial.println(ssid);

  unsigned long startAttempt = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startAttempt < CONNECT_TIMEOUT) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Connected!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    return true;
  } else {
    Serial.println("Connection failed.");
    return false;
  }
}

// 启动 AP 模式并开启 DNS + Web 服务器
void startConfigMode() {
  configMode = true;
  apStartTime = millis();

  WiFi.mode(WIFI_AP);
  WiFi.softAP(AP_SSID, AP_PASSWORD);
  IPAddress apIP = WiFi.softAPIP();
  Serial.println("AP mode started");
  Serial.print("AP IP address: ");
  Serial.println(apIP);

  // 启动 DNS 服务器,将所有域名请求指向 AP 的 IP
  dnsServer.start(DNS_PORT, "*", apIP);

  // 配置 Web 服务器路由
  server.on("/", handleRoot);
  server.on("/configure", HTTP_POST, handleConfigure);
  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

// 处理根路径:显示配置页面
void handleRoot() {
  String html = "<!DOCTYPE html><html>";
  html += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
  html += "<title>WiFi Config</title></head><body>";
  html += "<h2>ESP32 WiFi Configuration</h2>";
  html += "<form method=\"POST\" action=\"/configure\">";
  html += "SSID:<br><input type=\"text\" name=\"ssid\" placeholder=\"WiFi SSID\"><br>";
  html += "Password:<br><input type=\"password\" name=\"password\" placeholder=\"Password\"><br><br>";
  html += "<input type=\"submit\" value=\"Save and Restart\">";
  html += "</form></body></html>";
  server.send(200, "text/html", html);
}

// 处理配置表单提交
void handleConfigure() {
  if (server.hasArg("ssid") && server.hasArg("password")) {
    String ssid = server.arg("ssid");
    String password = server.arg("password");

    if (ssid.length() > 0) {
      saveWiFiCredentials(ssid, password);
      server.send(200, "text/html", "<h2>Configuration saved. Restarting...</h2>");
      delay(1000);
      ESP.restart();
    } else {
      server.send(400, "text/html", "<h2>SSID cannot be empty</h2>");
    }
  } else {
    server.send(400, "text/html", "<h2>Missing parameters</h2>");
  }
}

// 处理未找到的路径(用于 Android/iPhone 的浏览器探测)
void handleNotFound() {
  server.send(204, "text/plain", "");  // 204 No Content 让手机不弹出错误
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\nESP32 WiFi Config Example");

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // 尝试加载已保存的 WiFi 信息
  String savedSSID, savedPassword;
  bool hasCredentials = loadWiFiCredentials(savedSSID, savedPassword);

  if (hasCredentials) {
    Serial.println("Found saved WiFi credentials.");
    if (connectToWiFi(savedSSID, savedPassword)) {
      // 连接成功,执行用户主程序(此处为 LED 闪烁示例)
      Serial.println("Starting user program...");
      // 在主循环中执行用户逻辑(见 loop)
      return;  // 直接退出 setup,进入 loop
    } else {
      Serial.println("Saved WiFi connection failed.");
    }
  } else {
    Serial.println("No saved WiFi credentials.");
  }

  // 没有凭据或连接失败,启动配网模式
  startConfigMode();
}

void loop() {
  if (configMode) {
    // 处理 DNS 和 Web 请求
    dnsServer.processNextRequest();
    server.handleClient();

    // 超时检查:若 AP 模式运行超过指定时间且无配置,则重启尝试重新连接
    if (millis() - apStartTime > AP_TIMEOUT) {
      Serial.println("AP timeout, restarting...");
      ESP.restart();
    }
  } else {
    // 正常模式:用户程序
    // 示例:LED 闪烁,表示已连接 WiFi
    digitalWrite(ledPin, !digitalRead(ledPin));
    delay(1000);
    Serial.println("Wifi连接成功,进入用户程序.");
  }
}

关键代码解释

头文件与全局变量

  • WiFi.h:提供 WiFi 连接功能。

  • WebServer.h:用于建立 HTTP 服务器,处理配置页面。

  • DNSServer.h:实现 DNS 劫持,使所有域名请求指向 ESP32 的 IP。

  • Preferences.h:用于在 NVS 中保存 WiFi 信息(类似键值对存储)。

参数配置

  • AP_SSID 和 AP_PASSWORD:配置模式下的热点名称和密码(留空表示开放)。

  • CONNECT_TIMEOUT:连接 WiFi 的超时时间。

  • AP_TIMEOUT:AP 模式最长运行时间,避免一直占用。

Preferences 存储

  • preferences.begin("wifi", false):打开命名空间 wifi(可自定义),第二个参数 false 表示读写。

  • putString / getString:存储和读取字符串。

连接逻辑

  • setup() 中先尝试读取保存的 WiFi,如果能读到则尝试连接。

  • 连接成功则进入 loop() 的用户程序;失败则启动 AP 模式。

  • AP 模式下启动 DNS 和 Web 服务器,用户通过浏览器提交配置后保存并重启。

DNS 劫持

  • dnsServer.start(DNS_PORT, "*", apIP):将所有域名请求解析到 ESP32 的 AP IP(192.168.4.1)。

  • 这样用户连接热点后,无论访问什么网址(如 www.baidu.com)都会跳转到 ESP32 的配置页面,提升体验。

超时重启

  • AP 模式下记录开始时间 apStartTime,若超过 AP_TIMEOUT 仍未收到配置,则自动重启,再次尝试连接已保存的 WiFi(如果有)。

用户程序占位

  • 在 loop() 的 else 分支中写你自己的代码,示例中是一个 LED 闪烁,代表 WiFi 已连接。

注意:不同 ESP32 开发板的板载 LED 可能连接在不同 GPIO,常见的是 GPIO2 或 GPIO5。若不确定,可查阅开发板原理图或先测试。

6. 操作步骤

6.1 上传程序

  1. 用 USB 线连接 ESP32 和电脑。

  2. 在 Arduino IDE 中选择正确的开发板(如 ESP32 Dev Module)和端口(如 COM3)。

  3. 将上述代码复制到 IDE 中,点击上传按钮(→)。

  4. 等待编译和上传完成,打开串口监视器(波特率 115200),观察输出。

6.2 首次配置

  1. 如果 ESP32 没有保存过 WiFi 信息,它会在串口打印 未找到保存的 WiFi 凭据。 并启动 AP 模式,创建名为 ESP32_Config 的开放热点。

  2. 用手机或电脑连接该热点(无需密码)。

  3. 连接成功后,通常会自动弹出配置页面(若未弹出,使用浏览器访问 http://192.168.4.1)。

  4. 在页面中输入你家 WiFi 的 SSID 和密码,点击“保存并重启”。

  5. 保存成功界面如下:

  6. ESP32 保存信息后自动重启,尝试连接你输入的 WiFi。

6.3 正常使用

  • 连接成功后,串口会显示获得的 IP 地址,LED 开始每秒闪烁一次。

    且串口输出信息如下:
  • 此后每次开机,ESP32 都会自动连接上次保存的 WiFi,无需再次配置。

  • 如果 WiFi 连接失败(如密码更改、路由器关闭),程序会在超时后重新进入 AP 模式,等待新配置。

7. 常见问题与解决

Q1: 连接 ESP32 热点后,没有自动弹出配置页面怎么办?

  • 确保手机/电脑关闭了移动数据,并且已经连接到 ESP32_Config 热点。

  • 手动在浏览器输入 http://192.168.4.1 访问。

Q2: 配置页面提交后,ESP32 没有重启或连接不上?

  • 检查输入的 WiFi 密码是否正确。

  • 查看串口监视器输出:如果保存成功,会显示 配置已保存,设备即将重启...,然后重启。如果 WiFi 名称或密码错误,重启后仍会进入 AP 模式,可再次配置。

Q3: 如何修改配置热点的名称或密码?

  • 修改代码开头的 AP_SSID 和 AP_PASSWORD 即可。

  • 若希望 AP 模式有密码,可设置 AP_PASSWORD 为字符串,如 "12345678"(注意 WiFi 密码至少 8 位)。

Q4: 如何清除保存的 WiFi 信息?

  • 可以通过串口发送命令(需自己扩展)或重新上传一个空白程序来清除 NVS。

  • 快速方法:在 Arduino IDE 中,选择 工具 → ESP32 Sketch Data Upload 下方的 Erase Flash,擦除整个 Flash 后再上传程序。

Q5: 我想在用户程序中添加更多功能,比如读取传感器、控制继电器,应该怎么改?

  • 在 loop() 的 else 分支中(即 configMode == false 时)编写你的主逻辑即可。

  • 注意:不要使用 delay() 过长,以免影响 WiFi 维持连接。建议用 millis() 定时或改用多任务(FreeRTOS)。

8. 扩展思考

  • 多网络支持:可以修改程序,保存多个 WiFi 信息,按优先级尝试连接。

  • Web 页面美化:可添加 CSS 样式,使配置界面更美观。

  • 安全考虑:如果 AP 模式需要密码,务必设置复杂密码,防止他人误连。

  • 远程配置:若需要远程修改 WiFi,可配合 MQTT 或 BLE 实现。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐