☰
tp28xx_kdrv_tp9930嵌入式Linux触摸驱动移植与调试实战
2026/10/12 5:06:38 网站建设 项目流程

简介:tp28xx_kdrv_tp9930.tar.gz 是一份面向 Linux 嵌入式开发者的驱动源码包,覆盖 TP2828、TP2831 系列视频/控制芯片与 TP9930 触摸模块的适配逻辑,可用于物联网终端、工业控制等场景的底层驱动移植与调试。压缩包仅 76KB,共 14 个文件,包含 9 个 C 源文件、2 个头文件和 3 个 Makefile;其中 C 文件负责芯片初始化、数据传输与中断处理,头文件定义寄存器地址和接口常量,Makefile 则简化了内核模块的编译过程,整体结构紧凑、便于快速阅读。该资源已有 1432 人学习,属于小而精、针对性较强的参考实现。内容完整涵盖 TP2802、TP2827/TP2827C、TP2829、TP2831 等芯片驱动源文件,并配有 TP9930 相关代码,读者可直接借鉴其 probe、读写、中断处理等内核驱动写法。若能结合 Makefile 调整后加载到目标板,即可验证驱动行为,适合有一定 Linux 驱动基础、需要参考同类芯片适配方案的开发人员快速上手。

1. 拿到 tp28xx_kdrv_tp9930.tar.gz 之后的第一个问题:这个驱动包到底能干什么

在嵌入式 Linux 的触摸屏调试现场,我经常见到开发者在拿到一个以 .tar.gz 结尾的驱动源码包后,第一反应是赶紧解压找 README,然后照着里面几条命令原封不动敲一遍。tp28xx_kdrv_tp9930.tar.gz 就是这类包里非常典型的一个:它是触摸屏控制器原厂提供的 Linux 内核触摸驱动源码,tp28xx 代表驱动框架所覆盖的控制器系列,tp9930 是具体要支持的触摸 IC 型号,kdrv 即 kernel driver 的缩写。它的核心任务,是把一块通过 I2C 总线连接的电容触摸屏,变成内核 input 子系统里的标准触摸设备,让上层应用能通过 /dev/input/eventX 读到干净的坐标数据。这套方案只服务一类读者:要在自己板子上把 tp9930 触摸屏跑起来的嵌入式 Linux 工程师。无论你用 buildroot、Yocto 还是直接操作内核源码树,从解包到验证,我都会按一线调试的顺序讲清楚。

2. 解包驱动源码包:先看懂 tp28xx 驱动的文件结构与 probe 流程

拿到 tp28xx_kdrv_tp9930.tar.gz 后,第一步不是急着改代码,而是先把它解开,用十几分钟把每个文件的职责理清楚。我见过不少人在文件结构都没搞明白的情况下就动手改 Makefile,结果越改越乱。驱动包内部的文件组织,往往决定了你后面要动哪些地方,所以这一章值得先花时间。

2.1 解包命令与源码文件清单

在 Linux 主机上解包很简单:

tar -xzvf tp28xx_kdrv_tp9930.tar.gz cd tp28xx_kdrv_tp9930/ ls -l

解压之后,我习惯先用file命令确认关键源文件的类型和换行符是否正常,防止源码包在传输过程中被改坏:

file tp28xx.c tp9930.c Makefile

这一步看起来多余,但能避免后面编译时报一堆莫名其妙的\r字符错误。如果文件类型显示ASCII text,说明源码是正常的;如果出现with CRLF line terminators,就需要用dos2unix转换一遍。

典型的 tp28xx 驱动包结构大致包含这几类文件,可以用一个清单来对照:

文件 / 目录典型用途
tp28xx.c / tp28xx_ts.c驱动主逻辑:probe、中断处理、input 上报
tp9930.c / tp9930.h芯片相关:寄存器定义、初始化、固件升级
Kconfig / Makefile把驱动接入内核编译体系
*.bin / *.i触摸屏固件,可能用于启动时加载或单独升级
README原厂移植说明,只作参考,不能照抄

