eefocus_4087784 发表于 2025-12-29 19:03:48

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

​✔零知开源(零知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主频
手势传感器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 手势检测状态机// 手势检测逻辑不再读取传感器,而是分析传入的数据
// 参数: 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;
}

// 正常的交互逻辑
// ...
}状态机流程图


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;
   
    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;
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 = {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 = {
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 = 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 = 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 *= trailDecay;
    if(trailEffect < 0.01) trailEffect = 0;
}
}

// ***************************************************************************
// 快速衰减轨迹强度,用于待机效果
void updateTrailFast() {
for(int i = 0; i < NUM_LEDS; i++) {
    trailEffect *= 0.6;
    if(trailEffect < 0.01) trailEffect = 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;
    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;
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;
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.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/系统流程图


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


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


手部离开传感器区域,自动进入待机模式,每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/?share_source=copy_web&vd_source=75d3b293c1933aa8dc6757ac429e12da]【PAJ7620U2手势控制WS2812 RGB灯】 https://www.bilibili.com/video/BV1uivsBBEJo/?share_source=copy_web&vd_source=75d3b293c1933aa8dc6757ac429e12da
系统初始化灯带闪烁状态灯提示就绪,手势跟踪模式下白色光点跟随手部 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

页: [1]
查看完整版本: 零知IDE——基于STM32F103RBT6的PAJ7620U2手势控制WS2812 RGB灯带系统