回答

收藏

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

其他 其他 62 人阅读 | 0 人回复 | 2025-12-29

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


项目概述
       本项目基于零知标准板(主控芯片STM32F103RBT6)为核心控制器,结合先进的PAJ7620U2手势识别传感器和WS2812B RGB LED灯带,实现智能手势开关控制功能。系统能够实时检测手部在三维空间中的位置和运动轨迹,并将这些动作信息转换为直观、绚丽的灯光效果
项目难点及解决方案
        问题描述:WS2812B时序精度控制,STM32普通IO难以满足严格时序要求
驱动方案:使用SPI+DMA方式,确保时序准确
  1. WS2812B::WS2812Bspi.setClockDivider(SPI_CLOCK_DIV32);  // 444ns脉冲
  2. WS2812B::WS2812Bspi.dmaSendAsync(pixels, numBytes);    // 异步DMA传输
复制代码

一、系统接线部分1.1 硬件清单组件名称型号规格数量说明
主控开发板零知标准板(STM32F103RBT6)1主控制器,72MHz主频
手势传感器PAJ7620U21手势识别,I2C接口,最大检测距离15cm
RGB LED灯带WS2812-8 RGB模块216颗灯珠,SPI驱动,单线控制
连接线杜邦线(母对母)若干用于模块间连接
电源5V/2A直流电源1为系统供电
1.2 接线方案表
根据代码中的引脚定义,硬件接线方案如下:
PAJ7620U2传感器接线
零知标准板引脚PAJ7620U2引脚功能说明
A5SCLI2C时钟线(软件模拟)
A4SDAI2C数据线(软件模拟)
3.3VVCC传感器供电(3.3V)
GNDGND电源地WS2812B灯带接线
WS2812B需要较大电流,建议使用独立5V电源供电
零知标准板引脚WS2812B灯带功能说明
11DINSPI数据输出
5VVCC灯带供电(5V)
GNDGND电源地
1.3 具体接线图


1.4 接线实物图


二、安装与使用部分2.1 开源平台-输入PAJ7620U2 并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器


三、代码讲解部分
        采用"单次读取"策略,在主循环开头一次性读取isCursorInView/cursorX/cursorY,确保整个循环周期内使用同一组传感器数据
3.1 手势检测状态机
  1. // 手势检测逻辑不再读取传感器,而是分析传入的数据
  2. // 参数: isPresent(手是否在), y(当前Y坐标)
  3. // 返回: 0(无), 1(向上), -1(向下)
  4. int checkGestureLogic(bool isPresent, int y) {
  5.   static int gestureStartY = 0;
  6.   static unsigned long gestureStartTime = 0;
  7.   static bool gestureInProgress = false;
  8.   
  9.   // 冷却时间检查:避免重复触发
  10.   if (millis() - lastGestureTime < GESTURE_COOLDOWN) {
  11.     return 0;
  12.   }
  13.   
  14.   // 如果手移开了,重置检测状态
  15.   if (!isPresent) {
  16.     gestureInProgress = false;
  17.     return 0;
  18.   }
  19.   
  20.   // 如果还没开始检测,记录起点
  21.   if (!gestureInProgress) {
  22.     gestureStartY = y;
  23.     gestureStartTime = millis();
  24.     gestureInProgress = true;
  25.     return 0;
  26.   }
  27.   
  28.   // 计算变化量和速度
  29.   unsigned long duration = millis() - gestureStartTime;
  30.   int yChange = y - gestureStartY;
  31.   
  32.   // 只有当持续时间足够短且速度足够快时,才认为是手势
  33.   // 增加 duration > 50 是为了避免极其短暂的噪点
  34.   if (duration > 50 && abs(yChange) / (duration / 1000.0) > GESTURE_SPEED_THRESHOLD) {
  35.    
  36.     // 向上快速移动 (Y值减小)
  37.     if (yChange < -GESTURE_THRESHOLD) {
  38.       Serial.println("检测到: 向上挥手 (开启)");
  39.       lastGestureTime = millis();
  40.       gestureInProgress = false;
  41.       return 1;
  42.     }
  43.    
  44.     // 向下快速移动 (Y值增加)
  45.     if (yChange > GESTURE_THRESHOLD) {
  46.       Serial.println("检测到: 向下挥手 (关闭)");
  47.       lastGestureTime = millis();
  48.       gestureInProgress = false;
  49.       return -1;
  50.     }
  51.   }
  52.   
  53.   // 超时重置 (如果动作太慢,就视为普通移动而非手势)
  54.   if (duration > 800) {
  55.     gestureInProgress = false;
  56.   }
  57.   
  58.   return 0;
  59. }
