零知IDE——基于STM32F103RBT6的PAJ7620U2手势控制WS2812 RGB灯带系统

电子说

1.4w人已加入

描述

​ ✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

项目概述

       本项目基于零知标准板(主控芯片STM32F103RBT6)为核心控制器,结合先进的PAJ7620U2手势识别传感器和WS2812B RGB LED灯带,实现智能手势开关控制功能。系统能够实时检测手部在三维空间中的位置和运动轨迹,并将这些动作信息转换为直观、绚丽的灯光效果

项目难点及解决方案

        问题描述:WS2812B时序精度控制,STM32普通IO难以满足严格时序要求

驱动方案:使用SPI+DMA方式,确保时序准确

 

WS2812B::WS2812Bspi.setClockDivider(SPI_CLOCK_DIV32);  // 444ns脉冲
WS2812B::WS2812Bspi.dmaSendAsync(pixels, numBytes);    // 异步DMA传输

 

一、系统接线部分

1.1 硬件清单

组件名称 型号规格 数量 说明
主控开发板 零知标准板(STM32F103RBT6) 1 主控制器,72MHz主频
手势传感器 PAJ7620U2 1 手势识别,I2C接口,最大检测距离15cm
RGB LED灯带 WS2812-8 RGB模块 2 16颗灯珠,SPI驱动,单线控制
连接线 杜邦线(母对母) 若干 用于模块间连接
电源 5V/2A直流电源 1 为系统供电

1.2 接线方案表

根据代码中的引脚定义,硬件接线方案如下:

PAJ7620U2传感器接线

零知标准板引脚 PAJ7620U2引脚 功能说明
A5 SCL I2C时钟线(软件模拟)
A4 SDA I2C数据线(软件模拟)
3.3V VCC 传感器供电(3.3V)
GND GND 电源地

WS2812B灯带接线

WS2812B需要较大电流,建议使用独立5V电源供电

零知标准板引脚 WS2812B灯带 功能说明
11 DIN SPI数据输出
5V VCC 灯带供电(5V)
GND GND 电源地

1.3 具体接线图

WS2812

1.4 接线实物图

WS2812

二、安装与使用部分

2.1 开源平台-输入PAJ7620U2 并搜索-代码下载自动打开

WS2812

2.2 连接-验证-上传

WS2812

2.3 调试-串口监视器

WS2812

三、代码讲解部分

        采用"单次读取"策略,在主循环开头一次性读取isCursorInView/cursorX/cursorY,确保整个循环周期内使用同一组传感器数据

3.1 手势检测状态机

 

// 手势检测逻辑不再读取传感器,而是分析传入的数据
// 参数: isPresent(手是否在), y(当前Y坐标)
// 返回: 0(无), 1(向上), -1(向下)
int checkGestureLogic(bool isPresent, int y) {
  static int gestureStartY = 0;
  static unsigned long gestureStartTime = 0;
  static bool gestureInProgress = false;
  
  // 冷却时间检查:避免重复触发
  if (millis() - lastGestureTime < GESTURE_COOLDOWN) {
    return 0;
  }
  
  // 如果手移开了,重置检测状态
  if (!isPresent) {
    gestureInProgress = false;
    return 0;
  }
  
  // 如果还没开始检测,记录起点
  if (!gestureInProgress) {
    gestureStartY = y;
    gestureStartTime = millis();
    gestureInProgress = true;
    return 0;
  }
  
  // 计算变化量和速度
  unsigned long duration = millis() - gestureStartTime;
  int yChange = y - gestureStartY;
  
  // 只有当持续时间足够短且速度足够快时,才认为是手势
  // 增加 duration > 50 是为了避免极其短暂的噪点
  if (duration > 50 && abs(yChange) / (duration / 1000.0) > GESTURE_SPEED_THRESHOLD) {
    
    // 向上快速移动 (Y值减小)
    if (yChange < -GESTURE_THRESHOLD) {
      Serial.println("检测到: 向上挥手 (开启)");
      lastGestureTime = millis();
      gestureInProgress = false;
      return 1;
    }
    
    // 向下快速移动 (Y值增加)
    if (yChange > GESTURE_THRESHOLD) {
      Serial.println("检测到: 向下挥手 (关闭)");
      lastGestureTime = millis();
      gestureInProgress = false;
      return -1;
    }
  }
  
  // 超时重置 (如果动作太慢,就视为普通移动而非手势)
  if (duration > 800) {
    gestureInProgress = false;
  }
  
  return 0;
}

 

        手部进入检测区域,记录起始坐标和时间,持续跟踪Y坐标变化,计算移动速度和幅度