有些包会把主驱动和型号实现拆成多个 .c 文件,有些则把所有逻辑写在一个文件里。如果你看到目录里只有一个 tp28xx.c,不要奇怪,tp9930 的寄存器定义可能都写在头文件里了。关键是找到 Makefile 里的编译目标,确认它用的是单文件还是多文件合成的方式。

这个地方有个很容易踩的坑:主文件名和 Makefile 里的目标名可能不一致。比如 Makefile 里写的是obj-$(CONFIG_TOUCHSCREEN_TP28XX) += tp28xx_kdrv.o,但目录里根本没有 tp28xx_kdrv.c,只有 tp28xx.c 和 tp9930.c。这种情况只要看下面有没有类似tp28xx_kdrv-objs := tp28xx.o tp9930.o的写法,就能确定它用的是多文件合成一个模块的方式。

2.2 驱动入口:probe 函数如何把触摸屏"认"出来

不管是 tp28xx 还是 tp9930,I2C 触摸驱动的灵魂都在 probe 函数里。probe 做的工作可以归纳成五步:分配私有数据结构、配置复位和中断引脚、执行上电复位时序、读取芯片 ID、注册 input 设备。下面是一份典型的 probe 骨架代码:

static int tp28xx_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct tp28xx_ts *ts; int ret; ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; ts->client = client; i2c_set_clientdata(client, ts); /* 复位引脚的时序,这里以低有效为例 */ ts->reset_gpio = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ts->reset_gpio)) return PTR_ERR(ts->reset_gpio); gpiod_set_value(ts->reset_gpio, 1); msleep(20); gpiod_set_value(ts->reset_gpio, 0); msleep(80); /* 确认芯片在位:读 ID 失败多半不是驱动问题 */ ret = tp28xx_read_chip_info(ts); if (ret) return ret; /* 注册线程化中断,IRQF_ONESHOT 防止中断嵌套 */ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, tp28xx_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "tp28xx", ts); if (ret) return ret; /* 分配 input 设备并设置上报能力 */ ts->input_dev = devm_input_allocate_device(&client->dev); if (!ts->input_dev) return -ENOMEM; ... return 0; }

这段逻辑里,第二步和第三步的先后顺序非常关键。很多 tp9930 驱动翻车,就是因为先读芯片 ID 后做复位,读回来的全是 0xFF 或者 0x00,probe 直接返回 -ENODEV。正确顺序一定是先拉低复位引脚保持一段时间,再拉高释放,等芯片内部固件启动完成(通常需要 20ms 到 100ms),然后才能正常访问 I2C 寄存器。

IRQF_TRIGGER_FALLING这个标志要和设备树里的IRQ_TYPE_EDGE_FALLING保持一致,两者不一致时,中断行为会变得很奇怪,后面排查看半年也找不到原因。IRQF_ONESHOT的作用是在中断线程执行期间自动屏蔽该中断,防止新的触摸事件打断当前的事件处理,对 I2C 这种慢速总线上报场景是必要的。如果你在禁用IRQF_ONESHOT的情况下发现触摸事件偶尔丢失,可以优先怀疑这里。

2.3 输入上报的核心:理解 MT 协议 B 型上报

tp9930 是多点电容触摸屏,内核里上报触摸点用的是 MT 协议 B 型(multi-touch protocol type B),要求驱动维护 slot 状态,用input_mt_slot()和input_mt_report_slot_state()管理触点。下面是一段典型的上报代码:

static void tp28xx_report_touch(struct tp28xx_ts *ts, struct tp28xx_touch *t, int id) { input_mt_slot(ts->input_dev, id); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, t->status); if (t->status) { input_report_abs(ts->input_dev, ABS_MT_POSITION_X, t->x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, t->y); input_report_abs(ts->input_dev, ABS_MT_PRESSURE, t->pressure); } } /* 中断线程中,读完一帧触摸数据后统一上报 */ input_mt_sync_frame(ts->input_dev); input_sync(ts->input_dev);

