零知派ESP32-- VL53L1X手势控制智能灯
项目概述
本项目实现了一款手势控制的智能氛围灯,通过挥手、悬停、深握等手势切换 12 种灯光模式、调节亮度/色温/速度,并在 240×240 的 ST7789 屏幕上实时显示当前模式和操作提示。
核心硬件: ESP32主控、VL53L1X ToF测距传感器(检测距离 30~280mm)、WS2812B-64×3 共192颗LED灯板、ST7789 240×240 SPI彩色显示屏。
项目亮点
- 纯手势交互 — 无需任何物理按键,完全通过手掌距离和持续时间控制
- 12种灯光模式 — WHITE / RAINBOW / WAVE / CHASE / FIRE / METEOR / BREATHE / TWINKLE / AURORA / RANDOM / COLOR / HEART
- 悬停3秒进入换色模式 — 手掌停留在感应区3秒后自动进入颜色循环,近/远控制切换方向
- 深握复位 — 手掌距传感器 < 70mm 超过1秒,灯灭并恢复出厂设置
- EMA滤波 — 对原始测距数据进行指数移动平均滤波,消除抖动
- 屏幕局部刷新 — 仅状态变化时重绘,避免无效SPI写入
- 自适应悬停调节 — 正常悬停时近区调亮度,远区调色温/速度/色相
项目难点及解决方案
| 难点 | 解决方案 |
|---|---|
| 距离数据抖动导致误触 | 使用 EMA 滤波(α=0.3),配合去抖计数器(DEGLITCH=2) |
| 快速挥动和慢速悬停区分 | 计时器方法:< 500ms 为挥动,> 350ms 为悬停 |
| 192颗LED内存占用 | 使用 FastLED 库,NRF52/ESP32 用 I2S 驱动确保时序 |
| 显示闪烁 | 状态缓存 lastDisp,仅 mode/color/on/glow 变化才调用 fillRect |
| 连续50ms数据就绪 | 仅在 sensor.dataReady() 为 true 时读取,避免阻塞 |
目录
一、硬件系统部分
1.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | 零知派 ESP32 | 1 | 双核 240MHz,SPI/I2C 接口丰富 |
| 扩展板 | 零知派ESP32扩展板 | 1 | 方便接线 |
| 测距传感器 | VL53L1X | 1 | ST ToF 激光测距,最远 4m(Long模式) |
| LED灯板 | WS2812B 64×3 | 1 | 共192颗,5V供电,单总线控制 |
| USB转TTL模块 | 零知派USB转TTL模块 | 1 | 给灯板5V供电 |
| 显示屏 | ST7789 240×240 | 1 | SPI 彩色 IPS 屏 |
| 面包板 | 面包板 | 1 | 原型搭建 |
| 杜邦线 | 公对母 | 若干 | 接线用 |
1.2 接线方案
详细引脚分配:
| 信号 | ESP32 | 对应设备引脚 | 备注 |
|---|---|---|---|
| WS2812B_DATA | GPIO14 | WS2812B DIN | 控制灯光 |
| I2C_SDA | GPIO21 | VL53L1X SDA | 内置上拉 |
| I2C_SCL | GPIO22 | VL53L1X SCL | 内置上拉 |
1.3 硬件连接图

⚠ 关键:WS2812B 灯板必须独立 5V 供电,192颗全亮峰值电流约 5.5A,请使用 5V/8A 以上适配器,不可从 ESP32 3.3V 取电。所有设备共地。
1.4 实物连接图

