人体感应芯片PIR模块驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
I2C 上那串芯片告一段落。今天换一根高低电平——人体感应。脚号不能猜悬空会恒报有人。这一章不谈 I2C也不谈 UART。人体感应模块对外就一根数字脚有人经过电平翻一下没人电平待在另一边。软件上它就是 Linux 的sysfsGPIO。听起来简单现场却最容易把脚号写错。我第一次测这块模块的时候应用默认读的是GPIO74。模块上的指示灯跟着挥手亮灭屏幕上的数字却永远是1。我以为极性反了把「高有人」改成「低有人」文字跟着反数字还是1。后来把模块拔掉gpio74 仍然是1。那一刻才明白74 这根脚根本没接到 PIR 的 OUT 上它只是一根被内部上拉的悬空脚。真脚是GPIO73也就是GPIO2_B1。挥手0和1立刻对翻。本章把这条排查链原样写下来再给你完整的 C 和 ArkTS。不要再靠丝印和设备树注释猜脚号。1. 通信怎么走PIR 模块内部是热释电探头加比较器。人体红外变化超过阈值比较器翻转OUT 脚输出高或低。SoC 这边只要把对应 GPIO 配成输入周期读value。人体经过 │ ▼ PIR 探头 比较器 │ OUT数字 0/1 ▼ 底板扩展排针上的某根 GPIO │ 本板实测GPIO2_B1 linux gpio 73 ▼ 内核 gpiochip → /sys/class/gpio/gpio73/value │ ▼ 用户态C 工具 / NAPI gpioRead / ArkTS 页面它不是I2C 设备。i2cdetect扫不到它。同插槽上如果还坐着手势芯片 PAJ7620第 38 章地址0x73那是另一条总线、另一套代码。PIR 只认 GPIO。同排针上往往还有别的 GPIO。有人把超声波、电机、PIR 画在同一组注释里脚号就串台。记住节点在设备树里不在塑料外壳里。拔掉模块sysfs 的数字不会自己变成「没接」。悬空脚被上拉读出来就是恒1。2. Linux GPIO 编号先会算RK3568 的 GPIO 按 bank 切GPIO0、GPIO1、GPIO2、GPIO3、GPIO4。每个 bank 32 个脚组内再按 A/B/C/D 各 8 个。linux 编号 bank × 32 组内偏移 位号 组内偏移A0 B8 C16 D24 位号07几个本章用得到的例子原理图名字计算linux 编号GPIO2_B12×32 8 1**73**GPIO2_B22×32 8 2**74**GPIO0_B30×32 8 3**11**第 40 章 MQ-2 会用到也是 I2C1 SCLGPIO0_C10×32 16 117uart0 TX第 42 章板上核对 bank 基址hdc shell cat /sys/kernel/debug/gpio | head -40你应当看到类似gpiochip0: GPIOs 0-31, parent: platform/fdd60000.gpio, GPIO0: gpiochip1: GPIOs 32-63, parent: platform/fe760000.gpio, GPIO1: gpiochip2: GPIOs 64-95, parent: platform/fe770000.gpio, GPIO2: gpiochip3: GPIOs 96-127, parent: platform/fe780000.gpio, GPIO3:GPIO2 从 64 起B1 是 648173。数字对不上先别写应用先把这张表抄到笔记本上。3. 一次把 74 证伪、把 73 坐实下面是现场原样。hdc 已经连上当前用户是 root。模块插在扩展排针上指示灯能跟着挥手亮。3.1 先按「想当然的 74」读hdc shell echo 74 /sys/class/gpio/export hdc shell echo in /sys/class/gpio/gpio74/direction hdc shell cat /sys/class/gpio/gpio74/value输出1挥手再cat还是1。连续读十次hdc shell for i in 1 2 3 4 5 6 7 8 9 10; do cat /sys/class/gpio/gpio74/value; sleep 0.3; done1 1 1 1 1 1 1 1 1 1模块灯在闪数字不闪。有人会说「极性反了」。极性反的意思是有人时是 0没人时是 1总会有一边能翻。现在是完全不翻。看 debugfshdc shell cat /sys/kernel/debug/gpio | grep gpio-74gpio-74 (sysfs ) in hi内核也认为它是输入、高电平。配置没有写错。脚上就是高。3.2 拔掉模块把 PIR 模块从排针上拔下来再读hdc shell cat /sys/class/gpio/gpio74/value还是1。这一步是铁证。如果 74 真接到了 OUT拔掉之后要么变高阻被内部上拉也可能仍是 1要么漂。但结合「插着时挥手灯亮、数字不翻」一起看灯亮证明模块 OUT 在动数字不动证明OUT 没接到 74 这根 SoC 脚上。74 只是一根空脚上拉后恒 1。3.3 改试 73hdc shell echo 73 /sys/class/gpio/export hdc shell echo in /sys/class/gpio/gpio73/direction hdc shell cat /sys/class/gpio/gpio73/value空闲可能是0或1取决于模块极性和你人站的位置。关键是挥手hdc shell for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do cat /sys/class/gpio/gpio73/value; sleep 0.25; done对着探头挥手时输出会变成类似0 0 1 1 1 0 0 1 1 00和1对翻和模块灯同步。73 才是真脚。debugfshdc shell cat /sys/kernel/debug/gpio | grep gpio-73\|gpio-74gpio-73 (sysfs ) in lo gpio-74 (sysfs ) in hi挥手时 gpio-73 的lo/hi会变gpio-74 永远hi。3.4 脚号是怎么标错的设备树里有一个「一堆 GPIO 列在一起」的节点注释写着某号扩展口。列表去注释之后索引大致是索引 0 → GPIO1_A0 → linux 32 索引 1 → GPIO1_A1 → linux 33 索引 2 → GPIO2_B2 → linux 74 索引 3 → GPIO2_B1 → linux 73一份现成的人体感应例程用of_get_gpio(np, 3)取的是索引 3也就是 GPIO2_B1 73。应用和注释却一直把「第 2 项」的 74 当成 PIR。注释还把节点标到了另一个插槽号上。教训只有一句脚号以实测翻转为准以驱动取 GPIO 的索引为准不以注释和丝印为准。原理图、连接器名、DTS 行尾注释三份对不上是常态。4. 上电 12 分钟不要立刻判故障PIR 探头内部有稳定时间。上电之后大约12 分钟有的模块会一直输出「有人」那一侧电平。模块灯可能常亮sysfs 可能一直是1。这不是脚错。这是初始化。正确测法插好模块上电。人走开等满两分钟。看门狗那章如果没关这段时间板子不该自己复位。两分钟后再开监测循环。在探头前方 13 米走动或挥手看value翻不翻。人离开电平应回到空闲值并稳定。两分钟内报「一直检测到人」把这句话写进应用提示不要写成缺陷。客户现场最常见的误报就是上电 20 秒就下结论。如果两分钟之后人在探头正前方挥手数字仍然恒定先确认读的是 73不是 74再确认模块灯有没有跟着动灯动、数字不动 脚错灯也不动 模块没供电或坏了灯动且 73 仍不翻换一根空闲 GPIO 扫下面有扫描脚本思路73 能翻但文字和动作反着那是极性改判定不要改脚号。5. 极性以模块为准没有统一的「高电平有人」。有的模块 OUT 上拉有人拉低有的相反。板上没有额外的极性电路直进 SoC。所以应用不要写死「1 就是有人」。正确姿势是先跑监测看空闲值是 0 还是 1人走过看它翻到另一边把「空闲值」记成无人「翻转后的值」记成有人页面上给一个「对调极性」按钮方便现场模块换货。本章示例代码默认高电平有人因为手头那块模块是这样。你的模块如果相反把判定对调即可。监测循环能告诉你该不该对调挥手时数字翻了文字却说无人就是极性反了。6. sysfs 接口本身内核CONFIG_GPIO_SYSFS打开时本产品默认开着节点是/sys/class/gpio/export /sys/class/gpio/unexport /sys/class/gpio/gpioN/direction /sys/class/gpio/gpioN/value /sys/class/gpio/gpioN/edge /sys/class/gpio/gpioN/active_lowroot 导出 73echo 73 /sys/class/gpio/export echo in /sys/class/gpio/gpio73/direction cat /sys/class/gpio/gpio73/valuedirection写成in。写成out会把脚驱成输出可能和模块 OUT 对顶轻则读数乱重则伤脚。PIR 只做输入。active_low保持0。极性在应用里处理不要和 sysfs 的 active_low 叠两层排障时你会不知道是谁反的。edge可以设rising/falling/both然后poll()value做边沿等待。PIR 演示用 300 ms 轮询足够。人走过不是微秒级事件。导出失败常见原因现象原因write error: Device or resource busy这根脚已经被 pinctrl 或别的驱动要走write error: Invalid argument编号超出 gpiochip 范围Permission denied当前进程不是 rootHAP 就是这种情况目录已经存在已经 export 过不必再写 export直接设 direction已经被驱动占用的脚不要跟它抢。PIR 用 73 时确认没有别的节点把GPIO2_B1写进pinctrl-0。hdc shell cat /sys/kernel/debug/pinctrl/pinctrl-rockchip/pinmux-pins | grep -i gpio2-9\|2-b1\|gpio73grep 的关键字按你内核 debugfs 格式改。看到这根脚已经标成别的功能uart / i2c / pwm先改设备树腾脚再 export。7. HAP 为什么自己 export 不了ArkTS 应用跑在非 root 进程里。/sys/class/gpio/export的权限通常是hdc shell ls -l /sys/class/gpio/export--w------- 1 root root 4096 1970-01-01 08:00 /sys/class/gpio/export只有 root 能写。HAP 里这段 Cint ef open(/sys/class/gpio/export, O_WRONLY); if (ef 0) { write(ef, 73, 2); close(ef); }open直接返回-1errno是EACCES。后面再open(/sys/class/gpio/gpio73/value)目录根本不存在还是-1。应用层看到的就是gpioRead返回-1「读取失败」。有人在 hdc shell 里手动 export 一次应用立刻能读——因为 shell 是 root。重启之后又失败。这不是应用偶发 bug是权限模型。解决办法只有一条开机用 root 服务预导出并把value、direction改成 0666。HAP 只openread不再碰export。SELinux 在本产品 bring-up 阶段是 permissive挡你的是 DAC文件 owner/mode不是 seclabel。交付若改成 enforcing还要给 HAP 补策略那是后话。现在先把 mode 放到 0666。8. 开机放权脚本完整板级路径按教程代号device/board/rk/rk3568_evb/board_perm/board-perm.sh device/board/rk/rk3568_evb/board_perm/etc/board-perm.cfg device/board/rk/rk3568_evb/board_perm/BUILD.gn脚本装到/vendor/bin/board-perm.sh。init 配置装到/vendor/etc/init/board-perm.cfg。开源鸿蒙的 init 会加载 vendor 下这批 cfg不必改init.rk30board.cfg本体你若更想写进init.rk30board.cfg的services数组效果一样。两种挂法下面都给。board-perm.sh全文。PIR 用 73下一章 MQ-2 用 11一次导出两个免得写两份服务#!/system/bin/sh # 开机一次性预导出数字输入脚并放开 HAP 能读的节点。 # 以 root 跑。once1跑完退出。 TAGBOARD_PERM log() { echo [$TAG] $*; } log gpio 预导出开始 # 11 MQ-2 DOGPIO0_B3与 i2c1 SCL 共球导出失败见第 40 章 # 73 PIR OUTGPIO2_B1实测确认74 是悬空脚不要再导出 74 当 PIR for g in 11 73; do if [ ! -d /sys/class/gpio/gpio$g ]; then echo $g /sys/class/gpio/export 2/dev/null fi echo in /sys/class/gpio/gpio$g/direction 2/dev/null chmod 0666 /sys/class/gpio/gpio$g/value 2/dev/null chmod 0666 /sys/class/gpio/gpio$g/direction 2/dev/null done # 给串口留一行自检现场一眼能看出导没导上 for g in 11 73; do if [ -d /sys/class/gpio/gpio$g ]; then log gpio$g: direction$(cat /sys/class/gpio/gpio$g/direction) value$(cat /sys/class/gpio/gpio$g/value) else log gpio$g: 导出失败。73 查 pinctrl11 查是否被 i2c1 占用 fi done log gpio 预导出结束etc/board-perm.cfg{ services : [{ name : board-perm, path : [/system/bin/sh, /vendor/bin/board-perm.sh], uid : root, gid : [root], once : 1, console : 1, sandbox : 0, start-mode : boot, secon : u:r:su:s0 } ] }要点uid必须是 root否则写不了export。once 1不要做成常驻守护。console 1日志进串口开机搜[BOARD_PERM]。sandbox 0让它能碰到/sys。start-mode boot尽量早跑。gpiochip 一般在这之前已经注册。如果 gpiochip 枚举偏晚给脚本加几秒重试i0 while [ $i -lt 8 ]; do [ -e /sys/class/gpio/export ] break i$((i 1)) sleep 1 doneBUILD.gn骨架产品 part 名按你的仓库改教程写成device_rk3568_evbimport(//build/ohos.gni) ohos_prebuilt_etc(board_perm_script) { source board-perm.sh module_install_dir bin part_name device_rk3568_evb install_images [ chipset_base_dir ] install_enable true } ohos_prebuilt_etc(board_perm_init_cfg) { source etc/board-perm.cfg relative_install_dir init part_name device_rk3568_evb install_images [ chipset_base_dir ] install_enable true } group(board_perm_group) { deps [ :board_perm_init_cfg, :board_perm_script, ] }chipset_base_dir是vendor所以脚本进vendor.img。改完这组文件重打 vendor刷mmcblk1p8SD或对应 eMMC 的 vendor 分区。只重装 HAP不会让预导出生效。另一种挂法写进device/board/rk/rk3568_evb/下实际生效的init.rk30board.cfg记住硬件名是rk30board改init.rk3568.cfg不会被加载{ name : board-perm, path : [/system/bin/sh, /vendor/bin/board-perm.sh], uid : root, gid : [root], once : 1, console : 1, sandbox : 0, start-mode : boot }塞进该 cfg 的services数组。cmds里不要夹注释字符串整份服务会解析失败。两种挂法选一种即可不要挂两次。临时验证、还没重打 vendor 时root 手动跑一遍hdc shell echo 73 /sys/class/gpio/export hdc shell echo in /sys/class/gpio/gpio73/direction hdc shell chmod 0666 /sys/class/gpio/gpio73/value /sys/class/gpio/gpio73/direction hdc shell ls -l /sys/class/gpio/gpio73/value期望-rw-rw-rw- 1 root root 4096 ... /sys/class/gpio/gpio73/value然后非 root 的 HAP 才能open成功。9. 完整 Cgpio_watch.c这是给 root 或已经预导出之后用的命令行工具。它在还没导出时会尝试自己 export失败就打印 errno不再假装成功。循环打印电平方便你对着探头挥手。/* gpio_watch.c * OpenHarmony 用户态 32 位 musl。 * 用法 * gpio_watch 73 * gpio_watch 73 300 * 第二个参数是间隔毫秒默认 300。 */ #include errno.h #include fcntl.h #include stdio.h #include stdlib.h #include string.h #include unistd.h static int write_str(const char *path, const char *s) { int fd open(path, O_WRONLY); if (fd 0) return -1; ssize_t n write(fd, s, strlen(s)); close(fd); return (n (ssize_t)strlen(s)) ? 0 : -1; } static int gpio_export(int num) { char dir[64]; snprintf(dir, sizeof(dir), /sys/class/gpio/gpio%d, num); if (access(dir, F_OK) 0) return 0; /* 已经导出 */ char buf[8]; int len snprintf(buf, sizeof(buf), %d, num); int fd open(/sys/class/gpio/export, O_WRONLY); if (fd 0) { fprintf(stderr, open export: %sHAP 非 root 会在这里失败\n, strerror(errno)); return -1; } ssize_t n write(fd, buf, (size_t)len); int err errno; close(fd); if (n 0) { fprintf(stderr, write export %d: %s\n, num, strerror(err)); return -1; } usleep(50000); /* 等内核建目录 */ return 0; } static int gpio_set_in(int num) { char path[64]; snprintf(path, sizeof(path), /sys/class/gpio/gpio%d/direction, num); if (write_str(path, in) 0) { fprintf(stderr, direction: %s\n, strerror(errno)); return -1; } return 0; } static int gpio_read(int num) { char path[64]; snprintf(path, sizeof(path), /sys/class/gpio/gpio%d/value, num); int fd open(path, O_RDONLY); if (fd 0) return -1; char c 0; ssize_t n read(fd, c, 1); close(fd); if (n 0) return -1; if (c 0) return 0; if (c 1) return 1; return -1; } int main(int argc, char **argv) { if (argc 2) { fprintf(stderr, usage: %s gpio [interval_ms]\n, argv[0]); return 1; } int num atoi(argv[1]); int interval (argc 3) ? atoi(argv[2]) : 300; if (num 0 || interval 20) { fprintf(stderr, bad args\n); return 1; } if (gpio_export(num) 0) return 1; if (gpio_set_in(num) 0) return 1; printf(watching gpio%d every %d ms, Ctrl-C to stop\n, num, interval); printf(PIR: 上电 1-2 分钟内恒值是初始化不是故障\n); int last -2; int flips 0; int samples 0; for (;;) { int v gpio_read(num); samples; if (v 0) { printf(read gpio%d failed: %s\n, num, strerror(errno)); return 1; } if (v ! last last 0) flips; printf(gpio%d%d samples%d flips%d %s\n, num, v, samples, flips, (v 1) ? HIGH : LOW); last v; usleep((useconds_t)interval * 1000); } return 0; }编译必须走开源鸿蒙的 32 位 musl 工具链不要用aarch64-linux-gnu-gccOHOS$HOME/code/opcode/OpenHarmony # 改成你的源码根 CLANG$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang SYSROOT$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot $CLANG --targetarm-linux-ohos --sysroot$SYSROOT \ -O2 -Wall \ -Wl,--dynamic-linker/system/lib/ld-musl-arm.so.1 \ -o gpio_watch gpio_watch.c file gpio_watch # 期望ELF 32-bit LSB executable, ARM, ... interpreter /system/lib/ld-musl-arm.so.1丢到板上hdc file send gpio_watch /data/local/tmp/gpio_watch hdc shell chmod 0755 /data/local/tmp/gpio_watch先对比 74 和 73hdc shell /data/local/tmp/gpio_watch 74 200挥手十秒flips应接近 0。Ctrl-C 停掉。hdc shell /data/local/tmp/gpio_watch 73 200等过初始化再挥手flips往上加。空闲时flips停住。这就是验收。如果你在 HAP 里链了同样的gpio_export()没有预导出时它会打印open export: Permission denied。这正是我们要的失败不要把它吞掉改成返回 0。10. NAPIgpioRead 只读 value应用侧 native 不要再写 export。开机服务做完脏活HAP 只读/* napi 片段gpioRead(num) → 0/1失败 -1 */ static napi_value GpioRead(napi_env env, napi_callback_info info) { size_t argc 1; napi_value a[1]; napi_get_cb_info(env, info, argc, a, nullptr, nullptr); int num -1; napi_get_value_int32(env, a[0], num); int val -1; if (num 0) { char p[64]; snprintf(p, sizeof(p), /sys/class/gpio/gpio%d/value, num); int fd open(p, O_RDONLY); if (fd 0) { char c 0; if (read(fd, c, 1) 0) val (c 1) ? 1 : 0; close(fd); } } napi_value ret; napi_create_int32(env, val, ret); return ret; }对比「自作聪明」的版本读失败就去写export。在 HAP 里那一步永远失败还会让你误以为「偶发权限」。两种进程、两套权限不要混在一个函数里。命令行工具可以 exportlibboard.so不要 export。index.d.tsexport const gpioRead: (num: number) number;11. ArkTS 页面import board from libboard.so; Entry Component struct PirPage { State gpioNum: string 73; State live: string -; State detect: string ; State hint: string 上电满 2 分钟后再点监测。默认脚 73不是 74。; State watching: boolean false; State highIsPerson: boolean true; /* 极性true高有效 */ private timer: number -1; aboutToDisappear() { this.stop(); } detectText(v: number): string { if (v 0) { return ; } let person: boolean this.highIsPerson ? (v 1) : (v 0); return person ? 检测到人 : 无人; } readOnce() { let n: number parseInt(this.gpioNum); if (Number.isNaN(n)) { this.hint GPIO 号填数字; return; } let v: number board.gpioRead(n); this.live v 0 ? ERR : v.toString(); this.detect this.detectText(v); if (v 0) { this.hint gpio n 读不到。HAP 不能 export检查开机 board-perm 是否预导出 73value 是否 0666。; } else { this.hint 当前 (v 1 ? 高 : 低) 。对着探头挥手看监测是否 0/1 翻转。; } } toggle() { if (this.watching) { this.stop(); return; } let n: number parseInt(this.gpioNum); if (Number.isNaN(n)) { this.hint GPIO 号填数字; return; } let probe: number board.gpioRead(n); if (probe 0) { this.hint gpio n 读不到先查放权脚本。; return; } this.watching true; this.hint 监测中人走开看空闲值再挥手看翻转。翻了但文字反了点「对调极性」。; this.live probe.toString(); this.detect this.detectText(probe); this.timer setInterval(() { let v: number board.gpioRead(n); this.live (v 0) ? ERR : v.toString(); this.detect this.detectText(v); }, 300); } stop() { if (this.timer 0) { clearInterval(this.timer); this.timer -1; } this.watching false; } build() { Column({ space: 10 }) { Text(人体感应 PIR).fontSize(20).fontWeight(FontWeight.Bold) Text(真脚是 GPIO73GPIO2_B1。74 悬空恒 1不要再用。) .fontSize(13) Text(上电 12 分钟初始化期内恒「有人」是正常的。) .fontSize(13) Row() { Text(GPIO).margin({ right: 8 }) TextInput({ text: this.gpioNum }) .width(80) .onChange((v: string) { this.gpioNum v; }) Button(读一次).onClick(() { this.readOnce(); }).margin({ left: 8 }) Button(this.watching ? 停止监测 : 监测 PIR) .onClick(() { this.toggle(); }) .margin({ left: 8 }) } Button(this.highIsPerson ? 极性高有人点此对调 : 极性低有人点此对调) .onClick(() { this.highIsPerson !this.highIsPerson; }) Text(实时电平: this.live).fontSize(18) Text(this.detect).fontSize(22).fontWeight(FontWeight.Bold) Text(this.hint).fontSize(14) } .padding(16) .width(100%) } }横屏页面不要给 Column 写死height: 100%再往里塞按钮末尾条目会被导航栏挡住。第 67 章细讲。这里控件少先保证监测循环跑在 UI 线程 300 ms 一次只读一个字节不会卡。12. 不需要专用内核驱动有的 Linux 例程会写一个 misc 字符设备compatible绑那串 GPIO 列表of_get_gpio取索引再gpio_direction_input用户态ioctl读电平。开源鸿蒙侧完全可以不跟这条路。sysfs 已经把脚交给用户态了。不写驱动的好处改脚号不用重编内核没有compatible抢绑排障用cat value和应用读的是同一条路径。坏处是 HAP 权限要靠放权脚本。这是一笔划得来的账。超声波、电机那种要精确时序的才值得进内核。PIR 的时序是「人走过去」毫秒级轮询足够。如果设备树里已经有一个占着 GPIO2_B1 的节点且status okay而它的驱动会申请这根脚sysfs export 会busy。把那个节点disabled或者别让它申请 B1。PIR 和那个节点不能同时要同一根脚。13. 排障过程按这个顺序走把下面当成检查单。每一步都有命令不要跳。第一步确认读的是哪根脚。hdc shell ls -d /sys/class/gpio/gpio7*看到gpio73而不是只有gpio74。第二步root 读 73 和 74拔插对比。插着模块读两根拔掉再读两根。74 两遍都是 173 插着能翻、拔掉可能恒定——73 才是候选。第三步看 debugfs 有没有被别的名字占住。hdc shell cat /sys/kernel/debug/gpio | grep gpio-73sysfs表示当前是你在用。若名字是某个驱动先解决冲突。第四步权限。hdc shell ls -l /sys/class/gpio/gpio73/value hdc shell ls -l /sys/class/gpio/exportvalue必须rw-rw-rw-否则 HAP 读-1。export保持 root 可写即可不要 0666 给所有人 export没必要。第五步等两分钟。秒表看着。初始化期内不要改代码。第六步监测翻转再谈极性。gpio_watch 73的flips增加功能就通了。文字反了只改highIsPerson。第七步应用仍 -1。hdc shell ps -A | grep board-perm hdc shell grep BOARD_PERM /dev/kmsg # 或串口回看开机 [BOARD_PERM] gpio73: 已导出服务没起来查 vendor 里有没有/vendor/bin/board-perm.sh、/vendor/etc/init/board-perm.cfg。没有就是没打进 vendor不是应用写错。hdc shell ls -l /vendor/bin/board-perm.sh /vendor/etc/init/board-perm.cfg第八步脚仍然恒定灯也不亮。量模块 VCC、GND。探头窗口不要贴胶带。有的模块背面有灵敏度电位器拧到极端会永远触发或永远不触发。14. 验收同时满足才算过gpio73已导出directioninvalue权限 0666。上电满 2 分钟后人离开电平稳定在空闲值连续 10 秒flips不再增加。在探头前 13 米走动或挥手gpio_watch 73看到0↔1flips明显增加。人离开回到空闲值。HAP 页面监测同样翻转。HAP没有自己写export也能读。gpio_watch 74在插着和拔掉模块时都几乎不翻。74 不作为 PIR 脚写进任何默认值。不要用「灯亮了」当软件验收。灯可以只接在模块本地和 SoC 脚无关——本章开头那个坑就是这样。路径清单路径干什么device/board/rk/rk3568_evb/kernel/*.dts不要把 GPIO2_B1 交给别的 okay 节点device/board/rk/rk3568_evb/board_perm/board-perm.sh预导出 11、73chmod 0666device/board/rk/rk3568_evb/board_perm/etc/board-perm.cfginit 一次性 root 服务/sys/class/gpio/gpio73/value用户态读电平/sys/kernel/debug/gpio核对编号和占用者/vendor/bin/board-perm.sh板上实际跑的脚本libboard.so 的 gpioReadHAP 只读不 export系列第 39 篇 · 芯片瑞芯微 RK3568 · OpenHarmony 4.1API 11 · Linux 5.10

