3. I2C协议基础(下):时序图深度解析、7位地址与10位地址、标准模式与快速模式、多主机通信简介

上一节我们把I2C的协议框架搭起来了。这一节,咱们得钻进示波器的波形里,看看那些高低电平到底在说什么。说白了,I2C的时序就是一套“谁在什么时候拉低谁”的规矩。搞懂了这些,你调OLED驱动时心里就有底了。

3.1 I2C时序图深度解析

先看最核心的四个信号:起始条件、停止条件、数据有效性、应答信号。我习惯把这四个称为“I2C的四大金刚”。

信号 SCL状态 SDA变化 说明
起始条件 (S) 高电平 从高→低 SCL高时,SDA被拉低,总线开始通信
停止条件 (P) 高电平 从低→高 SCL高时,SDA被释放变高,总线释放
数据有效 高电平 必须稳定 SCL高时采样SDA,数据不能变
应答 (ACK/NACK) 第9个脉冲 从机拉低(ACK)或释放(NACK) 主机释放SDA,从机控制

你想想看,为什么起始条件要在SCL高时拉低SDA?因为平时总线空闲时,SCL和SDA都是高电平。如果SCL低时拉低SDA,那跟普通数据位就没区别了。这个设计很巧妙——用了一个“非法”的电平变化来宣告通信开始。

关键点:I2C总线上,数据只在SCL低电平时才能改变。SCL高电平时,SDA必须保持稳定。这是整个协议的基础。

我在项目中遇到过一个问题:OLED屏幕偶尔不显示,用逻辑分析仪抓波形,发现起始条件之后SDA莫名其妙多了一个毛刺。查了半天,原来是STM32的GPIO配置成了开漏输出,但外部上拉电阻焊错了,阻值太大,上升沿太慢。嗯,这里要注意——上拉电阻不是随便选的。

3.2 7位地址与10位地址

I2C设备地址分两种:7位和10位。咱们用的OLED屏,基本都是7位地址。比如SSD1306的默认地址是0x78(7位地址左移一位后的写地址),实际上它的7位地址是0x3C。

为什么会有这种“左移一位”的混乱?我刚开始学的时候也被绕晕了。其实很简单:

  • 7位地址:比如0x3C,占用高7位,最低位是读写位(0=写,1=读)
  • 8位地址:7位地址左移1位 + 读写位,比如0x78(写)或0x79(读)

很多数据手册里写的是8位地址,但你在代码里配置I2C外设时,填的是7位地址。这个坑我踩过——有一次把0x78直接填进I2C_OwnAddress寄存器,结果设备死活不响应。

避坑指南:我曾经因为地址搞混,浪费了整整一个下午。记住:STM32的I2C外设地址寄存器填7位地址,发送时硬件会自动左移并加上读写位。如果你手动左移了,那就错了。

10位地址用得少,但了解一下没坏处。它的格式是:起始条件后,先发11110xx(xx是10位地址的高2位)+ 读写位,然后再发剩下的8位地址。说白了就是分两帧发完。咱们OLED驱动基本用不上,知道就行。

3.3 标准模式(100kHz)与快速模式(400kHz)

I2C有两种常用速率:标准模式100kHz,快速模式400kHz。区别不只是快慢,时序参数也有差异。

参数 标准模式 (100kHz) 快速模式 (400kHz)
SCL频率 100kHz 400kHz
SCL低电平时间 (tLOW) ≥4.7μs ≥1.3μs
SCL高电平时间 (tHIGH) ≥4.0μs ≥0.6μs
上升时间 (tr) ≤1000ns ≤300ns
下降时间 (tf) ≤300ns ≤300ns

你可能会问:为什么快速模式的上升时间要求更严格?因为频率高了,每个周期变短,如果上升沿太慢,信号还没稳定就到了采样点,数据就错了。我调试一块LCD屏时,把I2C设成400kHz,结果屏幕闪烁。用示波器一看,上升沿都快1μs了——上拉电阻太大。换成4.7kΩ的电阻,问题解决。

个人经验:STM32F103的I2C外设,标准模式用4.7kΩ上拉电阻没问题。快速模式建议用2.2kΩ或1kΩ。如果总线上挂的设备多,电阻还要更小。具体算一下:Rmin = (VDD - VOL) / IOL,Rmax = tr / (Cb × 0.8473)。

3.4 多主机通信简介

I2C支持多个主机挂在同一条总线上。但咱们做OLED驱动,基本用不上——一个STM32控制一个OLED,单主机就够了。不过了解一下原理,万一以后做复杂系统呢?

多主机通信的核心是仲裁。两个主机同时拉低SDA时,谁先释放谁就输了。仲裁在硬件层面完成,你写代码时基本感觉不到。

具体流程是这样的:

  1. 两个主机都检测到总线空闲,同时发出起始条件
  2. 它们开始发送数据,每个bit都拉低SDA
  3. 如果一个主机想拉高SDA(发1),但另一个主机在拉低(发0),SDA实际是低电平
  4. 想发1的那个主机检测到SDA与自己预期不符,就知道仲裁失败,立即释放总线
  5. 仲裁胜出的主机继续通信,失败的主机稍后重试

说白了,就是“谁先发0谁赢”。因为I2C是线与逻辑,低电平优先。

注意:仲裁只在SCL高电平时发生。而且仲裁不会丢失数据——失败的主机在仲裁过程中没有破坏任何数据,因为它只是“想发1但没发出去”。

我记得有一次做多主机项目,两个STM32通过I2C交换数据。其中一个主机总是莫名其妙地卡死。查了三天,发现是中断优先级没配好——仲裁失败后的重试逻辑被高优先级中断打断了。嗯,多主机通信的软件设计比单主机复杂得多。

3.5 本章知识结构图

下面这张图把I2C协议的核心要素串起来了。你可以把它当作调试时的速查表。

I2C协议核心知识结构 I2C协议核心知识结构 I2C协议 时序图解析 地址机制 速率模式 多主机通信 起始/停止条件 数据有效性 应答机制 7位地址 10位地址 读写位组合 标准模式100kHz 快速模式400kHz 上拉电阻选择 总线仲裁 线与逻辑 重试机制 调试要点 • 用逻辑分析仪抓取SDA/SCL波形,确认起始条件、地址、应答信号完整 • 检查上拉电阻:标准模式4.7kΩ,快速模式2.2kΩ

这张图把I2C协议的四个核心维度拆开了。调试时,你可以对照着检查:时序对不对?地址配没配错?速率和上拉电阻匹配吗?如果是多主机,仲裁逻辑有没有问题?

我的调试习惯:拿到一块新OLED屏,先用逻辑分析仪抓一下I2C波形。看起始条件、地址字节、应答位、数据字节、停止条件——五个信号都对了,再开始写驱动代码。这一步能省掉后面80%的调试时间。