基于 ESP32-S3 与 ESP-IDF 的网页 LED 控制系统设计与实现

一、可选标题

  1. 基于 ESP32-S3 与 ESP-IDF 的网页 LED 控制系统设计与实现
  2. ESP32 Web 控制项目实战:从 SoftAP 热点到浏览器控制 GPIO
  3. 用 ESP-IDF 搭建一个可复现的 ESP32-S3 网页控制面板

二、文章摘要

本文基于一个真实 ESP-IDF 工程,复盘 ESP32-S3 网页控制 LED 的完整实现过程。项目运行后,ESP32-S3 以 SoftAP 模式创建 WiFi 热点,手机或电脑连接热点后访问 192.168.4.1,即可打开内置网页控制面板。网页通过 HTTP GET 请求访问 /led/on/led/off/led/blink/led/status 等接口,ESP32 端由 esp_http_server 接收请求,再调用 LED 控制模块修改 GPIO1 电平,实现 LED 常亮、熄灭和闪烁。项目还集成了 LCD 显示、NVS 初始化、FreeRTOS 任务、BSP 驱动组件和 16MB 自定义分区表,适合作为从 Arduino 过渡到 ESP-IDF 的 Web 控制入门工程。

三、正文

1. 项目简介

这个项目是一个基于 ESP32-S3 的网页控制硬件实验。它的核心目标并不复杂:ESP32-S3 创建 WiFi 热点,启动 HTTP Web Server,用户通过浏览器打开网页,点击按钮后控制开发板上的红色 LED。

项目使用的是 ESP-IDF,而不是 Arduino。代码中可以看到 WiFi、事件循环、HTTP Server、FreeRTOS 任务、NVS、BSP 驱动和自定义分区表等典型 ESP-IDF 工程元素。对于想从 Arduino 过渡到 ESP-IDF 的开发者来说,这个项目很适合用来理解“网页请求如何真正落到硬件 GPIO 上”。

2. 项目功能概述

当前版本已经实现的功能包括:

  • ESP32-S3 创建 WiFi SoftAP 热点;
  • 热点名称为 ESP32S3 WIFI
  • 热点密码为 123456789
  • 默认访问地址为 http://192.168.4.1/
  • 启动 ESP-IDF HTTP Web Server;
  • 浏览器访问 / 返回网页控制面板;
  • 网页提供 LED 打开、关闭、闪烁和状态查询按钮;
  • ESP32-S3 通过 GPIO1 控制红色 LED;
  • LCD 显示 AP 启动信息、IP 地址和设备连接状态;
  • LED 闪烁由独立 FreeRTOS 任务完成。

3. 项目目录结构解析

项目根目录主要内容如下:

WiFi_AP
├── CMakeLists.txt
├── README.md
├── sdkconfig
├── partitions-16MiB.csv
├── main
│   ├── CMakeLists.txt
│   ├── main.c
│   ├── led_ctrl.c
│   ├── led_ctrl.h
│   ├── web_server.c
│   └── web_server.h
└── components
    ├── BSP
    │   ├── CMakeLists.txt
    │   ├── LED
    │   ├── MYIIC
    │   ├── MYSPI
    │   ├── SPILCD
    │   └── XL9555
    └── Middlewares

在这里插入图片描述

根目录 CMakeLists.txt 中项目名为 03_WiFi_AP

cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS components/Middlewares)
add_compile_options(-fdiagnostics-color=always)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(03_WiFi_AP)

代码说明:

这是标准 ESP-IDF CMake 工程入口。project(03_WiFi_AP) 决定最终生成的固件名称,构建产物中可以看到 03_WiFi_AP.bin03_WiFi_AP.elf

main/CMakeLists.txt 注册了业务源码:

idf_component_register(SRCS "main.c"
                            "led_ctrl.c"
                            "web_server.c"
                    INCLUDE_DIRS "."
                    REQUIRES BSP esp_wifi esp_event esp_netif nvs_flash esp_http_server lwip)

代码说明:

