简介这份资源是 PAJ7620 手势识别传感器的开源库文件面向使用 Arduino 或嵌入式平台开发手势交互的创客、学生与硬件工程师。PAJ7620U2 将手势识别与通用 I2C 接口集成于单芯片可识别 9 种基本手势通过 I2C 总线即可读取手势信息常用于控制执行单元、计算机、手机、智能车或机器人等设备实现轻触即控的交互方式。压缩包共 8 个文件约 12KB包含 2 个 ino 示例程序、cpp 与 h 库源码、keywords.txt 关键字高亮文件、library.properties 库描述文件、LICENSE 授权协议及 README.md 说明文档覆盖从库安装到示例运行的基本要素。目前已有 724 人学习下载。借助其中的 9 种手势与 15 种手势示例读者可快速完成传感器接线、库导入与手势数据读取验证并在此基础上将手势识别集成到自己的 Arduino 项目中缩短底层驱动调试时间。1. 手势识别模块 PAJ7620 到底能做成什么从一颗传感器到一套可复现的手势交互如果你手上有一块 PAJ7620 手势识别传感器想用最低成本把它跑起来做出上下左右、接近、远离、顺时针、逆时针这类手势动作的识别那Gesture_PAJ7620-master.zip这个方向就是为你准备的。它解决的不是“手势识别算法怎么从零训练”的问题而是“我已经有一颗现成的红外手势传感器怎么在嵌入式主控上稳定读出九种手势并把它接到自己的交互逻辑里”。适合做智能灯控、非接触式翻页器、车载中控辅助、教学演示装置的开发者也适合刚接触 I2C 传感器、想找一个完整闭环练手的新手。核心难点不在驱动本身而在初始化时序、寄存器配置和中断读取的配合这几处不处理好读出来的数据会飘得让你怀疑人生。2. PAJ7620 的 I2C 通信与寄存器模型先搞懂它在跟谁说话PAJ7620 是一颗集成红外 LED、光学镜头和手势识别算法的传感器模组对外只暴露 I2C 接口。它内部有一块寄存器空间分 bank 0 和 bank 1bank 切换靠写0xEF寄存器实现。手势结果放在 bank 0 的0x43到0x45这几个地址里分别对应不同手势组。很多人第一次读不出数据不是接线错了而是没做 bank 切换或者初始化数组没写全。2.1 为什么必须先写初始化寄存器数组PAJ7620 上电后不会自动进入手势识别模式它需要一段固定的寄存器配置序列把内部增益、LED 驱动电流、手势检测阈值都设好。这段序列在常见做法里是一张几十项的{寄存器地址, 值}表通常放在驱动文件里作为常量数组。跳过这一步直接读手势寄存器返回值基本是 0 或者随机跳变。我一般会把初始化数组分成两段第一段是通用配置第二段是手势模式专属配置。写的时候按顺序逐条写每条之间不需要额外延时但整段写完后要留至少 10ms 让传感器稳定。下面是一个典型的初始化片段结构// PAJ7620 初始化寄存器配置表节选bank 切换已包含在序列中 typedef struct { uint8_t reg; uint8_t val; } paj7620_init_reg_t; static const paj7620_init_reg_t init_sequence[] { {0xEF, 0x00}, // 切换到 bank 0 {0x37, 0x07}, // 设置手势检测模式相关位 {0x38, 0x17}, {0x39, 0x06}, {0x41, 0x00}, {0x42, 0x00}, {0x46, 0x2D}, {0x47, 0x0F}, {0x48, 0x3C}, {0x49, 0x00}, {0x4A, 0x1E}, {0x4C, 0x20}, {0x51, 0x10}, {0x5E, 0x10}, {0x60, 0x20}, {0x80, 0x20}, {0x81, 0x40}, {0x82, 0x04}, {0x8B, 0x01}, {0x90, 0x00}, {0x95, 0x00}, {0x96, 0x00}, {0x97, 0x00}, {0x9A, 0x00}, {0x9C, 0x00}, {0xA5, 0x00}, {0xEF, 0x01}, // 切换到 bank 1 {0x00, 0x00}, {0x01, 0x1E}, {0x02, 0x0F}, {0x03, 0x10}, {0x04, 0x02}, {0x05, 0x00}, {0x06, 0x00}, {0x07, 0x00}, {0x08, 0x00}, {0xEF, 0x00}, // 切回 bank 0 {0x41, 0x00}, {0x42, 0x00}, {0x43, 0x00}, {0x44, 0x00}, {0x45, 0x00}, };这段代码的逻辑是先切 bank再按顺序写配置最后把手势结果寄存器清零。参数说明上0xEF是 bank 选择寄存器写 0 进 bank 0写 1 进 bank 1。0x41到0x45是手势中断和结果相关寄存器初始化末尾清零是为了防止上电残留数据被误读。注意不同批次的模组初始化序列可能有细微差异如果读不到手势优先检查这张表是否完整。2.2 手势结果寄存器的读取与解析初始化完成后手势结果会出现在 bank 0 的0x43、0x44、0x45三个寄存器里。常见做法是读0x43和0x44两个字节拼成一个 16 位值再按位判断具体手势。0x43的低几位对应上下左右0x44对应接近、远离、顺时针、逆时针。读取时建议先读0x43再读0x44中间不要插入其他 I2C 操作避免数据不同步。// 读取手势结果并解析为枚举值 typedef enum { GESTURE_NONE 0, GESTURE_UP 1, GESTURE_DOWN 2, GESTURE_LEFT 3, GESTURE_RIGHT 4, GESTURE_NEAR 5, GESTURE_FAR 6, GESTURE_CLOCKWISE 7, GESTURE_COUNTER 8, } gesture_t; gesture_t paj7620_read_gesture(void) { uint8_t data[2] {0}; i2c_read_reg(PAJ7620_ADDR, 0x43, data[0], 1); i2c_read_reg(PAJ7620_ADDR, 0x44, data[1], 1); uint16_t val ((uint16_t)data[1] 8) | data[0]; if (val 0x0001) return GESTURE_UP; if (val 0x0002) return GESTURE_DOWN; if (val 0x0004) return GESTURE_LEFT; if (val 0x0008) return GESTURE_RIGHT; if (val 0x0100) return GESTURE_NEAR; if (val 0x0200) return GESTURE_FAR; if (val 0x0400) return GESTURE_CLOCKWISE; if (val 0x0800) return GESTURE_COUNTER; return GESTURE_NONE; }逻辑说明先分别读两个寄存器再拼成 16 位。位判断的顺序会影响多手势同时触发时的优先级我一般把上下左右放在前面因为这几个手势误触发率相对低。参数上PAJ7620_ADDR通常是 0x737 位地址具体以模组手册为准。如果读出来一直是 0先确认 I2C 地址对不对再确认初始化是否成功。3. 在主流嵌入式平台上跑通最小可复现工程拿到Gesture_PAJ7620-master.zip这类工程后不要急着改业务逻辑先把最小读取跑通。不同平台的差异主要在 I2C 底层接口和延时函数上层寄存器操作是一样的。下面按常见做法给出两个平台的接入方式选你手上有的那个就行。3.1 基于 Arduino 框架的最小读取示例Arduino 平台适合快速验证。接线只有四根VCC、GND、SDA、SCL。注意 PAJ7620 的工作电压常见是 3.3V如果主控是 5VI2C 电平要确认是否兼容否则容易读不到数据。#include Wire.h #define PAJ7620_ADDR 0x73 // 初始化序列省略实际使用时把完整数组填入 void paj7620_init() { for (size_t i 0; i sizeof(init_sequence)/sizeof(init_sequence[0]); i) { Wire.beginTransmission(PAJ7620_ADDR); Wire.write(init_sequence[i].reg); Wire.write(init_sequence[i].val); Wire.endTransmission(); } delay(10); } uint16_t paj7620_read() { uint8_t lo 0, hi 0; Wire.beginTransmission(PAJ7620_ADDR); Wire.write(0x43); Wire.endTransmission(false); Wire.requestFrom(PAJ7620_ADDR, 1); if (Wire.available()) lo Wire.read(); Wire.beginTransmission(PAJ7620_ADDR); Wire.write(0x44); Wire.endTransmission(false); Wire.requestFrom(PAJ7620_ADDR, 1); if (Wire.available()) hi Wire.read(); return ((uint16_t)hi 8) | lo; } void setup() { Serial.begin(115200); Wire.begin(); paj7620_init(); } void loop() { uint16_t g paj7620_read(); if (g ! 0) { Serial.println(g, HEX); delay(200); // 防止同一次手势被连续上报 } }逻辑说明paj7620_init逐条写初始化表paj7620_read分两次读寄存器。参数上delay(200)是去抖用的PAJ7620 一次手势会持续输出一段时间不加延时你会看到同一个手势刷屏。如果串口一直输出 0先量 SDA/SCL 有没有上拉电阻很多裸模组不带需要外接 4.7k 上拉。3.2 基于 STM32 HAL 库的接入要点STM32 平台上I2C 读写要用 HAL 库的HAL_I2C_Mem_Read和HAL_I2C_Mem_Write。和 Arduino 的区别在于地址要左移一位HAL 库用的是 8 位地址形式。#define PAJ7620_ADDR_8BIT (0x73 1) void paj7620_write_reg(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(hi2c1, PAJ7620_ADDR_8BIT, reg, I2C_MEMADD_SIZE_8BIT, val, 1, 100); } uint8_t paj7620_read_reg(uint8_t reg) { uint8_t val 0; HAL_I2C_Mem_Read(hi2c1, PAJ7620_ADDR_8BIT, reg, I2C_MEMADD_SIZE_8BIT, val, 1, 100); return val; }逻辑说明PAJ7620_ADDR_8BIT是 7 位地址左移一位的结果HAL 库要求这种形式。I2C_MEMADD_SIZE_8BIT表示寄存器地址是 8 位。超时参数 100ms 在低速 I2C 下够用如果总线被拉死这个值会直接返回错误方便你定位。注意 STM32 的 I2C 在某些系列上有已知的时序问题如果读数据不稳定可以先把 I2C 速率降到 100kHz 以下试试。4. 避坑与排查手势读不出来、乱跳、重复触发怎么查这一章是我自己踩过的坑按现象、原因、解决三段写。你遇到问题的时候可以按顺序对照大概率能省下不少时间。4.1 现象上电后读到的全是 0 或者 0xFF原因通常是三类I2C 地址不对、初始化序列没写完整、bank 没切对。PAJ7620 的 7 位地址常见是 0x73但有些模组出厂时改了地址需要用 I2C 扫描确认。初始化序列如果漏了 bank 切换那两条后面写的配置全落在错误的 bank 里等于没配。解决方法是先用扫描程序确认地址再逐条核对初始化表重点看0xEF那两条有没有写。4.2 现象手势能读出来但方向经常反原因多半是传感器安装方向和你代码里的判断逻辑不一致。PAJ7620 的手势方向是相对传感器自身坐标系的如果你把模组旋转了 90 度装到面板上读出来的“上”在用户看来可能是“左”。解决办法是在解析函数里加一层方向映射或者在结构装配时就把模组方向固定好。我一般会在代码里留一个#define SENSOR_ROTATION宏方便后期调整。4.3 现象同一个手势连续触发很多次原因是手势结果寄存器在一次手势期间会保持置位你的轮询频率又高于手势持续时间。解决方法是加去抖读到手势后记录时间戳在 200ms 到 500ms 内忽略重复值。另一种做法是读完后手动清一下0x43和0x44但有些固件版本不支持手动清还是软件去抖更稳。4.4 现象手势识别距离很短手要贴得很近原因是红外 LED 驱动电流设小了或者环境光太强。PAJ7620 的检测距离和 LED 电流、环境红外噪声都有关。可以先检查初始化表里 LED 驱动相关的寄存器值适当调大。如果是在阳光直射或者白炽灯下红外干扰会明显缩短检测距离这种场景下建议加遮光罩或者换用带调制功能的模组。4.5 现象I2C 通信偶尔卡死程序跑飞原因是 I2C 总线被从机拉低主控没有超时机制。PAJ7620 在供电不稳或者热插拔时可能出现这种状态。解决办法是在 I2C 读写函数里加超时返回不要用死等。另外SDA/SCL 的上拉电阻不能省很多开发板自带上拉但外接模组时线一长就容易出问题。我一般会在总线卡死后加一个 GPIO 模拟时钟脉冲的恢复流程把从机状态踢出来。5. 把 PAJ7620 接进真实交互逻辑从读到手势到做出反应手势读出来只是第一步真正决定体验的是你怎么用它。这一章讲几个进阶用法和验证方法都是我在实际装置里用过的。5.1 手势组合与状态机设计单个手势能做的事有限把上下左右和接近远离组合起来可以做出层级操作。比如“接近”唤醒设备“左右”切换选项“上下”调参数“远离”休眠。实现上用一个简单状态机把手势事件映射成状态迁移。下面是一个状态机的骨架typedef enum { STATE_IDLE, STATE_ACTIVE, STATE_ADJUST, } ui_state_t; ui_state_t state STATE_IDLE; void handle_gesture(gesture_t g) { switch (state) { case STATE_IDLE: if (g GESTURE_NEAR) state STATE_ACTIVE; break; case STATE_ACTIVE: if (g GESTURE_LEFT) ui_prev(); if (g GESTURE_RIGHT) ui_next(); if (g GESTURE_UP) state STATE_ADJUST; if (g GESTURE_FAR) state STATE_IDLE; break; case STATE_ADJUST: if (g GESTURE_UP) ui_inc(); if (g GESTURE_DOWN) ui_dec(); if (g GESTURE_FAR) state STATE_ACTIVE; break; } }逻辑说明状态机把连续手势变成有上下文的操作避免误触发直接改参数。参数上状态迁移条件可以根据你的交互习惯调整关键是每个状态只响应它关心的手势其他手势忽略。这样即使用户手挥得快也不会把界面搞乱。5.2 用串口日志验证手势稳定性调手势的时候最有效的验证手段是打日志。把每次读到的手势值、时间戳、当前状态一起打到串口连续观察几分钟你就能看出哪些手势误触发率高、哪些方向容易混。我一般会加一个统计计数每 100 次手势打印一次各手势的触发次数这样调阈值的时候有数据可依不是靠感觉。5.3 灵敏度与响应速度的取舍PAJ7620 的寄存器里可以调检测阈值和响应速度但这两个是矛盾的阈值调低灵敏度高误触发多阈值调高稳定但手要挥得更用力。我的习惯是先按默认值跑记录误触发和漏触发的情况再针对性微调。如果误触发集中在某个方向优先调那个方向对应的阈值位不要全局改。响应速度上轮询周期建议在 20ms 到 50ms 之间太快了浪费 CPU太慢了丢手势。5.4 一个具体技巧用“接近”做唤醒用“远离”做休眠这是我用得最多的组合。设备平时休眠手靠近时 PAJ7620 输出接近手势主控被中断唤醒进入工作状态手离开后一段时间没有手势自动回到休眠。实现上接近和远离手势的误触发率相对低适合做状态切换。注意休眠前要把手势结果寄存器清一次防止残留值在唤醒瞬间被误读。这个技巧在电池供电的装置上尤其有用能明显降低待机功耗。我自己调 PAJ7620 最大的教训是不要相信第一次读出来的数据一定要用日志跑够时间再下结论。初始化序列、上拉电阻、去抖时间这三样任何一个没弄对后面所有逻辑都是白搭。希望帮到你。本文还有配套的精品资源点击获取