嵌入式触摸屏适配全链路排障复盘:从设备树到 Qt 输入层
一、背景与目标
在一块野火 NPi-nyika(i.MX6ULL,Debian 系统,内核 4.19.35-imx6)开发板上,需要让一块 5 寸 GT9157 电容触摸屏正常工作。目标不仅是驱动层跑通,还要让板子上预装的 Qt 应用能够响应触摸。
整个排障过程跨越了设备树(Device Tree)→ 内核驱动 → 用户态输入框架(evdev/tslib)→ Qt 应用层四个层次,几乎覆盖了嵌入式 HMI 开发中最典型的所有坑。本文按时间线复盘,逐层拆解每个报错的根因与解法。
二、问题演进全景
| 阶段 | 现象 | 根因层 | 关键结论 |
|---|---|---|---|
| 1 | new_device 手动创建设备报 request IRQ failed: -22 |
设备树 | 手动创建的设备没有 DT 节点,驱动拿不到 irq-gpios |
| 2 | dtc 报 syntax error(宏行) |
设备树 | 用了 GPIO_ACTIVE_LOW 等宏但没包含头文件 |
| 3 | dtc 报 #include syntax error |
工具链 | dtc 不处理 C 预处理器语法,需先 cpp |
| 4 | 编译成功但满屏 Warning (reg_format) |
设备树 | overlay 的已知误报,可忽略 |
| 5 | dmesg 出现 ID 1158 + input/input2 |
驱动 | 驱动跑通,中断注册成功 |
| 6 | 触摸能用了,但 Qt 程序不响应 | Qt 应用层 | 驱动通 ≠ Qt 能读,两套独立机制 |
| 7 | 环境变量设了仍无效 | 进程环境 | 脚本有独立环境,终端 export 对其无效 |
| 8 | 改 run.sh 后定位真正根因 | 厂商脚本 | 变量 typo + tslib/evdev 混用 |
三、第一层:设备树 overlay 的编译坑
3.1 宏未定义导致的 syntax error
最初写了使用 GPIO_ACTIVE_LOW、IRQ_TYPE_EDGE_FALLING 宏的设备树,dtc 在第 10 行(reset-gpios 那行)报 syntax error。
根因:这些是定义在头文件里的宏,不 #include 宏定义所在头文件,dtc 根本不认识它们,直接当成语法错误。这是设备树最典型的坑之一。
解法 A(补 include + cpp 预处理):#include 是 C 预处理器语法,dtc 本身不处理,必须先用 cpp 预处理再交给 dtc:
cpp -nostdinc -I /usr/include -undef -x assembler-with-cpp \
/tmp/touch-fix.dts > /tmp/touch-fix.tmp.dts
dtc -@ -I dts -O dtb -o /tmp/touch-fix.dtbo /tmp/touch-fix.tmp.dts
如果报 dt-bindings/gpio/gpio.h: No such file,说明板子上没装内核头文件,走解法 B。
解法 B(零宏,纯数字,推荐):直接用数字代替宏,彻底绕开头文件依赖:
| 宏 | 数值 |
|---|---|
GPIO_ACTIVE_LOW |
1 |
GPIO_ACTIVE_HIGH |
0 |
IRQ_TYPE_EDGE_FALLING |
2 |
IRQ_TYPE_LEVEL_LOW |
8 |
最终可用的 overlay(注意去掉了猜测的 pinctrl-0 标签,避免 undefined label):
/dts-v1/;
/plugin/;
&i2c1 {
gtxx_tsc@14 {
compatible = "goodix,gt911", "goodix,gt9xx";
reg = <0x14>;
reset-gpios = <&gpio3 4 1>;
irq-gpios = <&gpio5 9 0>;
interrupt-parent = <&gpio5>;
interrupts = <9 2>;
status = "okay";
};
};
3.2 满屏 reg_format 警告——可忽略的误报
编译成功后出现大量 Warning (reg_format)、pci_device_reg、simple_bus_reg 等警告。
为什么无害:overlay 里 dtc 拿不到父节点(i2c1)的 #address-cells/#size-cells,于是按默认值 #address-cells=2, #size-cells=1 校验,期望 reg 是 12 字节,而 reg = <0x14> 只有 4 字节。但实际 I2C 总线是 #address-cells=1, #size-cells=0,4 字节完全正确。这是 overlay 的已知误报,后面的 Failed prerequisite 警告都是 reg_format 的连带失败,全部忽略。
3.3 为什么必须走设备树,不能手动 new_device
回顾最初的 request IRQ failed: -22:手动 new_device 创建的设备没有设备树节点,驱动拿不到 irq-gpios 信息,申请了无效中断号。所以必须走设备树这条路,把中断配置一起写进节点——这是整条链路的起点认知。
四、第二层:驱动跑通的验证
部署 overlay 并重启后,dmesg | grep -iE 'goodix|gt9' 出现三行关键日志:
Goodix-TS 0-0014: ID 1158, version: 0100
Falling back to syfs fallback for: goodix_1158_cfg.bin
input: Goodix Capacitive TouchScreen as .../i2c-0/0-0014/input/input2
判断标准:
ID 1158, version: 0100→ 芯片通信正常,I2C 地址 0x14 正确input: ... input2→ 输入设备注册成功- 没有
request IRQ failed: -22→ 中断注册成功(设备树补上了中断信息)
中间的 Falling back to ... goodix_1158_cfg.bin 不是错误,只是驱动优先去 /lib/firmware/ 找厂商配置文件没找到,回退使用设备树里的 goodix,cfg-group0 数组。
/proc/bus/input/devices 确认 Handlers=kbd event2、PROP=2(支持多点触控),设备节点为 /dev/input/event2。用 evtest /dev/input/event2 实测触摸事件,坐标正常 → 驱动层完全跑通。
五、第三层:Qt 应用层的输入配置(真正的难点)
驱动通了,触摸能用,但预装 Qt 程序不响应。这是两套独立的东西:evtest 能读到事件只证明内核层 OK,Qt 还需要被告知去读哪个设备节点。
5.1 排除权限问题
ls -l /dev/input/event2 # crw-rw---- root input
sudo chmod 666 /dev/input/event2 # 临时验证
权限不是根因(排除)。
5.2 环境变量必须加在进程实际读取的地方
在终端 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=... 对预装程序完全无效——通过 ps aux 发现程序是 /usr/local/qt-app/run.sh 脚本拉起的(bash 380 → App 540)。脚本有自己独立的环境,终端里 export 的变量对它无效。同理,环境变量加在 /etc/profile 对 systemd 服务也无效。
5.3 阅读厂商脚本,定位三个致命问题
run.sh 里发现:
问题 1:变量名 typo,设备路径是空的
export TSLIB_TSDEVICE=/dev/input/$event # 少了 x!上面定义的是 eventx
导致 TSLIB_TSDEVICE 实际是 /dev/input/,tslib 根本打不开设备。
问题 2:tslib 与 evdev 混用,互相冲突
脚本同时设了 QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS(evdev 路线)、QT_QPA_GENERIC_PLUGINS=tslib:... 和 QT_QPA_FB_TSLIB=1(tslib 路线)。tslib 面向单点(电阻屏),调用 handleMouseEvent;evdev 面向多点(电容屏),生成真正的 QTouchEvent。两者同时开启会导致坐标错乱甚至完全无响应。
问题 3:电容屏却用 tslib 配置
QT_QPA_GENERIC_PLUGINS 写成了 tslib,而 GT9157 电容屏应该用 evdevtouch。
5.4 libinput 的隐性干扰
Qt 的 eglfs/linuxfb 插件默认优先用 libinput,只有 libinput 不可用或显式禁用时才回退到 Qt 自带的 evdev 处理程序。系统若装了 libinput,它可能抢先接管但没读对设备。强制走 evdev:
export QT_QPA_FB_NO_LIBINPUT=1 # linuxfb
# 或 export QT_QPA_EGLFS_NO_LIBINPUT=1 # eglfs
六、最终修复方案(evdev 路线)
修改 /usr/local/qt-app/run.sh(先备份):
- 修变量名:
export TSLIB_TSDEVICE=/dev/input/$eventx - 注释掉所有 tslib 相关行:
QT_QPA_GENERIC_PLUGINS=tslib:...、QWS_MOUSE_PROTO=tslib、QT_QPA_EGLFS_TSLIB=1、QT_QPA_FB_TSLIB=1、TSLIB_CONFFILE、TSLIB_CALIBFILE - 改用 evdev 配置(在启动行之前):
export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/$eventx:rotate=0
export QT_QPA_GENERIC_PLUGINS=evdevtouch:/dev/input/$eventx
export QT_QPA_FB_NO_LIBINPUT=1
export QT_LOGGING_RULES=qt.qpa.input=true
重启程序后通过日志判断:
| 日志现象 | 结论 |
|---|---|
Using device + min X: 0 max X: 799 |
读到了;若仍无响应是程序代码问题 |
Adding device 但无 Using device |
设备被其他进程独占 |
完全没有 evdevtouch |
脚本没生效,确认改的是同一文件 |
坐标镜像/旋转用 rotate=90/180/270 或 invertx/inverty 单轴翻转修正。
备选(若 App 只认鼠标事件,必须走 tslib):彻底关掉 evdev,unset QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS,并 export QT_QPA_FB_DISABLE_INPUT=1 禁用 evdev 避免冲突,且必须先跑 ts_calibrate 生成 /etc/pointercal。
七、通用诊断方法论沉淀
- 分层定位,逐层验证:设备树 → 驱动 → evdev → Qt,每一层用对应工具验证(dtc / dmesg / evtest / QT_LOGGING_RULES),确认一层通了再往下走,避免跨层瞎猜。
QT_LOGGING_RULES=qt.qpa.input=true是 Qt 触摸排障的黄金开关:能直接看出 Qt 是否读到设备、是否Using device、坐标范围是否异常。- 区分驱动层与应用层:evtest 能读事件 ≠ Qt 能响应,这是两个独立世界,绝大多数"触摸不灵"卡在这里。
- 警惕进程环境隔离:脚本、systemd 服务都有独立环境,终端 export 和
/etc/profile未必生效,必须改在进程真正读取的地方。 - tslib 与 evdev 不可混用:电容屏走 evdev(多点、真 QTouchEvent),电阻屏走 tslib(单点、鼠标事件),混用必出问题。
- overlay 的 reg_format 警告是误报:看到
#address-cells==2相关警告时,先想是不是 overlay 拿不到父节点 cells,而不是真的写错 reg。 - 手动 new_device 缺中断信息:需要中断的设备尽量走设备树,把
irq-gpios/interrupts一起写进去。
八、一句话总结
这次适配真正的难点从来不在驱动,而在厂商预置脚本本身是为电阻屏(tslib)写的、且带一个变量名 typo——它把"电容屏 + evdev"这条正确路径,用一堆 tslib 配置和空设备路径堵死了。能定位到这一层,靠的不是技术直觉,而是"逐层验证 + 读源码 + 开日志"的方法论。

浙公网安备 33010602011771号