这里的ABS_MT_POSITION_X/Y上报的是换算后的坐标,范围由设备树里的touchscreen-max-x/max-y决定。tp9930 芯片内部会上报原始坐标值,驱动需要按最大坐标比例换算到这个范围内,换算系数通常来自初始化时读取的芯片配置或设备树属性。ABS_MT_PRESSURE不是所有驱动都会报,但 tp9930 内部有压力估算,报出来可以让上层在判定"误触"时多一个依据。

B 型协议有一个容易忽略的规则:每个 slot 必须显式上报"抬起"状态,也就是把MT_TOOL_FINGER的 status 置 0,不能跳过某个 slot 直接上报下一个。如果驱动只上报按下触点、不处理抬起,上层看到的触摸点会一直悬在屏幕上,这就是后面要讲的"鬼影触点"问题的根源。

3. 把 tp28xx 驱动编进内核:树内编译、外部模块与 Kconfig 依赖

驱动源码看懂之后,接下来就是让它进到你的内核里。tp28xx_kdrv_tp9930.tar.gz 里面的驱动默认适配内核源码树,但实际项目里通常有三种接入方式,各有不同使用场景。选错方式,轻则浪费时间,重则导致内核和驱动版本互相不认。

3.1 三种编译方式怎么选

第一种是把驱动放进内核源码树内编译,将目录复制到drivers/input/touchscreen/下,修改 Kconfig 和 Makefile,最后编进 zImage。这种方式适合量产固件,驱动和内核一起发布,不用单独处理模块加载顺序,但每次改驱动都要重新编译内核,调试阶段迭代太慢。

第二种是编译成内核模块(.ko),启动后用 insmod 或 modprobe 加载。这是调试阶段最常用的一种,因为只编驱动不编内核,几秒钟就能出结果。缺点是模块依赖的内核版本、配置必须和运行中的内核完全一致,否则 insmod 直接报 "version magic" 错误。

第三种是 out-of-tree 外部模块编译,把驱动源码放在内核源码树外面,用内核的 Makefile 体系来编。很多用 buildroot、Yocto 的项目会把这类驱动作为外部包单独维护,这是最正规、最适合版本管理的做法。缺点是需要额外写一层构建脚本,第一次搭环境稍微费点时间。

我个人的工作习惯是:拿到 tp28xx_kdrv_tp9930.tar.gz 后,第一轮调试用第二种方式(外部模块编译),确认驱动能跑通、触摸能正常上报后,再切到第一种方式编进内核固化。这样既不用反复烧写整个内核,也不会在最开始就被复杂的 Kconfig 配置卡住。等逻辑稳定了再固化,风险和成本都最低。

3.2 Kconfig 配置项与依赖关系

不管是树内编译还是外部编译,驱动里的 Kconfig 都要先看懂。一个典型的 tp28xx 驱动 Kconfig 长这样:

config TOUCHSCREEN_TP28XX tristate "TP28xx series touchscreen driver" depends on I2C && INPUT && OF help Say Y here to support the TP28xx series touchscreen controllers connected via I2C, including TP9930.

这里的depends on I2C && INPUT && OF表明驱动的三个硬依赖:必须有 I2C 总线、必须有 input 子系统、必须开启设备树(OF,Open Firmware)。有些驱动还会额外依赖GPIOLIB和INPUT_MT,如果编译时发现找不到某些符号,需要回到内核配置里把这些选项先打开。

如果在menuconfig里找不到这个驱动选项,最常见原因是 Kconfig 没有被上级目录包含。打开drivers/input/touchscreen/Kconfig,确认里面有对应的一行:

source "drivers/input/touchscreen/tp28xx/Kconfig"

Makefile 的对应修改也不能漏:

obj-$(CONFIG_TOUCHSCREEN_TP28XX) += tp28xx/

