2、I2C总线基础:I2C协议时序、地址与读写操作、Linux I2C子系统架构

触摸屏与主处理器之间的通信,绝大多数情况下依赖I2C总线。无论是FT5x06还是GT911,其驱动移植的核心工作都围绕I2C展开。本章从物理层协议、设备寻址与读写流程,到Linux内核的I2C子系统软件分层,建立完整的知识基线。

2.1 I2C协议时序

I2C(Inter-Integrated Circuit)是一种同步、半双工、多主从的总线协议,仅需两根线:SCL(时钟线)和SDA(数据线)。所有设备通过开漏输出线与,依靠上拉电阻实现高电平。

2.1.1 基本时序单元

时序阶段SDA状态SCL状态说明
起始条件(S)高→低高SCL高电平时,SDA由高变低
数据采样稳定高→低SCL高电平时采样SDA,低电平时允许SDA变化
停止条件(P)低→高高SCL高电平时,SDA由低变高
应答(ACK)低高接收方在第9个SCL周期将SDA拉低
非应答(NACK)高高接收方释放SDA,主机可产生停止或重复起始
关键点:起始条件与停止条件均由主机产生。每次传输以起始条件开始,以停止条件结束。重复起始条件(Sr)可在不释放总线的情况下切换传输方向。
I2C完整写操作时序(主机→从机):
S → 从机地址(7bit) + W(0) → ACK → 寄存器地址(8bit) → ACK → 数据字节1 → ACK → 数据字节2 → ACK → ... → P
I2C 基本读写时序 I2C 写操作时序(7位地址模式) SCL SDA S 地址(7bit) W ACK Reg Addr ACK Data ACK P • 主机产生SCL时钟,SDA数据在SCL高电平时被采样。 • 每个字节后必须跟一个ACK/NACK位;NACK通常表示传输结束或从机忙。

2.2 地址与读写操作

2.2.1 7位与10位地址

I2C总线使用7位或10位设备地址。触摸屏芯片(FT5x06、GT911)均采用7位地址模式。7位地址范围0x08~0x77(0x00~0x07和0x78~0x7F为保留地址)。

芯片7位地址(典型)写地址(7位+W)读地址(7位+R)备注
FT5x060x380x700x71部分型号可通过引脚配置为0x3A
GT9110x5D / 0x140xBA / 0x280xBB / 0x29由INT引脚电平决定
地址冲突风险:当系统中同时存在多个I2C设备时,必须确保每个设备的7位地址唯一。FT5x06与GT911的默认地址不同,可共存于同一条I2C总线上。

2.2.2 读写操作流程

  1. 写操作:主机发送起始条件 → 从机地址+写位(0) → 等待ACK → 发送寄存器地址 → 等待ACK → 发送数据字节 → 等待ACK → 重复数据发送或发送停止条件。
  2. 读操作:主机先写寄存器地址(伪写),然后发送重复起始条件 → 从机地址+读位(1) → 等待ACK → 读取数据字节 → 主机发送NACK → 发送停止条件。
  3. 复合操作:一次传输中切换读写方向,使用重复起始条件(Sr)而不释放总线。
GT911 读触摸点数据示例:
// 伪写:设置寄存器地址为 0x814E(状态寄存器)
S → 0xBA(写) → ACK → 0x81 → ACK → 0x4E → ACK → Sr
// 读操作:从 0x814E 开始连续读取 1 字节状态 + 5点数据
S → 0xBB(读) → ACK → 读状态字节 → ACK → 读点1数据(7字节) → ACK → ... → 读点5数据 → NACK → P
I2C 复合读写操作流程 I2C 复合读写操作流程(读触摸点数据) 起始 S 写地址+W ACK 写寄存器地址 ACK 重复起始 Sr 读地址+R ACK 读数据字节 ACK 读更多数据 NACK 停止 P • 复合操作的关键:在伪写之后不发送停止条件,而是直接发送重复起始条件(Sr)切换为读模式。 • 读最后一个字节时主机发送NACK,告诉从机不再需要更多数据,随后发送停止条件。

2.3 Linux I2C子系统架构

Linux内核将I2C子系统分为三层,驱动开发者主要与I2C核心层和I2C设备驱动层打交道。

2.3.1 三层架构总览

层次文件/目录职责开发者关注度
I2C设备驱动drivers/input/touchscreen/实现具体设备(FT5x06、GT911)的初始化、配置、数据读取、中断处理★★★★★
I2C核心层drivers/i2c/i2c-core.c提供总线注册、设备/驱动匹配、数据传输模板(i2c_transfer)、SMBus兼容★★★☆☆
I2C总线适配器drivers/i2c/busses/硬件相关的I2C控制器驱动,操作SCL/SDA寄存器,产生时序★★☆☆☆

2.3.2 关键数据结构与API

  • struct i2c_client:代表一个I2C从设备,包含地址、适配器指针、设备名称等。
  • struct i2c_driver:代表I2C设备驱动,包含probe、remove、id_table等回调。
  • struct i2c_adapter:代表I2C总线适配器(控制器),提供master_xfer函数指针。
  • i2c_transfer():核心传输函数,发送一个或多个i2c_msg消息。
  • i2c_smbus_read_byte_data() / i2c_smbus_write_byte_data():SMBus兼容的便捷读写接口。
FT5x06 驱动中典型的I2C读操作:
static int ft5x06_i2c_read(struct i2c_client *client,
                           u8 reg, u8 *data, u8 len)
{
    struct i2c_msg msg[2];
    int ret;

    msg[0].addr = client->addr;
    msg[0].flags = 0;               // 写
    msg[0].len = 1;
    msg[0].buf = ®

    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;        // 读
    msg[1].len = len;
    msg[1].buf = data;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret == 2)
        return 0;
    else
        return -EIO;
}
Linux I2C 子系统三层架构 Linux I2C 子系统三层架构 I2C 设备驱动层 ft5x06_ts.c gt911_ts.c 其他I2C设备驱动 input子系统 I2C 核心层 (i2c-core.c) i2c_transfer() 设备/驱动匹配 SMBus兼容层 sysfs I2C 总线适配器层 (硬件相关) i2c-imx.c (i.MX) i2c-s3c2410.c (Samsung) i2c-omap.c (TI) 驱动开发者只需编写设备驱动层代码,调用核心层API i2c_transfer() 与硬件通信。

2.4 驱动开发中的I2C编程要点

实践建议:
  • 优先使用 i2c_smbus_read_byte_data() 和 i2c_smbus_write_byte_data() 进行单字节寄存器读写,代码更简洁。
  • 对于多字节连续读取(如触摸点坐标),使用 i2c_transfer() 配合两个 i2c_msg 实现复合操作。
  • 在probe函数中通过 i2c_check_functionality() 检查适配器是否支持所需功能(如I2C_FUNC_I2C)。
  • 设备树中需正确配置 reg 属性(7位I2C地址)和 interrupts 属性。
常见陷阱:
  • I2C地址在设备树中填写7位地址(如0x38),而非8位读写地址(0x70/0x71)。
  • 某些触摸芯片(如GT911)在上电后需要等待至少100ms才能进行I2C通信,否则可能无ACK响应。
  • I2C时钟频率(通常100kHz~400kHz)需与触摸芯片规格匹配,频率过高可能导致数据错位。