本章要点
ESP32 内置 2.4GHz WiFi 射频,只支持 2.4GHz——5GHz 频段的路由器无法连接,这是「连不上」的首要原因。它有 STA(客户端,连路由器)和 AP(热点,自己当路由器)两种模式。
永远不要用 delay(15000) 等连接。用 millis() 做带超时的循环,程序在这期间依然能响应。连接失败时降级为 AP 热点,保证用户至少能打开页面。
HTTP 是「请求-响应」模型:浏览器发 GET 请求,服务器回一段 HTML。WebServer 库把 TCP 细节封装好了,你只需要写「收到什么路径 → 返回什么内容」。
硬件清单与原理
| 名称 | 数量 | 作用与原理 |
|---|---|---|
| ESP32 DevKit V1 | 1 块 | 内置 2.4GHz WiFi 射频(802.11 b/g/n),天线为 PCB 走线或外置 IPEX。 |
| USB 数据线 | 1 根 | 供电 + 串口。必须是数据线。 |
| LED + 220Ω 电阻 | 1 套 | 状态可视化。板载 LED 已接 GPIO2,可直接复用。 |
接线说明
USB 数据线 → ESP32 → 电脑(仅供电与烧录)
板载 LED 已接 GPIO2,用于状态指示
① 代码顶部 WIFI_SSID / WIFI_PASSWORD 改成你的路由器信息
② 烧录后打开串口监视器,记下打印出的 IP 地址
提示:连接失败时代码会降级为 AP 热点 ESP32-Teaching,密码 12345678。
实战案例 05-1 · WiFi 联网 + 迷你 HTTP 服务器
让 ESP32 接入路由器,并在自己的 IP 上开一个小网站,显示实时状态并控制 LED。
串口打印 IP 地址,用手机连到同一路由器,浏览器输入该 IP 即可打开控制页面。
/* * ============================================================================ * 第 5 章 · 实战案例 05-1:WiFi 联网 + 迷你 HTTP 服务器 * ---------------------------------------------------------------------------- * 硬件清单: * - ESP32 DevKit V1 × 1 * - USB 数据线 × 1 * - LED × 1 + 220Ω 电阻 × 1 * * 接线方式: * GPIO2 → [220Ω] → LED 阳极,LED 阴极 → GND * * 接口/协议原理: * 1) ESP32 内置 WiFi 射频,支持 2.4GHz(注意:5GHz 频段的路由器无法连接)。 * 有 STA(客户端,连路由器)和 AP(热点,芯片自己当路由器)两种模式。 * 2) HTTP 是「请求-响应」模型:浏览器发 GET 请求,服务器回一段 HTML。 * 3) WebServer 库把 TCP 的底层细节封装好了,你只需要写「收到什么路径 → 返回什么内容」。 * * 功能用途: * 让 ESP32 接入路由器,并在自己的 IP 上开一个小网站: * - 首页显示实时状态(IP、信号强度、运行时长、LED 状态) * - 点击按钮可以直接控制 LED * * 预期现象: * 串口打印出「AP 模式」或「STA 模式连接成功」和 IP 地址, * 用手机/电脑连到同一路由器,浏览器输入该 IP 即可打开控制页面。 * * ⚠ 使用前必改:把下面的 WIFI_SSID / WIFI_PASSWORD 改成你自己的路由器信息。 * ============================================================================ */ #include <Arduino.h> // Arduino 核心库 #include <WiFi.h> // ESP32 WiFi 驱动库(ESP32 专属,Arduino UNO 没有) #include <WebServer.h> // ESP32 自带的轻量 HTTP 服务器库 /* ========================================================================== * 一、必须修改的配置区 * ======================================================================== */ const char* WIFI_SSID = class="st">"YOUR_WIFI_NAME"; // ← 改成你的 WiFi 名称 const char* WIFI_PASSWORD = class="st">"YOUR_WIFI_PASSWORD";// ← 改成你的 WiFi 密码 // 热点模式:没有路由器时的备用方案。设为 true 则跳过联网,直接开热点 const bool USE_AP_MODE = false; // false = 尝试连接路由器;true = 自建热点 const char* AP_SSID = class="st">"ESP32-Teaching"; // 自建热点的名称 const char* AP_PASSWORD = class="st">"12345678"; // 自建热点的密码(至少 8 位) /* ========================================================================== * 二、引脚与状态 * ======================================================================== */ #define LED_PIN 2 bool ledState = false; class="kw">unsigned long bootTime = 0; // 记录程序启动时刻,用于计算运行时长 // 创建 HTTP 服务器对象,监听 80 端口 // 80 端口是 HTTP 的默认端口,所以浏览器只要输 IP 地址,不用加端口号 WebServer server(80); /* ========================================================================== * 三、自定义函数:把 LED 状态写进 HTML 变量 * 目的:让首页能动态显示当前 LED 状态,而不是写死一段文字。 * ======================================================================== */ class="kw">String buildStatusHtml() { class="kw">unsigned long uptimeSec = (millis() - bootTime) / 1000; // 换算成秒 class="kw">String html; html += class="st">"<!DOCTYPE html><html><head><meta charset='UTF-8'>"; html += class="st">"<meta name='viewport' content='width=device-width,initial-scale=1'>"; html += class="st">"<title>ESP32 状态面板</title>"; html += class="st">"<style>"; html += class="st">"body{font-family:monospace;background:#0b0f14;color:#e6edf3;padding:24px;margin:0}"; html += class="st">"h1{color:#22d3ee;font-size:20px}"; html += class="st">".card{background:#111820;border:1px solid #22303c;border-radius:12px;padding:16px;margin:12px 0}"; html += class="st">"b{color:#f5a524}"; html += class="st">"a{display:inline-block;background:#22d3ee;color:#06121a;padding:12px 22px;border-radius:8px;text-decoration:none;font-weight:bold;margin:6px}"; html += class="st">"a.off{background:#334155;color:#cbd5e1}"; html += class="st">"</style></head><body>"; html += class="st">"<h1>ESP32 实时状态面板</h1>"; // 第一张卡:连接信息 html += class="st">"<div class='card'>"; html += class="st">"<p>运行模式:<b>" + class="kw">String(USE_AP_MODE ? class="st">"AP 热点模式" : class="st">"STA 联网模式") + class="st">"</b></p>"; html += class="st">"<p>IP 地址:<b>" + WiFi.localIP().toString() + class="st">"</b></p>"; html += class="st">"<p>信号强度:<b>" + class="kw">String(WiFi.RSSI()) + class="st">" dBm</b></p>"; html += class="st">"<p>运行时长:<b>" + class="kw">String(uptimeSec) + class="st">" 秒</b></p>"; html += class="st">"<p>芯片可用堆内存:<b>" + class="kw">String(ESP.getFreeHeap()) + class="st">" 字节</b></p>"; html += class="st">"</div>"; // 第二张卡:LED 控制 html += class="st">"<div class='card'><p>LED 当前状态:<b>" + class="kw">String(ledState ? class="st">"ON 点亮" : class="st">"OFF 熄灭") + class="st">"</b></p>"; html += class="st">"<a href='/on'>点亮 LED</a>"; html += class="st">"<a href='/off' class='off'>熄灭 LED</a>"; html += class="st">"<a href='/toggle' class='off'>翻转状态</a>"; html += class="st">"</div>"; html += class="st">"</body></html>"; class="kw">return html; } /* ========================================================================== * 四、HTTP 路由处理函数 * onServerNotFound 是「兜底」:任何没注册的路径都走这里。 * ======================================================================== */ void handleRoot() { server.send(200, class="st">"text/html", buildStatusHtml()); // 200 = OK,text/html = 内容类型 } void handleNotFound() { server.send(404, class="st">"text/plain", class="st">"404 - 页面不存在"); // 404 = Not Found } void handleOn() { ledState = true; digitalWrite(LED_PIN, LOW); // 低电平点亮 Serial.println(class="st">"[HTTP] /on → LED 点亮"); server.sendHeader(class="st">"Location", class="st">"/"); // 让浏览器跳回首页 server.send(302); // 302 = 临时重定向 } void handleOff() { ledState = false; digitalWrite(LED_PIN, HIGH); Serial.println(class="st">"[HTTP] /off → LED 熄灭"); server.sendHeader(class="st">"Location", class="st">"/"); server.send(302); } void handleToggle() { ledState = !ledState; digitalWrite(LED_PIN, ledState ? LOW : HIGH); Serial.println(class="st">"[HTTP] /toggle → 状态翻转"); server.sendHeader(class="st">"Location", class="st">"/"); server.send(302); } /* ========================================================================== * 五、初始化 * ======================================================================== */ void setup() { Serial.begin(115200); delay(200); bootTime = millis(); // 记录启动时刻 pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); // 初始熄灭 if (USE_AP_MODE) { /* ---------- AP 模式:芯片自己开热点 ---------- */ Serial.println(class="st">"[WIFI] 启动 AP 热点模式..."); WiFi.mode(WIFI_AP); WiFi.softAP(AP_SSID, AP_PASSWORD); Serial.print(class="st">"[WIFI] 热点名称:"); Serial.println(AP_SSID); Serial.print(class="st">"[WIFI] 热点 IP:"); Serial.println(WiFi.softAPIP()); // 通常是 192.168.4.1 } else { /* ---------- STA 模式:连接家中的路由器 ---------- */ Serial.print(class="st">"[WIFI] 正在连接 "); Serial.print(WIFI_SSID); Serial.println(class="st">" ..."); WiFi.mode(WIFI_STA); // 设置为客户端模式 WiFi.begin(WIFI_SSID, WIFI_PASSWORD); // 开始连接 // 用 millis 计时最多等 15 秒,而不是 delay(15000) 死等, // 这样即使连不上,程序也不会卡住,还能继续跑后面的逻辑 class="kw">unsigned long startTime = millis(); class="kw">while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < 15000) { delay(500); Serial.print(class="st">"."); } Serial.println(); if (WiFi.status() == WL_CONNECTED) { Serial.println(class="st">"[WIFI] 连接成功!"); Serial.print(class="st">"[WIFI] IP 地址:"); Serial.println(WiFi.localIP()); } else { Serial.println(class="st">"[WIFI] 连接失败,请检查 WiFi 名称/密码是否正确"); // 失败时自动降级成 AP 热点,保证用户至少能打开页面看到说明 WiFi.mode(WIFI_AP); WiFi.softAP(AP_SSID, AP_PASSWORD); Serial.print(class="st">"[WIFI] 已降级为热点模式,IP:"); Serial.println(WiFi.softAPIP()); } } /* ---------- 注册 HTTP 路由 ---------- */ server.on(class="st">"/", handleRoot); // 根路径 → 首页 server.on(class="st">"/on", handleOn); // /on → 点亮 server.on(class="st">"/off", handleOff); // /off → 熄灭 server.on(class="st">"/toggle", handleToggle);// /toggle → 翻转 server.onNotFound(handleNotFound); // 兜底处理 server.begin(); // 启动服务器,开始监听 80 端口 Serial.println(class="st">"[HTTP] 服务器已启动,浏览器输入上面的 IP 即可访问"); } /* ========================================================================== * 六、主循环 * ======================================================================== */ void loop() { // handleClient() 负责处理浏览器发来的 HTTP 请求。 // 它内部是阻塞式的,但单次调用极快(毫秒级),所以放在 loop 里连续调用即可。 server.handleClient(); }
本章小结
① ESP32 的 WiFi 射频只支持 2.4GHz,5GHz 路由器无法连接——这是首要原因。
② STA(客户端)和 AP(热点)是两种不同角色,代码里靠 WiFi.mode() 切换。
③ 永远不要用 delay(15000) 等连接,用 millis() 做带超时的循环。
④ WiFi.status() 返回 WL_CONNECTED 才算真正连上,光有 IP 不代表有网。
⑤ 加上「失败降级为 AP 热点」的逻辑,能让设备在路由器不在时依然可用。
本章练习
把 USE_AP_MODE 改成 true,验证热点模式能正常打开页面。
在首页多加一行显示 ESP.getChipId() 和 ESP.getFlashChipSize()。
增加 /blink?ms=500 路由,用 server.arg 读取查询参数。
故意填错 WiFi 密码,验证降级逻辑生效并观察串口日志。