3.2 系统状态管理

 

// 系统核心状态变量
bool systemOn = false;      // 系统开关状态,初始为关闭
bool isIdleMode = true;     // 是否处于待机模式
int idleEffectMode = 0;     // 待机效果模式:0=流水灯, 1=呼吸灯
uint8_t globalBrightness = 30;  // 全局亮度控制

void loop()
{
  // 获取传感器数据
  // ...
  
  // 处理系统开关逻辑
  if (!systemOn) {
    // 关机状态:只响应开启手势
    if (detectedDir == 1) {
      turnOnSystem();
    }
    delay(30);
    return;
  }
  
  // 开机状态:优先检查关闭手势
  if (detectedDir == -1) {
    turnOffSystem();
    return;
  }
  
  // 正常的交互逻辑
  // ...
}

 

状态机流程图

WS2812

3.3 视觉交互反馈

开机动画效果

        从中心向两侧渐变紫色光效,过渡到全彩虹色

 

void showStartupEffect() {
  strip.clear();
  strip.show();
  delay(100);
  
  // 从中心向两侧展开的紫色动画
  int center = NUM_LEDS / 2;
  int maxDistance = max(center, NUM_LEDS - center - 1);
  
  for (int step = 0; step <= maxDistance; step++) {
    strip.clear();
    float brightnessFactor = (float)step / maxDistance;
    brightnessFactor = constrain(brightnessFactor, 0.2, 1.0);
    
    // 紫色(RGB: 148,0,211)
    uint8_t r = 148 * brightnessFactor;
    uint8_t b = 211 * brightnessFactor;
    
    // 两侧对称点亮
    for (int dist = 0; dist <= step; dist++) {
      if (center + dist < NUM_LEDS) strip.setPixelColor(center + dist, r, 0, b);
      if (center - dist >= 0) strip.setPixelColor(center - dist, r, 0, b);
    }
    strip.show();
    delay(60);
  }
  
  // 过渡到彩虹色
  for (int i = 0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, wheel((i * 256 / NUM_LEDS) % 256));
  }
  strip.show();
  delay(500);
}

 

 关机效果

        关机效果为全部灯珠的亮度从当前值渐降至 0 后熄灭

 

void turnOffAllLEDs() {
  // 渐暗效果
  for (int b = globalBrightness; b > 0; b -= 10) {
    strip.setBrightness(b);
    strip.show();
    delay(10);
  }
  strip.clear();
  strip.show();
  globalBrightness = 30;  // 重置亮度
}

 

状态指示灯

        初始化提示,闪烁第 0 个灯珠 3 次提示就绪

 

void blinkStatusLED(int times, int delayTime) {
  for (int i = 0; i < times; i++) {
    strip.setPixelColor(0, 0, 0, 255);  // 蓝色闪烁
    strip.show();
    delay(delayTime);
    strip.setPixelColor(0, 0, 0, 0);
    strip.show();
    delay(delayTime);
  }
}

 

3.4 手势跟踪效果

 

void updateHandTrackingEffect(int x, int y) {
  int ledIndex = map(x, Y_MIN, Y_MAX, 0, NUM_LEDS - 1);
  ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1);
  
  for(int i = 0; i < NUM_LEDS; i++) {
    float intensity = trailEffect[i];
    
    if(i == ledIndex) {
      // 当前位置:白色高亮
      strip.setPixelColor(i, 255, 255, 255);
    } else if(intensity > 0.05) {
      // 尾影位置:彩虹色
      uint32_t col = wheel((i * 256 / NUM_LEDS) % 256);
      uint8_t r = ((col > > 16) & 0xFF) * intensity;
      uint8_t g = ((col > > 8) & 0xFF) * intensity;
      uint8_t b = (col & 0xFF) * intensity;
      strip.setPixelColor(i, r, g, b);
    } else {
      // 无尾影:关闭LED
      strip.setPixelColor(i, 0, 0, 0);
    }
  }
}

 