也就是说,驱动目录要以一个子目录的方式整目录参与编译,而不是单独加入某个 .o 文件。我见过不少人只改了 Kconfig 忘了 Makefile,或者反过来,折腾半天 menuconfig 里就是看不到选项。

3.3 外部模块编译的完整命令

在驱动源码目录里执行外部模块编译时,核心命令是:

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

这条命令只适用于主机上跑的内核,交叉编译时必须加上 ARCH 和 CROSS_COMPILE:

make -C /path/to/kernel_source M=$(pwd) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules

注意,-C后面的内核源码路径,必须和目标板子上运行的内核是同一份源码、同一个 .config,否则编出来的 .ko 模块放到板子上会报 "invalid module format" 或 "version magic" 不匹配。很多工程师在这个地方反复翻车,其实只是内核版本字符串对不上,特别是用 git 管理内核源码时,git describe生成的版本号里带了 commit 计数,差一个 commit 都不行。

编完之后,目录下会出现tp28xx_kdrv.ko或类似名字的模块文件。把它拷贝到板子的文件系统,加载时推荐用 modprobe,它加载前会自动解析模块依赖:

insmod /lib/modules/$(uname -r)/tp28xx_kdrv.ko # 或者 modprobe tp28xx_kdrv

加载成功后,在dmesg里应该能看到驱动打印的 probe 信息、芯片 ID 和 input 设备注册信息。如果没有,先别急着怀疑驱动,用lsmod确认模块到底装进去没有,再看/dev/input/下有没有新的 event 节点出现。驱动编译环节的问题,九成以上集中在版本匹配,而不是语法错误。

4. 设备树适配:让 tp9930 在你的板子上被正确识别

驱动本身只是代码,它要真正工作,必须通过设备树描述"这块板子上哪里接了 tp9930"。设备树配错,probe 不会被触发,后面所有调试都无从谈起。这一章的每一节解决一个具体的识别环节。

4.1 设备树节点的最小写法

TP9930 这类电容触摸芯片通常挂在某个 I2C 控制器下,设备树节点需要写清楚 compatible、reg(I2C 地址)、中断引脚和复位引脚。下面是一个最小可用的节点:

&i2c3 { tp9930@5d { compatible = "tp28xx,tp9930"; reg = <0x5d>; interrupt-parent = <&gpio1>; interrupts = <13 IRQ_TYPE_EDGE_FALLING>; reset-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>; irq-gpios = <&gpio1 13 GPIO_ACTIVE_LOW>; touchscreen-max-x = <1080>; touchscreen-max-y = <2400>; }; };

这里几个属性的含义分别是:reg = <0x5d>是 tp9930 在 I2C 总线上的从机地址,由芯片的地址引脚电平决定,常见值是 0x5d 或 0x5c,具体看原理图上的地址选择电阻。interrupts = <13 IRQ_TYPE_EDGE_FALLING>表示中断引脚接到 gpio1 的第 13 脚,下降沿触发,触摸屏的中断一般配成边沿触发,不要配电平触发,否则会导致中断风暴。reset-gpios和irq-gpios是驱动里devm_gpiod_get()查找时使用的属性名,必须和驱动代码里的命名一致。

注意:设备树里的 GPIO 编号是控制器内部偏移,不是 SoC 全局编号。<&gpio1 13>指的是 gpio1 这个控制器的第 13 脚,具体对应 SoC 的哪个 ball,要查对应的 GPIO 控制器 base 偏移,千万别拿着 SoC datasheet 里的全局编号直接填。

还有一个常见的误解:interrupts和irq-gpios不是一回事。interrupts是给内核中断框架用的,irq-gpios是驱动里用来手动读取中断引脚电平用的。有些驱动只用interrupts,有些两个都要求非空。如果只配了其中一个,probe 里获取 GPIO 失败,也是常见问题。

4.2 probe 匹配机制:compatible 是怎么和驱动关联起来的