main.c 负责系统入口、外设初始化、WiFi AP 启动;web_server.c 负责 HTTP 服务和网页接口;led_ctrl.c 负责 LED 状态管理。依赖项里包含 esp_wifiesp_http_serveresp_netifnvs_flash,说明这个项目是典型 ESP-IDF 网络控制工程。

4. 项目整体架构设计

整个项目可以分为四层:

  • 浏览器前端层:由 web_server.c 中的 s_index_html 字符串提供 HTML、CSS、JavaScript;
  • HTTP 接口层:由 ESP-IDF esp_http_server 提供,注册多个 GET 路由;
  • 业务控制层:由 led_ctrl.c 维护 LED 状态并统一控制 GPIO;
  • 硬件驱动层:由 components/BSP/LED/led.c 配置 GPIO1 并输出电平。

Mermaid 系统架构图

连接热点 ESP32S3 WIFI

访问 http://192.168.4.1/

GET / 返回网页

fetch('/led/on') 等请求

调用路由 handler

led_ctrl_set_state()

LED0(0) / LED0(1)

GPIO1 输出高低电平

连接 / 断开事件

显示 IP 与 MAC

手机 / 电脑浏览器

ESP32-S3 SoftAP

HTTP Web Server

内嵌 HTML/CSS/JS 页面

web_server.c

led_ctrl.c

BSP LED 驱动

红色 LED

wifi_event_handler()

SPI LCD

这张图可以看出,浏览器并不直接操作 GPIO,而是通过 HTTP 请求进入 ESP32-S3 的 Web Server,再由服务端调用 LED 控制模块,最后落到 BSP 驱动和 GPIO 电平。

5. 核心工作流程说明

项目启动后的主流程在 main.capp_main() 中:

ret = nvs_flash_init();

led_ctrl_init();
my_spi_init();
myiic_init();
xl9555_init();
spilcd_init();

wifi_init_softap(ap_ip, sizeof(ap_ip));
ESP_ERROR_CHECK(web_server_start(EXAMPLE_ESP_WIFI_SSID, ap_ip));

代码说明:

程序先初始化 NVS,再初始化 LED、SPI、IIC、XL9555 和 LCD。随后 ESP32-S3 进入 SoftAP 模式创建热点,最后启动 HTTP Web Server。主任务后续只保留 vTaskDelay() 空闲循环,不再直接翻转 LED,避免阻塞 WiFi、HTTP 和 LCD 事件处理。

Mermaid 启动流程图

app_main() 入口

初始化 NVS: nvs_flash_init()

NVS 是否需要擦除?

nvs_flash_erase() 后重新初始化

继续启动流程

led_ctrl_init(): 初始化 LED 控制模块

my_spi_init(): 初始化 SPI

myiic_init(): 初始化 IIC

xl9555_init(): 初始化 IO 扩展芯片

spilcd_init(): 初始化 LCD

LCD 显示 ESP32-S3 / WiFi AP Test

wifi_init_softap(): 创建 WiFi 热点

获取 AP IP 地址

web_server_start(): 启动 HTTP Server

主任务进入 vTaskDelay 空闲循环

6. WiFi SoftAP 初始化解析

项目中 WiFi 相关配置定义在 main.c

#define EXAMPLE_ESP_WIFI_SSID   "ESP32S3 WIFI"
#define EXAMPLE_ESP_WIFI_PASS   "123456789"
#define EXAMPLE_MAX_STA_CONN    5

代码说明:

ESP32-S3 不是连接已有路由器,而是自己创建热点。手机或电脑需要先连接 ESP32S3 WIFI,再访问开发板 IP。

SoftAP 初始化函数是 wifi_init_softap(),关键步骤包括:

ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());

esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

ESP_ERROR_CHECK(esp_event_handler_register(
    WIFI_EVENT,
    ESP_EVENT_ANY_ID,
    &wifi_event_handler,
    NULL
));

代码说明:

esp_netif_init() 初始化网络接口层;esp_event_loop_create_default() 创建默认事件循环;esp_netif_create_default_wifi_ap() 创建默认 AP 网络接口;esp_wifi_init() 初始化 WiFi 驱动;esp_event_handler_register() 注册 WiFi 事件回调。

AP 配置如下:

wifi_config_t wifi_config = {
    .ap = {
        .ssid = EXAMPLE_ESP_WIFI_SSID,
        .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
        .password = EXAMPLE_ESP_WIFI_PASS,
        .max_connection = EXAMPLE_MAX_STA_CONN,
        .authmode = WIFI_AUTH_WPA_WPA2_PSK
    },
};

代码说明:

项目最多允许 5 个设备连接热点,认证方式为 WPA/WPA2-PSK。如果密码长度为 0,代码会切换为开放热点。

启动 AP 的核心代码:

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());

启动完成后,项目通过 esp_netif_get_ip_info() 获取 AP 网关 IP,并用 inet_ntoa_r() 转成字符串,传给 Web Server 显示。

7. WiFi 事件回调与 LCD 显示

项目注册了 wifi_event_handler() 处理设备连接和断开事件。

当有设备连接时:

if (event_id == WIFI_EVENT_AP_STACONNECTED)
{
    wifi_event_ap_staconnected_t *event =
        (wifi_event_ap_staconnected_t *)event_data;

    ESP_LOGI(TAG, "station " MACSTR " join, AID=%d",
             MAC2STR(event->mac), event->aid);

    sprintf(lcd_buff, "MACSTR:"MACSTR, MAC2STR(event->mac));
    spilcd_show_string(0, 90, 320, 16, 16, lcd_buff, BLUE);
}

代码说明:

当手机或电脑连上热点时,ESP32-S3 会在串口打印 MAC 地址,并在 LCD 上显示连接设备的 MAC。设备断开时,回调处理 WIFI_EVENT_AP_STADISCONNECTED,同样更新 LCD。

[配图建议:LCD 显示 AP IP 与设备 MAC 的运行效果图]
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

8. HTTP Web Server 路由解析

Web 服务入口在 web_server.c

esp_err_t web_server_start(const char *ap_ssid, const char *ap_ip)

它接收当前热点名和 IP 地址,用于网页状态显示。

服务启动使用 ESP-IDF 默认 HTTP Server 配置:

httpd_config_t config = HTTPD_DEFAULT_CONFIG();

esp_err_t ret = httpd_start(&s_http_server, &config);

实际注册的路由如下:

GET /             返回网页控制面板
GET /led/on       打开 LED
GET /led/off      关闭 LED
GET /led/blink    LED 闪烁模式
GET /led/status   查询当前 LED 状态

路由注册代码:

ESP_ERROR_CHECK(httpd_register_uri_handler(s_http_server, &index_uri));
ESP_ERROR_CHECK(httpd_register_uri_handler(s_http_server, &led_on_uri));
ESP_ERROR_CHECK(httpd_register_uri_handler(s_http_server, &led_off_uri));
ESP_ERROR_CHECK(httpd_register_uri_handler(s_http_server, &led_blink_uri));
ESP_ERROR_CHECK(httpd_register_uri_handler(s_http_server, &led_status_uri));

代码说明:

当前版本使用 GET 请求,没有使用 POST。对于 LED 实验来说,GET 足够直观,浏览器直接访问接口也能验证效果。如果后续控制继电器、门锁等更严肃的设备,更建议改成 POST 并加入权限校验。

Mermaid HTTP 路由分发流程图

/

/led/on

/led/off

/led/blink

/led/status

其他 URI

浏览器发起 HTTP GET 请求

ESP-IDF HTTP Server

请求 URI 是什么?

index_get_handler()

返回 s_index_html 控制页面

led_on_get_handler()

led_ctrl_set_state(LED_ON)

led_off_get_handler()

led_ctrl_set_state(LED_OFF)

led_blink_get_handler()

led_ctrl_set_state(LED_BLINK)

led_status_get_handler()

send_led_status_json()

http_404_error_handler()

返回 404 页面