3.5 系统待机

 

void updateWaterFlowEffect() {
  // 清屏
  for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0, 0, 0);
  
  static int dir = 1;  // 移动方向
  idlePosition += dir;
  
  // 边界处理和方向反转
  if(idlePosition >= NUM_LEDS || idlePosition < 0) {
    dir *= -1;
    idlePosition += dir;
    idleColorIndex = (idleColorIndex + 1) % 7;  // 切换颜色
  }
  
  // 设置当前光点
  uint32_t c = rainbowColors[idleColorIndex];
  strip.setPixelColor(idlePosition, c);
  
  // 注意:这个版本简化了尾迹效果,可根据需要恢复
}

uint32_t wheel(uint8_t wheelPos) {
  wheelPos = 255 - wheelPos;  // 反转以获得更鲜艳的颜色
  if(wheelPos < 85) {
    return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3);  // 红- >紫
  }
  if(wheelPos < 170) {
    wheelPos -= 85;
    return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3);  // 紫- >青
  }
  wheelPos -= 170;
  return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0);  // 青- >绿
}

 

3.6 完整代码

 

/**************************************************************************************
 * 文件: /PAJ7620U2_Gesture_WS2812/PAJ7620U2_Gesture_WS2812.ino
 * 作者:零知实验室(深圳市在芯间科技有限公司)
 * -^^- 零知实验室,让电子制作变得更简单! -^^-
 * 时间: 2025-12-26
 * 说明: 基于零知标准板(STM32F103RBT6)驱动PAJ7620U2手势传感器实现WS2812B灯带控制,
 *       支持手部位置跟踪的彩虹尾影效果,无手部时自动切换为流水灯/呼吸灯待机效果
 *       采用"单次读取"策略,确保在任意状态下均能准确识别退出手势。
 ***************************************************************************************/

#include "RevEng_PAJ7620.h"
#include < WS2812B.h >

// 手势传感器对象
RevEng_PAJ7620 sensor = RevEng_PAJ7620();

// LED灯带配置
#define NUM_LEDS 16
WS2812B strip = WS2812B(NUM_LEDS);

// 系统状态
int lastCursorX = 0;
bool isIdleMode = true;
bool systemOn = false;  // 系统开关状态,初始为关闭

// 手部跟踪变量
float trailEffect[NUM_LEDS] = {0};
float trailDecay = 0.85;

// 待机效果变量
int idleEffectMode = 0;  // 0:流水灯, 1:呼吸灯
int idleColorIndex = 0;
int idlePosition = 0;
float idlePulse = 0;
unsigned long idleLastUpdate = 0;

// 亮度控制
uint8_t globalBrightness = 30;  // 全局亮度,0-255

// 手势检测变量
unsigned long lastGestureTime = 0;
const unsigned long GESTURE_COOLDOWN = 800;   // 稍微缩短冷却时间提高响应
const int GESTURE_THRESHOLD = 1000;            // 调低阈值使其更容易触发
const int GESTURE_SPEED_THRESHOLD = 600;      // 调低速度阈值

// 坐标范围
const int Y_MIN = 384;
const int Y_MAX = 3455;

// 预定义颜色(彩虹色)
uint32_t rainbowColors[7] = {
  strip.Color(255, 0, 0),     // 红色
  strip.Color(255, 127, 0),   // 橙色
  strip.Color(255, 255, 0),   // 黄色
  strip.Color(0, 255, 0),     // 绿色
  strip.Color(0, 0, 255),     // 蓝色
  strip.Color(75, 0, 130),    // 靛蓝色
  strip.Color(148, 0, 211)    // 紫色
};

// ***************************************************************************
void setup()
{
  Serial.begin(115200);
  
  // 初始化LED灯带
  strip.begin();
  strip.clear();
  strip.show();
  
  // 初始化手势传感器
  if(!sensor.begin())
  {
    Serial.println("PAJ7620初始化失败!");
    while(true) {}
  }
  
  Serial.println("PAJ7620U2初始化成功!");
  sensor.setCursorMode();  // 设置为光标模式
  
  Serial.println("n手势控制LED系统已启动!");
  Serial.println("向上快速挥手: 打开系统");
  Serial.println("向下快速挥手: 关闭系统");
  
  systemOn = false;
  turnOffAllLEDs();
  blinkStatusLED(3, 300);
}

