3、硬件链路分析:从触摸IC到主控的I2C/SPI总线延迟排查

触摸屏的响应延迟,很多时候不是算法的问题,而是数据在总线上堵车了。我见过不少团队,软件优化做了好几轮,延迟就是降不下来,最后拿示波器一抓——I2C时钟才100kHz,传输一帧数据要花掉好几毫秒。你说这能快得起来吗?

这一章,我们就从触摸IC到主控的物理链路入手,把I2C和SPI总线上的延迟一处处揪出来。

3.1 触摸数据是怎么从IC跑到主控的?

先看一张整体流程,心里有个谱。

触摸数据硬件链路流程图 触摸IC (FT5x06/GT911) I2C/SPI 主控MCU (STM32/ESP32) DMA/中断 内存缓冲区 (触摸数据帧) 轮询/回调 APP 延迟分解(典型值) ① 触摸IC内部扫描 + 滤波:1~3ms ② I2C/SPI总线传输:0.5~5ms(取决于速率和帧大小) ③ 主控中断响应 + DMA传输:0.1~0.5ms ④ 应用层处理 + 渲染:1~5ms ⑤ 总延迟 = ①+②+③+④,通常 3~12ms 其中②是最大的变量,也是我们优化的重点

从图上能看出来,触摸IC采集到原始数据后,通过I2C或SPI总线发给主控。主控收到中断信号,用DMA把数据搬到内存,然后应用层取走处理。每个环节都有延迟,但总线传输往往是最大的瓶颈。

3.2 I2C vs SPI:谁更适合触摸屏?

很多开发板默认用I2C,因为引脚少、接线简单。但I2C的速率上限摆在那里——标准模式100kHz,快速模式400kHz,高速模式3.4MHz。而SPI轻轻松松跑到几十MHz。

我做过一个对比测试,同样传输一帧触摸数据(约40字节),结果如下:

总线类型 时钟频率 传输时间(40字节) 适用场景
I2C 标准模式 100 kHz ~3.6 ms 低功耗、低速设备
I2C 快速模式 400 kHz ~0.9 ms 大多数消费电子
I2C 高速模式 3.4 MHz ~0.11 ms 高性能触摸屏
SPI 模式0/3 10 MHz ~0.032 ms 工业/车载触摸
SPI 模式0/3 40 MHz ~0.008 ms 超低延迟场景
注意:I2C的传输时间包含了地址位、控制位、应答位和起始/停止条件。实际有效数据吞吐量只有理论值的60%~70%。SPI没有这些开销,效率高得多。

3.3 实战排查:用示波器抓总线波形

理论说再多,不如动手测一测。我个人习惯的做法是这样的:

  1. 找到测试点——触摸IC的SCL/SDA(I2C)或SCLK/MOSI/MISO(SPI)引脚,最好在靠近主控端测量。
  2. 设置触发条件——用触摸中断信号作为触发源,抓取中断后的第一笔总线传输。
  3. 测量传输时间——从起始条件到停止条件的时间差,就是一帧数据的传输延迟。
  4. 检查信号质量——看上升沿/下降沿是否陡峭,有没有毛刺或振铃。

我曾经在一个项目里发现,I2C的SCL线上挂了两个上拉电阻,导致上升沿变得很缓,400kHz的时钟根本跑不上去。去掉一个电阻后,波形立马干净了,传输时间从1.2ms降到了0.4ms。

3.4 常见延迟陷阱与对策

在实际排查中,有几个坑特别容易踩到:

  • 时钟频率设置错误——主控的I2C外设时钟分频配置不对,实际速率只有目标值的一半。用示波器量一下SCL频率就知道了。
  • ACK等待超时——触摸IC处理内部数据时,可能会拉低SCL延长时钟(时钟拉伸)。如果主控的ACK超时设置太短,会反复重试,白白浪费几百微秒。
  • 中断响应延迟——主控收到触摸中断后,如果正在处理高优先级中断,响应时间可能被拖到几百微秒甚至几毫秒。建议把触摸中断设为最高优先级。
  • DMA配置不当——用CPU逐字节读取I2C数据,每读一个字节就要进一次中断,效率极低。用DMA+环形缓冲区,一次传输完成再通知CPU。
小技巧:如果硬件支持,把I2C换成SPI是最直接的优化手段。我有个项目把I2C 400kHz换成SPI 20MHz后,总线传输时间从0.9ms降到了0.02ms,几乎可以忽略不计。

3.5 代码级排查:检查驱动中的传输参数

硬件没问题了,还得看看驱动代码里有没有埋雷。下面是一段常见的I2C读取代码,注意看注释里的坑:

// 触摸IC读取函数(I2C示例)
int touch_read_reg(uint8_t reg_addr, uint8_t *buf, uint16_t len)
{
    struct i2c_msg msg[2];
    int ret;
    
    // 写寄存器地址
    msg[0].addr = TOUCH_I2C_ADDR;
    msg[0].flags = 0;               // 写
    msg[0].len = 1;
    msg[0].buf = &reg_addr;
    
    // 读数据
    msg[1].addr = TOUCH_I2C_ADDR;
    msg[1].flags = I2C_M_RD;        // 读
    msg[1].len = len;
    msg[1].buf = buf;
    
    // 注意:这里用了重复起始条件(restart),
    // 如果主控不支持,会退化成stop+start,多花几十微秒
    ret = i2c_transfer(TOUCH_I2C_BUS, msg, 2);
    if (ret != 2) {
        // 我曾经遇到过这里返回-110(超时),
        // 原因是触摸IC在忙,时钟拉伸太久
        return -EIO;
    }
    
    return 0;
}

排查时重点关注几个参数:

  • i2c_transfer的超时时间——设太短会频繁超时重试
  • 是否支持重复起始条件——不支持的话每次传输多花一个stop+start的时间
  • 单次读取长度——有些触摸IC支持批量读取,一次读完整帧,比逐字节读快得多

3.6 总结一下排查清单

嗯,这一章的内容差不多就这些。最后给一张排查清单,下次遇到触摸延迟问题,按这个顺序查一遍:

排查项 检查方法 典型优化手段
总线时钟频率 示波器测量SCL/SCLK 调整分频系数,匹配IC最高速率
信号完整性 观察上升沿/下降沿 调整上拉电阻、减少总线电容
传输帧大小 读取IC数据手册 启用批量读取模式
中断响应时间 GPIO toggling + 逻辑分析仪 提高中断优先级,减少嵌套
DMA配置 检查DMA传输完成标志 启用DMA,关闭CPU轮询
驱动超时参数 查看i2c_transfer返回值 适当增加超时时间,避免误重试