4. R-Car触摸驱动架构
中断处理,说白了就是触摸屏向主控芯片“喊报告”的方式。你想想看,手指点下去,信号从触摸屏传到驱动,再传到应用层,这中间每一步都有延迟。而中断处理,往往是整个链路里最容易出问题的一环。
我个人习惯,拿到一个新平台的触摸屏驱动,第一件事就是看中断处理函数。为什么?因为这里藏着最多的“坑”。
4.1 中断处理的基本模型
触摸屏的中断处理,通常有两种模型:
| 模型类型 | 触发方式 | 典型延迟 | 适用场景 |
|---|---|---|---|
| 边缘触发(Edge-triggered) | 电平跳变沿 | 低(微秒级) | 高精度触摸、游戏场景 |
| 电平触发(Level-triggered) | 电平持续有效 | 中(毫秒级) | 普通滑动、点击操作 |
| 共享中断(Shared IRQ) | 多个设备共用 | 高(不确定) | 低端硬件、引脚受限 |
我在项目中遇到过,某款车机用了共享中断,结果触摸屏和蓝牙模块抢中断。手指滑动时,蓝牙刚好在传输数据,触摸响应直接卡顿。嗯,后来改成独立中断,问题就解决了。
4.2 中断处理函数的“快与慢”
中断处理函数,一定要快。这是铁律。
为什么?因为中断上下文里,你不能做耗时操作。比如 I2C 读写、内存分配、打印日志,这些都不该出现在中断处理函数里。
正确的做法是:中断处理函数只做最轻量的事——读取状态寄存器、清除中断标志、然后唤醒一个工作队列或 tasklet。
// 错误示例:中断里做 I2C 读取
static irqreturn_t touch_irq_handler_bad(int irq, void *dev_id)
{
struct touch_device *ts = dev_id;
// 这里调用了 I2C 读取,耗时!
ts->read_data(ts);
input_report_key(ts->input, BTN_TOUCH, 1);
input_sync(ts->input);
return IRQ_HANDLED;
}
// 正确示例:中断只做标记,工作队列处理
static irqreturn_t touch_irq_handler_good(int irq, void *dev_id)
{
struct touch_device *ts = dev_id;
// 只清除中断标志
ts->clear_irq(ts);
// 唤醒工作队列
schedule_work(&ts->work);
return IRQ_HANDLED;
}
4.3 工作队列与 tasklet 的选择
中断下半部,有两种主流方式:工作队列(workqueue)和 tasklet。
我个人的经验是:
- tasklet:适合非常短的操作,比如更新一个状态变量。它在软中断上下文执行,不能睡眠。
- 工作队列:适合需要睡眠的操作,比如 I2C 读写、内存分配。它在进程上下文执行。
触摸屏驱动,我建议用工作队列。因为触摸数据读取,几乎必然涉及 I2C 或 SPI 通信,这些都需要睡眠等待。
4.4 中断延迟的测量方法
怎么知道你的中断处理到底慢不慢?别靠感觉,要测。
我常用的方法有两种:
- ftrace 跟踪:直接看中断处理函数的耗时分布。
- GPIO 示波器法:在中断处理函数入口和出口各拉一次 GPIO,用示波器看脉冲宽度。
第二种方法虽然土,但最直观。我在调试一款车机时,就是用示波器发现中断处理函数耗时 3.2ms,远超预期的 500μs。后来发现是 I2C 总线频率太低,改成 400kHz 后,耗时降到 800μs。
// 用 GPIO 测量中断处理耗时
static irqreturn_t touch_irq_handler(int irq, void *dev_id)
{
struct touch_device *ts = dev_id;
// 拉高 GPIO,开始计时
gpio_set_value(DEBUG_GPIO, 1);
// 实际处理
ts->clear_irq(ts);
schedule_work(&ts->work);
// 拉低 GPIO,结束计时
gpio_set_value(DEBUG_GPIO, 0);
return IRQ_HANDLED;
}
4.5 中断优先级与 CPU 亲和性
在多核系统里,中断可以绑定到特定 CPU 核心。这能避免中断在不同核心间迁移,减少缓存失效。
我建议:
- 把触摸屏中断绑定到一个独立核心(比如 CPU2 或 CPU3)。
- 避免和音频、显示等实时性要求高的中断抢核心。
# 设置中断亲和性,绑定到 CPU2
echo 4 > /proc/irq/123/smp_affinity
注意:这里的 4 是二进制 100,表示 CPU2。具体数值要看你的核心编号。
4.6 中断合并与防抖
触摸屏的物理特性决定了,一次触摸可能产生多次中断。比如手指按下时,信号会有抖动,导致中断反复触发。
处理方式有两种:
- 硬件防抖:触摸屏 IC 内部自带防抖,一般可以配置防抖时间(比如 10ms)。
- 软件防抖:在驱动里做时间戳过滤,两次中断间隔小于阈值就忽略。
我个人更倾向硬件防抖。软件防抖虽然灵活,但会增加中断处理开销。而且,如果防抖时间设置不当,反而会引入额外延迟。
// 软件防抖示例
static irqreturn_t touch_irq_handler(int irq, void *dev_id)
{
struct touch_device *ts = dev_id;
unsigned long now = jiffies;
// 如果距离上次中断不到 5ms,忽略
if (time_before(now, ts->last_irq + msecs_to_jiffies(5)))
return IRQ_HANDLED;
ts->last_irq = now;
ts->clear_irq(ts);
schedule_work(&ts->work);
return IRQ_HANDLED;
}
4.7 中断处理流程全景图
下面这张图,展示了触摸屏中断从触发到上报的完整流程。你可以对照自己的驱动,看看每一步是否都做到了最优。
4.8 常见问题与避坑指南
- 中断丢失:如果中断处理时间太长,后续中断会被丢弃。解决办法:缩短处理时间,或者使用 threaded IRQ。
- 中断风暴:触摸屏硬件故障,导致中断持续触发。解决办法:在驱动里加中断次数限制,超过阈值就禁用中断。
- 优先级反转:低优先级中断占用了 CPU,导致高优先级中断被延迟。解决办法:合理设置中断优先级,或者绑定独立核心。