项目概述

        本项目实现了一款手势控制的智能氛围灯,通过挥手、悬停、深握等手势切换 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 硬件清单

1.2 接线方案

1.3 硬件连接图

1.4 实物连接图

二、软件架构设计

2.1 系统初始化

2.2 主循环逻辑

三、代码拆分讲解

3.1 引脚与全局配置

3.2 手势参数

3.3 EMA 滤波

3.4 颜色与模式定义

3.5 手势引擎 — 核心逻辑

3.6 手势分发

3.7 换色模式

3.8 屏幕渲染 — 部分刷新

3.9 灯光模式详解

3.10 映射函数

四、操作过程及数据展示

4.1 操作步骤

4.2 演示视频

五、技术原理

5.1 工作原理

5.2 工作模式配置

六、常见问题指引

Q1: 上传代码后灯不亮

Q2: VL53L1X 初始化失败

Q3: 手势识别不灵敏

Q4: 灯带颜色显示异常

Q5: 如何修改默认参数

Q6: 如何增加新灯光模式


一、硬件系统部分

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;手较慢时增加到 600ms
  • HOVER_MIN_MS:感觉悬停误触时增加到 400~500ms
  • ZONE_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 刷新,还会导致屏幕闪烁。

当前缓存仅当 currentModecolorIdxlightStateproximityGlow 四个变量之一变化时才重绘。亮度/色温/速度等悬停调节不影响屏幕内容,不触发重绘。

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 操作步骤

上电启动:

  1. 连接 ESP32 到电脑 USB,点击验证上传
  2. 打开串口监视器 (115200 baud)
  3. 看到 Gesture Lamp v3 ready 表示初始化成功
  4. 屏幕显示 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: 上传代码后灯不亮

检查顺序:

  1. WS2812B 数据线是否接在 GPIO14
  2. 5V 电源是否接通,WS2812B 灯板 VCC 是否接 5V(不是 3.3V)
  3. FastLED 库是否正确安装(需要 3.6.0+)
  4. 串口是否有 Gesture Lamp v3 ready — 无输出则检查 USB 驱动

Q2: VL53L1X 初始化失败

串口输出 VL53L1X init failed!

  1. 检查 I2C 接线:GPIO21 → SDA, GPIO22 → SCL
  2. VL53L1X 是否接收到 3.3V 电源
  3. I2C 地址是否冲突 — VL53L1X 默认地址 0x29
  4. 如果接线没问题,尝试 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: 如何增加新灯光模式

  1. 在 LightMode 枚举中增加新枚举值
  2. 在 modeNames 数组增加对应名称
  3. 实现 renderXxx() 静态函数
  4. 在 updateLeds() 的 switch 中增加 case
  5. 手动将 MODE_COUNT 加 1(当前为显式常量 const int MODE_COUNT = 12;
Logo

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

更多推荐