返回 JSON: ssid / ip / state

9. 网页前后端交互逻辑说明

项目没有单独的 htmlcssjs 文件。整个网页写在 web_server.cs_index_html[] 字符串中。

页面包含:

  • 当前热点名称;
  • 当前开发板 IP;
  • 当前 LED 状态;
  • 打开 LED 按钮;
  • 关闭 LED 按钮;
  • 闪烁模式按钮;
  • 查询状态按钮。

核心 JavaScript 逻辑如下:

async function refreshStatus(){
  const r = await fetch('/led/status', {cache:'no-store'});
  const j = await r.json();
  document.getElementById('ssid').textContent = j.ssid;
  document.getElementById('ip').textContent = j.ip;
  document.getElementById('state').textContent = j.state;
}

async function setLed(uri){
  await fetch(uri, {cache:'no-store'});
  refreshStatus();
}

refreshStatus();
setInterval(refreshStatus, 2000);

代码说明:

refreshStatus() 请求 /led/status,拿到 JSON 后刷新页面显示。setLed(uri) 根据按钮传入的 URI 请求 /led/on/led/off/led/blink。页面加载后立即查询一次状态,并且每 2 秒自动刷新一次。

状态接口返回 JSON:

snprintf(json, sizeof(json),
         "{\"ssid\":\"%s\",\"ip\":\"%s\",\"state\":\"%s\"}",
         s_ap_ssid, s_ap_ip, state);

代码说明:

返回内容包含热点名称、IP 地址和 LED 状态。这里还设置了响应类型和缓存控制:

httpd_resp_set_type(req, "application/json; charset=utf-8");
httpd_resp_set_hdr(req, "Cache-Control", "no-store");

这可以减少浏览器缓存导致状态显示不更新的问题。

Mermaid 前端点击到硬件响应时序图

GPIO1 / LED led_ctrl.c web_server.c ESP32 HTTP Server 浏览器页面 用户 GPIO1 / LED led_ctrl.c web_server.c ESP32 HTTP Server 浏览器页面 用户 点击“打开 LED” GET /led/on led_on_get_handler() led_ctrl_set_state(LED_ON) LED0(0), GPIO1 输出低电平 LED 点亮 状态更新完成 send_led_status_json() 返回 {"state":"ON"} 页面状态显示 ON

10. 硬件控制逻辑说明

LED 状态定义在 led_ctrl.h

typedef enum
{
    LED_OFF = 0,
    LED_ON,
    LED_BLINK
} led_ctrl_state_t;

代码说明:

项目支持三种状态:关闭、打开、闪烁。

硬件上,红色 LED 接在 GPIO1:

#define LED0_GPIO_PIN    GPIO_NUM_1

components/BSP/LED/led.h 中定义了控制宏:

#define LED0(x) do { x ? \
    gpio_set_level(LED0_GPIO_PIN, 1): \
    gpio_set_level(LED0_GPIO_PIN, 0); \
} while(0)

代码说明:

本项目 LED 是低电平点亮。LED0(0) 输出低电平,LED 点亮;LED0(1) 输出高电平,LED 熄灭。这是很多开发板上常见的“低电平有效”设计,初学者很容易把电平逻辑写反。

led_ctrl.c 中真正执行状态切换的是:

static void led_ctrl_apply_level(led_ctrl_state_t state)
{
    switch (state)
    {
    case LED_ON:
        LED0(0);
        break;

    case LED_OFF:
        LED0(1);
        break;

    case LED_BLINK:
    default:
        LED0(0);
        break;
    }
}

代码说明:

网页并不直接操作 GPIO,而是通过状态枚举控制 LED。这样做的好处是业务层和底层 GPIO 解耦,后续把 LED 换成继电器或风扇时,只需要扩展控制模块。

闪烁任务如下:

static void led_blink_task(void *arg)
{
    while (1)
    {
        portENTER_CRITICAL(&s_led_mux);
        if (s_led_state == LED_BLINK)
        {
            LED0_TOGGLE();
        }
        portEXIT_CRITICAL(&s_led_mux);

        vTaskDelay(pdMS_TO_TICKS(LED_BLINK_PERIOD_MS));
    }
}

