1. 触摸屏基础:从物理原理到驱动接口

做触摸驱动这些年,我见过不少工程师一上来就翻 datasheet 调寄存器,结果连触摸屏是电阻还是电容都没搞清楚。嗯,这其实是个坑。今天咱们先把基础打牢,后面写代码时才不会跑偏。

1.1 电阻屏 vs 电容屏:原理与实战差异

先说电阻屏。它的结构很简单——两层透明导电薄膜中间夹着微小的隔离点。你用手指或者任何硬物按下去,上层薄膜就会和下层接触,形成一个分压电路。控制器检测到电压变化,就能算出触摸点的位置。

核心要点:电阻屏只认「压力」,不认「材质」。戴手套、用笔尖、甚至用信用卡都能操作。这在工业设备和医疗仪器上很常见。

我在一个工控项目里遇到过这种情况:现场环境有油污,操作员必须戴厚手套。电容屏根本没法用,最后只能上电阻屏。所以别觉得电阻屏过时了,它有自己的生存空间。

电容屏就完全不一样了。它利用的是人体电场和屏幕表面电极之间的耦合效应。你的手指一靠近,就会吸走一小部分电流,控制器通过检测四个角落的电流变化来定位触摸点。

对比项 电阻屏 电容屏
检测原理 压力导致物理接触 电场耦合改变电容
触控介质 任何硬物 导电物体(手指、电容笔)
多点触摸 基本不支持(单点为主) 原生支持(2~10点)
透光率 较低(约80%) 较高(约90%以上)
耐用性 易磨损(薄膜结构) 坚固(玻璃面板)
典型场景 工控、医疗、POS机 手机、平板、车载

说白了,现在消费电子里几乎全是电容屏。但做驱动适配时,你还是要先确认硬件类型——我见过有人把电阻屏的驱动硬套在电容屏上,结果触摸数据完全乱套。

1.2 多点触摸协议:MT Protocol A 与 B

Linux 内核里,多点触摸协议分两种:Protocol A 和 Protocol B。这个区分很关键,选错了协议,上层应用可能收不到正确的触摸事件。

Protocol A:无状态上报

Protocol A 是早期的方案。每次扫描完所有触摸点后,驱动会一次性上报所有点的坐标。它不区分「这个点是新出现的」还是「之前那个点移动了」。每次上报都是一份完整的快照。

// Protocol A 上报示例
ABS_MT_POSITION_X  x1
ABS_MT_POSITION_Y  y1
SYN_MT_REPORT
ABS_MT_POSITION_X  x2
ABS_MT_POSITION_Y  y2
SYN_MT_REPORT
SYN_REPORT

这种方式的优点是实现简单,缺点是数据量较大,而且上层需要自己做「点跟踪」——也就是判断哪个点是哪个。我早期做的一个单点触摸项目用的就是 Protocol A,当时觉得够用,后来升级到多点才发现坑。

Protocol B:带 ID 的跟踪上报

Protocol B 引入了 TRACKING_ID。每个触摸点都有一个唯一的 ID,驱动只上报变化的部分。比如手指没动,就不重复上报坐标;手指抬起了,就上报一个 TRACKING_ID = -1 表示离开。

// Protocol B 上报示例
ABS_MT_TRACKING_ID  45
ABS_MT_POSITION_X   x1
ABS_MT_POSITION_Y   y1
SYN_MT_REPORT
ABS_MT_TRACKING_ID  46
ABS_MT_POSITION_X   x2
ABS_MT_POSITION_Y   y2
SYN_MT_REPORT
SYN_REPORT

// 手指抬起时
ABS_MT_TRACKING_ID  -1
SYN_MT_REPORT
SYN_REPORT

我的建议:新项目一律用 Protocol B。它更省带宽,上层处理也更方便。除非你的触摸控制器实在太老,不支持硬件 ID 分配,否则别碰 Protocol A。