相关新闻

六杆急回机构运动学与动力学分析:从建模到Matlab仿真全流程

六杆急回机构运动学与动力学分析:从建模到Matlab仿真全流程

做机构分析这些年,遇到最多的情况就是:四杆机构人人都能算,曲柄摇杆、曲柄滑块讲得滚瓜烂熟,但一碰到六杆机构就抓瞎。市面上教材里对六杆快速回归机构的完整推导少之又少,公式绕、坐标多、代码更是没人贴。偏偏这东西…

2026/10/13 4:20:04 阅读更多 →
MySQL数据可视化实战:从SQL设计到图表落地全链路解析

MySQL数据可视化实战:从SQL设计到图表落地全链路解析

今天想跟你聊聊一个特别实在的问题:MySQL里的数据到底怎么变成业务方能看得懂、用得上、甚至能直接拍板的图表。干这行久了你会发现,数据可视化这件事的难度从来不在“画图”本身,而在“数据从哪来、怎么取、取出来之后怎么跟图表匹配”。很多…

2026/10/13 4:19:04 阅读更多 →
Android 分组列表实现:ExpandableListView 入门与踩坑

Android 分组列表实现:ExpandableListView 入门与踩坑

这些年做 Android 开发,我碰到最多的需求之一就是"带分组的列表"。通讯录按首字母分组的联系人、电商 App 里分类下的商品清单、设置页里分组排列的选项,都属于这种两级结构。用普通 ListView 去怼,要么代码写得绕,要么…