代码说明:

LED 闪烁由独立 FreeRTOS 任务负责,周期为 500ms。使用 vTaskDelay() 只会挂起当前 LED 任务,不会阻塞 HTTP Server 或 WiFi 事件循环。

项目还使用临界区保护 LED 状态:

static volatile led_ctrl_state_t s_led_state = LED_BLINK;
static portMUX_TYPE s_led_mux = portMUX_INITIALIZER_UNLOCKED;

代码说明:

HTTP 请求处理函数和 LED 闪烁任务可能同时访问 s_led_state,所以用 portENTER_CRITICAL()portEXIT_CRITICAL() 保护状态和 GPIO 更新,避免切换瞬间多翻转一次 LED。

Mermaid GPIO 控制执行流程图

LED_ON

LED_OFF

LED_BLINK

当前为 BLINK

当前非 BLINK

led_ctrl_set_state(state)

state 是否合法?

打印非法状态日志并返回

进入临界区 portENTER_CRITICAL

更新 s_led_state

目标状态

LED0(0): GPIO1 输出低电平

LED0(1): GPIO1 输出高电平

LED0(0): 先点亮 LED

退出临界区

打印 LED 状态切换日志

led_blink_task 周期执行

LED0_TOGGLE(): 翻转 GPIO1

保持 ON/OFF 状态

vTaskDelay(500ms)

请添加图片描述

11. BSP、LCD 与外设初始化说明

项目的 BSP 组件位于 components/BSP,包含:

  • LED:板载红色 LED 驱动;
  • MYIIC:IIC 初始化;
  • MYSPI:SPI 初始化;
  • SPILCD:SPI LCD 显示驱动;
  • XL9555:IO 扩展芯片驱动。

BSP 组件的 CMakeLists.txt 注册了这些目录:

set(src_dirs
            LED
            MYIIC
            XL9555
            MYSPI
            SPILCD)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

项目中实际用到的外设初始化顺序为:

led_ctrl_init();
my_spi_init();
myiic_init();
xl9555_init();
spilcd_init();

关键引脚信息:

LED0: GPIO1
IIC SDA: GPIO41
IIC SCL: GPIO42
SPI SCLK: GPIO12
SPI MOSI: GPIO11
SPI MISO: GPIO13
LCD DC: GPIO40
LCD CS: GPIO21
XL9555 地址: 0x20

README 中也说明了实验平台为正点原子 ESP32-S3 开发板,红色 LED 连接 IO1,XL9555 使用 IO41/IO42 作为 IIC 引脚。

图1:打开LED
在这里插入图片描述
图2:关闭LED
在这里插入图片描述

图3:闪烁模式
在这里插入图片描述

12. 分区表与 sdkconfig 关键配置

项目使用自定义分区表 partitions-16MiB.csv

# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,0x6000,,
phy_init,data,phy,0xf000,0x1000,,
factory,app,factory,0x10000,0x1F0000,,
vfs,data,fat,0x200000,0xA00000,,
storage,data,spiffs,0xc00000,0x400000,,

说明:

  • nvs 用于存储 WiFi、PHY 等非易失数据;
  • phy_init 用于 RF 校准相关数据;
  • factory 是当前应用固件分区,大小约 1984KB;
  • vfs 是 FAT 分区,大小 10MB;
  • storage 是 SPIFFS 分区,大小 4MB。

当前代码没有挂载 FATFS 或 SPIFFS,也没有从 Flash 文件系统加载网页。网页是编译进固件的字符串。保留 FAT 和 SPIFFS 分区,说明后续可以扩展为“外部网页资源文件”方案。

sdkconfig 关键配置包括:

CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_FILENAME="partitions-16MiB.csv"
CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
CONFIG_HTTPD_MAX_URI_LEN=512
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
CONFIG_LWIP_DHCPS=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240

build 目录可以反推出:

  • 目标芯片:ESP32-S3;
  • ESP-IDF 版本:v5.1.2;
  • 构建工具:Ninja;
  • 编译器:xtensa-esp32s3-elf-gcc
  • 串口监视波特率:115200;
  • Flash:16MB,80MHz;
  • App 固件大小约 839KB;
  • Bootloader 大小约 21KB;
  • 应用烧录地址:0x10000
  • 分区表烧录地址:0x8000
  • Bootloader 烧录地址:0x0

13. 为什么选择 ESP-IDF,而不是简单 Arduino 写法

如果只是点亮 LED,Arduino 的确更快。但这个项目的目标不是“点灯”,而是打通完整网络控制链路。

ESP-IDF 的优势体现在:

  • WiFi AP、事件循环、HTTP Server 都是组件化接口;
  • FreeRTOS 任务可以把 LED 闪烁、网络服务、主任务拆开;
  • esp_http_server 可以清晰注册路由,适合扩展更多接口;
  • sdkconfig 和分区表可控,适合后续加入 SPIFFS、OTA、NVS 配网;
  • BSP 驱动组件可以把 LED、LCD、IIC、SPI、XL9555 与业务逻辑分离。

简单说,Arduino 适合快速验证,ESP-IDF 更适合把原型变成可维护工程。

14. 编译、烧录与运行方法

进入项目目录:

cd E:\esp32_tool\WiFi_AP

设置目标芯片:

idf.py set-target esp32s3

编译:

idf.py build

烧录并打开串口监视器:

idf.py flash monitor

运行后,串口会打印类似信息:

AP 启动成功,IP: 192.168.4.1
热点名称: ESP32S3 WIFI,访问地址: http://192.168.4.1/
HTTP 服务器启动成功

手机或电脑连接热点:

SSID: ESP32S3 WIFI
Password: 123456789

然后浏览器访问:

http://192.168.4.1/

[配图建议:ESP32-S3 串口日志截图]
乱码的翻译如下:
在这里插入图片描述

15. 运行效果与调试经验

运行效果:

  • LCD 显示 ESP32-S3WiFi AP Test
  • AP 启动后 LCD 显示 IP;
  • 设备连接热点后,LCD 显示连接设备 MAC;
  • 浏览器打开控制面板;
  • 点击“打开 LED”,GPIO1 输出低电平,LED 点亮;
  • 点击“关闭 LED”,GPIO1 输出高电平,LED 熄灭;
  • 点击“闪烁模式”,LED 以 500ms 周期翻转;
  • 页面每 2 秒刷新一次状态。

调试建议:

  1. 看串口日志

重点观察 APWEB_SERVER 标签下的日志。如果看不到 HTTP 服务器启动成功,说明 Web Server 没有正常启动。

  1. 用浏览器直接访问接口

可以直接访问:

http://192.168.4.1/led/on
http://192.168.4.1/led/off
http://192.168.4.1/led/status

如果接口能返回 JSON,说明 HTTP 路由正常。

  1. 验证 GPIO 是否真的变化

LED 不亮时,不要只看网页。可以用万用表测 GPIO1 电平,或者在 led_ctrl_set_state() 中加日志确认请求是否到达。

  1. 排查连接问题

手机必须先连接 ESP32S3 WIFI 热点。没有连接热点时,访问 192.168.4.1 通常打不开。

16. 我踩过的坑

16.1 网页打不开

最常见原因是手机没有连接 ESP32 热点,或者手机自动切回了有互联网的 WiFi。解决方法是确认当前 WiFi 名称为 ESP32S3 WIFI

16.2 路由不匹配

项目注册的是 /led/on/led/off/led/blink/led/status。如果写成 /led/open/status,会进入 404 处理函数。

16.3 GPIO 电平逻辑反了

本项目 LED 是低电平点亮。LED0(0) 才是亮,LED0(1) 是灭。控制继电器时也要先确认模块是高电平触发还是低电平触发。

16.4 WiFi 连接失败

热点密码是 123456789。如果修改密码,要注意 WPA/WPA2 密码长度不能太短。代码中如果密码长度为 0,会切换为开放热点。

16.5 浏览器缓存导致页面没更新

项目已经在 HTML 和 JSON 响应里设置 Cache-Control: no-store,前端 fetch 也使用 {cache:'no-store'}。如果修改了网页但浏览器仍显示旧页面,可以强制刷新或换无痕窗口。

16.6 分区表设置不当

当前固件约 839KB,factory 分区约 1984KB,够用。但如果后续加入大网页资源、图片、OTA 双分区,现有分区表需要重新规划。

17. 面向初学者的几个概念解释

HTTP 路由:

可以理解为“浏览器访问的地址路径”。例如 /led/on 对应 C 端的 led_on_get_handler()

GET 请求:

浏览器请求资源或触发简单操作的一种方式。本项目用 GET 控制 LED,便于测试。工程化场景中,控制类操作更推荐 POST。

GPIO:

ESP32 的通用输入输出引脚。这里 GPIO1 被配置为输出,用高低电平控制 LED。

电平控制:

GPIO 输出 0 是低电平,输出 1 是高电平。本项目 LED 低电平亮,所以打开 LED 要输出 0。

事件驱动:

WiFi 连接、断开不是在主循环里一直查,而是由 ESP-IDF 在事件发生时调用回调函数,比如 wifi_event_handler()

18. 常见问题与解决方案

Q1:为什么连接热点后仍然打不开网页?

先确认手机当前连接的是 ESP32S3 WIFI。部分手机检测到热点不能上网后,会自动切换到其他 WiFi 或移动网络。可以临时关闭移动数据再访问 http://192.168.4.1/

Q2:为什么 LED 状态显示 ON,但灯没有亮?

检查开发板 LED 是否确实接在 GPIO1,并确认 LED 是否为低电平点亮。本项目的 LED0(0) 表示点亮。

Q3:为什么访问接口返回 404?

检查 URI 是否完全匹配。当前代码只注册了 //led/on/led/off/led/blink/led/status

Q4:能不能把网页放到 SPIFFS?

可以。当前分区表已经保留了 storage SPIFFS 分区,但代码还没有挂载 SPIFFS,也没有实现静态文件读取。后续可以把 HTML、CSS、JS 拆成文件放到 SPIFFS,再由 HTTP Server 按文件路径返回。

Q5:能不能控制继电器或风扇?

可以,但当前版本未实现。建议新增类似 device_ctrl.c 的模块,将 GPIO 编号、电平有效逻辑和设备状态封装起来,再新增 /relay/on/fan/off 等接口。

19. 项目总结与可扩展方向

这个项目的工程价值在于:它把“浏览器请求 -> HTTP 路由 -> 状态解析 -> GPIO 控制 -> 硬件响应”的链路完整打通了。

当前版本控制对象是 LED,但架构已经适合继续扩展:

  • 远程互联网控制:把 SoftAP 改为 STA 模式,连接路由器,再接入云服务器或 MQTT;
  • 传感器数据上报:增加温湿度、光照、电流等传感器接口,并通过 /sensor/status 返回 JSON;
  • WebSocket 实时状态刷新:当前 sdkconfig 未启用 HTTPD WebSocket,可后续打开 CONFIG_HTTPD_WS_SUPPORT
  • 账号登录与权限控制:增加登录页面、Token 或简单 Basic Auth;
  • OTA 在线升级:当前分区表没有 OTA 双分区,需要重新规划;
  • 多设备联动:把 led_ctrl 抽象为 device_ctrl,支持继电器、风扇、电机等多路 GPIO。

对于初学者来说,这个项目最值得学习的不是某一个 API,而是它的分层思路:网络请求交给 HTTP Server,业务状态交给 led_ctrl.c,底层电平交给 BSP 驱动。这样代码后续才容易继续长大,而不是所有逻辑都塞进 app_main()

需要源码的,请在评论区下留言。制作不易,请各位观众老爷点个赞和收藏

Logo

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

更多推荐