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
S → 从机地址(7bit) + W(0) → ACK → 寄存器地址(8bit) → ACK → 数据字节1 → ACK → 数据字节2 → ACK → ... → P
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) | 备注 |
|---|---|---|---|---|
| FT5x06 | 0x38 | 0x70 | 0x71 | 部分型号可通过引脚配置为0x3A |
| GT911 | 0x5D / 0x14 | 0xBA / 0x28 | 0xBB / 0x29 | 由INT引脚电平决定 |
地址冲突风险:当系统中同时存在多个I2C设备时,必须确保每个设备的7位地址唯一。FT5x06与GT911的默认地址不同,可共存于同一条I2C总线上。
2.2.2 读写操作流程
- 写操作:主机发送起始条件 → 从机地址+写位(0) → 等待ACK → 发送寄存器地址 → 等待ACK → 发送数据字节 → 等待ACK → 重复数据发送或发送停止条件。
- 读操作:主机先写寄存器地址(伪写),然后发送重复起始条件 → 从机地址+读位(1) → 等待ACK → 读取数据字节 → 主机发送NACK → 发送停止条件。
- 复合操作:一次传输中切换读写方向,使用重复起始条件(Sr)而不释放总线。
GT911 读触摸点数据示例:
// 伪写:设置寄存器地址为 0x814E(状态寄存器)
S → 0xBA(写) → ACK → 0x81 → ACK → 0x4E → ACK → Sr
// 读操作:从 0x814E 开始连续读取 1 字节状态 + 5点数据
S → 0xBB(读) → ACK → 读状态字节 → ACK → 读点1数据(7字节) → ACK → ... → 读点5数据 → NACK → P
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;
}
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)需与触摸芯片规格匹配,频率过高可能导致数据错位。