我曾经在一个平板项目里用了 Protocol A,结果上层手势识别库(比如 libinput)处理多点缩放时总是卡顿。换成 Protocol B 后,问题立刻消失。原因就是 Protocol A 缺少 ID,上层需要花大量 CPU 去做点匹配。

1.3 I2C 与 SPI 接口:选哪个?

触摸屏和主控之间的通信接口,主流就两种:I2C 和 SPI。选哪个,取决于你的场景。

I2C:引脚少,速度够用

I2C 只需要两根线(SCL、SDA),支持多设备挂载。标准模式 100kHz,快速模式 400kHz,高速模式能到 3.4MHz。对于大多数触摸屏(尤其是电容屏),I2C 完全够用。

// I2C 设备树节点示例
touchscreen@38 {
    compatible = "goodix,gt911";
    reg = <0x38>;
    interrupt-parent = <&gpio1>;
    interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
    touchscreen-size-x = <1024>;
    touchscreen-size-y = <600>;
};

SPI:速度快,适合高刷新率

SPI 需要 4 根线(MOSI、MISO、SCLK、CS),全双工通信,速度轻松上几十 MHz。如果你的触摸屏需要高报点率(比如 200Hz 以上),或者分辨率特别高(比如 4K 屏),SPI 是更好的选择。

// SPI 设备树节点示例
touchscreen@0 {
    compatible = "ilitek,ili2511";
    reg = <0>;
    spi-max-frequency = <10000000>;
    interrupt-parent = <&gpio2>;
    interrupts = <10 IRQ_TYPE_EDGE_RISING>;
    touchscreen-size-x = <1920>;
    touchscreen-size-y = <1080>;
};
接口 引脚数 速度 典型场景
I2C 2 100kHz~3.4MHz 手机、平板、车载
SPI 4 10MHz~50MHz 高刷触摸屏、大屏

注意:I2C 的地址冲突是个常见坑。多个 I2C 设备挂在同一总线上时,地址不能重复。我遇到过一块板子上触摸屏和传感器用了同一个地址,结果两个设备都工作异常。排查了整整一天才找到原因。

1.4 本章知识体系总览

下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引,后面写驱动时随时回来对照。

触摸屏驱动适配知识体系(第1章) 触摸屏类型 多点触摸协议 通信接口 电阻屏 电容屏 Protocol A Protocol B I2C SPI 电阻屏关键属性 压力检测 单点为主 电容屏关键属性 电场耦合 多点原生支持 接口选择依据 引脚数 vs 速度 报点率需求 💡 驱动适配核心:先确认硬件类型 → 再选协议 → 最后配接口 三者选错任何一个,触摸功能都可能无法正常工作

1.5 避坑指南:我踩过的几个坑

最后分享几个实战中容易忽略的点:

  • 中断引脚配置错误:触摸屏的中断触发方式(上升沿、下降沿、电平触发)必须和控制器匹配。我曾经把下降沿触发配成了上升沿,结果触摸屏完全没反应。查了两天才发现是设备树里 IRQ_TYPE_EDGE_FALLING 写成了 IRQ_TYPE_EDGE_RISING。
  • I2C 时序不匹配:有些触摸控制器对 I2C 时钟频率很敏感。我遇到过一块屏在 400kHz 下工作正常,换到 100kHz 反而丢数据。后来发现是控制器的内部 FIFO 在低速下超时了。
  • Protocol B 的 ID 管理:如果触摸控制器不支持硬件 ID 分配,驱动里要自己维护 ID 池。我见过一个驱动直接把 TRACKING_ID 设成固定值,结果多点触摸时所有点都认为是同一个手指。

做驱动适配时,先看芯片手册里的「Protocol」章节。如果手册提到 TRACKING_ID 或 CONTACT_ID,那大概率支持 Protocol B。如果只提坐标上报,那多半是 Protocol A。

好了,这一章的基础知识就到这里。记住:触摸屏驱动适配的核心不是写代码,而是理解硬件怎么工作、协议怎么通信。后面几章我们会一步步把这些知识落地到实际的驱动代码里。