设备树节点里的compatible = "tp28xx,tp9930",驱动里对应的是of_device_id表。内核在注册 I2C 设备时,会比较设备树节点的 compatible 和驱动表里的字符串,匹配成功就调用 probe。驱动里的匹配表通常长这样:

static const struct of_device_id tp28xx_of_match[] = { { .compatible = "tp28xx,tp9930", .data = &tp9930_chip_info }, { .compatible = "tp28xx,tp2802", .data = &tp2802_chip_info }, { }, }; MODULE_DEVICE_TABLE(of, tp28xx_of_match); static const struct i2c_device_id tp28xx_i2c_id[] = { { "tp28xx,tp9930", (kernel_ulong_t)&tp9930_chip_info }, { "tp28xx,tp2802", (kernel_ulong_t)&tp2802_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, tp28xx_i2c_id);

很多人在这一步卡住,是因为设备树里写的 compatible 和驱动表里的不一致。原厂驱动的 of_match_table 里如果写的是"tp28xx,tp28xx"这种泛化字符串,而你认为"应该"写"tp28xx,tp9930",那 probe 就永远不被调用。正确做法是:打开驱动源码文件,把 of_match_table 里的字符串原封不动复制到设备树里,再编译 dtb 验证。不要凭型号和常识去猜。

注意i2c_device_id表也是同样重要。即使设备树匹配成功了,有些内核版本在 I2C 设备注册阶段仍会检查i2c_device_id表,两张表里的字符串要尽量保持一致,避免一个匹配成功另一个失败。很多驱动在 4.x 内核上只写了 of_match_table,到了 5.x 内核就出现 probe 不触发,原因就在这里。

4.3 多款机型共用一个驱动的兼容写法

tp28xx 系列驱动往往要同时支持多个型号,设备树里有两种区分方式。一种是通过 compatible 区分,比如 tp9930 和 tp2802 分别对应不同的data指针,这种方式适合芯片型号完全不同的场景。另一种是同一个 compatible,但通过 I2C 地址、最大坐标或自定义属性来区分屏幕规格。

我在实际项目中更倾向于第二种方式,因为同一个 tp9930 芯片用在不同尺寸的屏幕上时,芯片型号不变,但最大坐标、是否需要翻转方向都不同。设备树里改touchscreen-max-x、touchscreen-max-y,驱动里通过touchscreen_parse_properties()这个内核通用解析函数自动读取,就能做到一套固件兼容多种屏幕。这种方式的好处是,驱动代码不需要为每种屏幕尺寸重新编译,只需要在设备树里改几个数字。

这里也建议在设备树里预留方向属性,比如touchscreen-inverted-x、touchscreen-inverted-y、touchscreen-swapped-x-y。因为 tp9930 芯片在主板上的安装方向可能和屏幕模组的可视方向不一致,在设备树里调整方向比在驱动代码里写死更灵活,也方便产线在没有重新编译的条件下做硬件兼容。

5. 驱动加载与触摸异常排查:tp9930 调试中常见的 5 个坑

这一章是我最想写的内容。给不同项目排查触摸问题的过程中,我发现 tp28xx 驱动的问题翻来覆去就那几类,规律性很强。只要按"现象 -> 原因 -> 解决"的思路定位,大部分问题能在半小时内解决,下面按出现频率从高到低列出。

5.1 现象一:insmod / modprobe 成功,但 dmesg 里没有任何 probe 日志

原因:驱动模块加载了,但内核没有找到匹配的设备节点。最常见是设备树 compatible 写错、I2C 地址不对,或者设备树节点根本没有编进 dtb。

排查步骤:

# 确认驱动模块是否真的被加载 lsmod | grep tp28xx # 查看 I2C 总线上有没有识别出设备 i2cdetect -y 3 # 在板子上查看设备树节点是否存在 ls /proc/device-tree/i2c3/tp9930@5d/ cat /proc/device-tree/i2c3/tp9930@5d/compatible

解决:如果i2cdetect里看不到 0x5d 设备,先检查触摸屏供电、I2C 上拉电阻和地址引脚配置。如果设备树节点存在但 compatible 对不上,打开驱动源码文件,把 of_match_table 里的 compatible 字符串复制到设备树里,重新编译 dtb 后再试。这一步没有捷径,只能核对源码。

5.2 现象二:probe 成功执行了,但触摸没有任何上报

原因:probe 成功、input 设备也注册了,说明 I2C 通信正常、芯片 ID 读对了。触摸没反应,最可能出在中断线上。要么中断号不对,要么触发电平不对,要么 GPIO 被其他驱动占用。

排查方法:

# 确认中断有没有注册成功,并观察计数变化 cat /proc/interrupts | grep tp28xx # 查看中断引脚当前电平 cat /sys/kernel/debug/gpio

如果/proc/interrupts里 tp28xx 的中断计数在你按屏幕时快速增长,说明中断在触发,问题在中断处理线程里的 I2C 读取或坐标解析;如果计数一直是 0,说明中断根本没进来。触摸屏中断一般配置为下降沿触发(IRQ_TYPE_EDGE_FALLING),如果配成了高电平触发,手指按下和松开时的电平没有跳变沿,自然不会有中断产生。

5.3 现象三:坐标方向反了或镜像

原因:tp9930 芯片安装在主板上的方向和屏幕可视方向不一致,例如屏幕竖着安装,芯片的 X 轴正好对着屏幕的 Y 轴。

解决:优先在设备树里调整。Linux input 子系统支持四个方向属性:

touchscreen-inverted-x = <1>; touchscreen-inverted-y = <1>; touchscreen-swapped-x-y = <1>;

但要特别留意,这些属性只有在驱动调用了touchscreen_parse_properties()时才生效。如果你检查 tp28xx.c 的 probe,发现它是直接input_set_abs_params()写死的坐标范围,没有调用这个解析函数,那么在设备树里加这些属性就是白费功夫,只能改驱动代码里的坐标换算逻辑。

5.4 现象四:屏幕上出现"鬼影触点"或触摸点不消失

原因:这是典型的 MT 协议 B 型上报问题。手指抬起时,驱动没有上报 status = 0 的 slot 状态,内核就认为那个触点还一直按着。

解决:检查中断处理函数里的触摸解析逻辑。一个完整的 B 型上报,应该在一帧数据结束后把所有曾经活动但当前不活动的 slot 更新为抬起状态。常见做法是维护一个 bitmask 记录每个 slot 的上一次状态:

static void tp28xx_report_all(struct tp28xx_ts *ts, struct tp28xx_touch *touches, int count) { int i; /* 先上报当前有效的触点 */ for (i = 0; i < count; i++) { input_mt_slot(ts->input_dev, touches[i].id); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, 1); input_report_abs(ts->input_dev, ABS_MT_POSITION_X, touches[i].x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, touches[i].y); ts->slot_state |= (1 << touches[i].id); } /* 把上次有、这次没有的 slot 显式清掉 */ for (i = 0; i < ts->max_slots; i++) { if ((ts->slot_state & (1 << i)) && !tp28xx_find_touch(touches, count, i)) { input_mt_slot(ts->input_dev, i); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, 0); } } input_mt_sync_frame(ts->input_dev); input_sync(ts->input_dev); }

