零知IDE——基于STMF103RBT6结合PAJ7620U2手势控制192位WS2812 RGB立方体矩阵

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

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

www.lingzhilab.com

目录

一、系统接线部分

[1.1 硬件清单](#1.1 硬件清单)

[1.2 接线方案表](#1.2 接线方案表)

[1.3 具体接线图](#1.3 具体接线图)

[1.4 接线实物图](#1.4 接线实物图)

二、安装与使用部分

三、代码讲解部分

[3.1 核心映射函数](#3.1 核心映射函数)

[3.2 手势处理机制](#3.2 手势处理机制)

[3.3 动画效果实现](#3.3 动画效果实现)

[3.4 软件 I2C 通信配置](#3.4 软件 I2C 通信配置)

[3.5 完整代码](#3.5 完整代码)

四、操作流程

[4.1 系统操作](#4.1 系统操作)

[4.2 手势操作演示](#4.2 手势操作演示)

[4.3 视频演示](#4.3 视频演示)

[五、PAJ7620U2 手势传感器知识点讲解](#五、PAJ7620U2 手势传感器知识点讲解)

[5.1 I2C通信原理](#5.1 I2C通信原理)

[5.2 寄存器系统架构](#5.2 寄存器系统架构)

六、常见问题解答(FAQ)

[Q1:WS2812 矩阵灯光乱闪,动画显示错位](#Q1:WS2812 矩阵灯光乱闪,动画显示错位)

Q2:动画卡顿,帧率低


想象一下,只需在空中轻轻一挥,就能控制一个绚丽的RGB立方体变换出不同图案------本文将手把手教你如何使用零知标准板,结合PAJ7620U2手势传感器和192个WS2812 LED,实现这个酷炫的交互系统。

项目概述

本项目基于零知标准板(主控STM32F103RBT6)PAJ7620U2手势识别传感器 以及三块8x8 WS2812 RGB矩阵(共192个LED),打造了一个高度智能化的手势交互立方体系统。通过精确的手势识别技术,用户可以在空中做出9种不同手势(向左、向右、向上、向下、向前、向后、顺时针旋转、逆时针旋转、挥手),系统实时响应

项目难点及解决方案

问题描述:物理 LED 矩阵的灯珠索引值(范围 0-191),与各 RGB 矩阵模块对应的逻辑坐标(face, x, y)存在映射不匹配

**解决方案:**设计getIndex()核心映射函数,统一逻辑坐标到物理 LED 索引的映射

一、系统接线部分

1.1 硬件清单

组件名称 规格参数 数量 备注
零知标准板 STM32F103RBT6主控 1 核心控制器
扩展板 零知标准板-扩展板 1 传感器扩展板
PAJ7620U2手势传感器 I²C接口,支持9种手势 1 手势输入设备
WS2812 RGB矩阵 8x8,共64个LED/块 3 显示设备,共192个LED
电源模块 5V/3A输出 1 为LED矩阵提供充足电流
连接线 杜邦线 若干 信号与电源连接

1.2 接线方案表

本项目使用软件I²C与PAJ7620U2通信,并使用单个GPIO引脚控制WS2812灯带。具体接线如下

零知标准板引脚 目标设备 连接说明 代码对应
A5 PAJ7620U2 SCL I²C时钟线 SoftWire库配置
A4 PAJ7620U2 SDA I²C数据线 SoftWire库配置
11 WS2812矩阵 IN 数据输入线 WS2812B库配置
3.3V PAJ7620U2 VIN 电源正极 模块工作电压
5V WS2812矩阵 V+ 电源正极 LED工作电压
GND 所有设备GND 电源地线 共地连接

PAJ7620U2手势模块采用扩展板直插,主控、传感器必须共地,WS2812矩阵输入信号为SPI通信的MOSI引脚

1.3 具体接线图

注意:级联 WS2812 矩阵时,按照 "DIN→DOUT" 顺序,最后一块矩阵的DOUT悬空;将RGB矩阵的V+和V-连接,单独使用外部电源+5V 供电

1.4 接线实物图

手势传感器距离手掌建议保持5-15cm,角度垂直;单独测试一块LED矩阵,确认数据引脚和亮度正常

二、安装与使用部分

2.1 开源平台-输入"WS2812 RGB立方体矩阵"并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、代码讲解部分

代码整体结构

3.1 核心映射函数

整个项目的几何核心,负责将三维逻辑坐标转换为物理LED索引

cpp 复制代码
int getIndex(int face, int x, int y) {
    if (x < 0 || x > 7 || y < 0 || y > 7) return -1;
    
    int pixelIndex = 0;

    // 面1 (LED 0-63):从左上角开始,蛇形排列
    if (face == 1) {
        if (y % 2 == 0) {  // 偶数行:从右向左
            pixelIndex = y * 8 + (7 - x);
        } else {           // 奇数行:从左向右
            pixelIndex = y * 8 + x;
        }
        return pixelIndex;
    }
    
    // 面2 (LED 64-127):旋转180度后的蛇形排列
    else if (face == 2) {
        int base = 64;
        int physRow = 7 - x;      // X轴翻转
        int physCol = 7 - y;      // Y轴翻转
        
        if (physRow % 2 == 0) {
            pixelIndex = base + (physRow * 8) + physCol;
        } else {
            pixelIndex = base + (physRow * 8) + (7 - physCol);
        }
        return pixelIndex;
    }
    
    // 面3 (LED 128-191):标准蛇形排列
    else if (face == 3) {
        int base = 128;
        int physRow = x;
        int physCol = y;
        
        if (physRow % 2 == 0) {
            pixelIndex = base + (physRow * 8) + physCol;
        } else {
            pixelIndex = base + (physRow * 8) + (7 - physCol);
        }
        return pixelIndex;
    }
    
    return -1;  // 无效坐标
}

3.2 手势处理机制

系统通过状态机模式GestureState管理手势识别与动画更新:

cpp 复制代码
void loop() {
    // 1. 读取手势传感器
    Gesture sensorGesture = gestureSensor.readGesture();
    GestureState newGestureState = sensorGestureToState(sensorGesture);
    
    // 2. 手势防抖处理
    unsigned long currentTime = millis();
    bool canChangeGesture = (currentTime - lastGestureChangeTime) >= GESTURE_DEBOUNCE_TIME;
    
    // 3. 手势状态切换
    if (newGestureState != GESTURE_NONE && 
        newGestureState != currentGesture && 
        canChangeGesture) {
        handleGestureChange(newGestureState);
    }
    
    // 4. 更新当前手势的动画效果
    switch (currentGesture) {
        case GESTURE_LEFT:
            updateLeftEffect();
            break;
        // ... 其他手势处理
    }
    
    delay(10);  // 主循环延迟,防止CPU过载
}

调用handleGestureChange函数初始化新动画,根据当前手势调用对应的动画更新函数

3.3 动画效果实现

cpp 复制代码
// 初始化向上手势状态
void initUpGesture() {
    if (arrowMode) {
        upPos = -4;  // 箭头模式:箭头从底部开始
    } else {
        upPhase = 0; // 原始模式:火焰效果初始相位
        upFrame = 0;
    }
    upLastUpdate = millis(); // 记录最后更新时间
}

// 更新向上手势动画
void updateUpEffect() {
    unsigned long currentTime = millis();
    if (currentTime - upLastUpdate < 40) return; // 控制帧率(每40ms更新一帧)
    upLastUpdate = currentTime;
    
    strip.clear(); // 清空上一帧画面
    
    if (arrowMode) {
        // 箭头模式:红色箭头从面2底部流向面1顶部
        // 面2部分
        for(int x=0; x<8; x++) {
            for(int y=0; y<8; y++) {
                int globalY = y;
                if (shouldLightVerticalArrowPixel(x, globalY, upPos, true)) {
                    int distY = upPos - globalY;
                    int intensity = 255 - distY * 50; // 箭头渐变亮度
                    if (intensity < 100) intensity = 100;
                    setPixel(2, x, y, intensity, 0, 0); // 红色箭头
                }
            }
        }
        // 面1部分
        for(int x=0; x<8; x++) {
            for(int y=0; y<8; y++) {
                int globalY = (7 - y) + 8; // 面1y坐标反转,实现从下往上流动
                if (shouldLightVerticalArrowPixel(x, globalY, upPos, true)) {
                    int distY = upPos - globalY;
                    int intensity = 255 - distY * 50;
                    if (intensity < 100) intensity = 100;
                    setPixel(1, x, y, intensity, 0, 0);
                }
            }
        }
        upPos++; // 箭头位置前进
        if (upPos >= 20) upPos = -4; // 循环流动
    } else {
        // 原始模式:火焰升腾效果
        for(int f_idx=0; f_idx<3; f_idx++) {
            int actualFace = f_idx + 1;
            for(int x=0; x<8; x++) {
                drops[f_idx][x]++; // 火焰位置上移
                int head = drops[f_idx][x];
                // 绘制火焰的渐变效果(头部亮,尾部暗)
                for(int k=0; k<3; k++) {
                    int drawY = head - k;
                    if(drawY >= 0 && drawY < 8) {
                        uint8_t bri = (k==0) ? 200 : ((k==1)?100:40);
                        setPixel(actualFace, x, drawY, bri, bri/4, 0); // 火焰色(红橙)
                    }
                }
                // 火焰到顶后重置
                if(head > 12) {
                    drops[f_idx][x] = random(-6, 0);
                    dropSpeeds[f_idx][x] = random(1, 3);
                }
            }
        }
    }
    strip.show(); // 刷新显示
}

箭头模式 / 原始模式分别处理,保证同一手势在不同模式下的差异化效果

3.4 软件 I2C 通信配置

SoftWire库可自定义 SDA/SCL 引脚,适配零知标准板的引脚布局

cpp 复制代码
void SoftWire::i2c_start() {
    set_sda(LOW);
    set_scl(LOW);
}

void SoftWire::i2c_stop() {
    set_sda(LOW);
    set_scl(HIGH);
    set_sda(HIGH);
}

void SoftWire::i2c_repeated_start() {
    set_sda(HIGH);
    set_scl(HIGH);
    set_sda(LOW);
}

bool SoftWire::i2c_get_ack() {
    set_scl(LOW);
    set_sda(HIGH);
    set_scl(HIGH);

    bool ret = !digitalRead(this->sda_pin);
    set_scl(LOW);
    return ret;
}

void SoftWire::i2c_send_ack() {
    set_sda(LOW);
    set_scl(HIGH);
    set_scl(LOW);
}

void SoftWire::i2c_send_nack() {
    set_sda(HIGH);
    set_scl(HIGH);
    set_scl(LOW);
}

3.5 完整代码

cpp 复制代码
/**************************************************************************************
 * 文件: /GestureRGB_Cube_Control/GestureRGB_Cube_Control.ino
 * 作者:零知实验室(深圳市在芯间科技有限公司)
 * -^^- 零知实验室,让电子制作变得更简单! -^^-
 * 时间: 2026-1-6
 * 功能:基于PAJ7620手势识别传感器控制三面8x8 WS2812 RGB灯阵列(共192个LED),支持9种手势识别,每种手势对应一种灯光效果。
 * 手势效果:
 * - 向左: 青色箭头从右向左 / 向右: 橙色箭头从左向右 / 向上: 红色火焰向上升腾 / 向下: 蓝色雨滴向下落下 / 向前: 切换到箭头模式(上下左右都是箭头指示)
 * - 向后: 切换回原始模式(火焰和雨滴) / 顺时针: 彩虹旋转 / 逆时针: 雷达扫描 / 挥手: 关闭所有灯光
 ***************************************************************************************/

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

// --- 全局配置 ---
#define NUM_LEDS 192
#define DEFAULT_BRIGHTNESS 60
#define MAX_BRIGHTNESS 200
#define GESTURE_DEBOUNCE_TIME 800  // 手势识别防抖时间(毫秒)

WS2812B strip(NUM_LEDS);
RevEng_PAJ7620 gestureSensor;

uint8_t currentBrightness = DEFAULT_BRIGHTNESS;

// 手势状态枚举
enum GestureState {
  GESTURE_NONE,
  GESTURE_LEFT,
  GESTURE_RIGHT,
  GESTURE_UP,
  GESTURE_DOWN,
  GESTURE_FORWARD,
  GESTURE_BACKWARD,
  GESTURE_CLOCKWISE,
  GESTURE_ANTICLOCKWISE,
  GESTURE_WAVE
};

GestureState currentGesture = GESTURE_NONE;
GestureState previousGesture = GESTURE_NONE;
unsigned long gestureStartTime = 0;
unsigned long lastGestureChangeTime = 0;  // 上次手势切换时间,用于防抖

// 模式切换:false=原始模式(火焰/雨滴), true=箭头模式
bool arrowMode = false;

// 各手势的动画状态变量
int leftPos = 0;
unsigned long leftLastUpdate = 0;

int rightPos = 0;
unsigned long rightLastUpdate = 0;

int upPos = 0;
unsigned long upLastUpdate = 0;

int downPos = 0;
unsigned long downLastUpdate = 0;

// 原始向上手势状态(火焰升腾)
int upPhase = 0;
int upFrame = 0;

// 原始向下手势状态(雨滴)
int drops[3][8];
int dropSpeeds[3][8];

int clockwiseRotation = 0;
unsigned long clockwiseLastUpdate = 0;

int anticlockwiseScan = 0;
unsigned long anticlockwiseLastUpdate = 0;

unsigned long waveStartTime = 0;

/**
 * 核心映射函数:将逻辑坐标 (face, x, y) 映射到物理LED索引
 * @param face 面编号 (1, 2, 3)
 * @param x X坐标 (0-7)
 * @param y Y坐标 (0-7)
 * @return 物理LED索引 (0-191),无效坐标返回-1
 */
int getIndex(int face, int x, int y) {
    if (x < 0 || x > 7 || y < 0 || y > 7) return -1;
    
    int pixelIndex = 0;

    // 面1 (LED 0-63)
    if (face == 1) {
        if (y % 2 == 0) {
            pixelIndex = y * 8 + (7 - x);
        } else {
            pixelIndex = y * 8 + x;
        }
        return pixelIndex;
    }
    
    // 面2 (LED 64-127)
    else if (face == 2) {
        int base = 64;
        int physRow = 7 - x;
        int physCol = 7 - y;
        
        if (physRow % 2 == 0) {
            pixelIndex = base + (physRow * 8) + physCol;
        } else {
            pixelIndex = base + (physRow * 8) + (7 - physCol);
        }
        return pixelIndex;
    }
    
    // 面3 (LED 128-191)
    else if (face == 3) {
        int base = 128;
        int physRow = x;
        int physCol = y;
        
        if (physRow % 2 == 0) {
            pixelIndex = base + (physRow * 8) + physCol;
        } else {
            pixelIndex = base + (physRow * 8) + (7 - physCol);
        }
        return pixelIndex;
    }
    
    return -1;
}

/**
 * 设置单个像素颜色
 * @param f 面编号
 * @param x X坐标
 * @param y Y坐标
 * @param r 红色值 (0-255)
 * @param g 绿色值 (0-255)
 * @param b 蓝色值 (0-255)
 */
void setPixel(int f, int x, int y, uint8_t r, uint8_t g, uint8_t b) {
    int idx = getIndex(f, x, y);
    if (idx >= 0 && idx < NUM_LEDS) {
        strip.setPixelColor(idx, r, g, b);
    }
}

/**
 * HSV转RGB颜色
 * @param h 色相 (0-255)
 * @param s 饱和度 (0-255)
 * @param v 亮度 (0-255)
 * @return RGB颜色值
 */
uint32_t HSVtoRGB(uint8_t h, uint8_t s, uint8_t v) {
    uint8_t r, g, b;
    uint8_t region, remainder, p, q, t;

    if (s == 0) {
        return strip.Color(v, v, v);
    }

    region = h / 43;
    remainder = (h - (region * 43)) * 6; 

    p = (v * (255 - s)) >> 8;
    q = (v * (255 - ((s * remainder) >> 8))) >> 8;
    t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;

    switch (region) {
        case 0: r = v; g = t; b = p; break;
        case 1: r = q; g = v; b = p; break;
        case 2: r = p; g = v; b = t; break;
        case 3: r = p; g = q; b = v; break;
        case 4: r = t; g = p; b = v; break;
        default: r = v; g = p; b = q; break;
    }
    return strip.Color(r, g, b);
}

// 清除所有LED
void clearAll() {
    strip.clear();
}

// 设置全局亮度
void setGlobalBrightness(uint8_t b) {
    currentBrightness = b;
    strip.setBrightness(b);
}

/**
 * 水平箭头形状判断(从左向右)
 * @param x 当前X坐标
 * @param y 当前Y坐标
 * @param arrowTipX 箭头尖端X坐标
 * @return 是否应该点亮该像素
 */
bool shouldLightArrowPixel(int x, int y, int arrowTipX) {
    int distX = arrowTipX - x;
    
    if (distX >= 0 && distX < 7) {
        // 箭头主干(中间两行)
        if (y == 3 || y == 4) {
            return true;
        }
        
        // 箭头尖端
        if (distX == 0) {
            return false;
        } else if (distX == 1) {
            return (y == 2 || y == 5);
        } else if (distX == 2) {
            return (y == 1 || y == 6);
        } else if (distX == 4) {
            return false;
        }
    }
    
    return false;
}

/**
 * 水平箭头形状判断(从右向左,镜像版本)
 * @param x 当前X坐标
 * @param y 当前Y坐标
 * @param arrowTipX 箭头尖端X坐标
 * @return 是否应该点亮该像素
 */
bool shouldLightArrowPixelMirror(int x, int y, int arrowTipX) {
    int distX = x - arrowTipX;
    
    if (distX >= 0 && distX < 7) {
        // 箭头主干(中间两行)
        if (y == 3 || y == 4) {
            return true;
        }
        
        // 箭头尖端
        if (distX == 0) {
            return false;
        } else if (distX == 1) {
            return (y == 2 || y == 5);
        } else if (distX == 2) {
            return (y == 1 || y == 6);
        } else if (distX == 4) {
            return false;
        }
    }
    
    return false;
}

/**
 * 垂直箭头形状判断
 * @param x 当前X坐标
 * @param y 当前Y坐标
 * @param arrowTipY 箭头尖端Y坐标
 * @param pointingUp true表示向上箭头,false表示向下箭头
 * @return 是否应该点亮该像素
 */
bool shouldLightVerticalArrowPixel(int x, int y, int arrowTipY, bool pointingUp) {
    int distY = pointingUp ? (arrowTipY - y) : (y - arrowTipY);
    
    if (distY >= 0 && distY < 7) {
        // 箭头主干(中间两列)
        if (x == 3 || x == 4) {
            return true;
        }
        
        // 箭头尖端
        if (distY == 0) {
            return false;
        } else if (distY == 1) {
            return (x == 2 || x == 5);
        } else if (distY == 2) {
            return (x == 1 || x == 6);
        } else if (distY == 4) {
            return false;
        }
    }
    
    return false;
}

// --- 手势初始化函数 ---

// 初始化向左手势
void initLeftGesture() {
    leftPos = arrowMode ? 18 : 18;
    leftLastUpdate = millis();
}

// 初始化向右手势
void initRightGesture() {
    rightPos = -4;
    rightLastUpdate = millis();
}

// 初始化向上手势
void initUpGesture() {
    if (arrowMode) {
        upPos = -4;  // 箭头模式从底部开始
    } else {
        upPhase = 0;
        upFrame = 0;
    }
    upLastUpdate = millis();
}

// 初始化向下手势
void initDownGesture() {
    if (arrowMode) {
        downPos = 24;  // 箭头模式从顶部开始
    } else {
        // 原始模式:初始化雨滴位置和速度
        for(int f=0; f<3; f++) {
            for(int x=0; x<8; x++) {
                drops[f][x] = random(0, 16);
                dropSpeeds[f][x] = random(1, 3);
            }
        }
    }
    downLastUpdate = millis();
}

// 初始化向前手势(切换到箭头模式)
void initForwardGesture() {
    arrowMode = true;
    Serial.println("[模式切换] 已切换到箭头模式(上下左右为箭头指示)");
    // 重新初始化当前手势效果
    switch(currentGesture) {
        case GESTURE_LEFT: initLeftGesture(); break;
        case GESTURE_RIGHT: initRightGesture(); break;
        case GESTURE_UP: initUpGesture(); break;
        case GESTURE_DOWN: initDownGesture(); break;
    }
}

// 初始化向后手势(切换回原始模式)
void initBackwardGesture() {
    arrowMode = false;
    Serial.println("[模式切换] 已切换回原始模式(上下为火焰/雨滴效果)");
    // 重新初始化当前手势效果
    switch(currentGesture) {
        case GESTURE_LEFT: initLeftGesture(); break;
        case GESTURE_RIGHT: initRightGesture(); break;
        case GESTURE_UP: initUpGesture(); break;
        case GESTURE_DOWN: initDownGesture(); break;
    }
}

// 初始化顺时针手势
void initClockwiseGesture() {
    clockwiseRotation = 0;
    clockwiseLastUpdate = millis();
}

// 初始化逆时针手势
void initAnticlockwiseGesture() {
    anticlockwiseScan = 0;
    anticlockwiseLastUpdate = millis();
}

// 初始化挥手手势(关闭所有灯光)
void initWaveGesture() {
    waveStartTime = millis();
    clearAll();
    strip.show();
}

// --- 向左手势效果 ---
void updateLeftEffect() {
    unsigned long currentTime = millis();
    if (currentTime - leftLastUpdate < 30) return;
    leftLastUpdate = currentTime;
    
    strip.clear();
    
    if (arrowMode) {
        // 箭头模式:青色箭头从右向左
        for(int f=2; f<=3; f++) {
            for(int x=0; x<8; x++) {
                int globalX = (f==3) ? x : (x+8);  // 面3: 0-7, 面2: 8-15
                int arrowTipX = leftPos;
                
                for(int y=0; y<8; y++) {
                    if (shouldLightArrowPixelMirror(globalX, y, arrowTipX)) {
                        int distX = globalX - arrowTipX;
                        int intensity = 255 - distX * 50;
                        if (intensity < 100) intensity = 100;
                        
                        setPixel(f, x, y, 0, intensity, intensity); // 青色
                    }
                }
            }
        }
        
        leftPos--;
        if (leftPos < -4) {
            leftPos = 18;
        }
    } else {
        // 原始模式:能量波从右向左
        for(int w = 0; w < 3; w++) {
            int currentX = leftPos + w;
            int f = 0, realX = 0;
            
            if(currentX >= 8 && currentX < 16) {
                f = 2; realX = currentX - 8;
            } else if (currentX >= 0 && currentX < 8) {
                f = 3; realX = currentX;
            }
            
            if (f != 0) {
                for(int y = 0; y < 8; y++) {
                    uint8_t bri = (w==1) ? 255 : 100;
                    setPixel(f, realX, y, 0, bri, bri);
                }
            }
        }
        
        leftPos--;
        if (leftPos < -2) {
            leftPos = 18;
        }
    }
    
    strip.show();
}

// --- 向右手势效果 ---
void updateRightEffect() {
    unsigned long currentTime = millis();
    if (currentTime - rightLastUpdate < 30) return;
    rightLastUpdate = currentTime;
    
    strip.clear();
    
    // 橙色箭头从左向右
    for(int f=2; f<=3; f++) {
        for(int x=0; x<8; x++) {
            int globalX = (f==3) ? x : (x+8);
            int arrowTipX = rightPos;
            
            for(int y=0; y<8; y++) {
                if (shouldLightArrowPixel(globalX, y, arrowTipX)) {
                    int distX = arrowTipX - globalX;
                    int intensity = 255 - distX * 50;
                    if (intensity < 100) intensity = 100;
                    
                    setPixel(f, x, y, 255, intensity, 0); // 橙色
                }
            }
        }
    }
    
    rightPos++;
    if (rightPos >= 20) {
        rightPos = -4;
    }
    
    strip.show();
}

// --- 向上手势效果 ---
void updateUpEffect() {
    unsigned long currentTime = millis();
    if (currentTime - upLastUpdate < 40) return;
    upLastUpdate = currentTime;
    
    strip.clear();
    
    if (arrowMode) {
        // 箭头模式:面1显示红色箭头 + 面2显示火焰效果
        
        // 面1:红色箭头从下往上
        for(int x=0; x<8; x++) {
            for(int y=0; y<8; y++) {
                int globalY = (7 - y) + 8;  // 将y=0-7映射为globalY=15-8(从下往上)
                
                if (shouldLightVerticalArrowPixel(x, globalY, upPos, true)) {
                    int distY = upPos - globalY;
                    int intensity = 255 - distY * 50;
                    if (intensity < 100) intensity = 100;
                    
                    setPixel(1, x, y, intensity, intensity/4, 0);
                }
            }
        }
        
        // 面2:火焰效果
        for(int x=0; x<8; x++) {
            drops[1][x]++;
            
            int head = drops[1][x];
            
            for(int k=0; k<3; k++) {
                int drawY = head - k;
                if(drawY >= 0 && drawY < 8) {
                    uint8_t bri = (k==0) ? 200 : ((k==1)?100:40);
                    setPixel(2, x, drawY, bri, bri/4, 0); // 红色火焰
                    setPixel(3, x, drawY, bri, bri/4, 0);
                }
            }
            
            if(head > 12) {
                drops[1][x] = random(-6, 0);
                dropSpeeds[1][x] = random(1, 3);
            }
        }
        
        upPos++;
        if (upPos >= 20) {
            upPos = -4;
        }
    } else {
        // 原始模式:火焰升腾效果
        for(int f_idx=0; f_idx<3; f_idx++) {
            int actualFace = f_idx + 1;
            for(int x=0; x<8; x++) {
                drops[f_idx][x]++;
                
                int head = drops[f_idx][x];
                
                for(int k=0; k<3; k++) {
                    int drawY = head - k;
                    if(drawY >= 0 && drawY < 8) {
                        uint8_t bri = (k==0) ? 200 : ((k==1)?100:40);
                        setPixel(actualFace, x, drawY, bri, bri/4, 0); // 红色火焰
                    }
                }
                
                if(head > 12) {
                    drops[f_idx][x] = random(-6, 0);
                    dropSpeeds[f_idx][x] = random(1, 3);
                }
            }
        }
    }
    
    strip.show();
}

// --- 向下手势效果 ---
void updateDownEffect() {
    unsigned long currentTime = millis();
    if (currentTime - downLastUpdate < 50) return;
    downLastUpdate = currentTime;
    
    strip.clear();
    
    if (arrowMode) {
        // 箭头模式:面1显示蓝色箭头 + 面2显示雨滴效果
        
        // 面1:蓝色箭头从上往下
        for(int x=0; x<8; x++) {
            for(int y=0; y<8; y++) {
                int globalY = 7 - y;  // 将y=0-7映射为globalY=7-0
                
                if (shouldLightVerticalArrowPixel(x, globalY, downPos, false)) {
                    int distY = globalY - downPos;
                    int intensity = 255 - distY * 50;
                    if (intensity < 100) intensity = 100;
                    
                    setPixel(1, x, y, 0, intensity, intensity/2);
                }
            }
        }
        
        // 面2:下落效果
        for(int x=0; x<8; x++) {
            drops[1][x]--;
            
            int head = drops[1][x];
            
            for(int k=0; k<3; k++) {
                int drawY = head + k;
                if(drawY >= 0 && drawY < 8) {
                    uint8_t bri = (k==0) ? 200 : ((k==1)?100:40);
                    setPixel(2, x, drawY, 0, bri, bri/2);
                    setPixel(3, x, drawY, 0, bri, bri/2);
                }
            }
            
            if(head < -5) {
                drops[1][x] = random(8, 14);
                dropSpeeds[1][x] = random(1, 3);
            }
        }
        
        downPos--;
        if (downPos < -4) {
            downPos = 24;
        }
    } else {
        // 原始模式:雨滴效果
        for(int f_idx=0; f_idx<3; f_idx++) {
            int actualFace = f_idx + 1;
            for(int x=0; x<8; x++) {
                drops[f_idx][x]--;
                
                int head = drops[f_idx][x];
                
                for(int k=0; k<3; k++) {
                    int drawY = head + k;
                    if(drawY >= 0 && drawY < 8) {
                        uint8_t bri = (k==0) ? 200 : ((k==1)?100:40);
                        setPixel(actualFace, x, drawY, 0, bri/2, bri);
                    }
                }
                
                if(head < -5) {
                    drops[f_idx][x] = random(8, 14);
                    dropSpeeds[f_idx][x] = random(1, 3);
                }
            }
        }
    }
    
    strip.show();
}

// 顺时针彩虹旋转效果
void updateClockwiseEffect() {
    unsigned long currentTime = millis();
    if (currentTime - clockwiseLastUpdate < 50) return;
    clockwiseLastUpdate = currentTime;
    
    strip.clear();
    
    for(int i=0; i<NUM_LEDS; i++) {
       if((i+clockwiseRotation)%5 == 0) {
           strip.setPixelColor(i, Wheel((i+clockwiseRotation*5)&255));
       }
    }
    
    clockwiseRotation++;
    if (clockwiseRotation >= 20) clockwiseRotation = 0;
    
    strip.show();
}

// 逆时针雷达扫描效果
void updateAnticlockwiseEffect() {
    unsigned long currentTime = millis();
    if (currentTime - anticlockwiseLastUpdate < 80) return;
    anticlockwiseLastUpdate = currentTime;
    
    strip.clear();
    
    for(int i=0; i<NUM_LEDS; i++) { 
       if((i+anticlockwiseScan)%8 == 0) {
           strip.setPixelColor(i, 0, 255, 0);
       }
    }
    
    anticlockwiseScan--;
    if (anticlockwiseScan < 0) anticlockwiseScan = 20;
    
    strip.show();
}

// 挥手关闭所有灯光
void updateWaveEffect() {
    clearAll();
    strip.show();
}

// 彩虹色轮函数
uint32_t Wheel(byte 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);
}

/**
 * 处理手势切换
 * @param newGesture 新手势状态
 */
void handleGestureChange(GestureState newGesture) {
    if (newGesture == currentGesture) return;
    
    previousGesture = currentGesture;
    currentGesture = newGesture;
    gestureStartTime = millis();
    lastGestureChangeTime = millis();  // 记录手势切换时间
    
    clearAll();
    strip.show();
    
    // 根据新手势初始化相应效果
    switch (currentGesture) {
        case GESTURE_LEFT:
            initLeftGesture();
            break;
        case GESTURE_RIGHT:
            initRightGesture();
            break;
        case GESTURE_UP:
            initUpGesture();
            break;
        case GESTURE_DOWN:
            initDownGesture();
            break;
        case GESTURE_FORWARD:
            initForwardGesture();
            break;
        case GESTURE_BACKWARD:
            initBackwardGesture();
            break;
        case GESTURE_CLOCKWISE:
            initClockwiseGesture();
            break;
        case GESTURE_ANTICLOCKWISE:
            initAnticlockwiseGesture();
            break;
        case GESTURE_WAVE:
            initWaveGesture();
            break;
        case GESTURE_NONE:
            setGlobalBrightness(DEFAULT_BRIGHTNESS);
            break;
    }
}

/**
 * 传感器手势转换为状态枚举
 * @param gesture 传感器识别的手势
 * @return 对应的手势状态
 */
GestureState sensorGestureToState(Gesture gesture) {
    switch (gesture) {
        case GES_LEFT:          return GESTURE_LEFT;
        case GES_RIGHT:         return GESTURE_RIGHT;
        case GES_UP:            return GESTURE_UP;
        case GES_DOWN:          return GESTURE_DOWN;
        case GES_FORWARD:       return GESTURE_FORWARD;
        case GES_BACKWARD:      return GESTURE_BACKWARD;
        case GES_CLOCKWISE:     return GESTURE_CLOCKWISE;
        case GES_ANTICLOCKWISE: return GESTURE_ANTICLOCKWISE;
        case GES_WAVE:          return GESTURE_WAVE;
        default:                return GESTURE_NONE;
    }
}

// 启动动画:彩虹呼吸效果
void bootAnimation() {
    for(int cycle = 0; cycle < 2; cycle++) {
        // 渐亮
        for(int b = 5; b <= 150; b += 3) {
            for(int i = 0; i < NUM_LEDS; i++) {
                uint32_t c = HSVtoRGB((i * 2 + cycle * 50) % 255, 255, b);
                strip.setPixelColor(i, c);
            }
            strip.show();
            delay(10);
        }
        delay(100);
        // 渐暗
        for(int b = 150; b >= 5; b -= 3) {
            for(int i = 0; i < NUM_LEDS; i++) {
                uint32_t c = HSVtoRGB((i * 2 + cycle * 50) % 255, 255, b);
                strip.setPixelColor(i, c);
            }
            strip.show();
            delay(10);
        }
    }
    strip.clear();
    strip.show();
}

// 错误指示:四个角闪烁红灯
void showErrorIndicator() {
    for(int blinkCount = 0; blinkCount < 10; blinkCount++) {
        strip.clear();
        
        // 面1四个角
        strip.setPixelColor(0, 255, 0, 0);
        strip.setPixelColor(7, 255, 0, 0);
        strip.setPixelColor(56, 255, 0, 0);
        strip.setPixelColor(63, 255, 0, 0);
        
        // 面2四个角
        strip.setPixelColor(64, 255, 0, 0);
        strip.setPixelColor(71, 255, 0, 0);
        strip.setPixelColor(120, 255, 0, 0);
        strip.setPixelColor(127, 255, 0, 0);

        // 面3四个角
        strip.setPixelColor(128, 255, 0, 0);
        strip.setPixelColor(135, 255, 0, 0);
        strip.setPixelColor(184, 255, 0, 0);
        strip.setPixelColor(191, 255, 0, 0);
        
        strip.show();
        delay(500);
        
        strip.clear();
        strip.show();
        delay(500);
    }
    
    // 保持一个角点亮表示错误状态
    strip.setPixelColor(0, 255, 0, 0);
    strip.setPixelColor(7, 255, 0, 0);
    strip.setPixelColor(56, 255, 0, 0);
    strip.setPixelColor(63, 255, 0, 0);
    strip.show();
}

// 初始化设置
void setup() {
    Serial.begin(115200); // 串口初始化
    // 初始化LED灯带
    strip.begin();
    strip.setBrightness(DEFAULT_BRIGHTNESS);
    strip.clear();
    strip.show();

    // 初始化随机数种子
    randomSeed(analogRead(0));

    // 初始化手势传感器
    if (!gestureSensor.begin()) {
        Serial.println("错误:手势传感器初始化失败!"); 
        showErrorIndicator();  // 传感器初始化失败,显示错误
        while(true);  // 停止运行
    } else {
        Serial.println("系统初始化成功!");
        bootAnimation();  // 传感器初始化成功,播放启动动画
        Serial.println("手势传感器和LED灯带已就绪。");
    }
}

// 主循环
void loop() {
    // 读取手势传感器
    Gesture sensorGesture = gestureSensor.readGesture();
    GestureState newGestureState = sensorGestureToState(sensorGesture);
    
    // 手势防抖:在防抖时间内不响应新手势
    unsigned long currentTime = millis();
    bool canChangeGesture = (currentTime - lastGestureChangeTime) >= GESTURE_DEBOUNCE_TIME;
    
    // 如果检测到新手势且不同于当前手势,并且已过防抖时间
    if (newGestureState != GESTURE_NONE && 
        newGestureState != currentGesture && 
        canChangeGesture) {
        handleGestureChange(newGestureState);
    }
    
    // 更新当前手势的动画效果
    switch (currentGesture) {
        case GESTURE_LEFT:
            updateLeftEffect();
            break;
        case GESTURE_RIGHT:
            updateRightEffect();
            break;
        case GESTURE_UP:
            updateUpEffect();
            break;
        case GESTURE_DOWN:
            updateDownEffect();
            break;
        case GESTURE_CLOCKWISE:
            updateClockwiseEffect();
            break;
        case GESTURE_ANTICLOCKWISE:
            updateAnticlockwiseEffect();
            break;
        case GESTURE_WAVE:
            updateWaveEffect();
            break;
        case GESTURE_NONE:
            break;
    }
    
    delay(10);
}

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

系统流程图

四、操作流程

4.1 系统操作

上电后,3 块矩阵先播放彩虹呼吸启动动画,随后进入待机状态,串口输出初始化日志;动画结束后,LED全部熄灭,等待手势输入

4.2 手势操作演示

向 PAJ7620U2 传感器做出对应手势(如向上挥手),矩阵立即显示对应动画;

向前手势切换为 "箭头模式",上下手势显示箭头;向后手势切换回 "原始模式",同时串口打印切换后的信息

挥手手势关闭所有灯光,再次做出其他手势恢复显示。

4.3 视频演示

零知标准板手势控制RGB立方体全功能演示

视频展示了基于零知标准板的智能手势控制RGB立方体系统的完整功能。系统启动与初始化过程、9种手势的识别与响应(向左、向右、向上、向下、向前、向后、顺时针、逆时针、挥手)、两种显示模式的切换

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

PAJ7620U2是一款高度集成的光学手势识别传感器,其核心工作原理基于红外成像与模式识别

工作原理

传感器内部集成了一个红外发射LED(IR LED)和一个光电二极管阵列。

内置940nm红外LED,可在完全黑暗环境中工作;内置光学带通滤波器,有效滤除环境光干扰;手势识别算法直接在传感器硬件中实现,减轻主控负担;支持正常模式(60-600°/s)和快速模式(60-1200°/s)

技术参数

参数 数值 说明
工作电压 2.8V-3.6V 使用3.3V供电
I2C频率 100kHz/400kHz 支持标准/快速模式
检测距离 5-15cm 最佳识别距离
视角范围 60° 有效识别角度

5.1 I2C通信原理

PAJ7620U2 采用 I2C 总线与主控通信,I2C 地址默认为 0x73,可通过硬件引脚修改为 0x72、通信速率支持 100Kbps(标准模式)/400Kbps(快速模式)

主控板通过 I2C 总线周期性读取 GESTURE_VALID_FLAG 寄存器,当检测到标志位为 1 时,读取 GESTURE_DATA 寄存器的手势编码,转换为对应的手势指令

5.2 寄存器系统架构

PAJ7620U2采用Bank寄存器架构,分为Bank 0和Bank 1两个区域:

访问寄存器 0xEF 并写入 0x00 进入 Bank0(常用手势数据都在这里);访问寄存器 0xEF 并写入 0x01 进入 Bank1(用于高级配置)。

本项目使用到的核心寄存器:手势使能寄存器 和手势结果寄存器

函数底层已经帮我们封装好了对 0x43 寄存器的读取,并将其映射为 GES_UPGES_DOWN 等枚举值

核心寄存器

寄存器地址 寄存器名称 功能说明
0x00 MODE_SELECT 模式选择寄存器,用于配置传感器工作模式,本项目设置为手势识别模式(0x01)
0x01-0x03 INIT_CONFIG 初始化配置寄存器,用于设置传感器的采样率、增益等参数,确保手势识别精度
0x43-0x44 GESTURE_DATA 手势数据寄存器,存储识别到的手势类型编码,主机通过读取该寄存器获取手势信息
0x45 GESTURE_VALID_FLAG 手势有效标志寄存器,当检测到有效手势时,该寄存器置 1,主机可通过该标志判断是否有手势输入
0x60-0x6F GESTURE_CONFIG 手势配置寄存器,用于使能 / 禁用特定手势类型(如上下、左右、旋转等),本项目使能全部 9 种手势

六、常见问题解答(FAQ)

Q1:WS2812 矩阵灯光乱闪,动画显示错位

A:请排查: 坐标映射函数getIndex()的面 2 / 面 3 转换逻辑错误、DIN/DOUT矩阵级联顺序错误、WS2812 数据引脚接触不良、供电不足导致信号不稳定

Q2:动画卡顿,帧率低

*A:每帧更新间隔过短,亮度设置过高,数据传输量过大,CPU 处理不过来:*增大动画更新间隔,降低DEFAULT_BRIGHTNESS,简化动画绘制逻辑,减少嵌套循环

项目资源整合

PAJ7620U2 数据手册: PAJ7620U2 Datasheet

PAJ7620U2 库文件: RevEng_PAJ7620

相关推荐
go_bai10 小时前
生产消费模型-简洁线程池
linux·c++·笔记
mingren_131410 小时前
c++和qml交互
c++·qt·交互
cn_mengbei10 小时前
鸿蒙PC上Qt原生应用开发:从零搭建开发环境到部署实战,附HarmonyOS SDK配置与避坑指南(C++实现)
c++·qt·harmonyos
脏脏a10 小时前
手撕 vector:从 0 到 1 模拟实现 STL 容器
开发语言·c++·vector
郝学胜-神的一滴10 小时前
Linux 读写锁深度解析:原理、应用与性能优化
linux·服务器·c++·程序人生·性能优化
闻缺陷则喜何志丹10 小时前
【图论 DFS 换根法】3772. 子图的最大得分|2235
c++·算法·深度优先·力扣·图论·换根法
boneStudent10 小时前
STM32 CAN总线数据采集与转发系统完整代码
stm32·单片机·嵌入式硬件
开开心心就好10 小时前
音频格式互转工具,支持Mp3ApeWavFlac互转
java·网络·c++·windows·qt·电脑·excel
你要飞10 小时前
Part 2 矩阵
笔记·线性代数·考研·矩阵