4. 关键性能指标

驱动层是触摸响应延迟的「第一道关卡」。你想想看,手指点下去,第一个知道这件事的就是驱动。驱动怎么把这件事告诉上层,直接决定了延迟的起点有多高。

我个人习惯把驱动层的优化分成两条路:中断模式和轮询模式。选对了,延迟能砍掉一半。选错了,CPU 跑得冒烟,延迟还下不来。

4.1 中断模式:默认选择,但坑不少

大部分触摸屏驱动默认用中断模式。手指一碰,硬件拉高中断引脚,驱动在中断服务程序(ISR)里读数据,然后上报。听起来很直接,对吧?

但我在项目中遇到过一个问题:某款芯片的中断处理函数里,居然做了 I2C 读写的同步等待。I2C 本身就跑在 400kHz,一次读写几十微秒,再加上中断上下文不能睡眠,整个系统都被拖住了。

注意:中断服务程序里不要做耗时操作。尤其是 I2C/SPI 的同步读写、内存分配、打印日志——这些都会让触摸响应延迟飙升。

正确的做法是:ISR 里只做最轻量的事——标记一个标志位,或者唤醒一个内核线程。真正的数据读取和上报,放到下半部(Bottom Half)去做。

/* 不推荐:在 ISR 里直接读数据 */
static irqreturn_t touch_isr_bad(int irq, void *dev_id)
{
    struct touch_data *ts = dev_id;
    /* 这里做了 I2C 同步读,耗时约 2ms */
    i2c_master_recv(ts->client, ts->buf, 10);
    input_report_key(ts->input, BTN_TOUCH, 1);
    return IRQ_HANDLED;
}

/* 推荐:ISR 只唤醒工作队列 */
static irqreturn_t touch_isr_good(int irq, void *dev_id)
{
    struct touch_data *ts = dev_id;
    schedule_work(&ts->work);  /* 下半部处理 */
    return IRQ_HANDLED;
}

4.2 轮询模式:什么时候该用?

轮询模式听起来很「笨」——CPU 不停地问「你按了吗?你按了吗?」。但有些场景下,轮询反而比中断快。

为什么会这样?因为中断有开销。中断从触发到 CPU 响应,要经过异常向量表、保存上下文、跳转 ISR……这一套下来,少说几十微秒。如果触摸屏的采样率很高(比如 1000Hz),中断频繁触发,这部分开销就不可忽视了。

我做过一个实验:某款触控芯片在 1000Hz 采样率下,中断模式的平均响应延迟是 1.8ms,而轮询模式(用高精度定时器,1ms 轮询一次)只有 0.9ms。轮询反而快了一倍。

对比项 中断模式 轮询模式
延迟稳定性 受中断负载影响大 稳定,可预测
CPU 占用 低(事件触发) 高(持续占用)
适用场景 低频触摸(≤200Hz) 高频采样(≥500Hz)
实现复杂度 简单 需要调优轮询间隔

4.3 混合策略:我常用的方案

其实没必要非黑即白。我常用的方案是:中断唤醒 + 轮询读取。

具体做法:

  • 手指按下时,中断触发,唤醒 CPU
  • 唤醒后,驱动切换到轮询模式,以 1ms 间隔连续读取数据
  • 手指抬起后,切回中断等待模式

这样既避免了空闲时的 CPU 浪费,又保证了触摸过程中的低延迟。

经验之谈:轮询间隔不要设得太短。1ms 是常见值,再短的话 CPU 占用率会飙升,而且触摸芯片的采样率也跟不上。我曾经试过 0.5ms 轮询,结果 CPU 占用率从 5% 涨到 30%,延迟只降了 0.1ms——不值。

4.4 Android 特有的考量:Input HAL 与驱动交互

在 Android 系统里,驱动之上还有一层 Input HAL。这里有个容易踩的坑:驱动上报数据后,HAL 层可能会做额外的滤波或坐标转换,这些操作也会增加延迟。

我建议的做法是:

  1. 驱动层只做最原始的坐标读取,不做任何滤波
  2. 滤波和校准放到 HAL 层或上层去做
  3. 驱动和 HAL 之间使用共享内存或快速通道传递数据

嗯,这里要注意:有些厂商的 HAL 实现里,居然用了 Binder 通信来传递触摸数据。Binder 一次调用几十微秒,累积下来延迟就上去了。如果发现这种情况,建议改成共享内存 + 原子操作。

4.5 驱动层优化决策树

说了这么多,到底怎么选?我画了一张图,帮你快速决策:

驱动层优化决策树 触摸屏驱动 采样率 < 200Hz? 是 中断模式(默认) 否 考虑轮询模式 ISR 是否耗时? CPU 占用是否可接受? 使用下半部(Workqueue) ISR 只做标记,不读数据 中断唤醒 + 轮询读取 按下后切轮询,抬起后切中断 核心原则 ISR 要轻量 → 下半部处理数据 → 根据采样率选择中断/轮询

4.6 避坑指南

最后,分享几个我踩过的坑:

  • 中断共享问题:有些 SoC 的触摸中断和别的设备共享一个 IRQ 号。我曾经遇到过触摸中断被另一个设备频繁触发,导致触摸响应被「饿死」。解决办法是确认中断是否独占,或者使用 threaded IRQ。
  • 轮询定时器精度:Linux 的普通定时器精度只有 jiffy(通常 4ms),用来做 1ms 轮询根本不够。必须用高精度定时器(hrtimer)。
  • 电源管理冲突:轮询模式会阻止 CPU 进入深度睡眠。如果设备在待机时还需要响应触摸(比如唤醒屏幕),轮询模式就不合适了。
一句话总结:驱动层的优化核心就两件事——让 ISR 足够轻量,以及根据采样率选对中断/轮询策略。别小看这几微秒的优化,累积到上层就是几十毫秒的差距。