零知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主频 |
| 手势传感器 | 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 具体接线图

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[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.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/
系统流程图

四、操作流程
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/?vd_source=a31e3d8d8ce008260eee442534c2f63d
系统初始化灯带闪烁状态灯提示就绪,手势跟踪模式下白色光点跟随手部 X 轴,亮度随手部 Y 轴平滑变化,尾影效果自然;移开手部自动切换流水灯 / 呼吸灯
五、PAJ7620U2 手势传感器知识点讲解
PAJ7620U2是一款内置光源和环境光抑制滤波器集成的 LED,镜头和手势感测器在一个小的立方体模组,能在黑暗或低光环境下工作,其模块功能框图如下图所示

IIC 接口,支持高达 400KHz 通信速率;内置 9 个手势类型(上、下、左、右、前、后、顺时针旋转、逆时针旋转、挥动),支持输出中断;支持接近检测功能,检测物体体积大小和亮度
5.1 软件I2C通信
MCU 通过 I2C 接口来控制 PAJ7620U2,PAJ7620U2 工作时通过内部 LED 驱动器
I2C 时序参数

| 参数说明 | 符号 | 标准模式 | 快速模式 | 单位 |
|---|---|---|---|---|
| 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 接口对原始数据和手势识别的结果进行读取
光标模式数据读取时序

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 数据手册: 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
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !