6 分钟阅读

ESP32 双灯氛围灯项目实践记录

基于 ESP32 + WS2812B 的 Web 控制与人体感应智能氛围灯系统完整实现

一、项目背景与动机

在学习嵌入式开发和物联网的过程中,我希望做一个综合度较高、可交互性强、并且能真实落地使用的小项目。相比单纯点灯或传感器实验,一个集成了 WiFi 通信、网页交互、LED 动效和人体感应 的系统更能锻炼工程能力。

上了大学以后,第一时间就去网上查找有没有什么入门级别的硬件,于是首先遇到了ASR-Pro,后遇到了ESP32。我买了这两个单片机,并很快对ESP32产生了极大的兴趣。我去网上查了很多资料,并在deepseek的协助下开始使用VSCode的进行编程和调试。一开始我还是很懵懂,不知道从哪里开始,但是后来逛pdd的时候又看到了一条灯带。我又产生了浓厚的兴趣,买来玩了一下,感觉可以做些什么。当是时也,我去参加了学校的工创基地,了解了更多的知识,顺便薅来一个灯环。关于这个灯环,我后面可以再来聊一聊。

因此,我设计并实现了这个项目:

ESP32 双灯环智能氛围灯(Web 控制 + 人体感应)

项目目标包括:

  • 使用 ESP32 搭建最小可用 WebServer,用于网页控制(这是后加功能)
  • 驱动WS2812B RGB LED,运行简单的效果程序
  • 实现人体红外传感器与灯光逻辑的联动
  • 最重要的一点,学习如何书写工程类代码,怎么充分使用面向对象编程

我在这个项目里,收获最大的就是如何合理分布代码到多个文件,如何科学调用,如何规范使用各个功能。我还学到了单片机程序的基本运行逻辑,学习使用状态机来控制模式。deepseek帮我手搓了一个很不错的网页,我用来日常控制。这个灯给我每天带来了很多陪伴,我来了就会自己呼吸,然后亮起来迎接我,我走了就会熄灭。另一方面,也给我的桌面增加了不少美观度。

二、系统整体架构

从系统结构上看,该项目可以分为四个核心模块:

  1. 硬件层:ESP32 + 双 WS2812B 灯环 + 2.4G 红外人体感应
  2. 驱动层:FastLED 库、GPIO 读取
  3. 逻辑层:灯光状态机 + 自动模式逻辑
  4. 交互层:ESP32 WebServer + 前端控制页面

整体运行流程如下:

上电 → WiFi 连接 → LED 初始化 → WebServer 启动
                  ↓
            人体传感器检测
                  ↓
          状态机切换 + 灯效更新

2.4G人体存在传感器使用的是HLK-LD2402-24G-人体传感器模块,相关资料网络可以获取。关于这个传感器,我也有很多想说的,等后面有机会可以说一说。

三、硬件设计与选型

1. 核心器件

模块 说明
ESP32 主控芯片,负责 WiFi + 逻辑控制
WS2812B × 60 主灯环,用于主要灯效
WS2812B × 16 副灯环,用于辅助灯效
2.4G人体存在传感器 人体存在检测
5V 电源模块(学长的馈赠) 全器件供电

2. 引脚分配

所有引脚集中在 config.h 中统一管理:

static constexpr int MAIN_LED_PIN = 19;
static constexpr int RING_LED_PIN = 18;
static constexpr int MOTION_SENSOR_PIN = 15;
static constexpr int BOARD_LED_PIN = 2;

也就是说

GPIO19针脚->灯带信号线
GPIO18针脚->灯环信号线
GPIO15针脚->人体存在传感器信号引脚
GPIO2->板载LED,无须接线

在config.h文件中统一定义的好处是:

  • 修改硬件连接时不需要全局搜索
  • 提高代码的可维护性

四、软件结构设计

1. 文件组织结构

项目采用了类 + 模块化的写法,结构如下:

├── main.cpp              // 主循环
├── config.h              // 全局配置
├── LED_Controller.h/.cpp // LED 控制与状态机
├── motion_sensor.h/.cpp  // 人体感应逻辑

各模块职责非常清晰:

  • main.cpp:系统初始化 + loop 调度
  • LEDController:灯效、Web、状态管理
  • MotionSensor:只负责“是否有人”这一判断

后期期望将网页分离出来,单独写成文件,但是目前来说,不太理想,所以直接内嵌在现有文件(应该是LEDController文件里)可能会效果更好一点…

五、核心设计:灯光状态机

1. 为什么要用状态机?

一开始如果用 if-else 直接写逻辑,很快就会变成:

  • 条件嵌套复杂
  • 自动模式和手动模式相互干扰
  • 很难插入“渐入 / 渐出”动画

因此我使用了deepseek教我的有限状态机FSM的设计思想。

2. 状态定义

config.h 中定义系统状态:

enum SystemState {
  STATE_AUTO_BREATH,
  STATE_AUTO_FADE_IN,
  STATE_AUTO_NORMAL,
  STATE_AUTO_FADE_OUT,
  STATE_AUTO_OFF,

  STATE_OFF,
  STATE_BREATHE,
  STATE_FADE_IN,
  STATE_NORMAL,
  STATE_FADE_OUT,
  STATE_MANUAL
};

可以看到:

  • 自动模式手动模式 是严格区分的
  • 自动模式拥有完整的生命周期

有一点,不知道能不能进一步优化自动情况,但是目前来说应该算是最佳优化。后期可以直接加入新的模式和功能。

3. 状态转移逻辑

典型自动模式流程:

检测到人体
   ↓
STATE_AUTO_BREATH(呼吸提示)(白色流水呼吸)
   ↓
STATE_AUTO_FADE_IN(渐亮)(彩虹状态亮度渐强)
   ↓
STATE_AUTO_NORMAL(彩虹流动)(正常RGB流动效果)
   ↓
无人 → STATE_AUTO_FADE_OUT → STATE_AUTO_OFF

这一逻辑全部集中在 LEDController::update() 中处理,结构清晰、易于维护。

六、LED 灯效实现思路

1. 呼吸灯效果

呼吸灯效果现在还没完成,后面会完善效果。我正在思考是选择之前的独立生命周期粒子效果还是单纯的光环效果。现在我还是更偏向于独立的生命周期的粒子,这样子更自然,更优雅一点。这就要复用我之前的代码了。

2. 彩虹流动效果

彩虹模式基于我自己写的 fill_rainbow函数:

fill_rainbow(mainig(mainLeds, NUM, hue, step);
hue += 2;

配合 millis() 定时更新,避免阻塞主循环。

七、Web 控制界面设计

ESP32 内置 WebServer,直接返回 HTML 页面:

  • 模式切换按钮
  • 亮度滑块
  • RGB 颜色选择器

前端通过 fetch('/control?...') 与 ESP32 通信,实现:

  • 即点即生效
  • 无需额外 App,只需要连接到局域网
  • 全端口均可访问

这是我认为性价比最高的一种 IoT 交互方式。

现有问题:无网络连接的时候相当于是板砖(或者说刮痧片(?)),需要在无网络模式下依然可以进行正常操作的一套逻辑。我正在考虑入手一个触控板或者更简单一点的按钮面板,来直接控制模式。

八、人体感应联动逻辑

人体检测模块被单独封装为 MotionSensor 类:

if (currentMotionState) {
    ledController.setState(STATE_AUTO_BREATH);
} else {
    ledController.setState(STATE_AUTO_FADE_OUT);
}

并且只在自动模式下生效,避免和手动控制冲突。

九、踩过的坑与经验总结

  • WS2812B 对时序敏感,FastLED.show() 前后加微小延时更稳定
  • WebServer 回调中不要写阻塞代码
  • 状态机最好记录 lastState,否则一方面可能会有未知的问题,另一方面方便后期的调试和维护,或者添加新功能。
  • 所有“可调参数”都放进 config.h,方便后期维护
  • 对于我那个灯环,还要添加一个电阻来避免信号反射导致的不稳定和烧灯珠的危险。

十、项目总结与后续改进

这个项目可以在我的仓库看到,如果你喜欢这个项目,也欢迎来star一下~

通过这个项目,我完整走了一遍:

硬件接线 → 底层驱动 → 状态机设计 → Web 交互 → 系统整合

后续可以改进的方向包括:

  • 使用 WebSocket 实现实时同步
  • 增加更多灯效模式
  • 使用 FreeRTOS 进一步解耦任务(啊?这是啥?GPT你讲什么呢)

这是一个非常适合嵌入式与 IoT 入门的综合项目,也为之后更复杂的系统打下了基础。


本文为项目实践记录,代码与设计仍在持续优化中,欢迎交流与指正。

最后来放锦的美图(确信),尾页就是这样来用的(二度确信)

话说我一个人写这些东西的时候就是这个状态↑

留下评论