一个面向初学者的 ESP32 Web 服务器项目。插上电,ESP32 会自动连上 Wi-Fi 并启动一个网页,你用手机或电脑打开浏览器就能访问。
想象你的 ESP32 是一台微型计算机。这个项目让它做了两件事:
- 连上你家的 Wi-Fi(就像手机连接 Wi-Fi 一样)
- 开启一个网站(在浏览器中输入 ESP32 的 IP 地址,就能看到一个网页)
最终效果:你可以在浏览器中看到 ESP32 的在线状态,未来还可以添加按钮来控制 LED、查看温度、看摄像头画面等。
| 硬件 | 说明 |
|---|---|
| ESP32 开发板 (推荐 ESP32-S3 或 ESP32-CAM) | 主控芯片,项目运行的核心 |
| Micro USB 数据线 | 给开发板供电 & 烧录程序 |
| 电脑(Windows / macOS / Linux) | 用来编写和编译代码 |
| Wi-Fi 热点(手机热点或家用路由器) | ESP32 会连接这个网络 |
在电脑上安装以下工具(如果已经装过可以跳过):
打开终端,按下面的命令操作(以 Linux / macOS 为例):
# 克隆 ESP-IDF 仓库(大约 1GB)
git clone --recursive https://github.com/espressif/esp-idf.git ~/esp/esp-idf
# 安装工具链(编译器、调试器等)
cd ~/esp/esp-idf
./install.sh esp32s3
# 设置环境变量(每次打开新终端都要执行)
source ~/esp/esp-idf/export.sh
# 安装 Python 依赖
pip install --upgrade pip
pip install -r ~/esp/esp-idf/requirements.txtWindows 用户建议使用 ESP-IDF 的 CMD 或 PowerShell 快捷方式,会自动设置好环境。
idf.py --version如果看到版本号,说明安装成功。
esp_web/ ← 项目根目录
│
├── CMakeLists.txt ← 顶层配置文件(告诉 ESP-IDF 这是一个项目)
│
├── main/ ← 主程序
│ ├── CMakeLists.txt ← main 的配置
│ └── esp_web.c ← 入口文件,相当于"总指挥"
│
├── components/ ← 自己写的功能模块
│ ├── CMakeLists.txt ← 所有模块的编译配置
│ │
│ ├── Hardware/ ← 硬件相关模块
│ │ └── wifi_sta/ ← Wi-Fi 模块
│ │ ├── wifi_sta.h ← 头文件(公开哪些函数)
│ │ └── wifi_sta.c ← 实现文件(具体怎么连 Wi-Fi)
│ │
│ └── Driver/ ← 驱动相关模块
│ └── Web/ ← Web 服务器模块
│ ├── web_server.h ← 头文件(公开哪些函数)
│ └── web_server.c ← 实现文件(具体怎么开网站)
│
├── build/ ← 编译产生的文件(自动生成,不用管)
└── sdkconfig ← ESP-IDF 配置(自动生成,不用管)
把 ESP32 插上 USB 后,它就像电脑开机一样,按顺序执行代码。下面是完整的流程:
┌─────────────────────────────────────────────────────────┐
│ ESP32 上电 │
│ → 引导程序(ROM bootloader)启动 │
│ → 加载主程序到内存 │
│ → 调用 app_main() ← 这就是我们写的入口函数 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ① app_main() 启动(esp_web.c) │
│ ├── 打印 "系统启动" │
│ └── 调用 wifista_init() ← 去连 Wi-Fi │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ② wifista_init() 开始工作(wifi_sta.c) │
│ ├── 第1步:创建"事件组"(短信通知箱) │
│ ├── 第2步:初始化 NVS(类似于格式化存储卡) │
│ ├── 第3步:启动 TCP/IP(先搭好网络通道) │
│ ├── 第4步:创建事件循环(搭好通信线路) │
│ ├── 第5步:注册回调(告诉芯片:Wi-Fi 有事就通知我) │
│ ├── 第6步:创建 Wi-Fi 网卡 │
│ ├── 第7步:初始化 Wi-Fi 驱动 │
│ ├── 第8步:设置为 STA 模式(就像手机的"连接热点"模式) │
│ ├── 第9步:设置你家 Wi-Fi 的 名字和密码 │
│ ├── 第10步:启动 Wi-Fi(开始搜索信号) │
│ ├── 第11步:设置信号强度 │
│ └── 第12步:等待 10 秒 → 等 Wi-Fi 连上 │
│ │
│ ┌─ 如果连上了? ──→ 返回 ESP_OK │
│ └─ 如果没连上? ──→ 返回 ESP_FAIL(但芯片继续运行) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ③ 回到 app_main() │
│ ├── 检查 wifista_init() 返回值 │
│ │ ├── 成功 → 继续 │
│ │ └── 失败 → 打印警告,继续(不阻塞) │
│ └── 调用 start_http_server() ← 去开网站 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ④ start_http_server() 开始(web_server.c) │
│ ├── 第1步:拿到默认配置(端口80、栈大小4096等) │
│ ├── 第2步:打开 LRU 清理(内存不够时自动断掉旧连接) │
│ ├── 第3步:设置通配符匹配(支持 /path/* 这种灵活路由) │
│ ├── 第4步:调用 httpd_start() ← 真正创建服务器 │
│ │ ├── 创建 TCP 套接字(相当于装一部电话) │
│ │ ├── 绑定端口 80(分配电话号码) │
│ │ ├── 开始监听(等待别人打电话进来) │
│ │ ├── 创建三个后台任务(接电话的、管事的、发消息的) │
│ │ └── 进入主循环 → 有连接就处理 │
│ │ │
│ ├── 第5步:注册根路由 "/" │
│ │ └── 告诉服务器:如果有人访问首页 → 调用 root_handler │
│ └── 返回 ESP_OK │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ⑤ app_main() 结束 │
│ └── 打印 "系统初始化完成!" │
│ └── 从此,ESP32 就静静地等着别人来访问它的网站了... │
└─────────────────────────────────────────────────────────┘
当你在浏览器中输入 ESP32 的 IP 地址时:
你的浏览器 ──HTTP 请求 GET /──→ ESP32 的 HTTP 服务器
│
▼
查找路由表 → 找到 "/" 对应 root_handler
│
▼
执行 root_handler(web_server.c)
├── 组装 HTML 页面
└── 通过 httpd_resp_send() 发回浏览器
│
▼
你的浏览器 ←── HTML 网页 ───────── ESP32 返回响应
这是整个程序的 "总指挥"。它只做三件事:
void app_main(void)
{
ESP_LOGI(TAG, "系统启动"); // ① 说一声"我开始工作了"
wifista_init(); // ② 连 Wi-Fi(最多等10秒)
if (start_http_server() != ESP_OK) // ③ 开网站
return;
ESP_LOGI(TAG, "初始化完成"); // ④ 说一声"我搞定了"
}关键设计:
- Wi-Fi 即使超时失败,网站也会继续开。这样 ESP32 在没有网络时也能通过局域网访问。
- 但如果网站开不起来(比如端口被占用了),程序就直接退出。
这个模块的任务就是:连上你家的 Wi-Fi。
// 你可以在代码中改这两个宏来设置 Wi-Fi 名称和密码
#define DEFAULT_SSID "hhlong" // ← 改成你家的 Wi-Fi 名字
#define DEFAULT_PWD "hhlong0326" // ← 改成你家的 Wi-Fi 密码
esp_err_t wifista_init(void); // 调用这个函数来连 Wi-Fi事件回调机制(新人可能觉得难,这里用生活的例子来解释):
想象你叫了一份外卖:
- 你注册了一个回调:你告诉外卖小哥"到了打我电话"(
esp_event_handler_register) - 外卖小哥出发了(
WIFI_EVENT_STA_START)→ 你什么都不用做,他到了会打你电话 - 外卖小哥到了小区门口(
WIFI_EVENT_STA_CONNECTED)→ 你记了一下但还不用下楼 - 外卖小哥到了你家门口(
IP_EVENT_STA_GOT_IP)→ 你终于可以开门拿外卖了! - 外卖小哥走了(
WIFI_EVENT_STA_DISCONNECTED)→ 你记了一下,并重新点一份(自动重连)
这就是 事件驱动编程:你不用一直盯着 Wi-Fi 的状态,芯片会在状态变化时自动通知你。
函数 wifista_init() 的 12 个步骤:
| 步骤 | 做了什么 | 生活类比 |
|---|---|---|
| 1 | 创建事件组(xEventGroupCreate) |
准备一个通知板 |
| 2 | 初始化 NVS(nvs_flash_init) |
检查存储卡是否正常 |
| 3 | 启动 TCP/IP(esp_netif_init) |
搭好网线 |
| 4 | 创建事件循环(esp_event_loop_create_default) |
搭好电话线 |
| 5 | 注册回调(esp_event_handler_register) |
告诉系统:Wi-Fi 有事就通知我 |
| 6 | 创建网卡(esp_netif_create_default_wifi_sta) |
装上网卡硬件 |
| 7 | 初始化 Wi-Fi 驱动(esp_wifi_init) |
装上网卡驱动 |
| 8 | 设置为 STA 模式(esp_wifi_set_mode) |
设置为"连接热点"模式 |
| 9 | 设置名称和密码(esp_wifi_set_config) |
输入你家 Wi-Fi 名称和密码 |
| 10 | 启动 Wi-Fi(esp_wifi_start) |
打开 Wi-Fi 开关 |
| 11 | 设置信号强度(esp_wifi_set_max_tx_power) |
把发射功率调到最大 |
| 12 | 等待结果(xEventGroupWaitBits) |
等外卖,最多等 10 秒 |
这个模块的任务是:开一个网站,让别人能访问。
esp_err_t root_handler(httpd_req_t *req); // 处理首页访问
esp_err_t start_http_server(void); // 启动整个服务器start_http_server() 内部流程:
- 准备配置 → 拿到 ESP-IDF 的默认 HTTP 服务器配置
- 启动服务器 → 调用
httpd_start(),此时 ESP32 开始监听 80 端口 - 注册路由 → 告诉服务器:如果有人访问
/,就用root_handler处理
root_handler() 做了什么:
当浏览器访问 ESP32 时,这个函数会生成一段完整的 HTML 代码,返回给浏览器。这段 HTML 包含了:
- 一个深色的卡片式页面
- 显示 "ESP32 在线" 的状态文字
- 一个"查看实时画面"按钮(按钮暂时不可用,留给以后扩展)
顶层 CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp_web)这 3 行是 ESP-IDF 项目的固定模板。include(...) 引入了 ESP-IDF 的构建系统,project(esp_web) 声明了项目名称。
components/CMakeLists.txt(最重要的配置):
# 告诉编译器:去这两个目录找源代码
set(src_dirs "Hardware/wifi_sta" "Driver/Web")
# 告诉编译器:去这两个目录找头文件
set(include_dirs "Hardware/wifi_sta" "Driver/Web")
# 告诉编译器:我们这个模块需要依赖以下 ESP-IDF 官方模块
set(requires
esp_http_server # HTTP 服务器(Web 模块需要)
driver # GPIO 驱动
esp_driver_uart # 串口驱动
nvs_flash # 存储配置(Wi-Fi 模块需要)
esp_wifi # Wi-Fi 功能(Wi-Fi 模块需要)
)为什么需要 requires?
- 你在代码里写了
#include "esp_http_server.h",编译器要去哪找这个文件? requires就是告诉编译器:去esp_http_server这个官方模块的目录里找。- 不加的话,编译器会报错
esp_http_server.h: No such file or directory。
打开 components/Hardware/wifi_sta/wifi_sta.h,把你的 Wi-Fi 名称和密码填进去:
#define DEFAULT_SSID "你的WiFi名字" // 注意大小写,不要有中文
#define DEFAULT_PWD "你的WiFi密码"在项目根目录打开终端,执行:
# 设置 ESP-IDF 环境(如果还没设置)
source ~/esp/esp-idf/export.sh
# 选择目标芯片
idf.py set-target esp32s3
# 编译
idf.py build第一次编译会下载一些依赖,需要几分钟时间。
# 用 USB 连接 ESP32 和电脑
# 查看 ESP32 的端口号(Linux/macOS)
ls /dev/ttyUSB* # 或者 /dev/ttyACM*
# 烧录(把 /dev/ttyUSB0 换成你实际的端口)
idf.py -p /dev/ttyUSB0 flash
# 或者直接烧录 + 打开串口监视器(看芯片的日志输出)
idf.py -p /dev/ttyUSB0 flash monitor要退出串口监视器,按
Ctrl + ]。
烧录成功后,串口监视器会输出类似这样的日志:
I (1234) wifi_sta: Wi-Fi 连接成功!IP 地址:192.168.1.100
I (1234) web_server: HTTP 服务器启动成功!监听端口 80
I (1234) esp_web: ===== 系统初始化完成!=====
记下 IP 地址(这里是 192.168.1.100),在浏览器的地址栏输入:
http://192.168.1.100
你就能看到 ESP32 的网页了!
改 wifi_sta.h 中的 DEFAULT_SSID 和 DEFAULT_PWD 即可。
修改 web_server.c 中 root_handler() 函数的 html 字符串。HTML 代码就写在 C 代码里,你可以:
- 修改标题文字
- 修改按钮链接
- 修改页面颜色
在 web_server.c 的 start_http_server() 函数中,在后面添加:
// 添加 /hello 路由
httpd_uri_t hello_uri = {
.uri = "/hello",
.method = HTTP_GET,
.handler = hello_handler, // 你需要先实现这个函数
.user_ctx = NULL
};
httpd_register_uri_handler(server, &hello_uri);然后实现 hello_handler 函数:
esp_err_t hello_handler(httpd_req_t *req)
{
httpd_resp_send(req, "Hello from ESP32!", 17);
return ESP_OK;
}这个项目的 HTML 中已经预留了 /stream 按钮,后续可以接入 ESP32-CAM 的摄像头功能。
原因: CMakeLists.txt 的 requires 中没有加上 esp_http_server。
解决: 检查 components/CMakeLists.txt 中是否包含:
set(requires
esp_http_server ← 确保这行存在
...
)如果加了还报错,删除 build 目录重新编译:
rm -rf build
idf.py build| 系统 | 常见端口 |
|---|---|
| Linux | /dev/ttyUSB0 或 /dev/ttyACM0 |
| macOS | /dev/cu.usbserial-XXXX 或 /dev/cu.SLAB_USBtoUART |
| Windows | COM3 或 COM4(在设备管理器查看) |
如果 Linux 下没有权限,执行:
sudo usermod -a -G dialout $USER
# 然后注销重新登录- 检查 SSID 和密码是否写对(注意大小写)
- 检查 Wi-Fi 是否是 2.4GHz(ESP32 不支持 5GHz)
- 检查 Wi-Fi 是否有中文名称(不支持,请用英文)
- 检查路由器是否开启了 MAC 地址过滤
- 如果以上都正常,可能是距离太远,把 ESP32 靠近路由器试试
原因: ESP32 没有进入下载模式。
解决:
- 检查 USB 线是否插紧(有些 USB 线只能充电不能传数据)
- 手动进入下载模式:按住 BOOT 键 → 按一下 RESET 键 → 松开 BOOT 键
- 然后再试一次烧录命令
这个项目是一个起点,你可以在此基础上继续扩展:
- 添加传感器 — 在 Wi-Fi 模块中添加读取 DHT11 温湿度传感器的代码
- 添加控制功能 — 添加 POST 路由,让网页按钮能控制 GPIO 输出(如开关 LED)
- 添加摄像头 — 接入 ESP32-CAM 摄像头,实现
/stream实时视频流 - 添加 MQTT — 连接到 MQTT 服务器,实现远程控制
- 添加 WebSocket — 实现网页与 ESP32 的实时双向通信
最后的话: 如果你是第一次接触 ESP32 和嵌入式开发,遇到问题很正常。记住三个原则:
- 看日志 — ESP32 会在串口输出详细的调试信息,大部分问题的答案都在日志里
- 问搜索引擎 — 你遇到的大多数问题,别人早就遇到并解决了
- 不要怕改代码 — 代码写错了最多重烧一次,不会烧坏芯片的