在 RK3588 这类 ARM 嵌入式平台上做 AI 开发时,注意力通常集中在模型转换、NPU 推理和算法精度上,最容易忽略的一层却是设备如何接收人的指令。无论是工业面板上的 GPIO 功能键,还是临时接入一块 USB 键盘做调试,都涉及同一套输入事件处理机制。很多项目到了联调阶段才暴露问题:按键按下没有反应、USB 键盘事件拿不到、设备节点重启后变了、推理程序在读取输入时把整条逻辑卡住了。这篇文章从零开始讲透 RK3588 上用户按键与 USB 键盘的编程方法,目标是在常见 RK3588 板卡上,把两种输入统一成可被嵌入式 AI 应用消费的事件流,并叠加按键触发 YOLOv8 推理这类典型用法。文中会给出设备树配置、C 和 Python 读取方案、事件分发模型,以及一套可以直接落地的排查路径。
1. 先理解 RK3588 上输入编程的基础:Linux input 子系统
1.1 用户按键和 USB 键盘在内核里最终汇成同一种事件
很多人第一次接触嵌入式输入时会下意识地认为,GPIO 按键和 USB 键盘是两套完全不同的东西:按键要走 GPIO 中断,键盘要走 USB HID 协议。这个理解没错,但从应用编程角度讲,它们在内核层面最终会汇聚成同一种对象:struct input_event。
Linux 内核专门为输入设备设计了 input 子系统。它把驱动层产生的原始信号统一抽象为事件,应用层不需要关心底层是 GPIO 还是 USB,只需要从 /dev/input/eventX 节点读取结构化事件。GPIO 按键由 gpio-keys 驱动注册为 input 设备,USB 键盘则由 HID 驱动注册为 input 设备。对于 RK3588 上的嵌入式 AI 开发来说,这种统一带来的好处非常明显:我们只需要写一套事件读取逻辑,就能同时处理物理按键和 USB 键盘。
每个 struct input_event 包含三组关键信息:type 表示事件类型,code 表示具体按键或动作码,value 表示状态值。对于按键事件,EV_KEY 对应的 value 为 1 表示按下,0 表示抬起,2 表示自动重复。这个模型很接近 PC 键盘编程,但它发生在 ARM 板卡上,使用方式要考虑设备节点、权限、热插拔和内核配置等细节。
1.2 用户态编程只需要处理 /dev/input/eventX 吗
严格说不一定。实际项目中有两条路径:
- 如果应用只需要“某个按键被按下”这个结果,可以从
/dev/input/eventX直接读取。 - 如果应用需要更复杂的输入法、组合键、键盘布局,可以走
libinput、evdev库,或者在 Qt/GTK 事件层处理。
对于 RK3588 嵌入式 AI 场景,大部分需求是第一类:一个 GPIO 按键触发拍照、推理、保存日志,或者一个 USB 键盘控制菜单和切换模式。此时直接处理 input 事件最清晰,也最容易和推理线程集成。直接读取 /dev/input/eventX 时,读取只是第一步,真正要设计好的是事件循环、队列和线程边界。
1.3 学习环境与生产环境的差异要先分清楚
学习环境下,开发板往往使用桌面版或完整 SDK 镜像,默认带 root 权限和 evtest 工具,键盘插上后立刻能看到事件。生产环境则完全不同:设备可能没有桌面、没有输入组权限、内核被裁剪、设备节点不需要稳定、系统通过 systemd 服务启动程序。这些差异直接决定代码怎么写:
- 学习环境可以直接用
evtest观察事件,生产环境没有这个工具就要自己写读取代码。 - 学习环境可以用 root 运行程序,生产环境应避免长期用 root,应该为程序添加 input 组或配置 udev 规则。
- 学习环境中
/dev/input/eventX是稳定的,生产环境中每次插拔和重启都可能导致编号变化。
把这条主线先立住,后面所有配置才有意义。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RK3588 硬件接入与环境准备
2.1 硬件连接与原理图确认:按键是高有效还是低有效
在写设备树之前,先确认板卡上用户按键的原理图。常见接法中,按键一端接 GPIO,另一端接 GND 或 VCC:
- 按键一端接 GPIO,另一端接 GND:平时 GPIO 被内部上拉电阻拉高,按下后变成低电平,属于低有效(
GPIO_ACTIVE_LOW)。 - 按键一端接 GPIO,另一端接 VCC:平时被下拉电阻拉低,按下后变成高电平,属于高有效(
GPIO_ACTIVE_HIGH)。
一个常见错误是不看原理图,直接照抄开发板默认设备树里的 GPIO_ACTIVE_LOW。如果板卡实际是高有效,按键状态会被反读,表现为按下无响应或事件方向错误。RK3588 板卡的引脚编号在不同品牌、不同型号上经常不一致,落地前必须以自己板卡原理图和厂家 SDK 为准。
还应该确认按键所在 GPIO 是否被复用为其他功能。比如某些板卡上按键引脚和 I2C、UART、PWM 复用,设备树里没有解除复用,就会导致 GPIO 读不到正确的电平变化。这个问题的排查点不在用户态代码,而在 pinctrl 和设备树。
2.2 镜像、工具链和常用命令准备
RK3588 开发通常有三种运行方式:
- 在板卡本地编译 C 程序,使用板卡自带的 gcc。
- 在 PC 上使用
aarch64-linux-gnu-gcc交叉编译,再通过网络或 U 盘拷贝到板卡。 - 直接使用 Python 脚本,不依赖 C 工具链,但生产环境需要 Python 运行时。
交叉编译命令示例:
bash复制sudo apt install gcc-aarch64-linux-gnu
aarch64-linux-gnu-gcc -o keyreader keyreader.c
scp keyreader root@192.168.1.100:/root/
在板端直接编译则更简单:
bash复制gcc -o keyreader keyreader.c
需要注意,交叉编译时如果程序依赖第三方库,比如 libevdev,那么 PC 上的目标架构库版本要和板卡一致,否则会出现 No such file or directory 或版本符号错误。学习阶段尽量在板端直接编译,或者使用纯标准库代码。
2.3 确认系统已经识别 USB 键盘和 GPIO 按键
键盘插上后,先检查内核是否识别了设备:
bash复制cat /proc/bus/input/devices
ls /dev/input/
dmesg | grep -i usb
dmesg | grep -i input
/proc/bus/input/devices 输出中可以看到每个输入设备的 Bus、Vendor、Name、Handlers。USB 键盘通常带有 Handlers=kbd eventN,GPIO 按键通常带有 Handlers=eventN。如果键盘插上后没有出现新的 event 节点,说明可能是 USB 供电不足、内核 HID 配置被裁剪,或者设备树没有使能 USB 2.0/3.0 对应节点。
常用观察工具是 evtest:
bash复制sudo apt install evtest
sudo evtest /dev/input/event5
运行后按下按键或键盘,屏幕上会逐条打印事件。这一步能快速区分问题出在硬件层还是应用层。
3. 用户按键的设备树配置与验证
3.1 gpio-keys 设备树节点怎么写
RK3588 SDK 的设备树中,用户按键通常通过 gpio-keys 实现。一个最小节点如下:
dts复制gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&key_user_pin>;
status = "okay";
key-user {
label = "GPIO Key User";
linux,code = <KEY_POWER>;
gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>;
debounce-interval = <20>;
gpio-key,wakeup;
};
};
对应的 pinctrl 节点通常放在设备树底部的 pinctrl 子节点中:
dts复制pinctrl_key_user: key-user-pin {
rockchip,pins =
<3 RK_PB7 RK_FUNC_GPIO &pcfg_pull_up>;
};
关键属性含义如下:
| 属性 | 作用 | 注意点 |
|---|---|---|
label |
描述按键名称,用于调试 | 建议写成和原理图一致的名称 |
linux,code |
上报给用户态的按键码 | 可使用 KEY_POWER 等宏,也可直接写数值 |
gpios |
指定 GPIO 控制器、引脚和有效电平 | 引脚号必须和原理图一致 |
debounce-interval |
软件消抖时间,单位毫秒 | 太小会重复触发,太大则反应迟钝 |
gpio-key,wakeup |
允许按键唤醒系统 | 生产环境根据需求决定是否保留 |
很多内核版本编译时把 linux,code 宏替换成数字,也有部分设备树直接写数字,例如 linux,code = <116> 对应 KEY_POWER。为了可读性,优先使用内核头文件中的宏名,前提是设备树预处理器能正确包含对应头文件。
3.2 配置 pinctrl 和电源域时的三个坑
第一个坑是 pinctrl 引脚的 bank 和 pin 号写错。RK3588 的 GPIO 分为多个 bank,每个 bank 内又有 A、B、C、D 组。照抄开发板默认配置时,板卡不同,按键可能接在不同 bank 上,必须按原理图改。
第二个坑是上下拉方向。如果原理图是高有效按键,通常配置 pcfg_pull_down,低有效则配置 pcfg_pull_up。配置成相反方向后,GPIO 默认电平不对,即使按键按下时电平发生变化,驱动也可能因为初始状态判断错误而过滤事件。
第三个坑是引脚被复用或电源域未打开。在 RK3588 SDK 里,某些引脚同时连接多个功能外设,设备树中其他节点如果引用了同一引脚,pinctrl 子系统会报冲突,甚至导致内核启动时输出 pin request failed 或 failed to get gpio。查看日志:
bash复制dmesg | grep -i pinctrl
dmesg | grep -i gpio-keys
如果有 pin is already requested,检查同文件的 I2C、SPI、UART 或 LCD 节点是否占用了该 GPIO。
3.3 按键事件验证:从 /proc 到 evtest
设备树配置正确并重新编译内核或 dtb 后,重启系统并检查:
bash复制cat /proc/bus/input/devices
正常会新增一个 Name 类似 gpio-keys 的设备。然后运行:
bash复制sudo evtest /dev/input/eventN
按下用户按键,应看到类似输出:
code复制Event: time 1712345678.123456, type 1 (EV_KEY), code 116 (KEY_POWER), value 1
Event: time 1712345678.223456, type 1 (EV_KEY), code 116 (KEY_POWER), value 0
如果能看到按下和抬起两条事件,说明设备树、pinctrl 和内驱动链路已经打通。此时用户态读取代码才能生效。
4. USB 键盘事件采集:从设备节点到用户态代码
4.1 定位 USB 键盘对应的输入设备节点
USB 键盘插入后,不一定会一直使用 event0 或 event1,尤其是系统同时存在 GPIO 按键、触摸屏、鼠标等设备时,编号会变化。定位节点有两个常用方法。
方法一,查看 /proc/bus/input/devices:
bash复制cat /proc/bus/input/devices
找到 Name 为 USB Keyboard 或包含键盘字样的段落,记录它的 Handlers 行,例如 Handlers=sysrq kbd event5,说明设备节点是 /dev/input/event5。
方法二,使用 /dev/input/by-id 或 /dev/input/by-path:
bash复制ls -l /dev/input/by-id/
ls -l /dev/input/by-path/
USB 键盘通常会出现类似 usb-xxx-kbd 的稳定链接。生产环境强烈建议使用这个路径,而不是硬编码 eventN,否则拔插键盘后节点漂移会导致程序打开失败。
下面是示例输出:
code复制I: Bus=0003 Vendor=046d Product=c31c Version=0111
N: Name="USB Keyboard"
P: Phys=usb-0000:00:14.0/input0
H: Handlers=sysrq kbd event5
对于 RK3588,Bus=0003 表示 USB 设备,Phys 通常包含控制器地址,例如 usb-0000:00:14.0,也可能显示为 usb-fts-hub-1-1。重点是 Handlers 中的 event 编号。
4.2 C 语言读取 input_event 的最小示例
下面代码可以直接在板卡上编译运行。它打开一个输入设备节点,阻塞读取 struct input_event,只打印 EV_KEY 事件:
c复制#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "usage: %s /dev/input/eventX\n", argv[0]);
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
struct input_event ev;
ssize_t n;
while (1) {
n = read(fd, &ev, sizeof(ev));
if (n < 0) {
perror("read");
break;
}
if (n != sizeof(ev)) {
continue;
}
if (ev.type == EV_KEY) {
printf("code=%d value=%d\n", ev.code, ev.value);
}
}
close(fd);
return 0;
}
编译并运行:
bash复制gcc -o keyreader keyreader.c
sudo ./keyreader /dev/input/event5
这段代码有几个关键点:
struct input_event在内核头文件linux/input.h中定义,实际大小依赖架构。在 ARM64 上通常是 24 字节,但最好使用sizeof(struct input_event),不要硬编码固定长度。- 默认
O_RDONLY打开后,read会阻塞,直到有事件产生。 - 这里只处理
EV_KEY,实际应用中还会出现EV_SYN同步事件,很多程序用EV_SYN作为一组事件的边界,批量刷新 UI。
4.3 Python 读取事件与按键码映射
如果 AI 推理部分使用 Python,可以直接读取二进制事件,也可以用 evdev 库。二进制的做法不依赖第三方库:
python复制import struct
import sys
EV_KEY = 1
fmt = "llHHI"
event_size = struct.calcsize(fmt)
path = sys.argv[1]
with open(path, "rb") as fp:
while True:
data = fp.read(event_size)
if len(data) < event_size:
break
sec, usec, type_, code, value = struct.unpack(fmt, data)
if type_ == EV_KEY:
print(f"code={code} value={value} time={sec}.{usec:06d}")
fmt = "llHHI" 对应两个 long long 时间戳加 unsigned short 的 type、code 和 int 的 value。这个格式在 ARM64 主流平台上可用。如果程序要移植到 32 位 ARM 或不同编译器环境,建议先打印 struct.calcsize 和各字段偏移确认,避免因为结构体对齐导致读取错位。
使用 evdev 库的写法更接近生产实践:
bash复制pip3 install evdev
python复制from evdev import InputDevice, ecodes
dev = InputDevice("/dev/input/event5")
print(dev)
for event in dev.read_loop():
if event.type == ecodes.EV_KEY:
if event.value == 1:
print("pressed key code:", event.code)
evdev 库会把内核原始事件转换成 Python 对象,省去手工 decode 的步骤。它对 udev 设备发现、按键名映射也做了封装。缺点是需要额外依赖,在最小化 Linux 环境里要先确认 Python 和 pip 可用。
终端键盘和普通按键的映射关系由内核 HID 层维护,用户态拿到的是内核 keycode。常用键码:
| 键名 | 内核 keycode | 说明 |
|---|---|---|
| Enter | 28 | 确认、触发 |
| A | 30 | 字母键,实际键码不按 ASCII 排布 |
| B | 48 | 字母键 |
| Space | 57 | 空格 |
| F1 | 59 | 功能键 |
| Esc | 1 | 退出 |
4.4 设备节点漂移问题应该用 by-id/by-path 解决
生产环境不能写死 /dev/input/event5。每次系统重启、设备插入顺序变化,甚至 USB Hub 初始化顺序改变,节点编号都可能变化。推荐在启动脚本里动态查找:
bash复制it=$(ls /dev/input/by-id/*-kbd 2>/dev/null | head -n 1)
if [ -z "$it" ]; then
echo "keyboard not found"
exit 1
fi
exec python3 app.py "$it"
如果板卡同时接了多个键盘或键盘鼠标一体设备,-kbd 后缀不一定存在,这时要根据 /proc/bus/input/devices 的完整信息匹配设备名,不要只靠通配符。
5. 把按键和键盘事件接入嵌入式 AI 应用
5.1 一个典型场景:按键触发 YOLOv8 推理
在 RK3588 上部署 YOLOv8 时,一般流程是先用 RKNN-Toolkit2 把模型转成 .rknn 格式,再在板卡上用 RKNN runtime 加载推理。公开资料中 RK3588 集成 NPU,算力常见说法为 6 TOPS,具体以官方数据手册为准。模型部署本身不是本文重点,但输入事件如何触发推理是一个很常见的设计问题。
假设产品形态是智能安防终端:USB 键盘负责输入密码或切换到调试菜单,GPIO 用户按键负责“立即抓拍并执行一次推理”。如果直接在推理线程里阻塞读取按键事件,推理时系统就再也无法响应按键,这在小内存嵌入式环境中很容易被忽略。
5.2 事件分发线程模型,避免推理阻塞输入采集
推荐用一个独立线程读取输入事件,把结果放入 Queue,推理线程消费队列。这样即使一次推理耗时较长,按键事件也不会丢失太多。下面是 Python 示例:
python复制import queue
import threading
import time
import cv2
from evdev import InputDevice, ecodes
event_queue = queue.Queue(maxsize=32)
def input_worker(path):
dev = InputDevice(path)
for event in dev.read_loop():
if event.type != ecodes.EV_KEY:
continue
if event.value == 1: # 只处理按下
try:
event_queue.put_nowait(event.code)
except queue.Full:
print("event queue full, drop key")
def load_rknn_model(model_path):
# 示意代码,实际以 RKNN-Toolkit2 / RKNNLite 文档为准
from rknnlite.api import RKNNLite
rknn = RKNNLite()
rknn.load_rknn(model_path)
rknn.init_runtime()
return rknn
def ai_worker(model, camera_id):
cap = cv2.VideoCapture(camera_id)
while True:
code = event_queue.get()
if code == ecodes.KEY_A:
ret, frame = cap.read()
if ret:
outputs = model.inference(inputs=[frame])
print("inference done", outputs[0].shape)
elif code == ecodes.KEY_ESC:
print("exit by ESC")
break
if __name__ == "__main__":
model = load_rknn_model("yolov8s.rknn")
t1 = threading.Thread(target=input_worker, args=("/dev/input/by-id/usb-kbd",))
t2 = threading.Thread(target=ai_worker, args=(model, 0))
t1.start()
t2.start()
t1.join()
t2.join()
这个设计解决了三类问题:
- 输入事件被按键自动重复干扰。
value == 1只处理按下事件,value == 2的自动重复被忽略。 - 队列满导致卡死。使用
put_nowait并处理queue.Full,避免输入线程阻塞。 - 推理线程退出机制。用 Esc 键设置退出条件,方便开发和现场调试。
5.3 结合 RKNN runtime 时要注意的执行时机问题
RKNN 推理通常会占用 NPU 和较多内存,实际项目里应避免每个按键都触发一次推理。常见的做法是加冷启动时间或空闲队列清理逻辑。比如在上述代码中,Queue 内可能堆积多个 KEY_A,理论上有多少 A 就会推理多少次,这在嵌入式设备上不是好的体验。可以在消费端丢弃过快产生的重复触发,或者在队列读取时只保留最新的一个事件:
python复制while not event_queue.empty():
try:
code = event_queue.get_nowait()
except queue.Empty:
break
这样做虽然会丢失部分按键,但对 AI 应用来说结果更可控。RKNN 推理性能本身取决于模型精度、输入尺寸、NPU 调度和内存带宽,输入侧的抖动控制不应该影响推理主链路。
6. 常见问题排查链路
6.1 按键无响应,先从设备树和 pinctrl 查起
现象:设备树看起来配置了 gpio-keys,但按下按键完全没有事件。
排查顺序:
- 确认内核是否启用了
CONFIG_KEYBOARD_GPIO:bash复制
zcat /proc/config.gz | grep KEYBOARD_GPIO - 确认设备树节点是否真正编译进 dtb,并且
status为okay。 - 查看
dmesg | grep -i gpio-keys是否有gpio-keys gpio-keys: GPIO Key User: gpio-?之类初始化信息。 - 确认引脚没有被其他节点占用:
bash复制
dmesg | grep -i pinctrl - 用通用 GPIO 工具手动读电平,确认硬件回路正常:
bash复制如果手动读到的电平在按下时不变化,说明问题在硬件或引脚复用,不在应用层。sudo cat /sys/kernel/debug/gpio
6.2 事件读取权限、阻塞和重复事件
权限问题是用户态程序最常见的错误。/dev/input/eventX 通常属于 root:input,默认权限可能为 crw-rw----。普通用户运行程序会报:
code复制open: Permission denied
解决方式:
bash复制sudo usermod -aG input yourname
修改后需要重新登录或重启 session 生效。生产环境建议写 udev 规则,统一给某个设备用户组授权。
阻塞问题是第二个高频错误。read 默认会一直等待事件,如果按键没有消抖或键盘没有事件,程序看起来像卡死。需要配合 poll 或 select 设置超时,避免推理线程被一个永远不来的输入事件拖住。使用 evdev 库时,可以考虑 read_loop() 的阻塞特性是否适合业务。
重复事件是第三个问题。按住按键不放时,内核会自动上报 value=2 的重复事件。如果业务上只希望单次触发,必须通过 value == 1 过滤。此外,硬件消抖不够时,一次物理按下会出现多次 value=1,此时应加大 debounce-interval,而不是在应用层加复杂判断。
6.3 USB 键盘休眠后无法唤醒与设备节点热插拔问题
RK3588 设备进入休眠后,USB 键盘不一定能唤醒系统。USB keyboard wakeup 依赖设备树的 USB 唤醒配置、内核 USB HID 驱动和系统电源管理策略。如果产品必须支持键盘唤醒,需要确认:
bash复制cat /sys/bus/usb/devices/*/power/wakeup
看对应设备是否显示 enabled。如果显示 disabled,需要修改内核参数或设备树。这个功能和 Linux PC 上的 USB 唤醒机制类似,但 RK3588 的电源域和 DT 配置更依赖具体 SDK。
热插拔导致节点漂移时,程序打开旧路径会报 No such file or directory。解决方式是启动时动态查找,并在运行中监控设备节点是否存在:
bash复制udevadm monitor --property
在程序里,可以监听 libudev 事件或定期检查路径。简单场景下,启动脚本每次从 by-id 重新解析路径就够了。
7. 嵌入式输入编程的最佳实践与扩展方向
7.1 生产环境落地清单
把输入事件接入 RK3588 AI 应用后,上线前建议逐项检查:
| 检查项 | 要求 |
|---|---|
| 设备节点稳定性 | 使用 /dev/input/by-id 或动态查找,不硬编码 eventN |
| 运行权限 | 避免 root 运行,使用 input 组或 udev 规则 |
| 事件过滤 | 只处理 EV_KEY,只响应 value=1 |
| 队列设计 | 输入线程与推理线程解耦,队列要有最大长度 |
| 日志 | 记录按键事件时间、设备路径、推理耗时 |
| 异常退出 | Esc、断电、设备拔除时程序能正常退出并通知上层 |
| 休眠策略 | 确认 GPIO 按键和键盘在休眠场景下是否可唤醒 |
| 系统服务 | 使用 systemd 管理应用进程,配置 Restart 和日志输出 |
| 交叉编译依赖 | 所有动态库都使用板卡同版本或静态编译 |
对于 AI 推理服务,还要额外关注模型加载失败、NPU 初始化失败、相机掉线等情况。输入事件只是整个系统的入口之一,入口通了,后面才值得继续优化。
7.2 从单个按键到组合键、菜单系统和远程控制
单个 GPIO 按键能承载的逻辑有限,通常只能实现“开始、停止、抓拍”这类单动作。USB 键盘的价值在于可以组合出更复杂的操作。用户态可以建立一张按键映射表:
| 业务动作 | 触发方式 | 推荐按键码 |
|---|---|---|
| 开始连续推理 | 按下 | KEY_S (31) |
| 停止推理 | 按下 | KEY_E (16) |
| 单次抓拍 | 按下 | KEY_A (30) |
| 保存日志快照 | Ctrl + S | 需自己维护修饰键状态 |
| 退出程序 | 按下 | KEY_ESC (1) |
组合键需要自己维护修饰键状态,比如记录 KEY_LEFTCTRL 或 KEY_RIGHTCTRL 是否处于按下状态,再在处理字母键时判断组合动作。这套逻辑可以使用 libevdev 或 evdev 库管理,比直接读原始结构体更清晰。
如果设备有网络连接,键盘事件还可以转换成远程控制接口。比如把按键事件通过 MQTT 或 HTTP 转发到其他地方,形成“本地输入 + 云端状态同步”的方案。但要注意,本地设备轮询和远程指令竞争同一个 AI 推理服务时,需要在应用层加状态锁,避免多个入口同时触发推理导致 NPU 冲突。
7.3 适合继续研究的技术方向
把用户按键和 USB 键盘打通之后,可以沿着几个方向深入:
- YOLOv8 部署到 RK3588 的完整链路,包括模型转换、量化精度、NPU 多线程调度。
- RK3588 摄像头采集与推理帧同步,解决按键触发时抓到的画面是旧帧的问题。
- Linux udev 和 systemd 的深度配合,实现外围设备热插拔感知、服务自动重启。
- 更复杂的输入框架,比如
libinput如何在嵌入式设备上做事件转发。 - 组合键和触摸屏同时使用时,输入仲裁应该放在哪个层。
对于新手来说,最值得做的练习是:在一块 RK3588 板卡上用 GPIO 按键触发一次 YOLOv8 推理,再用 USB 键盘控制连续推理的开始和停止。这个练习会同时覆盖设备树、input 子系统、线程模型、RKNN 推理和异常处理,比单独看模型部署教程更能理解嵌入式 AI 开发的完整链路。
