零知派ESP32——基于WiFi+TCP的WS2812多节点智能灯控系统
本文介绍了一个基于ESP32的多节点WS2812智能灯控系统,采用WiFi AP+TCP星型拓扑结构。系统包含硬件接线方案(包括ESP32开发板、WS2812B灯带、SSD1306 OLED等组件)、安装使用教程(节点烧录与配置)、代码架构解析(通信协议、灯光控制、TCP连接管理等核心功能)以及常见问题解答。该系统支持网页远程控制多盏LED灯,实现独立开关、亮度调节和颜色切换功能,具有局域网内低延
目录
项目概述
本文详细介绍一套基于 零知派ESP32 的多节点 WS2812 智能灯控系统,采用 WiFi AP + TCP 星型拓扑,支持多盏灯独立控制、网页远程操控、OLED 本地状态显示。无需云服务器、无需手机 APP,局域网内低延迟直连,适合有一定 ESP32 基础的朋友参考实现。
一、系统接线部
1.1 硬件清单
每个节点(根节点和子节点硬件完全一致)所需元件如下:
|
元件 |
规格参数 |
数量 |
备注 |
|---|---|---|---|
|
ESP32 开发板 |
零知派 ESP32 |
1 块 |
根节点和子节点各一块起 |
|
WS2812B 灯带 |
3.3V~5V 供电,8 颗灯珠 |
1 条 |
灯珠数量可在代码中修改 |
|
SSD1306 OLED |
0.96 寸,I2C 接口,128×64 分辨率 |
1 块 |
注意区分 I2C 和 SPI 版本 |
|
杜邦线 |
公对公 / 公对母 |
若干 |
— |
|
面包板 |
SYB-170 |
1 块 |
可选,用于快速搭建原型 |
|
5V 电源 |
电流建议 ≥ 1A |
1 个 |
灯珠数量多时需要独立供电 |
💡 选购提示:本项目使用 4 针 I2C 版本(GND / VCC / SCK / SDA)。注意区分,SPI 版本引脚定义完全不同。
1.2 接线方案表
WS2812B 灯带接线:
|
WS2812B 引脚 |
ESP32 引脚 |
说明 |
|---|---|---|
|
DIN(数据输入) |
GPIO5 |
建议串联 330Ω 限流电阻 |
|
VCC |
3.3V或5V |
灯珠较多时建议独立 5V 供电 |
|
GND |
GND |
独立供电时两块 GND 需共地 |
SSD1306 OLED 接线(I2C):
|
OLED 引脚 |
ESP32 引脚 |
说明 |
|---|---|---|
|
VCC |
3.3V |
部分模块支持 5V,看模块规格 |
|
GND |
GND |
— |
|
SDA |
GPIO21 |
ESP32 默认 I2C SDA |
|
SCL |
GPIO22 |
ESP32 默认 I2C SCL |
⚠️ 注意:GPIO5 输出 3.3V 电平,WS2812B 大多数型号可以直接驱动。若出现灯带颜色异常或随机闪烁,在数据线上串一个 330Ω 电阻可以有效改善信号质量。
1.3 连接示意图

1.4 具体接线图
单个节点完整接线(根节点与子节点相同):

💡 供电说明:每颗 WS2812B 满亮白光约 60mA,8 颗约 480mA。多个节点同时工作建议使用独立 5V/2A 以上电源,避免 ESP32 USB 供电不足导致灯带亮度异常或重启。
二、安装与使用教程
2.1 烧录根节点
①开源平台-搜索"TCP"(根节点)-代码下载自动打开

②连接-验证-上传

③串口输出
打开串口监视器(波特率 115200),看到以下输出说明正常:

2.2 烧录子节点
①开源平台-搜索"TCP"(分节点)-代码下载自动打开

②连接-验证-上传

③串口输出

重复此步骤烧录所有分节点
2.4 使用方法
连接网络:
-
手机或电脑打开 WiFi 设置
-
连接热点:
SmartLight-Net -
输入密码:
smartlight2026
打开控制台:
-
浏览器地址栏输入:
http://192.168.10.1 -
页面自动加载所有在线节点,每 3 秒刷新一次
控制功能说明:
|
功能区域 |
操作 |
效果 |
|---|---|---|
|
全局控制栏 |
点击「全部开灯」 |
所有节点同时亮灯 |
|
全局控制栏 |
点击「全部关灯」 |
所有节点同时关灯 |
|
全局控制栏 |
拖动亮度滑块 |
所有节点亮度同步调节(0-255) |
|
全局控制栏 |
点击色板色块 |
所有节点切换到对应颜色 |
|
全局控制栏 |
点击拾色器圆圈 |
弹出颜色选择器,自定义颜色 |
|
节点卡片 |
拨动开关拨杆 |
单独控制该节点开/关 |
|
节点卡片 |
拖动亮度滑块 |
单独调节该节点亮度 |
|
节点卡片 |
点击色板 |
单独切换该节点颜色 |
2.5 自定义配置
如需修改网络参数,编辑 light_network.h:
#define AP_SSID "SmartLight-Net" // 热点名称
#define AP_PASSWORD "smartlight2026" // 热点密码
#define AP_IP "192.168.10.1" // 网关IP
#define TCP_PORT 9527 // TCP端口
#define MAX_NODES 10 // 最大子节点数
#define HEARTBEAT_MS 3000 // 心跳间隔(毫秒)
#define NODE_TIMEOUT_MS 10000 // 离线判定超时(毫秒)
#define LED_COUNT 8 // 每条灯带灯珠数量
三、代码讲解部分
3.1 项目文件结构
SmartLight_Gateway/
├── SmartLight_Gateway.ino 主程序入口 + 内嵌网页 HTML/CSS/JS
├── gateway.h / .cpp AP热点 + TCP服务端 + Web路由
├── light_network.h / .cpp 公共数据结构 + 校验函数
├── ws2812_ctrl.h / .cpp WS2812 灯带控制封装
└── oled_display.h / .cpp SSD1306 OLED 显示封装
SmartLight_Node/
├── SmartLight_Node.ino 主程序入口
├── light_node.h WiFi+TCP客户端 + 指令执行
├── light_network.h / .cpp 公共数据结构(与网关共用)
├── ws2812_ctrl.h / .cpp WS2812 灯带控制(与网关共用)
└── oled_display.h / .cpp OLED 显示(与网关共用)
3.2 通信协议
节点之间传输定长结构体,加 XOR 校验,共 11 字节:
// 灯光状态,5字节
struct __attribute__((packed)) LightState {
bool power; // 开关状态
uint8_t brightness; // 亮度 0-255
uint8_t r, g, b; // RGB 颜色分量
};
// 网络数据包,11字节
struct __attribute__((packed)) NetPacket {
uint8_t msgType; // 消息类型
uint32_t nodeId; // 节点ID(基于MAC地址生成)
LightState state; // 灯光状态
uint8_t checksum; // XOR校验字节
};
为什么要加 __attribute__((packed))?
不加的话,编译器会在 uint8_t msgType 和 uint32_t nodeId 之间自动插入 3 字节对齐填充。两端填充内容不同导致 XOR 校验结果永远不一致,包会被全部丢弃。加了 packed 后禁止填充,结构体固定 11 字节,两端完全一致。
// 编译期断言,确认 packed 生效
static_assert(sizeof(NetPacket) == 11, "NetPacket大小不对!");
消息类型定义:
#define MSG_HEARTBEAT 0x01 // 心跳包(子节点→网关,携带当前状态)
#define MSG_SET_COLOR 0x02 // 设置颜色
#define MSG_SET_POWER 0x03 // 设置开关
#define MSG_SET_BRIGHT 0x04 // 设置亮度
#define MSG_STATUS_REQ 0x05 // 请求状态上报
#define MSG_STATUS_RPT 0x06 // 状态回报(子节点→网关)
3.3 WS2812 灯光控制
亮度控制采用颜色融合方式而非库自带的 setBrightness(),避免全局缩放带来的色偏:
void WS2812Controller::_render() {
if (!_state.power) {
_strip.clear();
_strip.show();
return;
}
// 亮度系数直接乘入 RGB 各分量
float scale = _state.brightness / 255.0f;
uint8_t r = (uint8_t)(_state.r * scale);
uint8_t g = (uint8_t)(_state.g * scale);
uint8_t b = (uint8_t)(_state.b * scale);
_strip.fill(_strip.Color(r, g, b));
_strip.show();
}
3.4 状态增量更新
网页每次只发送改变的那一项(亮度 / 颜色 / 开关),必须基于目标灯当前状态做增量修改,否则改亮度会重置颜色,改颜色会重置亮度:
// 取目标灯的当前状态作为基础
LightState base = isAll ? myState : getBaseState(tid);
if (action == "brightness") {
base.brightness = val; // 只改亮度,RGB 保持不变
} else if (action == "color") {
base.r = r; base.g = g; base.b = b; // 只改颜色,亮度保持不变
} else if (action == "power") {
base.power = on; // 只改开关,其他全部保留
}
3.5 TCP 连接管理
根节点用 WiFiServer 管理所有子节点连接。WiFiClient 是 C++ 对象,不能放在用 memset 初始化的结构体里,必须单独声明:
class Gateway {
NodeInfo _nodes[MAX_NODES]; // POD 结构体,可以 memset 清零
WiFiClient _clients[MAX_NODES]; // C++ 对象,靠默认构造函数初始化
int _nodeCount;
};
// _nodes[i] 和 _clients[i] 通过下标一一对应
新节点接入流程:
void Gateway::_acceptClients() {
WiFiClient client = _tcpServer.accept();
if (!client) return;
int idx = _nodeCount++;
_clients[idx] = client; // 正确:赋值给独立数组
_nodes[idx].nodeId = 0; // 等待首个心跳包确认真实ID
_nodes[idx].online = true;
_nodes[idx].lastSeen = millis();
}
3.6 OLED 双色屏布局
0.96 寸 SSD1306 是物理双色屏,顶部 Y:0-15 固定黄色,下方 Y:16-63 固定蓝色,这是硬件特性无法修改。布局必须严格按分界线划分:
Y: 0 ┌─────────────────────────────┐
│ [GW] ID:1A2B3C4D │ ← 黄色区,角色标签 + 节点ID
Y: 15 ├─────────────────────────────┤ ← 硬件分界线
│ Power: ON Bri: 128 │
│ R:255 G:128 B: 64 │ ← 蓝色区,灯光状态
│ [R▓▓▓░] [G▓▓░░] [B▓░░░] │
Y: 63 │ [████████████░░░░░░░░░░░░] │ ← 亮度进度条
└─────────────────────────────┘
根节点显示 [GW],子节点显示 [NODE],通过 myRole 变量区分:
const char* roleStr = (role == ROLE_GATEWAY) ? " GW " : " NODE ";
3.7 子节点断线重连
子节点 loop() 中分两层检测,WiFi 和 TCP 各自独立重连:
void LightNode::loop() {
// 第一层:WiFi 断线检测
if (WiFi.status() != WL_CONNECTED) {
if (millis() - _lastConnectAttempt > 5000) {
_connectWiFi();
_lastConnectAttempt = millis();
}
return; // WiFi 未连上,不继续后续逻辑
}
// 第二层:TCP 断线检测
if (!_client.connected()) {
if (millis() - _lastConnectAttempt > 3000) {
_connectTCP();
_lastConnectAttempt = millis();
}
return;
}
// 正常工作
_readServer();
_sendHeartbeatIfNeeded();
}
四、项目结果演示
零知派ESP32——基于WiFi+TCP的多节点智能灯控系统
五、常见问题解答(FAQ)
Q1:子节点上电后灯带没有蓝色闪烁,说明什么?
蓝色闪3下是 TCP 连接成功的提示。没有闪烁说明 TCP 连接失败,可能原因:
-
根节点未上电或未正常启动,先检查根节点串口输出
-
子节点没有连上根节点的 WiFi 热点,串口会打印
WiFi连接失败 -
代码里的
AP_SSID/AP_PASSWORD与根节点不一致,检查light_network.h
Q2:网页打开后看不到子节点,只有 Gateway 一个卡片?
原因可能有两个:
-
子节点还未上电或连接中,等待子节点蓝色闪烁后再刷新网页
-
子节点注册靠首个心跳包,心跳间隔 3 秒,稍等片刻或点击「刷新」按钮
Q3:控制灯光时网页提示「操作失败」?
检查以下几点:
-
手机或电脑是否仍连着
SmartLight-Net热点,有时手机会自动切换到其他 WiFi -
根节点是否还在正常运行,串口看有无报错
-
浏览器地址栏确认是
http://192.168.10.1,不是https
Q4:WS2812 灯带颜色显示异常或随机闪烁?
-
在 GPIO5 数据线上串一个 330Ω 电阻,改善信号质量
-
检查灯带供电是否充足,8颗全亮白光约需 480mA,USB 供电可能不够
-
多个节点同时工作时,建议使用独立 5V/2A 电源,并确保所有 GND 共地
Q5:OLED 不亮或花屏?
-
用 I2C 扫描程序确认 OLED 地址是否为
0x3C,部分模块地址是0x3D,在oled_display.h中修改#define OLED_ADDR 0x3C -
检查 SDA/SCL 接线是否正确,GPIO21=SDA,GPIO22=SCL
-
确认 OLED 供电是 3.3V 还是 5V,看模块背面的规格标注
Q6:OLED 顶部文字被黄色和蓝色分成两段颜色?
这是 0.96 寸 SSD1306 的物理特性,顶部 16 像素固定黄色、下方固定蓝色,无法软件修改。代码已针对此做了布局适配,黄色区域只放节点ID,蓝色区域放所有状态信息,如果出现分割说明烧录的是旧版代码,重新烧录最新版即可。
Q7:想增加更多子节点怎么办?
直接给新 ESP32 烧录 SmartLight_Node 固件,上电自动入网,无需修改任何代码。默认最多支持 10 个子节点,如需更多修改 light_network.h 中的 #define MAX_NODES 10 并重新烧录根节点固件。
Q8:想换成更多灯珠的灯带怎么改?
修改 light_network.h 中一行:
#define LED_COUNT 8 // 改成你的灯珠数量,如 30、60 等
两个项目(Gateway 和 Node)都要改,然后重新烧录。供电需同步升级,每增加 8 颗灯珠约需增加 480mA 电流余量。
Q9:能不能不用 OLED,去掉这个模块?
可以。删除主程序中 #include "oled_display.h" 以及 setup() 里的 oled.begin() 和 loop() 里的 oled.update(...) 三处调用即可,其余代码不需要修改。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)