TA的每日心情 | 奋斗 2025-7-11 08:33 |
|---|
签到天数: 2 天 连续签到: 1 天 [LV.1]初来乍到
童生
- 积分
- 113
|
✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
项目概述
本项目基于零知标准板(主控芯片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; // 重置亮度
- }
复制代码 状态指示灯
- 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 视频演示
[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
|
|