二、软件架构设计
2.1 系统初始化
void setup() {
Serial.begin(115200); // 波特率115200bps
delay(300); // 等待串口稳定
// 1. 初始化 WS2812B (FastLED)
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(255);
FastLED.clear(); FastLED.show();
// 2. 初始化 I2C 和 VL53L1X
Wire.begin(SDA_PIN, SCL_PIN);
if (!sensor.init()) { // 检测传感器是否连接
Serial.println("VL53L1X init failed!");
while (1); // 初始化失败则停机
}
sensor.setDistanceMode(VL53L1X::Long); // 远距离模式(最远约4米)
sensor.setMeasurementTimingBudget(50000); // 50ms 测量预算
sensor.startContinuous(50); // 50ms 间隔连续读取
// 3. 初始化 ST7789
tft.init();
tft.setRotation(3); // USB朝上方向
tft.fillScreen(TFT_BLACK);
tft.setTextWrap(false); // 文字超过边界不换行
Serial.println("Gesture Lamp v3 ready");
}
初始化顺序:串口→LED→I2C传感器→SPI显示。三个子系统彼此独立,若 VL53L1X 未连接则直接 while(1) 停机提示。
2.2 主循环逻辑
loop()
│
├─ VL53L1X.dataReady()?
│ ├─ 读取原始距离 raw
│ ├─ EMA滤波 → d
│ ├─ 手势识别 → g (bitfield)
│ ├─ 距离开/关逻辑 (proximityGlow)
│ ├─ 手势分发 (handleGesture)
│ ├─ 换色模式检测 (checkColorMode)
│ └─ (else skip)
│
├─ 更新LED (updateLeds)
├─ 刷新显示 (renderDisplay)
└─ delay(20)
每个周期约 20ms,VL53L1X 以 50ms 间隔就绪,其余时间 CPU 处理 LED 渲染和显示刷新。所有 LED 模式通过 millis() 时间差驱动,与帧率解耦。
三、代码拆分讲解
3.1 引脚与全局配置
#define LED_PIN 14 // WS2812B 数据引脚
#define NUM_LEDS 192 // 3×64 LED 灯板
#define SDA_PIN 21 // VL53L1X I2C 数据线
#define SCL_PIN 22 // VL53L1X I2C 时钟线
#define FASTLED_FORCE_ESP32_I2S — 强制使用 I2S 驱动 WS2812B,确保 ESP32 上的精确时序。
CRGB leds[NUM_LEDS]; // FastLED 帧缓冲区,192×3字节
VL53L1X sensor; // ToF 传感器对象
TFT_eSPI tft; // 屏幕对象
配置缓冲区大小约 576 字节(192×3),加上显示缓冲和栈空间,ESP320 的 520KB SRAM 绰绰有余。
3.2 手势参数
const uint16_t HOVER_MIN = 30; // 感应区下限 (mm)
const uint16_t HOVER_MAX = 280; // 感应区上限 (mm)
const uint32_t SWIPE_MAX_MS = 500; // 挥动最长时间
const uint32_t HOVER_MIN_MS = 350; // 悬停最短时间
const uint32_t DEEP_HOLD_MS = 1000; // 深握保持时间
const uint16_t DEEP_HOLD_MM = 70; // 深握阈值距离
const uint16_t ZONE_NEAR_MAX = 100; // 近区上限
const uint16_t ZONE_FAR_MIN = 120; // 远区下限
const uint8_t DEGLITCH = 2; // 去抖采样数
const uint32_t HOVER_UPDATE = 80; // 悬停调节更新间隔
参数调优建议:
SWIPE_MAX_MS:手较快时降低到 300ms;手较慢时增加到 600msHOVER_MIN_MS:感觉悬停误触时增加到 400~500msZONE_NEAR_MAX / ZONE_FAR_MIN:区分近/远区的阈值,有 20mm 死区防止临界抖动DEEP_HOLD_MM / DEEP_HOLD_MS:70mm+1s 可改为 50mm+500ms 让复位更灵敏
3.3 EMA 滤波
const float ALPHA = 0.3f;
uint16_t filteredDist = 0;
uint16_t emaFilter(uint16_t raw) {
if (filteredDist == 0) filteredDist = raw; // 首次读数直接赋值
else filteredDist = (uint16_t)(ALPHA * raw + // 加权平均
(1.0f - ALPHA) * filteredDist);
return filteredDist;
}
EMA(指数移动平均)相对于算术平均的优势:响应快、内存占用低(仅 2 字节)、计算简单。α=0.3 表示新数据贡献 30%,历史数据 70%,在平滑度和响应速度之间取得平衡。
3.4 颜色与模式定义
12 种灯光模式以 enum 定义:
enum LightMode {
MODE_WHITE, MODE_RAINBOW, MODE_WAVE, MODE_CHASE,
MODE_FIRE, MODE_METEOR, MODE_BREATHE, MODE_TWINKLE,
MODE_AURORA, MODE_RANDOM, MODE_COLOR, MODE_HEART
};
8 种颜色用于 COLOR 模式和换色模式:
const CRGB colorPalette[] = {
CRGB::White, CRGB::Red, CRGB::Green, CRGB::Blue,
CRGB::Yellow, CRGB(148, 0, 211), CRGB::Cyan, CRGB::Orange
};
色温预设 warm (255,155,70) 和 cool (245,250,255) 用于 WHITE 模式下通过悬停调节色温。
3.5 手势引擎 — 核心逻辑
processGesture() 返回 8-bit 位域,每位代表一种手势事件:
| 位 | 值 | 含义 |
|---|---|---|
| bit0 | 1 | 单次挥动 |
| bit2 | 4 | 深握复位 |
| bit3 | 8 | 悬停近区 (≤100mm) |
| bit4 | 16 | 悬停远区 (≥120mm) |
| bit5 | 32 | 挥动方向 (≤150mm = 向前) |
状态机逻辑:
空闲 → 手进入(30~280mm内)
│
├─ 计时开始,记录最小距离(minDist)
│
├─ 手在感应区内:
│ ├─ <70mm 且 >1s → 深握(bit2)
│ ├─ ≥350ms:
│ │ ├─ ≤100mm → 悬停近区(bit3)
│ │ └─ ≥120mm → 悬停远区(bit4)
│ └─ <350ms → 继续等待
│
└─ 手离开(连续2帧检测不到)
├─ 离开时间 50~500ms → 挥动(bit0+方向bit5)
└─ 离开 >500ms → 挥手太慢,忽略
关键设计细节:
minDist监测整个手的运动轨迹:手先靠近(minDist 变小)再远离,如果 minDist ≤ 150mm 判定为"向前"挥动deepHoldFired标记防止一次深握中同时触发悬停- 去抖计数器
absentCount:手必须连续 2 帧在感应区外才判定真正离开,防止读数抖动
3.6 手势分发
void handleGesture(uint8_t g, uint16_t d) {
bool more = (g & 32); // true=挥手轨迹较近(≤150mm) → 模式索引+1
if (g & 1) { // 挥动
if (lightState == OFF) {
lightState = ON; // 灯关→开灯
proximityGlow = false;
Serial.println("SWIPE ON");
} else {
if (more) currentMode++; // 模式+1
else currentMode--; // 模式-1
Serial.println(modeNames[currentMode]);
}
} else if (g & 4) {
doDeepHold(); // 深握复位
}
if (g & 8 || g & 16) { // 悬停调节
static uint32_t lastHover = 0;
if (millis() - lastHover >= HOVER_UPDATE) {
if (g & 8) doHoverNear(d);
if (g & 16) doHoverFar(d);
lastHover = millis();
}
}
}
完整动作表:
| 手势 | 灯状态 | 效果 |
|---|---|---|
| 挥动 (≤150mm 向前) | OFF | 开灯 (默认 WHITE) |
| 挥动 (≤150mm 向前) | ON | 切换到下一个模式 |
| 挥动 (>150mm 向后) | OFF | 开灯 |
| 挥动 (>150mm 向后) | ON | 切换到上一个模式 |
| 悬停 350ms~3s (近区) | 任意 | 调亮度 (近→亮,远→暗) |
| 悬停 350ms~3s (远区) | ON | 调色温/动效速度/色相 |
| 悬停 3s 后 (近区) | 任意 | 换色模式 → 向前切换颜色 |
| 悬停 3s 后 (远区) | 任意 | 换色模式 → 向后切换颜色 |
| 深握 <70mm >1s | 任意 | 灯灭 + 全部恢复默认 |
3.7 换色模式
void checkColorMode(uint16_t d) {
static uint32_t entryTime = 0; // 手进入范围的时间戳
static bool active = false; // 是否已进入颜色选择模式
static uint32_t lastCycle = 0; // 上次颜色切换时间
bool present = (d >= HOVER_MIN && d <= HOVER_MAX);
if (present) {
if (entryTime == 0) entryTime = millis();
// 持续停留超过 3秒 → 激活换色模式
if (!active && millis() - entryTime > 3000) {
active = true;
lastCycle = millis();
Serial.println("COLOR MODE ON");
return;
}
// 激活后每 500ms 根据距离切换颜色
if (active && millis() - lastCycle > 500) {
if (d <= ZONE_NEAR_MAX) colorIdx++; // 近区→正向
else if (d >= ZONE_FAR_MIN) colorIdx--; // 远区→反向
else return;
activeColor = colorPalette[colorIdx];
lastCycle = millis();
}
} else {
if (active) { active = false; Serial.println("COLOR MODE OFF"); }
entryTime = 0; // 复位计时器
}
}
与普通悬停共用距离传感器,靠时间区分:0.35s~3s 内为普通调节,3s 后进入换色模式。换色模式下每 500ms 自动切换一次颜色,手停留在近区就正向走,远区就反向走。
3.8 屏幕渲染 — 部分刷新
struct {
LightMode m; // 上次显示的模式
int c; // 上次显示的颜色索引
bool on; // 上次显示的开关状态
bool glow; // 上次显示的微光状态
} lastDisp = { (LightMode)0xFF, -1, false, false };
void renderDisplay() {
// 缓存命中(四变量均无变化)→ 直接跳过
if (lastDisp.m == currentMode && lastDisp.c == colorIdx &&
lastDisp.on == lightState && lastDisp.glow == proximityGlow) return;
// 整屏清黑
tft.fillRect(0, 0, 240, 240, TFT_BLACK);
// 绘制标题 "GESTURE LAMP"
// ├─ OFF + 无悬停微光 → 显示 "LIGHT OFF"
// └─ ON 或悬停中 → 显示完整UI
// ├─ 模式行: 左上一个(暗) 中"--当前--"(亮) 右下一个(暗)
// ├─ 分隔线
// └─ 操作备忘 (5条操作提示)
// 更新缓存
lastDisp = { currentMode, colorIdx, lightState, proximityGlow };
}
为什么部分刷新重要: 每次 fillRect 全屏需要向 SPI 发送 240×240×2 ≈ 115KB 数据。如果不做缓存,loop 每 20ms 发送一次,SPI 总线上将充满冗余数据,不仅拖慢 LED 刷新,还会导致屏幕闪烁。
当前缓存仅当 currentMode、colorIdx、lightState、proximityGlow 四个变量之一变化时才重绘。亮度/色温/速度等悬停调节不影响屏幕内容,不触发重绘。
3.9 灯光模式详解
① WHITE — 纯白光,色温可调(悬停远区从冷白渐变到暖白)
case MODE_WHITE:
fill_solid(leds, NUM_LEDS, whiteColor);
// whiteColor 由 doHoverFar 通过 colorTempAtHeight() 计算
② RAINBOW — 彩虹渐变,动效速度可调
case MODE_RAINBOW: {
uint32_t t = millis() % effectPeriod;
fill_rainbow(leds, NUM_LEDS, (t * 256) / effectPeriod, 1);
// fill_rainbow 是 FastLED 内置,从头到尾填充色环
}
③ WAVE — 波浪效果,单个颜色沿灯带做正弦波明暗变化
uint16_t pos = (millis() * 256 / effectPeriod) % 256;
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t b = sin8(i * 512 / NUM_LEDS + pos); // 正弦亮度
leds[i] = activeColor;
leds[i].nscale8(b); // 按亮度缩放
}
④ CHASE — 追逐光点,12个光点依次追逐
fill_solid(leds, NUM_LEDS, CRGB::Black); // 先全灭
uint16_t pos = (millis() * NUM_LEDS / effectPeriod) % NUM_LEDS;
for (int i = 0; i < 12; i++) {
int idx = (pos - i + NUM_LEDS) % NUM_LEDS;
leds[idx] = activeColor;
leds[idx].nscale8(255 - i * 21); // 拖尾衰减
}
⑤ FIRE — 火焰模拟,使用一维热扩散算法
// 1. 冷却 — 每个像素按冷却系数随机降低热度
for (int i = 0; i < NUM_LEDS; i++)
heat[i] = qsub8(heat[i], random8(2, cooling));
// 2. 热源 — 底部注入随机热量
heat[0] = qadd8(heat[0], random8(128, 255));
// 3. 扩散 — 后向差分,热向上传播
for (int i = NUM_LEDS - 1; i >= 2; i--)
heat[i] = (heat[i-1] + heat[i-2] + heat[i-1]) / 3;
// 4. 映射 — HeatColor 将 0~255 映射为黑→红→黄→白
for (int i = 0; i < NUM_LEDS; i++)
leds[i] = HeatColor(heat[i]);
⑥ METEOR — 流星拖尾,单颗亮点划过带渐隐尾巴
// 先全灭,计算流星头部位置
uint16_t pos = (millis() * (NUM_LEDS + 20) / effectPeriod) % (NUM_LEDS + 20);
for (int i = 0; i < 20; i++) { // 尾巴长度 20 颗
int idx = (int)pos - i;
if (idx >= 0 && idx < NUM_LEDS) {
leds[idx] = activeColor;
leds[idx].nscale8((20 - i) * 12 + 15); // 尾部渐暗
}
}
⑦ BREATHE — 呼吸效果,正弦波全局明暗
uint8_t phase = (millis() * 256 / effectPeriod) % 256;
uint8_t b = sin8(phase + 192); // +192 调整相位,使起始为暗
b = map(b, 0, 255, 10, 255); // 映射到 10~255,最低10保持微光
fill_solid(leds, NUM_LEDS, activeColor);
for (int i = 0; i < NUM_LEDS; i++) leds[i].nscale8(b);
⑧ TWINKLE — 闪烁繁星,每帧随机点亮若干颗
for (int i = 0; i < NUM_LEDS; i++) leds[i].nscale8(248); // 缓慢衰减
uint16_t chance = map(effectPeriod, 400, 10000, 40, 4);
for (int i = 0; i < chance; i++) {
if (random8() < 8) {
int idx = random16(NUM_LEDS);
leds[idx] = activeColor; // 随机点亮一颗
}
}
⑨ AURORA — 极光效果,HSV 各通道独立正弦波
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t h = 120 + sin8(i * 8 + t / (effectPeriod / 8)) / 3;
uint8_t s = 180 + sin8(i * 12 + t / 40) / 4;
uint8_t v = 220 + sin8(i * 6 + t / 60) / 5;
leds[i] = CHSV(h, s, v);
}
色相偏绿/青(120±),饱和度 180±,亮度 220±,时间偏移使各通道产生波浪。
⑩ RANDOM — 随机彩色闪烁,每 1/4 effectPeriod 刷新
if (millis() - lastChange > effectPeriod / 4) {
for (int i = 0; i < NUM_LEDS; i++)
leds[i] = CHSV(random(256), 255, 255);
}
⑪ COLOR — 纯色模式,用 activeColor 全局填充
⑫ HEART — 心跳动画,显示 8×8 心形图案(复制3份覆盖192颗灯),整体亮度按正弦脉动
static void renderHeart() {
// 8×8 心形图案,CRGB(0,0,0)=灭, CRGB(255,0,0)=红
static const CRGB heartData[64] = { ... };
// 心跳搏动:亮度按正弦变化
uint8_t beat = sin8((millis() * 256 / effectPeriod) % 256);
uint8_t brightness = map(beat, 0, 255, 60, 255);
// 将心形图案复制3份(3条灯带×64颗)
for (int m = 0; m < 3; m++)
memcpy(leds + m * 64, heartData, 64 * sizeof(CRGB));
// 整体缩放亮度实现心跳脉动
for (int i = 0; i < NUM_LEDS; i++)
leds[i].nscale8(brightness);
}
3.10 映射函数
// 距离 → 色温插值(冷白→暖白线性渐变)
CRGB colorTempAtHeight(uint16_t h) {
float t = (float)(h - HOVER_MIN) / (HOVER_MAX - HOVER_MIN);
t = constrain(t, 0, 1);
return CRGB(cool.r + t * (warm.r - cool.r),
cool.g + t * (warm.g - cool.g),
cool.b + t * (warm.b - cool.b));
}
// 距离 → 亮度 (近则亮,远则暗)
uint8_t brightnessAtHeight(uint16_t h) {
return map(constrain(h, HOVER_MIN, HOVER_MAX),
HOVER_MIN, HOVER_MAX, 200, 10);
}
// 距离 → 动效周期 (近则快,远则慢)
uint16_t periodAtHeight(uint16_t h) {
return (uint16_t)map(constrain(h, HOVER_MIN, HOVER_MAX),
HOVER_MIN, HOVER_MAX, 400, 10000);
}
三个映射关系独立作用于不同模式。其中 colorTempAtHeight 专门用于 WHITE 模式做冷-暖白光渐变。
四、操作过程及数据展示
4.1 操作步骤
上电启动:
- 连接 ESP32 到电脑 USB,点击验证上传
- 打开串口监视器 (115200 baud)
- 看到
Gesture Lamp v3 ready表示初始化成功 - 屏幕显示
GESTURE LAMP标题 +LIGHT OFF
基本操作流程:
操作 屏幕显示 灯光效果
─────────────────────────────────────────────────────
挥手(靠近) SWIPE +/- : MODE WHITE 模式亮起
→ ──WHITE──
模式切换到 RAINBOW
再次挥手(靠近) SWIPE +/- : MODE RAINBOW 渐变
→ ──RAINBOW──
手悬停靠近(3s) HOVER 3s : COLOR MODE 颜色自动切换
→ COLOR MODE (颜色名) 每0.5s跳变
深握(<70mm,1s) DEEP HOLD : OFF + RESET 灯灭,全部复位
→ LIGHT OFF
悬停近区(350ms) HOVER NEAR : BRIGHTNESS 亮度变化
手靠近→更亮,手远离→变暗
悬停远区(350ms) HOVER FAR : TEMP/SPEED 色温/速度变化
WHITE模式下→冷暖渐变
其他模式→效果速度
串口调试输出示例:
Gesture Lamp v3 ready
250 ← 距离数据 (mm)
50 ← 手快速靠近
GLOW ← 悬停发光触发
SWIPE ON ← 挥手开灯
COLOR MODE ON ← 悬停3秒进入换色模式
CM COLOR 1 ← 颜色切换到索引1 (RED)
CM COLOR 2 ← 颜色切换到索引2 (GREEN)
COLOR MODE OFF ← 手松开退出换色
HOVER NEAR bri:120 ← 悬停近区调节亮度
HOVER FAR 150 ← 悬停远区调节色温
MODE+ RAINBOW ← 切换模式
DEEP HOLD -> RESET OFF ← 深握复位
调试对照表:
| 串口输出 | 含义 |
|---|---|
数字 (mm) |
滤波后的距离值 |
GLOW |
悬停发光激活(OFF 状态下) |
SWIPE ON |
挥手开灯 |
MODE+ WHITE |
切换到下一个模式 |
MODE- CHASE |
切换到上一个模式 |
COLOR MODE ON |
换色模式激活 |
CM COLOR 3 |
换色模式切换到索引3 |
COLOR MODE OFF |
退出换色模式 |
HOVER NEAR bri:150 |
亮度调到150 |
HOVER FAR 180 |
远区悬停(180mm) |
DEEP HOLD -> RESET OFF |
深握复位 |
4.2 演示视频
零知派ESP32-- VL53L1X手势控制智能灯
五、技术原理
5.1 工作原理
手势识别原理:
VL53L1X 使用飞行时间(ToF)测量距离:发射 940nm 激光,测量反射光返回时间。传感器内部集成了 SPAD 阵列和时间数字转换器,直接输出毫米级精度的距离值。
ESP32 主控以 20ms 为周期轮询传感器,当检测到手在 30~280mm 范围内时触发手势识别算法。算法根据"手存在持续时间"和"距离变化轨迹"区分三种手势:
距离 vs 时间示意图:
距离(mm)
│
280 ──────────── 感应区上界
│ ╱ ╲
│ ╱╲ ╱ ╲
│ ╱╲ ╱ ╲ ╱ ╲
│ ╱ ╲╱ ╲ ╲
30 ──── 感应区下界
│
└────────── 时间(ms)
│←→│←───── 悬停 ────→│
挥动
- 挥动 (50~500ms):手快速进出感应区,距离曲线呈 V 形
- 悬停 (350ms+):手停留在感应区内,距离基本稳定
- 深握 (<70mm, >1s):距离极近且持续,触发安全复位
LED 驱动原理:
FastLED 库使用 I2S 外设(FASTLED_FORCE_ESP32_I2S)产生 WS2812B 所需的精确时序:每个 bit 通过 800kHz 的载波发送 0 码(350ns 高 + 900ns 低)或 1 码(700ns 高 + 600ns 低)。192 颗 LED 每次刷新发送 192×24=4608 bit。
5.2 工作模式配置
远区悬停对不同模式的影响:
| 当前模式 | 远区悬停效果 | 映射范围 |
|---|---|---|
| WHITE | 调节色温(冷↔暖) | 30~280mm |
| COLOR | 调节色相 (HSV H) | H: 0~240 |
| RAINBOW | 改变旋转速度 | 周期: 400~10000ms |
| WAVE | 改变波浪频率 | 周期: 400~10000ms |
| CHASE | 改变追逐速度 | 周期: 400~10000ms |
| FIRE | 改变燃烧强度 | cooling: 8→2 |
| METEOR | 改变流星速度 | 周期: 400~10000ms |
| BREATHE | 改变呼吸频率 | 周期: 400~10000ms |
| TWINKLE | 改变闪烁密度 | chance: 40→4 |
| AURORA | 改变极光波动速度 | 周期: 400~10000ms |
| RANDOM | 改变刷新间隔 | period/4 |
| HEART | 改变心跳频率 | 周期: 400~10000ms |
关键参数可调范围:
| 参数 | 最小值 | 最大值 | 默认值 | 调节方式 |
|---|---|---|---|---|
| 亮度 | 10 | 200 | 80 | 悬停近区 |
| 色温 | 冷白(245,250,255) | 暖白(255,155,70) | 冷白 | WHITE模式下悬停远区 |
| 动效周期 | 400ms | 10000ms | 2000ms | 悬停远区(除WHITE/COLOR) |
| 颜色索引 | 0 | 7 | 0 | 悬停3s换色模式 |
六、常见问题指引
Q1: 上传代码后灯不亮
检查顺序:
- WS2812B 数据线是否接在 GPIO14
- 5V 电源是否接通,WS2812B 灯板 VCC 是否接 5V(不是 3.3V)
- FastLED 库是否正确安装(需要 3.6.0+)
- 串口是否有
Gesture Lamp v3 ready— 无输出则检查 USB 驱动
Q2: VL53L1X 初始化失败
串口输出 VL53L1X init failed!:
- 检查 I2C 接线:GPIO21 → SDA, GPIO22 → SCL
- VL53L1X 是否接收到 3.3V 电源
- I2C 地址是否冲突 — VL53L1X 默认地址 0x29
- 如果接线没问题,尝试
sensor.setTimeout(500)增加超时
Q3: 手势识别不灵敏
- 挥动没反应:降低
SWIPE_MAX_MS到 300ms,或增加DEGLITCH到 3 - 悬停误触:增加
HOVER_MIN_MS到 500ms,或缩小感应区HOVER_MAX到 200mm - 深握太灵敏:降低
DEEP_HOLD_MM到 50mm,或增加DEEP_HOLD_MS到 1500ms - 换色模式难触发:将
checkColorMode()中的3000改为2000(当前硬编码为 3000ms)
Q4: 灯带颜色显示异常
FastLED.addLeds<WS2812B, LED_PIN, RGB>的 RGB 顺序错误?大部分 WS2812B 是 GRB- 尝试
GRB/RGB/BRG三种顺序 - 电源不足会导致尾端偏色 — 短接测试(仅接 10 颗 LED)排除
Q5: 如何修改默认参数
在代码头部 手势参数 区域直接修改常量值:
const uint16_t HOVER_MIN = 30; // 改小→更近触发
const uint32_t SWIPE_MAX_MS = 500; // 改小→需要挥手更快
const uint32_t HOVER_MIN_MS = 350; // 改大→需要悬停更久
const uint16_t ZONE_NEAR_MAX = 100; // 改大→近区范围更大
修改后重新编译上传即可。
Q6: 如何增加新灯光模式
- 在
LightMode枚举中增加新枚举值 - 在
modeNames数组增加对应名称 - 实现
renderXxx()静态函数 - 在
updateLeds()的switch中增加case - 手动将
MODE_COUNT加 1(当前为显式常量const int MODE_COUNT = 12;)
更多推荐

所有评论(0)