复制代码
手部进入检测区域,记录起始坐标和时间,持续跟踪Y坐标变化,计算移动速度和幅度

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

  6. void loop()
  7. {
  8.   // 获取传感器数据
  9.   // ...
  10.   
  11.   // 处理系统开关逻辑
  12.   if (!systemOn) {
  13.     // 关机状态:只响应开启手势
  14.     if (detectedDir == 1) {
  15.       turnOnSystem();
  16.     }
  17.     delay(30);
  18.     return;
  19.   }
  20.   
  21.   // 开机状态:优先检查关闭手势
  22.   if (detectedDir == -1) {
  23.     turnOffSystem();
  24.     return;
  25.   }
  26.   
  27.   // 正常的交互逻辑
  28.   // ...
  29. }
复制代码
状态机流程图


3.3 视觉交互反馈开机动画效果
        从中心向两侧渐变紫色光效,过渡到全彩虹色
  1. void showStartupEffect() {
  2.   strip.clear();
  3.   strip.show();
  4.   delay(100);
  5.   
  6.   // 从中心向两侧展开的紫色动画
  7.   int center = NUM_LEDS / 2;
  8.   int maxDistance = max(center, NUM_LEDS - center - 1);
  9.   
  10.   for (int step = 0; step <= maxDistance; step++) {
  11.     strip.clear();
  12.     float brightnessFactor = (float)step / maxDistance;
  13.     brightnessFactor = constrain(brightnessFactor, 0.2, 1.0);
  14.    
  15.     // 紫色(RGB: 148,0,211)
  16.     uint8_t r = 148 * brightnessFactor;
  17.     uint8_t b = 211 * brightnessFactor;
  18.    
  19.     // 两侧对称点亮
  20.     for (int dist = 0; dist <= step; dist++) {
  21.       if (center + dist < NUM_LEDS) strip.setPixelColor(center + dist, r, 0, b);
  22.       if (center - dist >= 0) strip.setPixelColor(center - dist, r, 0, b);
  23.     }
  24.     strip.show();
  25.     delay(60);
  26.   }
  27.   
  28.   // 过渡到彩虹色
  29.   for (int i = 0; i < NUM_LEDS; i++) {
  30.     strip.setPixelColor(i, wheel((i * 256 / NUM_LEDS) % 256));
  31.   }
  32.   strip.show();
  33.   delay(500);
  34. }
复制代码
关机效果
        关机效果为全部灯珠的亮度从当前值渐降至 0 后熄灭
  1. void turnOffAllLEDs() {
  2.   // 渐暗效果
  3.   for (int b = globalBrightness; b > 0; b -= 10) {
  4.     strip.setBrightness(b);
  5.     strip.show();
  6.     delay(10);
  7.   }
  8.   strip.clear();
  9.   strip.show();
  10.   globalBrightness = 30;  // 重置亮度
  11. }
复制代码
状态指示灯
        初始化提示,闪烁第 0 个灯珠 3 次提示就绪
  1. void blinkStatusLED(int times, int delayTime) {
  2.   for (int i = 0; i < times; i++) {
  3.     strip.setPixelColor(0, 0, 0, 255);  // 蓝色闪烁
  4.     strip.show();
  5.     delay(delayTime);
  6.     strip.setPixelColor(0, 0, 0, 0);
  7.     strip.show();
  8.     delay(delayTime);
  9.   }
  10. }
复制代码

3.4 手势跟踪效果
  1. void updateHandTrackingEffect(int x, int y) {
  2.   int ledIndex = map(x, Y_MIN, Y_MAX, 0, NUM_LEDS - 1);
  3.   ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1);
  4.   
  5.   for(int i = 0; i < NUM_LEDS; i++) {
  6.     float intensity = trailEffect[i];
  7.    
  8.     if(i == ledIndex) {
  9.       // 当前位置:白色高亮
  10.       strip.setPixelColor(i, 255, 255, 255);
  11.     } else if(intensity > 0.05) {
  12.       // 尾影位置:彩虹色
  13.       uint32_t col = wheel((i * 256 / NUM_LEDS) % 256);
  14.       uint8_t r = ((col >> 16) & 0xFF) * intensity;
  15.       uint8_t g = ((col >> 8) & 0xFF) * intensity;
  16.       uint8_t b = (col & 0xFF) * intensity;
  17.       strip.setPixelColor(i, r, g, b);
  18.     } else {
  19.       // 无尾影:关闭LED
  20.       strip.setPixelColor(i, 0, 0, 0);
  21.     }
  22.   }
  23. }