// ***************************************************************************
void loop()
{
  // 在循环开始时,一次性读取所有传感器数据
  bool currentInView = sensor.isCursorInView();
  int currentX = 0;
  int currentY = 0;
  
  if (currentInView) {
    currentX = sensor.getCursorX();
    currentY = sensor.getCursorY();
  }

  // 将读取到的数据传入手势检测函数 (不让函数内部再次读取)
  // 返回值: 0=无, 1=向上(开), -1=向下(关)
  int detectedDir = checkGestureLogic(currentInView, currentY);

  // 处理系统开关逻辑
  if (!systemOn) {
    // 关机状态:只响应开启手势
    if (detectedDir == 1) {
      turnOnSystem();
    }
    delay(30); // 简单的防抖延迟
    return;
  }
  
  // 开机状态:优先检查关闭手势
  if (detectedDir == -1) {
    turnOffSystem();
    return;
  }

  // 如果没有开关机手势,执行正常的LED显示逻辑
  strip.setBrightness(globalBrightness);
  
  if(currentInView)
  {
    // 手部跟踪模式
    isIdleMode = false;
    
    // 亮度跟随Y轴变化
    int brightness = map(currentY, Y_MIN, Y_MAX, 30, 255);
    brightness = constrain(brightness, 30, 255);
    
    if (abs(brightness - globalBrightness) > 5) {
      globalBrightness = brightness;
      strip.setBrightness(globalBrightness);
    }
    
    updateTrail();
    
    // 使用刚才读取的 currentX 更新效果
    updateHandTrackingEffect(currentX, currentY);
    
    // 添加高亮
    int ledIndex = map(currentX, Y_MIN, Y_MAX, 0, NUM_LEDS - 1);
    ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1);
    trailEffect[ledIndex] = 1.0;
    
    lastCursorX = currentX;
  }
  else
  {
    // 待机模式
    if(!isIdleMode) {
      resetIdleEffects();
      isIdleMode = true;
    }
    updateTrailFast();
    updateIdleEffect();
  }
  
  strip.show();
  delay(30);
}

// ***************************************************************************
// 手势检测不再读取传感器,而是分析传入的数据
// 参数: isPresent(手是否在), y(当前Y坐标)
// 返回: 0(无), 1(向上), -1(向下)
int checkGestureLogic(bool isPresent, int y) {
  static int gestureStartY = 0;
  static unsigned long gestureStartTime = 0;
  static bool gestureInProgress = false;
  
  unsigned long currentTime = millis();
  
  // 冷却时间检查
  if (currentTime - lastGestureTime < GESTURE_COOLDOWN) {
    return 0;
  }
  
  // 如果手移开了,重置检测状态
  if (!isPresent) {
    gestureInProgress = false;
    return 0;
  }
  
  // 如果还没开始检测,记录起点
  if (!gestureInProgress) {
    gestureStartY = y;
    gestureStartTime = currentTime;
    gestureInProgress = true;
    return 0; // 刚开始,还没结果
  }
  
  // 计算变化量和速度
  unsigned long duration = currentTime - gestureStartTime;
  int yChange = y - gestureStartY;
  
  // 避免除以零
  if (duration == 0) return 0;
  
  float speed = abs(yChange) / (duration / 1000.0);
  
  // 只有当持续时间足够短且速度足够快时,才认为是手势
  // 增加 duration > 50 是为了避免极其短暂的噪点
  if (duration > 50 && speed > GESTURE_SPEED_THRESHOLD) {
    
    // 向上快速移动 (Y值减小)
    if (yChange < -GESTURE_THRESHOLD) {
      Serial.println("检测到: 向上挥手 (开启)");
      lastGestureTime = currentTime;
      gestureInProgress = false;
      return 1;
    }
    
    // 向下快速移动 (Y值增加)
    if (yChange > GESTURE_THRESHOLD) {
      Serial.println("检测到: 向下挥手 (关闭)");
      lastGestureTime = currentTime;
      gestureInProgress = false;
      return -1;
    }
  }
  
  // 超时重置 (如果动作太慢,就视为普通移动而非手势)
  if (duration > 800) {
    gestureInProgress = false; // 重置,这也允许连续跟踪而不误触发
  }
  
  return 0;
}

// ***************************************************************************
// 打开系统
void turnOnSystem() {
  systemOn = true;
  Serial.println(" >> > 系统启动");
  
  resetIdleEffects();
  clearTrailEffects();
  globalBrightness = 30;
  strip.setBrightness(globalBrightness);
  
  showStartupEffect();
}

// ***************************************************************************
// 关闭系统
void turnOffSystem() {
  systemOn = false;
  Serial.println(" >> > 系统关闭");
  turnOffAllLEDs();
}

// ***************************************************************************
// 启动效果
void showStartupEffect() {
  strip.clear();
  strip.show();
  delay(100);
  
  int center = NUM_LEDS / 2;
  int maxDistance = max(center, NUM_LEDS - center - 1);
  
  for (int step = 0; step <= maxDistance; step++) {
    strip.clear();
    float brightnessFactor = (float)step / maxDistance;
    brightnessFactor = constrain(brightnessFactor, 0.2, 1.0);
    
    uint8_t r = 148 * brightnessFactor;
    uint8_t b = 211 * brightnessFactor;
    
    for (int dist = 0; dist <= step; dist++) {
      if (center + dist < NUM_LEDS) strip.setPixelColor(center + dist, r, 0, b);
      if (center - dist >= 0) strip.setPixelColor(center - dist, r, 0, b);
    }
    strip.show();
    delay(60);
  }
  
  // 快速过渡到彩虹
  for (int i = 0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, wheel((i * 256 / NUM_LEDS) % 256));
  }
  strip.show();
  delay(500);
}

// ***************************************************************************
// LED 灯带平滑渐暗关闭
void turnOffAllLEDs() {
  for (int b = globalBrightness; b > 0; b -= 10) {
    strip.setBrightness(b);
    strip.show();
    delay(10);
  }
  strip.clear();
  strip.show();
  globalBrightness = 30;
}

// ***************************************************************************
// 清除所有手势跟踪的轨迹变量,重置手势轨迹效果
void clearTrailEffects() {
  for(int i = 0; i < NUM_LEDS; i++) trailEffect[i] = 0;
}

// ***************************************************************************
// 控制第1个LED(索引0)按指定次数和间隔闪烁,用于设备状态初始化成功提示
void blinkStatusLED(int times, int delayTime) {
  for (int i = 0; i < times; i++) {
    strip.setPixelColor(0, 0, 0, 255);
    strip.show();
    delay(delayTime);
    strip.setPixelColor(0, 0, 0, 0);
    strip.show();
    delay(delayTime);
  }
}

// ***************************************************************************
// 缓慢衰减轨迹强度,用于手势效果
void updateTrail() {
  for(int i = 0; i < NUM_LEDS; i++) {
    trailEffect[i] *= trailDecay;
    if(trailEffect[i] < 0.01) trailEffect[i] = 0;
  }
}

// ***************************************************************************
// 快速衰减轨迹强度,用于待机效果
void updateTrailFast() {
  for(int i = 0; i < NUM_LEDS; i++) {
    trailEffect[i] *= 0.6;
    if(trailEffect[i] < 0.01) trailEffect[i] = 0;
  }
}

// ***************************************************************************
// 根据手势坐标(x,y)更新LED灯带的手势跟踪显示效果
void updateHandTrackingEffect(int x, int y) {
  int ledIndex = map(x, Y_MIN, Y_MAX, 0, NUM_LEDS - 1);
  ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1);
  
  for(int i = 0; i < NUM_LEDS; i++) {
    float intensity = trailEffect[i];
    if(i == ledIndex) {
      strip.setPixelColor(i, 255, 255, 255);  // 手势当前位置的LED显示白色 (255,255,255)
    } else if(intensity > 0.05) {
      uint32_t col = wheel((i * 256 / NUM_LEDS) % 256); // 彩虹色拖尾
      uint8_t r = ((col > > 16) & 0xFF) * intensity;
      uint8_t g = ((col > > 8) & 0xFF) * intensity;
      uint8_t b = (col & 0xFF) * intensity;
      strip.setPixelColor(i, r, g, b);  
    } else {
      strip.setPixelColor(i, 0, 0, 0);
    }
  }
}

// ***************************************************************************
// 重置所有待机状态特效的参数
void resetIdleEffects() {
  idlePosition = 0;
  idlePulse = 0;
  idleColorIndex = 0;
  idleLastUpdate = millis();
  idleEffectMode = random(0, 2);
}

// ***************************************************************************
// 每5秒自动切换空闲状态模式
void updateIdleEffect() {
  unsigned long currentTime = millis();
  if(currentTime - idleLastUpdate > 5000) {
    idleEffectMode = (idleEffectMode + 1) % 2;
    idleLastUpdate = currentTime;
  }
  if(idleEffectMode == 0) updateWaterFlowEffect();
  else updateBreathingEffect();
}

// ***************************************************************************
// 空闲状态流水灯效果
void updateWaterFlowEffect() {
  for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0, 0, 0);
  
  static int dir = 1;
  idlePosition += dir;
  if(idlePosition >= NUM_LEDS || idlePosition < 0) {
    dir *= -1;
    idlePosition += dir;
    idleColorIndex = (idleColorIndex + 1) % 7;
  }
  
  uint32_t c = rainbowColors[idleColorIndex];
  strip.setPixelColor(idlePosition, c);
}

// ***************************************************************************
// 空闲状态呼吸灯效果
void updateBreathingEffect() {
  idlePulse += 0.05;
  if(idlePulse > 6.28) {
    idlePulse = 0;
    idleColorIndex = (idleColorIndex + 1) % 7;
  }
  float val = (sin(idlePulse) + 1.0) / 2.0 * 0.8 + 0.2; // 周期性生成 0~1 之间的亮度系数val
  uint32_t c = rainbowColors[idleColorIndex];
  uint8_t r = ((c > > 16) & 0xFF) * val;
  uint8_t g = ((c > > 8) & 0xFF) * val;
  uint8_t b = (c & 0xFF) * val;
  for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, r, g, b);
}

// ***************************************************************************
// 生成对应的彩虹颜色RGB,实现HSV到RGB的简易转换
uint32_t wheel(uint8_t wheelPos) {
  wheelPos = 255 - wheelPos;
  if(wheelPos < 85) return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3);
  if(wheelPos < 170) {
    wheelPos -= 85;
    return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3);
  }
  wheelPos -= 170;
  return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0);
}