2026/10/13 4:19:04 阅读更多 →

最新新闻

工业上位机选型与架构设计:从IPC-510到高可靠数据采集系统

工业上位机选型与架构设计:从IPC-510到高可靠数据采集系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:41:36 阅读更多 →
中级开发者AI能力升级:从API调用到系统构建的四层实战路径

中级开发者AI能力升级:从API调用到系统构建的四层实战路径

1. 为什么“AI能力”不是加个API调用就完事了?“给中级开发者的 AI 能力升级路线图”——这个标题里藏着一个被严重低估的真相:绝大多数中级开发者正在用写CRUD的思维去学AI,结果越学越焦虑,越学越不敢动手。我见过太多人&#xf…

2026/10/13 5:41:36 阅读更多 →
GaussView 5 Linux版运行故障排查与兼容性修复指南

GaussView 5 Linux版运行故障排查与兼容性修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:41:36 阅读更多 →
AnyPS5:PS5系统能力解耦协议与跨平台运行时实践

AnyPS5:PS5系统能力解耦协议与跨平台运行时实践

项目标题:“AnyPS5”这个词最近在多个技术社区和数码爱好者圈子里频繁出现,但目前没有任何官方产品、开源项目、商业服务或主流媒体明确将其定义为一个标准术语。它既不是索尼官方命名的设备型号,也不在PlayStation 5的硬件规格文档、系统固件…

2026/10/13 5:41:36 阅读更多 →
C语言分支循环:编程逻辑的核心构建块

C语言分支循环:编程逻辑的核心构建块

1. 为什么C语言的分支循环如此重要?记得我刚开始学编程时,导师在黑板上画了个大大的流程图说:"这就是程序的灵魂"。当时不理解,直到自己写出第一个能交互的小游戏才恍然大悟——分支和循环就是让代码"活起来"…

2026/10/13 5:41:36 阅读更多 →
Camshift目标跟踪算法详解:原理、OpenCV实现与调参实战

Camshift目标跟踪算法详解:原理、OpenCV实现与调参实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:40:35 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →