3. 硬件接口基础:I2C 总线协议详解
I2C(Inter-Integrated Circuit)总线是一种由 Philips 公司(现 NXP)开发的同步、多主从、串行通信总线。它仅需两根信号线——串行数据线(SDA)和串行时钟线(SCL),即可实现多个设备之间的数据交换。在网关外设驱动开发中,I2C 常用于连接传感器、EEPROM、RTC 时钟、ADC 转换器等低速外设。理解 I2C 协议的底层机制,是编写正确、健壮的 HAL 层驱动的基础。
关键特性: I2C 是同步通信(由 SCL 时钟驱动)、半双工(同一时刻只能单向传输)、多主从(总线上可挂载多个主设备与从设备)、地址寻址(每个从设备有唯一 7 位或 10 位地址)。
3.1 物理层:SCL 与 SDA 信号线
I2C 总线由两根双向开漏(Open-Drain)信号线构成,必须通过外部上拉电阻连接到 VCC(通常为 3.3V 或 5V)。
- SCL(Serial Clock): 串行时钟线,由主设备驱动,用于同步数据传输。时钟频率决定了总线速率,标准模式为 100 kHz,快速模式为 400 kHz,高速模式可达 3.4 MHz。
- SDA(Serial Data): 串行数据线,用于传输地址、数据和控制信号。数据在 SCL 时钟的上升沿被采样,在下降沿变化(标准模式)。
由于采用开漏结构,总线具有“线与”特性:任何设备拉低总线,总线即被拉低;只有当所有设备释放总线时,总线才会被上拉电阻拉高。这种机制是 I2C 仲裁和时钟拉伸的基础。
驱动开发要点: 在嵌入式 Linux 中,I2C 控制器驱动通过操作 SoC 内部的 I2C 适配器寄存器来控制 SCL 和 SDA 的电平。HAL 层通常不直接操作 GPIO 模拟时序,而是通过
struct i2c_adapter 和 struct i2c_algorithm 来抽象硬件差异。
3.2 起始条件与停止条件
I2C 总线的数据传输由起始条件(START)开始,由停止条件(STOP)结束。这两个条件由主设备产生,是总线状态转换的关键标志。
| 条件 | 信号时序 | 说明 |
|---|---|---|
| 起始条件 (S) | SCL 为高电平时,SDA 从高电平切换到低电平 | 表示总线从空闲状态进入忙碌状态,所有从设备开始监听地址 |
| 停止条件 (P) | SCL 为高电平时,SDA 从低电平切换到高电平 | 表示总线从忙碌状态回到空闲状态,释放总线控制权 |
常见错误: 在驱动开发中,如果起始条件或停止条件的时序不满足 I2C 规范(例如 SDA 在 SCL 低电平时变化),从设备可能无法正确识别总线状态,导致通信失败或总线挂死。HAL 层应确保控制器硬件正确生成这些条件。
3.3 地址帧与读写位
在起始条件之后,主设备必须发送一个地址帧来指定要通信的从设备。I2C 使用 7 位地址(标准)或 10 位地址(扩展),后跟一个读写位(R/W)。
- 7 位地址: 地址范围 0x00 ~ 0x7F,其中 0x00 为通用呼叫地址,0x04 ~ 0x77 为用户可用地址。每个从设备在硬件设计时被赋予一个固定地址(部分可配置)。
- 读写位(R/W): 地址帧的第 8 位。0 表示主设备将要写入(发送数据到从设备),1 表示主设备将要读取(从从设备接收数据)。
- 地址传输顺序: 先发送地址的高位(MSB),最后发送读写位。从设备在接收到匹配的地址后,会在第 9 个 SCL 周期发送应答位(ACK)。
HAL 层映射: 在 Linux 内核中,
struct i2c_msg 结构体包含 addr(从设备地址)、flags(读写标志)、len(数据长度)和 buf(数据缓冲区)。驱动开发者通过填充此结构体来发起 I2C 传输。
3.4 数据帧与字节传输
当地址帧被从设备确认后,主设备开始传输数据帧。每个数据帧包含 8 位数据(一个字节),后跟一个应答位。数据传输遵循以下规则:
- 字节顺序: 每个字节的 MSB(最高位)先传输,LSB(最低位)最后传输。
- 数据长度: 每次传输的字节数没有限制,但受限于从设备的缓冲区大小和总线速率。
- 传输方向: 在写入操作中,主设备发送数据,从设备接收;在读取操作中,主设备接收数据,从设备发送。
- 时钟同步: 每个字节传输需要 9 个 SCL 时钟周期(8 位数据 + 1 位应答)。
性能考量: 在网关应用中,I2C 速率通常为 100 kHz 或 400 kHz。对于大数据量传输(如固件升级),建议使用 SPI 或更高带宽的接口。I2C 更适合小数据量的控制类通信。
3.5 应答机制(ACK/NACK)
应答机制是 I2C 协议可靠性的核心。每个字节(地址帧或数据帧)传输完成后,接收方必须在第 9 个 SCL 时钟周期内产生应答信号。
| 应答类型 | SDA 状态(第 9 周期) | 含义 | 典型场景 |
|---|---|---|---|
| ACK(应答) | 低电平(接收方拉低 SDA) | 接收成功,可以继续发送下一字节 | 从设备确认地址匹配;从设备确认数据写入成功 |
| NACK(非应答) | 高电平(接收方释放 SDA) | 接收失败或传输结束 | 主设备在读取操作最后发送 NACK 表示停止读取;从设备忙或地址不匹配 |
驱动调试要点: 如果 I2C 通信中收到 NACK,常见原因包括:从设备地址错误、从设备未上电或复位、总线被其他设备占用、时钟拉伸超时。在 HAL 层驱动中,应检查
i2c_transfer() 的返回值,并实现重试或错误恢复机制。
3.6 完整 I2C 通信流程
一个完整的 I2C 写操作或读操作遵循以下流程。以主设备向从设备写入 2 个字节为例:
- 起始条件: 主设备在 SCL 高电平时将 SDA 从高拉低。
- 发送地址帧: 主设备发送 7 位从设备地址 + 写位(0)。
- 等待 ACK: 从设备在地址匹配后拉低 SDA 作为应答。
- 发送数据字节 1: 主设备发送 8 位数据,从设备在第 9 周期发送 ACK。
- 发送数据字节 2: 主设备发送第二个字节,从设备发送 ACK。
- 停止条件: 主设备在 SCL 高电平时将 SDA 从低拉高,释放总线。
读取操作类似,但在地址帧中读写位为 1,且主设备在接收完最后一个字节后发送 NACK 以通知从设备停止发送。