这段逻辑的顺序很重要:先上报有效触点并更新 bitmask,再遍历 bitmask 找出消失的 slot 上报抬起。很多移植代码只做了前半段,后半段漏掉,触摸屏"粘手指"的毛病就怎么调都调不掉。input_mt_sync_frame()和input_sync()缺一不可,前者同步 MT 协议的 slot 状态,后者触发整个 input 事件向上层派发。

5.5 现象五:系统休眠唤醒后触摸失效

原因:系统 suspend 时,驱动没有禁用中断,同时触摸屏电源被切断,唤醒后芯片状态混乱,中断信号虽然触发但驱动没有正确读取,触摸自然无反应。

解决:在驱动的 suspend 回调里做清理,resume 里重新复位芯片:

static int tp28xx_suspend(struct device *dev) { struct tp28xx_ts *ts = dev_get_drvdata(dev); disable_irq(ts->client->irq); gpiod_set_value(ts->reset_gpio, 0); /* 拉低复位 */ return 0; } static int tp28xx_resume(struct device *dev) { struct tp28xx_ts *ts = dev_get_drvdata(dev); gpiod_set_value(ts->reset_gpio, 1); /* 释放复位 */ msleep(50); tp28xx_read_chip_info(ts); /* 重新确认芯片状态 */ enable_irq(ts->client->irq); return 0; }