复制代码

3.5 系统待机
  1. void updateWaterFlowEffect() {
  2.   // 清屏
  3.   for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0, 0, 0);
  4.   
  5.   static int dir = 1;  // 移动方向
  6.   idlePosition += dir;
  7.   
  8.   // 边界处理和方向反转
  9.   if(idlePosition >= NUM_LEDS || idlePosition < 0) {
  10.     dir *= -1;
  11.     idlePosition += dir;
  12.     idleColorIndex = (idleColorIndex + 1) % 7;  // 切换颜色
  13.   }
  14.   
  15.   // 设置当前光点
  16.   uint32_t c = rainbowColors[idleColorIndex];
  17.   strip.setPixelColor(idlePosition, c);
  18.   
  19.   // 注意:这个版本简化了尾迹效果,可根据需要恢复
  20. }

  21. uint32_t wheel(uint8_t wheelPos) {
  22.   wheelPos = 255 - wheelPos;  // 反转以获得更鲜艳的颜色
  23.   if(wheelPos < 85) {
  24.     return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3);  // 红->紫
  25.   }
  26.   if(wheelPos < 170) {
  27.     wheelPos -= 85;
  28.     return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3);  // 紫->青
  29.   }
  30.   wheelPos -= 170;
  31.   return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0);  // 青->绿
  32. }
