I2C协议基础(上):物理层与协议层核心
各位同学,今天我们来聊聊I2C通信的基础。说实话,很多初学者一上来就写代码,结果OLED死活不亮,最后发现是I2C时序出了问题。我个人习惯是,先把物理层和协议层搞明白,再动手写驱动。这样出了问题,你才知道往哪个方向查。
一、I2C总线物理层特性
I2C总线只有两根线:SDA(数据线)和SCL(时钟线)。别看线少,里面的门道可不少。
1. SDA与SCL:开漏输出与线与逻辑
I2C总线的SDA和SCL都是开漏输出结构。什么叫开漏?说白了,就是引脚只能拉低到GND,不能主动输出高电平。高电平靠外部上拉电阻提供。
这种设计有个好处:多个设备可以共用一根线,谁想拉低就拉低,不会短路。这就是所谓的“线与”逻辑——只要有一个设备拉低,总线就是低电平。
2. 上拉电阻:选多大才合适?
上拉电阻的取值直接影响通信质量和功耗。选小了,功耗大;选大了,上升沿变缓,高速通信容易出错。
| 总线电容 | 推荐上拉电阻 | 适用场景 |
|---|---|---|
| < 100 pF | 4.7 kΩ ~ 10 kΩ | 低速、短距离、设备少 |
| 100 pF ~ 400 pF | 2.2 kΩ ~ 4.7 kΩ | 标准模式(100 kHz) |
| > 400 pF | 1 kΩ ~ 2.2 kΩ | 快速模式(400 kHz) |
STM32F103的I2C外设,标准模式用4.7kΩ基本没问题。但如果你像我一样,喜欢把I2C频率调到400kHz,建议换成2.2kΩ。嗯,这里要注意:上拉电阻不是越小越好,太小的电阻会让总线拉低时的电流过大,可能损坏引脚。
二、I2C协议层:通信的“交通规则”
物理层搞定了,接下来看协议层。I2C协议定义了设备之间怎么“说话”。我把它拆成四个核心要素:起始条件、停止条件、数据有效性、应答机制。
1. 起始条件(Start Condition)
SCL为高电平时,SDA从高电平跳变到低电平。这就是起始信号。主设备发出这个信号后,总线上的所有从设备都知道:要开始通信了,准备好接收地址。
2. 停止条件(Stop Condition)
SCL为高电平时,SDA从低电平跳变到高电平。这就是停止信号。主设备发出这个信号,表示通信结束,总线释放。
关键点:起始和停止条件都是由主设备产生的。从设备不能主动发起或终止通信。我见过有人写代码时忘了发停止条件,结果总线一直被占用,其他设备无法通信。
3. 数据有效性
I2C协议规定:SCL为高电平时,SDA上的数据必须稳定。数据只能在SCL为低电平时改变。你想想看,如果SCL高电平时SDA变了,那就会被误判为起始或停止条件。
每个数据字节是8位,从最高位(MSB)开始传输。发送完8位数据后,第9个SCL时钟用于应答位。
4. 应答机制(ACK/NACK)
这是I2C协议里最容易出问题的地方。每发送一个字节,接收方必须在第9个时钟周期拉低SDA,表示“收到了”。如果接收方不拉低(SDA保持高电平),那就是NACK,表示“没收到”或“不想收”。
| 应答类型 | SDA状态(第9个SCL) | 含义 |
|---|---|---|
| ACK | 低电平 | 字节接收成功,继续发送 |
| NACK | 高电平 | 接收失败或传输结束 |
三、I2C通信的完整流程
把上面四个要素串起来,一次完整的I2C通信流程是这样的:
- 主设备发出起始条件:总线被占用
- 主设备发送7位从设备地址 + 1位读写位(0=写,1=读)
- 从设备应答ACK:表示“我在,我可以通信”
- 主设备发送/接收数据字节,每个字节后跟一个应答位
- 主设备发出停止条件:总线释放
以OLED驱动为例,写一个字节到SSD1306控制器的流程:
// 伪代码:I2C写一个字节到OLED
I2C_Start(); // 1. 起始条件
I2C_SendByte(0x78 << 1 | 0); // 2. 地址0x3C + 写位(0)
I2C_WaitAck(); // 3. 等待ACK
I2C_SendByte(0x00); // 4. 发送控制字节(命令模式)
I2C_WaitAck(); // 5. 等待ACK
I2C_SendByte(0xAF); // 6. 发送命令(开显示)
I2C_WaitAck(); // 7. 等待ACK
I2C_Stop(); // 8. 停止条件
我的习惯:写I2C驱动时,我会在每步后面加一个超时判断。比如等待ACK时,如果超过一定时间没收到,就主动发停止条件并报错。这样即使从设备死机,主设备也不会卡死。这个习惯救过我很多次。
四、常见故障与定位思路
学完理论,咱们看看实际调试中会遇到哪些坑。我整理了一个表格,方便你对照排查:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| SCL/SDA一直为高 | 上拉电阻缺失或虚焊 | 万用表测电阻两端电压 |
| 起始条件后无ACK | 地址错误或从设备未上电 | 核对地址,检查电源 |
| 数据传一半卡住 | 时钟拉伸或从设备忙 | 示波器看SCL是否被拉低 |
| 显示花屏/乱码 | 时序太快或上拉电阻不合适 | 降低I2C频率,调整上拉 |