参数说明:resume 里的msleep(50)不能省,这是给芯片上电启动后留出的稳定时间,太短会导致后续 I2C 读取失败。如果你的系统用的是pm_runtime框架,还需要确认dev_pm_ops注册正确,否则 suspend/resume 回调根本不会被执行,这个坑在 5.10 之后的内核里尤其容易遇到。

6. 验证驱动的最后一关:先看中断再抓事件,最后用一段 Python 脚本做压力测试

驱动移植完之后,"能加载"不等于"能干活"。我习惯按一条固定流程验证触摸链路,每一步都有明确的预期结果,出偏差就能立刻定位到具体层。这也是我多次在量产前夜处理触摸问题总结出来的固定动作。

6.1 先看中断计数,再找事件节点

cat /proc/interrupts | grep tp28xx ls -l /dev/input/

预期是:/proc/interrupts里有 tp28xx 的中断计数,按屏幕时数字增长;/dev/input/下出现新的 eventX。中断计数在涨但没有 event 节点,说明中断处理流程后半段有问题;event 节点在但计数不动,说明中断配置根本没生效。

6.2 用 getevent 抓坐标上报

getevent -l /dev/input/eventX

按一下屏幕,应该看到ABS_MT_SLOT、ABS_MT_TRACKING_ID、ABS_MT_POSITION_X/Y和SYN_REPORT这类事件。如果POSITION_X恒等于 0 或最大值,坐标换算配置有问题;如果事件只出现一次就停住,多半是芯片供电不稳,触摸屏在反复复位。rootfs 里没有 getevent 时,可以用hexdump -C /dev/input/eventX代替,input 事件每条 16 字节,能确认事件流是否在持续产生。

6.3 用 Python 脚本验证多点跟踪

单点正常后,用一段短脚本验证多点跟踪和 slot 管理:

import struct, sys fmt = 'llHHI' size = struct.calcsize(fmt) slot = 0; x = 0; y = 0 with open(sys.argv[1], 'rb') as f: while True: data = f.read(size) if not data: break _, _, type, code, value = struct.unpack(fmt, data) if type == 3 and code == 0x2f: # ABS_MT_SLOT slot = value elif type == 3 and code == 0x35: # ABS_MT_POSITION_X x = value elif type == 3 and code == 0x36: # ABS_MT_POSITION_Y y = value elif type == 3 and code == 0x39: # ABS_MT_TRACKING_ID if value == -1: print(f'slot {slot}: release') else: print(f'slot {slot}: touch {value} at ({x},{y})')

脚本只做一件事:打印每个 slot 的 tracking ID 变化。在屏幕上画圆,观察每个 slot 的 ID 是否稳定。ID 频繁更替,说明触摸 IC 的滤波参数没调好,芯片在信号抖动;slot 顺序乱跳,说明驱动层状态管理有 bug,回到 5.4 节的逻辑去查。

6.4 压力测试是量产前的最后一道坎

脚本验证通过后,我还会再花半天做压力测试:连续滑动屏幕一小时,同时用top盯中断线程的 CPU 占用,用dmesg抓有没有i2c transfer error。触摸驱动的压力测试能过,才算真正达到量产标准。这套流程跑通之后,即使后面再出问题,我也知道该先看中断计数还是先看事件节点——这是我在好几个触控项目上花钱买来的排查习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询