手势感应传感器芯片PAJ7620驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
温湿度的软件边界标过了。今天换手势芯片。常常没有内核节点,芯片在位不等于会认挥手,还要写初始化表。
现场就卡在这一步。i2cdetect -y -r 1 在 0x73 上有 ACK。写寄存器 0xEF = 0x00 选 bank0,读 0x00 得到 0x20,芯片在。然后把手伸到窗口前面左挥、右挥、上推、下压,读 0x43,回来永远是 0。
不是总线挂了。不是地址写错。是这颗芯片出厂后内部 DSP 处于「没喂过初始化表」的状态。手势引擎不跑,0x43 就不会置位。表有 219 条,按顺序写,中间还要切两次 bank。少写、写乱、写完停在 bank1,现象都是「在位,但不认手势」。
内核没有 PAJ7620 的 IIO 节点,也没有 input 设备。DTS 里通常不声明这颗从设备。整条路是用户态:/dev/i2c-1 + I2C_RDWR。和 MAX30100 那种「驱动绑上了却没数」不一样——这里连驱动都没有,不写表就什么都没有。
距离大约 15~20 cm,动作慢一点。贴着窗口甩,芯片看的是一团糊。


1. 总线上它长什么样
地址 0x73,挂在 i2c1,100 kHz。寄存器空间切成两个 bank,靠 0xEF 选择:
| 写 0xEF | 进入 |
|---|---|
| 0x00 | bank0。part id 在 `0x00`,手势位图在 `0x43`,挥手在 `0x44` |
| 0x01 | bank1。初始化表后半段在这里 |
part id 低字节,bank0 的 0x00,必须是 0x20。读到别的值,要么没切 bank,要么根本不是这颗料。读失败(ioctl 返 -1,或数据 0xFF)就是 0x73 没有 ACK:模块没插、没上电、总线被 CAN 镜像关掉。
手势结果在 bank0 的 0x43,一个字节八个位:
| bit | 手势 |
|---|---|
| 0 | 右 |
| 1 | 左 |
| 2 | 上 |
| 3 | 下 |
| 4 | 前(靠近) |
| 5 | 后(远离) |
| 6 | 顺时针 |
| 7 | 逆时针 |
0x44 的 bit0 是挥手。一次动作可能置多位,读完芯片会清,所以要轮询,间隔 50~100 ms 够用,400 ms 也行,再慢容易漏。
读写都用组合事务:写一个寄存器地址,Repeated Start 再读。和 HDC1080 的裸读不是一路。初始化是纯写:寄存器号 + 一个数据字节,219 次。
应用
│ open("/dev/i2c-1") + ioctl I2C_RDWR
▼
i2c1 @ 100 kHz
│
▼
PAJ7620 @ 0x73
├─ 0xEF 选 bank
├─ 0x00 part id(bank0,期望 0x20)
├─ 0x43 手势位图(bank0)
└─ 0x44 挥手(bank0)
没有 /sys/bus/iio/devices/.../paj*。ls /sys/bus/i2c/devices/ 也常常看不到 1-0073,因为设备树没声明它。i2cdetect 能扫到,用户态就能打。不要去内核里找一份不存在的 paj7620.c。
2. 设备树:只需要 i2c1 活着
板级:
device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts
&i2c1 {
status = "okay";
clock-frequency = <100000>;
/* PAJ7620 @0x73 不声明节点。用户态直接打。 */
};
不要画蛇添足写一个 paj7620@73 却配不上任何内核 compatible——probe 失败会在 dmesg 里吵,对用户态没有帮助。若已经写了节点、驱动又找不到,i2cdetect 上显示 UU 或地址空洞,先把节点删掉或 disabled。
/dev/i2c-1 要 chmod 0666。HAP 不是 root,写不了 export,也打不开 0600 的 i2c 节点。放权脚本和 MAX30100、HDC1080 共用这一条。
CAN 和 i2c1 共脚(GPIO0_B3 / B4)。开 CAN 的镜像上 0x73 会从世界上消失,不是手势芯片坏了。
3. 当时那次「在位却没手势」
第一步,确认地址:
hdc shell
chmod 0666 /dev/i2c-1
i2cdetect -y -r 1
73 在。-- 就先查模块、3.3 V、SDA/SCL,别往下写表。
第二步,手写两条 ioctl 等价操作——下面 C 里的 probe 子命令:
写 0xEF = 0x00
读 0x00 → 0x20
得到 0x20,当时觉得完事了。ArkTS 里每 400 ms 读一次 0x43,挥手十分钟,日志全是 0x43=0x00。
第三步,对照手册才看到「Gesture 模式需要把初始化数组写进 bank0/bank1」。数组在参考代码里叫 initRegisterArray,219 项。不写,DSP 不进手势状态机。
第四步,把表原样写进去,最后再把 0xEF 写回 0x00。同一套轮询,左挥立刻出现 0x43=0x02。
漏掉最后那次切回 bank0,读 0x43 读的是 bank1 的无关寄存器,值乱七八糟或者恒定,不是手势。表的中段有 {0xEF,0x01},末尾附近还有一次切 bank。写完必须显式回 bank0。
4. 完整 C:探测、写 219 条、轮询手势
存成 paj7620_tool.c。用户态 32 位 musl,自带 i2c_msg,不依赖 NDK 里有没有 linux/i2c-dev.h。
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#ifndef I2C_RDWR
#define I2C_RDWR 0x0707
#endif
#ifndef I2C_M_RD
#define I2C_M_RD 0x0001
#endif
struct i2c_msg {
uint16_t addr;
uint16_t flags;
uint16_t len;
uint8_t *buf;
};
struct i2c_rdwr_ioctl_data {
struct i2c_msg *msgs;
uint32_t nmsgs;
};
#define ADDR 0x73
#define REG_BANK 0xEF
#define REG_PART 0x00
#define REG_GES 0x43
#define REG_WAVE 0x44
static int i2cWr(int fd, uint8_t addr, uint8_t reg, const uint8_t *data, int len)
{
uint8_t b[24];
int i;
b[0] = reg;
for (i = 0; i < len && i < 23; i++)
b[1 + i] = data[i];
struct i2c_msg m = { addr, 0, (uint16_t)(len + 1), b };
struct i2c_rdwr_ioctl_data d = { &m, 1 };
return ioctl(fd, I2C_RDWR, &d);
}
static int i2cRdReg(int fd, uint8_t addr, uint8_t reg, uint8_t *data, int len)
{
struct i2c_msg m[2] = {
{ addr, 0, 1, ® },
{ addr, I2C_M_RD, (uint16_t)len, data }
};
struct i2c_rdwr_ioctl_data d = { m, 2 };
return ioctl(fd, I2C_RDWR, &d);
}
static int wr1(int fd, uint8_t reg, uint8_t v)
{
return i2cWr(fd, ADDR, reg, &v, 1);
}
static int rd1(int fd, uint8_t reg, uint8_t *v)
{
return i2cRdReg(fd, ADDR, reg, v, 1);
}
/* 219 条。顺序就是状态机,不要排序、不要去重。 */
static const unsigned char PAJ_INIT[][2] = {
{0xEF,0x00}, {0x32,0x29}, {0x33,0x01}, {0x34,0x00}, {0x35,0x01}, {0x36,0x00},
{0x37,0x07}, {0x38,0x17}, {0x39,0x06}, {0x3A,0x12}, {0x3F,0x00}, {0x40,0x02},
{0x41,0xFF}, {0x42,0x01}, {0x46,0x2D}, {0x47,0x0F}, {0x48,0x3C}, {0x49,0x00},
{0x4A,0x1E}, {0x4B,0x00}, {0x4C,0x20}, {0x4D,0x00}, {0x4E,0x1A}, {0x4F,0x14},
{0x50,0x00}, {0x51,0x10}, {0x52,0x00}, {0x5C,0x02}, {0x5D,0x00}, {0x5E,0x10},
{0x5F,0x3F}, {0x60,0x27}, {0x61,0x28}, {0x62,0x00}, {0x63,0x03}, {0x64,0xF7},
{0x65,0x03}, {0x66,0xD9}, {0x67,0x03}, {0x68,0x01}, {0x69,0xC8}, {0x6A,0x40},
{0x6D,0x04}, {0x6E,0x00}, {0x6F,0x00}, {0x70,0x80}, {0x71,0x00}, {0x72,0x00},
{0x73,0x00}, {0x74,0xF0}, {0x75,0x00}, {0x80,0x42}, {0x81,0x44}, {0x82,0x04},
{0x83,0x20}, {0x84,0x20}, {0x85,0x00}, {0x86,0x10}, {0x87,0x00}, {0x88,0x05},
{0x89,0x18}, {0x8A,0x10}, {0x8B,0x01}, {0x8C,0x37}, {0x8D,0x00}, {0x8E,0xF0},
{0x8F,0x81}, {0x90,0x06}, {0x91,0x06}, {0x92,0x1E}, {0x93,0x0D}, {0x94,0x0A},
{0x95,0x0A}, {0x96,0x0C}, {0x97,0x05}, {0x98,0x0A}, {0x99,0x41}, {0x9A,0x14},
{0x9B,0x0A}, {0x9C,0x3F}, {0x9D,0x33}, {0x9E,0xAE}, {0x9F,0xF9}, {0xA0,0x48},
{0xA1,0x13}, {0xA2,0x10}, {0xA3,0x08}, {0xA4,0x30}, {0xA5,0x19}, {0xA6,0x10},
{0xA7,0x08}, {0xA8,0x24}, {0xA9,0x04}, {0xAA,0x1E}, {0xAB,0x1E}, {0xCC,0x19},
{0xCD,0x0B}, {0xCE,0x13}, {0xCF,0x64}, {0xD0,0x21}, {0xD1,0x0F}, {0xD2,0x88},
{0xE0,0x01}, {0xE1,0x04}, {0xE2,0x41}, {0xE3,0xD6}, {0xE4,0x00}, {0xE5,0x0C},
{0xE6,0x0A}, {0xE7,0x00}, {0xE8,0x00}, {0xE9,0x00}, {0xEE,0x07}, {0xEF,0x01},
{0x00,0x1E}, {0x01,0x1E}, {0x02,0x0F}, {0x03,0x10}, {0x04,0x02}, {0x05,0x00},
{0x06,0xB0}, {0x07,0x04}, {0x08,0x0D}, {0x09,0x0E}, {0x0A,0x9C}, {0x0B,0x04},
{0x0C,0x05}, {0x0D,0x0F}, {0x0E,0x02}, {0x0F,0x12}, {0x10,0x02}, {0x11,0x02},
{0x12,0x00}, {0x13,0x01}, {0x14,0x05}, {0x15,0x07}, {0x16,0x05}, {0x17,0x07},
{0x18,0x01}, {0x19,0x04}, {0x1A,0x05}, {0x1B,0x0C}, {0x1C,0x2A}, {0x1D,0x01},
{0x1E,0x00}, {0x21,0x00}, {0x22,0x00}, {0x23,0x00}, {0x25,0x01}, {0x26,0x00},
{0x27,0x39}, {0x28,0x7F}, {0x29,0x08}, {0x30,0x03}, {0x31,0x00}, {0x32,0x1A},
{0x33,0x1A}, {0x34,0x07}, {0x35,0x07}, {0x36,0x01}, {0x37,0xFF}, {0x38,0x36},
{0x39,0x07}, {0x3A,0x00}, {0x3E,0xFF}, {0x3F,0x00}, {0x40,0x77}, {0x41,0x40},
{0x42,0x00}, {0x43,0x30}, {0x44,0xA0}, {0x45,0x5C}, {0x46,0x00}, {0x47,0x00},
{0x48,0x58}, {0x4A,0x1E}, {0x4B,0x1E}, {0x4C,0x00}, {0x4D,0x00}, {0x4E,0xA0},
{0x4F,0x80}, {0x50,0x00}, {0x51,0x00}, {0x52,0x00}, {0x53,0x00}, {0x54,0x00},
{0x57,0x80}, {0x59,0x10}, {0x5A,0x08}, {0x5B,0x94}, {0x5C,0xE8}, {0x5D,0x08},
{0x5E,0x3D}, {0x5F,0x99}, {0x60,0x45}, {0x61,0x40}, {0x63,0x2D}, {0x64,0x02},
{0x65,0x96}, {0x66,0x00}, {0x67,0x97}, {0x68,0x01}, {0x69,0xCD}, {0x6A,0x01},
{0x6B,0xB0}, {0x6C,0x04}, {0x6D,0x2C}, {0x6E,0x01}, {0x6F,0x32}, {0x71,0x00},
{0x72,0x01}, {0x73,0x35}, {0x74,0x00}, {0x75,0x33}, {0x76,0x31}, {0x77,0x01},
{0x7C,0x84}, {0x7D,0x03}, {0x7E,0x01},
};
static int probe(int fd, uint8_t *id)
{
uint8_t zero = 0x00;
*id = 0xFF;
if (wr1(fd, REG_BANK, zero) < 0)
return -1;
if (rd1(fd, REG_PART, id) < 0)
return -1;
return 0;
}
static int load_init(int fd)
{
unsigned i, n = (unsigned)(sizeof(PAJ_INIT) / sizeof(PAJ_INIT[0]));
uint8_t v;
for (i = 0; i < n; i++) {
v = PAJ_INIT[i][1];
if (i2cWr(fd, ADDR, PAJ_INIT[i][0], &v, 1) < 0) {
fprintf(stderr, "ERR: init[%u] 写 0x%02X 失败(共 %u 条)\n",
i, PAJ_INIT[i][0], n);
return -1;
}
}
if (wr1(fd, REG_BANK, 0x00) < 0) {
fprintf(stderr, "ERR: 切回 bank0 失败\n");
return -1;
}
printf("init %u 条已写,bank0\n", n);
return 0;
}
static void decode(uint8_t g, uint8_t w, char *out, int cap)
{
int n = 0;
out[0] = 0;
if (g & 0x01) n += snprintf(out + n, cap - n, "右 ");
if (g & 0x02) n += snprintf(out + n, cap - n, "左 ");
if (g & 0x04) n += snprintf(out + n, cap - n, "上 ");
if (g & 0x08) n += snprintf(out + n, cap - n, "下 ");
if (g & 0x10) n += snprintf(out + n, cap - n, "前 ");
if (g & 0x20) n += snprintf(out + n, cap - n, "后 ");
if (g & 0x40) n += snprintf(out + n, cap - n, "顺时针 ");
if (g & 0x80) n += snprintf(out + n, cap - n, "逆时针 ");
if (w & 0x01) n += snprintf(out + n, cap - n, "挥手 ");
if (n == 0)
snprintf(out, cap, "(无)");
}
static int cmd_probe(int fd)
{
uint8_t id = 0;
if (probe(fd, &id) < 0) {
fprintf(stderr, "ERR: 0x73 无应答,查 i2c1 / 模块 / 供电\n");
return 1;
}
if (id != 0x20) {
fprintf(stderr, "ERR: part id=0x%02X,期望 0x20(没切 bank0?料号不对?)\n", id);
return 2;
}
printf("part id=0x%02X 在位\n", id);
return 0;
}
static int cmd_init(int fd)
{
uint8_t id = 0;
int rc = cmd_probe(fd);
if (rc != 0)
return rc;
if (load_init(fd) < 0)
return 1;
if (probe(fd, &id) < 0 || id != 0x20) {
fprintf(stderr, "ERR: 写表之后 part id 丢了\n");
return 1;
}
return 0;
}
static int cmd_poll(int fd, int seconds)
{
uint8_t g, w;
char buf[80];
int i, n = seconds * 10;
if (wr1(fd, REG_BANK, 0x00) < 0) {
fprintf(stderr, "ERR: 选 bank0 失败,先跑 init\n");
return 1;
}
printf("轮询 %d 秒,窗口上方 15~20cm 慢速挥手\n", seconds);
for (i = 0; i < n; i++) {
g = 0;
w = 0;
if (rd1(fd, REG_GES, &g) < 0) {
fprintf(stderr, "ERR: 读 0x43 失败\n");
return 1;
}
rd1(fd, REG_WAVE, &w);
if (g != 0 || (w & 0x01)) {
decode(g, w, buf, (int)sizeof(buf));
printf("0x43=0x%02X 0x44=0x%02X %s\n", g, w, buf);
}
usleep(100000);
}
return 0;
}
static void usage(const char *argv0)
{
fprintf(stderr,
"用法: %s probe | init | poll [秒]\n"
" probe 读 part id,期望 0x20\n"
" init 写 219 条并切回 bank0\n"
" poll 先 init 再轮询手势,默认 20 秒\n",
argv0);
}
int main(int argc, char **argv)
{
const char *dev = "/dev/i2c-1";
const char *cmd = "probe";
int fd, sec = 20;
if (argc >= 2)
cmd = argv[1];
if (argc >= 3)
sec = atoi(argv[2]);
if (sec < 1)
sec = 20;
fd = open(dev, O_RDWR);
if (fd < 0) {
fprintf(stderr, "open %s: %s (chmod 0666?)\n", dev, strerror(errno));
return 1;
}
if (strcmp(cmd, "probe") == 0) {
int rc = cmd_probe(fd);
close(fd);
return rc;
}
if (strcmp(cmd, "init") == 0) {
int rc = cmd_init(fd);
close(fd);
return rc;
}
if (strcmp(cmd, "poll") == 0) {
int rc = cmd_init(fd);
if (rc == 0)
rc = cmd_poll(fd, sec);
close(fd);
return rc;
}
usage(argv[0]);
close(fd);
return 1;
}
上面用了 atoi,补一行头文件:
#include <stdlib.h>
放进 #include <stdio.h> 那一组。漏了会警告,部分工具链当错误。
编译、送上板:
OHOS=$HOME/code/OpenHarmony
CLANG=$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang
SYSROOT=$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot
$CLANG --target=arm-linux-ohos --sysroot=$SYSROOT -O2 -Wall \
-Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1 \
-o paj7620_tool paj7620_tool.c
hdc file send paj7620_tool /data/local/tmp/paj7620_tool
hdc shell chmod 0755 /data/local/tmp/paj7620_tool
hdc shell chmod 0666 /dev/i2c-1
先探测,再写表,再挥:
hdc shell /data/local/tmp/paj7620_tool probe
# part id=0x20 在位
hdc shell /data/local/tmp/paj7620_tool init
# init 219 条已写,bank0
hdc shell /data/local/tmp/paj7620_tool poll 20
# 轮询 20 秒,窗口上方 15~20cm 慢速挥手
# 0x43=0x02 0x44=0x00 左
# 0x43=0x01 0x44=0x00 右
# 0x43=0x04 0x44=0x00 上
只跑 probe、不跑 init,poll 里读 0x43 会一直安静。这就是开头那个现场。poll 子命令内部会先 init 一次,避免「以为在测手势,其实表没写」。
sizeof(PAJ_INIT)/sizeof(PAJ_INIT[0]) 必须是 219。改表的时候用:
grep -o '{0x' paj7620_tool.c | wc -l
工具源码里除了表还有几处 { addr, 结构体,数字会略大于 219,大致对上即可。真正准的是 C 里那句 printf("init %u 条。
5. 表为什么不能「精简」
219 条看起来像魔法。里面混着增益、阈值、手势窗口、DSP 时钟,以及两次 0xEF 切 bank。没有一份公开的「最小 20 条就能用手势」的裁剪表在这块产品上验证过。
现场试过三种自作聪明,全失败:
只写 bank0 前 40 条。part id 还在,0x43 仍 0。后半段在 bank1,手势状态机的参数在那里。
按寄存器地址排序再写。表里同一个地址出现多次,后写覆盖前写,而且中间夹着切 bank。排序等于打乱时序。
写完不切回 bank0。随后读 0x43 读到 bank1 的 0x43,值是初始化表里写进去的 0x30,于是应用解码出「上+右」的鬼手势,没有人动。
所以表是整块搬运,写完 0xEF=0x00。不要在生产固件里对它做「优化」。
上电后表在芯片 RAM 里,掉电丢失。每次进程起来都要写一遍。应用 aboutToAppear 调一次 init,不要每个手势读之前写 219 条,I2C 会卡 100 ms 量级,轮询就抖。
6. 动作怎么做,芯片才置位
窗口朝上或朝外,看模块丝印。手掌张开,在窗口上方 15~20 cm,一次一个动作,做完停半秒。
左 / 右:手掌平面平行窗口,横向扫过。太快变成挥手(0x44),太贴变成「前」。
上 / 下:同样距离,纵向扫。
前 / 后:手掌正对窗口,靠近或拉远,不要横着切。
顺时针 / 逆时针:在窗口上方画圈,半径大约一个手掌,慢。画得小,DSP 当噪声丢掉。
日光灯正对窗口、或者旁边有红外测温的 LED 在闪,误触发会多。用手遮一下环境光再试。
一次读到多 bit 是正常的。解码按位拼字符串,不要 switch 只认单 bit,否则「右上」会被丢掉。
7. ArkTS:先 init,再连续读
libboard.so 三个接口:
export const paj7620Init: () => number; // 0 成功 / -1 无应答 / -2 part id 不是 0x20
export const i2cWriteReg: (bus: number, addr: number, reg: number, val: number) => number;
export const i2cReadReg: (bus: number, addr: number, reg: number) => number; // 0..255 或 -1
NAPI 的 paj7620Init 就是 C 里的 cmd_init:probe + 写表 + 回 bank0。i2cWriteReg / i2cReadReg 走同一套 i2cWr / i2cRdReg,bus 传 1,addr 传 0x73。
import board from 'libboard.so';
@Entry
@Component
struct PajPage {
@State line: string = '先「初始化」,再「连续读」,窗口上方 15~20cm 慢挥';
@State ready: boolean = false;
private timer: number = -1;
aboutToDisappear(): void {
if (this.timer >= 0) {
clearInterval(this.timer);
this.timer = -1;
}
}
build() {
Column() {
Text('PAJ7620 手势').fontSize(20).margin({ bottom: 8 })
Text('i2c1 地址 0x73,无内核节点。不写 219 条则 0x43 恒 0。')
.fontSize(14).fontColor('#666').margin({ bottom: 12 })
Row() {
Button('在位自检').onClick(() => { this.probe(); })
Button('初始化').margin({ left: 8 }).onClick(() => { this.init(); })
Button(this.timer >= 0 ? '停止' : '连续读')
.margin({ left: 8 })
.onClick(() => { this.toggle(); })
}
Text(this.line).fontSize(16).margin({ top: 16 }).fontColor('#1565C0')
}
.padding(16)
.width('100%')
.alignItems(HorizontalAlign.Start)
}
probe(): void {
board.i2cWriteReg(1, 0x73, 0xEF, 0x00);
let id: number = board.i2cReadReg(1, 0x73, 0x00);
if (id < 0) {
this.line = '0x73 无应答(模块、i2c1、chmod 0666)';
this.ready = false;
return;
}
this.line = 'part id=0x' + id.toString(16) + (id === 0x20 ? ' 在位' : ' 不是 0x20');
}
init(): void {
let r: number = board.paj7620Init();
if (r === 0) {
this.ready = true;
this.line = '219 条已写。连续读,慢挥。';
} else if (r === -1) {
this.ready = false;
this.line = '无应答';
} else if (r === -2) {
this.ready = false;
this.line = 'part id 不是 0x20';
} else {
this.line = 'init 返回 ' + r.toString();
}
}
readOnce(): void {
board.i2cWriteReg(1, 0x73, 0xEF, 0x00);
let g: number = board.i2cReadReg(1, 0x73, 0x43);
let w: number = board.i2cReadReg(1, 0x73, 0x44);
if (g < 0) {
this.line = '读 0x43 失败,先初始化';
return;
}
let s: string = '';
if (g & 0x01) s += '右 ';
if (g & 0x02) s += '左 ';
if (g & 0x04) s += '上 ';
if (g & 0x08) s += '下 ';
if (g & 0x10) s += '前 ';
if (g & 0x20) s += '后 ';
if (g & 0x40) s += '顺时针 ';
if (g & 0x80) s += '逆时针 ';
if (w > 0 && (w & 0x01)) s += '挥手 ';
if (s.length === 0) {
this.line = '无手势 0x43=0' + (this.ready ? '' : ' (表还没写?)');
} else {
this.line = s + ' (0x43=0x' + g.toString(16) + ')';
}
}
toggle(): void {
if (this.timer >= 0) {
clearInterval(this.timer);
this.timer = -1;
return;
}
this.readOnce();
this.timer = setInterval(() => { this.readOnce(); }, 200);
}
}
连续读走 setInterval 200 ms。i2cReadReg 是同步 ioctl,单次几十毫秒,UI 扛得住。不要把 219 条 init 放进 interval。
NAPI 里 paj7620Init 的返回值约定写死在 index.d.ts 旁边的注释里:0 / -1 / -2。ArkTS 不要把 -1 当无符号 4294967295,napi_create_int32 即可。
8. 和 PIR 不要搅在一块
同一块模块上经常还有人体感应,那是一根 GPIO,linux 编号实测是 73(GPIO2_B1),不是 74。74 拔掉模块仍恒 1,悬空上拉。手势是 0x73 这个 I2C 地址。一个数字 73、一个地址 0x73,现场口头容易说乱。
测手势时 PIR 的电平会跟着人晃,那是另一条路径,别在手势页里读 GPIO。测 PIR 时也不要抱怨 0x43 是 0——表没写,手势页本来就该是 0。
HAP 读 GPIO 同样要开机预 export。那是另一篇的事。这里只保证 /dev/i2c-1 的 0666。
9. 排障
probe 得 0xFF:先看 i2cdetect 有没有 73,再看 1-0073/driver 是否被占。poll 无手势:确认 219 条写完、距离 15~20 cm、动作慢。恒定 0x30:停在 bank1。应用读失败:工具和应用不要共用一个 fd;0666 在 reload 后要重做。i2c_msg 自己定义,不要拿 64 位主机头文件。表放 C 里,不要 JSON 一条条从 ArkTS 写。
过关:part id=0x20;不 init 时 0x43 恒 0;init 后左/右/上能置位;拔模块必须失败。/dev/i2c-1 chmod 0666。
系列第 38 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10
更多推荐




所有评论(0)