/******************************************************************************
 * 深圳市在芯间科技有限公司
 * 淘宝店铺:在芯间科技零知板
 * 店铺网址:https://shop533070398.taobao.com
 * 版权说明:
 *  1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
 *  2.严禁将本代码或其衍生版本用于任何商业用途(包括但不限于产品开发、付费服务、企业内部使用等)。
 *  3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/

 

系统流程图

WS2812

四、操作流程

4.1 系统操作

        初始化状态:灯带全灭,第 0 个灯珠闪烁 3 次,串口提示 “向上快速挥手:打开系统”;

WS2812

在传感器上方快速向上挥手开启系统, 在开机状态下,快速向下挥手关闭系统, LED灯带会逐渐变暗关闭

         手势控制:将手放在传感器上方5-10cm处,LED灯带会对应显示白色光标;左右平行移动手部,光标跟随移动并留下彩虹色尾影

WS2812

手部离开传感器区域,自动进入待机模式,每5秒在流水灯和呼吸灯之间切换

灵敏度调整

 

// 如果手势不易触发,降低阈值
const int GESTURE_THRESHOLD = 800;      // 降低幅度要求
const int GESTURE_SPEED_THRESHOLD = 400; // 降低速度要求

// 如果误触发过多,增加阈值
const int GESTURE_THRESHOLD = 1200;     // 增加幅度要求
const int GESTURE_SPEED_THRESHOLD = 800; // 增加速度要求

 

视觉效果调整

 

// 调整尾影持续时间
float trailDecay = 0.9;  // 值越大,尾影持续时间越长

// 调整亮度范围
int brightness = map(currentY, Y_MIN, Y_MAX, 20, 100);  // 缩小亮度变化范围

 

4.2 视频演示

​https://www.bilibili.com/video/BV1uivsBBEJo/?vd_source=a31e3d8d8ce008260eee442534c2f63d

系统初始化灯带闪烁状态灯提示就绪,手势跟踪模式下白色光点跟随手部 X 轴,亮度随手部 Y 轴平滑变化,尾影效果自然;移开手部自动切换流水灯 / 呼吸灯

五、PAJ7620U2 手势传感器知识点讲解

PAJ7620U2是一款内置光源和环境光抑制滤波器集成的 LED,镜头和手势感测器在一个小的立方体模组,能在黑暗或低光环境下工作,其模块功能框图如下图所示

WS2812

IIC 接口,支持高达 400KHz 通信速率;内置 9 个手势类型(上、下、左、右、前、后、顺时针旋转、逆时针旋转、挥动),支持输出中断;支持接近检测功能,检测物体体积大小和亮度

5.1 软件I2C通信

MCU 通过 I2C 接口来控制 PAJ7620U2,PAJ7620U2 工作时通过内部 LED 驱动器

I2C 时序参数

WS2812

参数说明 符号 标准模式 快速模式 单位
SCL 时钟频率 fscl 10 ~ 100 10 ~ 400 kHz
起始/重复起始条件保持时间(此后产生第一个时钟脉冲) tHD,STA ≥ 4 ≥ 0.6 µs
重复起始条件建立时间 tSU,STA ≥ 4.7 ≥ 0.6 µs
SCL 时钟低电平时间 tLOW ≥ 4.7 ≥ 1.3 µs
SCL 时钟高电平时间 tHIGH ≥ 4 ≥ 0.6 µs
数据保持时间 tHD,DAT​ ≥ 0 ≥ 0 µs
数据建立时间 tSU,DATt ≥ 250 ≥ 100 ns
SDA 与 SCL 信号上升时间 tr ≤ 1000 ≤ 300 ns
SDA 与 SCL 信号下降时间 tf ≤ 300 ≤ 300 ns
停止条件建立时间 tSU,STO ≥ 4 ≥ 0.6 µs
停止条件到起始条件的总线空闲时间 tBUF ≥ 4.7 ≥ 1.3 µs

当传感器阵列在有效的距离中探测到物体时,目标信息提取阵列会对探测目标进行特征原始数据的获取,获取的数据会存在寄存器中,同时手势识别阵列会对原始数据进行识别处理,最后将手势结果存到寄存器中,用户可根据 I2C 接口对原始数据和手势识别的结果进行读取

光标模式数据读取时序

WS2812

5.2 寄存器配置

PAJ7620U2 的内部有两个 BANK 寄存器区域,分别为 BANK0 和 BANK1。不同的区域用于访问不同的功能寄存器,访问某一 BANK 区域下的寄存器前需发送控制指令进入该寄存器区域

WS2812

进入 BANK0 区域需向传感器 0xEF 地址写 0x00,而 BANK1 区域需向传感器 0xEF 地址写 0x01

 使能工作寄存器

WS2812

该寄存器地址为 0X72,用于使能 PAJ7620 工作,bit0 位设置为 1 则使能 PAJ7620 工作,设置为 0 则失能 PAJ7620 工作 

 手势检测输出中断使能寄存器

WS2812

该寄存器地址为 0X41,用于手势识别,bit0~bit7 位用于使能不同手势识别结果的中断输出,默认值为 0XFF

六、常见问题解答(FAQ)

Q1:手势开关机偶尔误触发?

        A:解决方案:增大GESTURE_THRESHOLD 或GESTURE_SPEED_THRESHOLD 阈值,提高触发门槛;延长GESTURE_COOLDOWN(如 1000ms),避免短时间重复触发

Q2:如何增加更多LED?

        A:修改步骤:更新NUM_LEDS定义,调整trailEffect数组大小,可能需要增加电源功率

项目资源整合

PAJ7620U2 数据手册:         https://files.seeedstudio.com/wiki/Grove_Gesture_V_1.0/res/PAJ7620U2_DS_v1.5_05012022_Confidential.pdf

PAJ7620U2 库文件:        https://github.com/acrandal/RevEng_PAJ7620


审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分