复制代码

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

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

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

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

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

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

  24. // 待机效果变量
  25. int idleEffectMode = 0;  // 0:流水灯, 1:呼吸灯
  26. int idleColorIndex = 0;
  27. int idlePosition = 0;
  28. float idlePulse = 0;
  29. unsigned long idleLastUpdate = 0;

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

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

  37. // 坐标范围
  38. const int Y_MIN = 384;
  39. const int Y_MAX = 3455;

  40. // 预定义颜色(彩虹色)
  41. uint32_t rainbowColors[7] = {
  42.   strip.Color(255, 0, 0),     // 红色
  43.   strip.Color(255, 127, 0),   // 橙色
  44.   strip.Color(255, 255, 0),   // 黄色
  45.   strip.Color(0, 255, 0),     // 绿色
  46.   strip.Color(0, 0, 255),     // 蓝色
  47.   strip.Color(75, 0, 130),    // 靛蓝色
  48.   strip.Color(148, 0, 211)    // 紫色
  49. };

  50. // ***************************************************************************
  51. void setup()
  52. {
  53.   Serial.begin(115200);
  54.   
  55.   // 初始化LED灯带
  56.   strip.begin();
  57.   strip.clear();
  58.   strip.show();
  59.   
  60.   // 初始化手势传感器
  61.   if(!sensor.begin())
  62.   {
  63.     Serial.println("PAJ7620初始化失败!");
  64.     while(true) {}
  65.   }
  66.   
  67.   Serial.println("PAJ7620U2初始化成功!");
  68.   sensor.setCursorMode();  // 设置为光标模式
  69.   
  70.   Serial.println("\n手势控制LED系统已启动!");
  71.   Serial.println("向上快速挥手: 打开系统");
  72.   Serial.println("向下快速挥手: 关闭系统");
  73.   
  74.   systemOn = false;
  75.   turnOffAllLEDs();
  76.   blinkStatusLED(3, 300);
  77. }

  78. // ***************************************************************************
  79. void loop()
  80. {
  81.   // 在循环开始时,一次性读取所有传感器数据
  82.   bool currentInView = sensor.isCursorInView();
  83.   int currentX = 0;
  84.   int currentY = 0;
  85.   
  86.   if (currentInView) {
  87.     currentX = sensor.getCursorX();
  88.     currentY = sensor.getCursorY();
  89.   }

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

  93.   // 处理系统开关逻辑
  94.   if (!systemOn) {
  95.     // 关机状态:只响应开启手势
  96.     if (detectedDir == 1) {
  97.       turnOnSystem();
  98.     }
  99.     delay(30); // 简单的防抖延迟
  100.     return;
  101.   }
  102.   
  103.   // 开机状态:优先检查关闭手势
  104.   if (detectedDir == -1) {
  105.     turnOffSystem();
  106.     return;
  107.   }

  108.   // 如果没有开关机手势,执行正常的LED显示逻辑
  109.   strip.setBrightness(globalBrightness);
  110.   
  111.   if(currentInView)
  112.   {
  113.     // 手部跟踪模式
  114.     isIdleMode = false;
  115.    
  116.     // 亮度跟随Y轴变化
  117.     int brightness = map(currentY, Y_MIN, Y_MAX, 30, 255);
  118.     brightness = constrain(brightness, 30, 255);
  119.    
  120.     if (abs(brightness - globalBrightness) > 5) {
  121.       globalBrightness = brightness;
  122.       strip.setBrightness(globalBrightness);
  123.     }
  124.    
  125.     updateTrail();
  126.    
  127.     // 使用刚才读取的 currentX 更新效果
  128.     updateHandTrackingEffect(currentX, currentY);
  129.    
  130.     // 添加高亮
  131.     int ledIndex = map(currentX, Y_MIN, Y_MAX, 0, NUM_LEDS - 1);
  132.     ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1);
  133.     trailEffect[ledIndex] = 1.0;
  134.    
  135.     lastCursorX = currentX;
  136.   }
  137.   else
  138.   {
  139.     // 待机模式
  140.     if(!isIdleMode) {
  141.       resetIdleEffects();
  142.       isIdleMode = true;
  143.     }
  144.     updateTrailFast();
  145.     updateIdleEffect();
  146.   }
  147.   
  148.   strip.show();
  149.   delay(30);
  150. }

  151. // ***************************************************************************
  152. // 手势检测不再读取传感器,而是分析传入的数据
  153. // 参数: isPresent(手是否在), y(当前Y坐标)
  154. // 返回: 0(无), 1(向上), -1(向下)
  155. int checkGestureLogic(bool isPresent, int y) {
  156.   static int gestureStartY = 0;
  157.   static unsigned long gestureStartTime = 0;
  158.   static bool gestureInProgress = false;
  159.   
  160.   unsigned long currentTime = millis();
  161.   
  162.   // 冷却时间检查
  163.   if (currentTime - lastGestureTime < GESTURE_COOLDOWN) {
  164.     return 0;
  165.   }
  166.   
  167.   // 如果手移开了,重置检测状态
  168.   if (!isPresent) {
  169.     gestureInProgress = false;
  170.     return 0;
  171.   }
  172.   
  173.   // 如果还没开始检测,记录起点
  174.   if (!gestureInProgress) {
  175.     gestureStartY = y;
  176.     gestureStartTime = currentTime;
  177.     gestureInProgress = true;
  178.     return 0; // 刚开始,还没结果
  179.   }
  180.   
  181.   // 计算变化量和速度
  182.   unsigned long duration = currentTime - gestureStartTime;
  183.   int yChange = y - gestureStartY;
  184.   
  185.   // 避免除以零
  186.   if (duration == 0) return 0;
  187.   
  188.   float speed = abs(yChange) / (duration / 1000.0);
  189.   
  190.   // 只有当持续时间足够短且速度足够快时,才认为是手势
  191.   // 增加 duration > 50 是为了避免极其短暂的噪点
  192.   if (duration > 50 && speed > GESTURE_SPEED_THRESHOLD) {
  193.    
  194.     // 向上快速移动 (Y值减小)
  195.     if (yChange < -GESTURE_THRESHOLD) {
  196.       Serial.println("检测到: 向上挥手 (开启)");
  197.       lastGestureTime = currentTime;
  198.       gestureInProgress = false;
  199.       return 1;
  200.     }
  201.    
  202.     // 向下快速移动 (Y值增加)
  203.     if (yChange > GESTURE_THRESHOLD) {
  204.       Serial.println("检测到: 向下挥手 (关闭)");
  205.       lastGestureTime = currentTime;
  206.       gestureInProgress = false;
  207.       return -1;
  208.     }
  209.   }
  210.   
  211.   // 超时重置 (如果动作太慢,就视为普通移动而非手势)
  212.   if (duration > 800) {
  213.     gestureInProgress = false; // 重置,这也允许连续跟踪而不误触发
  214.   }
  215.   
  216.   return 0;
  217. }

  218. // ***************************************************************************
  219. // 打开系统
  220. void turnOnSystem() {
  221.   systemOn = true;
  222.   Serial.println(">>> 系统启动");
  223.   
  224.   resetIdleEffects();
  225.   clearTrailEffects();
  226.   globalBrightness = 30;
  227.   strip.setBrightness(globalBrightness);
  228.   
  229.   showStartupEffect();
  230. }

  231. // ***************************************************************************
  232. // 关闭系统
  233. void turnOffSystem() {
  234.   systemOn = false;
  235.   Serial.println(">>> 系统关闭");
  236.   turnOffAllLEDs();
  237. }

  238. // ***************************************************************************
  239. // 启动效果
  240. void showStartupEffect() {
  241.   strip.clear();
  242.   strip.show();
  243.   delay(100);
  244.   
  245.   int center = NUM_LEDS / 2;
  246.   int maxDistance = max(center, NUM_LEDS - center - 1);
  247.   
  248.   for (int step = 0; step <= maxDistance; step++) {
  249.     strip.clear();
  250.     float brightnessFactor = (float)step / maxDistance;
  251.     brightnessFactor = constrain(brightnessFactor, 0.2, 1.0);
  252.    
  253.     uint8_t r = 148 * brightnessFactor;
  254.     uint8_t b = 211 * brightnessFactor;
  255.    
  256.     for (int dist = 0; dist <= step; dist++) {
  257.       if (center + dist < NUM_LEDS) strip.setPixelColor(center + dist, r, 0, b);
  258.       if (center - dist >= 0) strip.setPixelColor(center - dist, r, 0, b);
  259.     }
  260.     strip.show();
  261.     delay(60);
  262.   }
  263.   
  264.   // 快速过渡到彩虹
  265.   for (int i = 0; i < NUM_LEDS; i++) {
  266.     strip.setPixelColor(i, wheel((i * 256 / NUM_LEDS) % 256));
  267.   }
  268.   strip.show();
  269.   delay(500);
  270. }

  271. // ***************************************************************************
  272. // LED 灯带平滑渐暗关闭
  273. void turnOffAllLEDs() {
  274.   for (int b = globalBrightness; b > 0; b -= 10) {
  275.     strip.setBrightness(b);
  276.     strip.show();
  277.     delay(10);
  278.   }
  279.   strip.clear();
  280.   strip.show();
  281.   globalBrightness = 30;
  282. }

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

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

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

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

  316. // ***************************************************************************
  317. // 根据手势坐标(x,y)更新LED灯带的手势跟踪显示效果
  318. void updateHandTrackingEffect(int x, int y) {
  319.   int ledIndex = map(x, Y_MIN, Y_MAX, 0, NUM_LEDS - 1);
  320.   ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1);
  321.   
  322.   for(int i = 0; i < NUM_LEDS; i++) {
  323.     float intensity = trailEffect[i];
  324.     if(i == ledIndex) {
  325.       strip.setPixelColor(i, 255, 255, 255);  // 手势当前位置的LED显示白色 (255,255,255)
  326.     } else if(intensity > 0.05) {
  327.       uint32_t col = wheel((i * 256 / NUM_LEDS) % 256); // 彩虹色拖尾
  328.       uint8_t r = ((col >> 16) & 0xFF) * intensity;
  329.       uint8_t g = ((col >> 8) & 0xFF) * intensity;
  330.       uint8_t b = (col & 0xFF) * intensity;
  331.       strip.setPixelColor(i, r, g, b);  
  332.     } else {
  333.       strip.setPixelColor(i, 0, 0, 0);
  334.     }
  335.   }
  336. }

  337. // ***************************************************************************
  338. // 重置所有待机状态特效的参数
  339. void resetIdleEffects() {
  340.   idlePosition = 0;
  341.   idlePulse = 0;
  342.   idleColorIndex = 0;
  343.   idleLastUpdate = millis();
  344.   idleEffectMode = random(0, 2);
  345. }

  346. // ***************************************************************************
  347. // 每5秒自动切换空闲状态模式
  348. void updateIdleEffect() {
  349.   unsigned long currentTime = millis();
  350.   if(currentTime - idleLastUpdate > 5000) {
  351.     idleEffectMode = (idleEffectMode + 1) % 2;
  352.     idleLastUpdate = currentTime;
  353.   }
  354.   if(idleEffectMode == 0) updateWaterFlowEffect();
  355.   else updateBreathingEffect();
  356. }

  357. // ***************************************************************************
  358. // 空闲状态流水灯效果
  359. void updateWaterFlowEffect() {
  360.   for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0, 0, 0);
  361.   
  362.   static int dir = 1;
  363.   idlePosition += dir;
  364.   if(idlePosition >= NUM_LEDS || idlePosition < 0) {
  365.     dir *= -1;
  366.     idlePosition += dir;
  367.     idleColorIndex = (idleColorIndex + 1) % 7;
  368.   }
  369.   
  370.   uint32_t c = rainbowColors[idleColorIndex];
  371.   strip.setPixelColor(idlePosition, c);
  372. }

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

  388. // ***************************************************************************
  389. // 生成对应的彩虹颜色RGB,实现HSV到RGB的简易转换
  390. uint32_t wheel(uint8_t wheelPos) {
  391.   wheelPos = 255 - wheelPos;
  392.   if(wheelPos < 85) return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3);
  393.   if(wheelPos < 170) {
  394.     wheelPos -= 85;
  395.     return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3);
  396.   }
  397.   wheelPos -= 170;
  398.   return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0);
  399. }

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


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

在传感器上方快速向上挥手开启系统, 在开机状态下,快速向下挥手关闭系统, LED灯带会逐渐变暗关闭
         手势控制:将手放在传感器上方5-10cm处,LED灯带会对应显示白色光标;左右平行移动手部,光标跟随移动并留下彩虹色尾影

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

灵敏度调整
  1. // 如果手势不易触发,降低阈值
  2. const int GESTURE_THRESHOLD = 800;      // 降低幅度要求
  3. const int GESTURE_SPEED_THRESHOLD = 400; // 降低速度要求

  4. // 如果误触发过多,增加阈值
  5. const int GESTURE_THRESHOLD = 1200;     // 增加幅度要求
  6. const int GESTURE_SPEED_THRESHOLD = 800; // 增加速度要求
复制代码
视觉效果调整
  1. // 调整尾影持续时间
  2. float trailDecay = 0.9;  // 值越大,尾影持续时间越长

  3. // 调整亮度范围
  4. int brightness = map(currentY, Y_MIN, Y_MAX, 20, 100);  // 缩小亮度变化范围
复制代码


4.2 视频演示
[url=【PAJ7620U2手势控制WS2812 RGB灯】 https://www.bilibili.com/video/B ... aa8dc6757ac429e12da]【PAJ7620U2手势控制WS2812 RGB灯】 https://www.bilibili.com/video/B ... aa8dc6757ac429e12da[/url]
系统初始化灯带闪烁状态灯提示就绪,手势跟踪模式下白色光点跟随手部 X 轴,亮度随手部 Y 轴平滑变化,尾影效果自然;移开手部自动切换流水灯 / 呼吸灯


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



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

5.1 软件I2C通信
MCU 通过 I2C 接口来控制 PAJ7620U2,PAJ7620U2 工作时通过内部 LED 驱动器
I2C 时序参数



参数说明符号标准模式快速模式单位
SCL 时钟频率fscl10 ~ 10010 ~ 400kHz
起始/重复起始条件保持时间
(此后产生第一个时钟脉冲)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≥ 100ns
SDA 与 SCL 信号上升时间tr≤ 1000≤ 300ns
SDA 与 SCL 信号下降时间tf≤ 300≤ 300ns
停止条件建立时间tSU,STO≥ 4≥ 0.6µs
停止条件到起始条件的总线空闲时间tBUF≥ 4.7≥ 1.3µs当传感器阵列在有效的距离中探测到物体时,目标信息提取阵列会对探测目标进行特征原始数据的获取,获取的数据会存在寄存器中,同时手势识别阵列会对原始数据进行识别处理,最后将手势结果存到寄存器中,用户可根据 I2C 接口对原始数据和手势识别的结果进行读取

光标模式数据读取时序


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

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

使能工作寄存器

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

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

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

六、常见问题解答(FAQ)Q1:手势开关机偶尔误触发?
        A:解决方案:增大GESTURE_THRESHOLD 或GESTURE_SPEED_THRESHOLD 阈值,提高触发门槛;延长GESTURE_COOLDOWN(如 1000ms),避免短时间重复触发
Q2:如何增加更多LED?
        A:修改步骤:更新NUM_LEDS定义,调整trailEffect数组大小,可能需要增加电源功率
项目资源整合
PAJ7620U2 数据手册:       PAJ7620U2 Datasheet
PAJ7620U2 库文件:     RevEng_PAJ7620

分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /2 下一条