2、I2C通信协议详解:I2C总线时序、从机地址、读写操作、时钟拉伸、速率选择

各位同学,咱们今天来聊聊I2C。说实话,在热电堆传感器这个领域,I2C是绝对的主角。你随便拿一个数字输出的红外温度传感器,比如MLX90614、TMP117,背后基本都是I2C在干活。我刚开始搞嵌入式那会儿,总觉得I2C简单,不就是两根线嘛。结果呢?被时序坑过,被地址搞晕过,还被时钟拉伸折磨过。今天我就把这些坑一个个给你填平。

2.1 I2C总线长什么样?

I2C总线就两根线:SCL(时钟线)和SDA(数据线)。这两根线都是开漏输出,所以必须接上拉电阻。我个人习惯用4.7kΩ,但具体要看总线电容和速率。你想想看,如果总线太长,电容大了,上拉电阻就得小一点,不然上升沿太慢,信号就糊了。

总线上可以挂多个设备。每个设备都有一个唯一的地址。主机(通常是MCU)发起通信,从机(比如传感器)响应。嗯,这里要注意:I2C是多主机总线,但咱们做传感器驱动,基本只用单主机模式,简单可靠。

2.2 时序细节:别小看这些波形

I2C的时序其实很固定。我直接给你拆解成几个关键动作:

  • 起始条件:SCL高电平时,SDA从高变低。这是通信开始的标志。
  • 停止条件:SCL高电平时,SDA从低变高。通信结束。
  • 数据位传输:SCL低电平时改变SDA,SCL高电平时采样SDA。每个字节8位,后面跟一个应答位(ACK)。
  • 应答位:第9个时钟周期。接收方拉低SDA表示ACK,不拉低表示NACK。

我曾经在项目里遇到一个奇怪的问题:传感器偶尔不响应。查了半天,发现是起始条件保持时间不够。有些传感器对时序要求比较苛刻,尤其是那些便宜的国产芯片。所以我的建议是:写代码时严格按照标准时序来,别偷懒。

关键时序参数(标准模式100kHz):

  • SCL时钟频率:100kHz(标准)或400kHz(快速)
  • 起始条件保持时间:≥4.7μs
  • 数据建立时间:≥250ns
  • 数据保持时间:≥0ns(但建议≥300ns)
  • 停止条件建立时间:≥4.0μs

2.3 从机地址:7位还是10位?

大多数传感器用7位地址。比如MLX90614的默认地址是0x5A(7位)。但注意,I2C通信时,主机发送的是8位地址:7位地址左移1位,最低位是读写位(0表示写,1表示读)。所以你在代码里看到的地址往往是0xB4(写)和0xB5(读)。

我刚开始做的时候,直接拿7位地址去发,结果设备死活没反应。后来才意识到,地址需要左移。这个坑,我估计你也可能会踩。

10位地址用得少,但有些高精度ADC会用。处理方式也简单:第一个字节是11110xx加上地址高两位,第二个字节是地址低8位。不过说实话,我做了这么多年,也就碰到过两次10位地址的设备。

2.4 读写操作:主机怎么跟从机说话?

写操作很简单:主机发起始条件,发从机地址+写位,等ACK,然后发寄存器地址,等ACK,再发数据,等ACK,最后发停止条件。

读操作稍微复杂一点。通常需要先写寄存器地址,然后重新发起始条件(这叫重复起始条件),再发从机地址+读位,然后从机开始发数据,主机每收一个字节发ACK,收完最后一个字节发NACK,最后发停止条件。

我举个例子,读取MLX90614的RAM地址0x07(物体温度):

// 伪代码示例
I2C_Start();
I2C_SendByte(0xB4);  // 0x5A << 1 | 0 (写)
I2C_WaitAck();
I2C_SendByte(0x07);  // 寄存器地址
I2C_WaitAck();
I2C_Start();         // 重复起始条件
I2C_SendByte(0xB5);  // 0x5A << 1 | 1 (读)
I2C_WaitAck();
data_low = I2C_ReadByte();
I2C_SendAck();
data_high = I2C_ReadByte();
I2C_SendNack();
I2C_Stop();

小技巧:很多传感器支持多字节读取。你可以在发完寄存器地址后,连续读多个字节,每读一个发ACK,最后一个发NACK。这样效率高很多。

2.5 时钟拉伸:从机说“等等我”

时钟拉伸是I2C里一个挺有意思的机制。从机可以在传输过程中拉低SCL,告诉主机“我还没准备好,你等等”。主机检测到SCL被拉低,就会等待,直到SCL变高再继续。

为什么需要这个?因为有些传感器内部处理数据需要时间。比如热电堆传感器在做ADC转换时,可能几十毫秒才能出结果。如果主机不管不顾地继续发时钟,从机就来不及响应。

我记得有一次调试一个红外阵列传感器,读数据时总是读到0xFF。后来用逻辑分析仪一看,发现从机在发数据前拉低了SCL,但我的I2C驱动没有处理时钟拉伸,直接超时退出了。从那以后,我写I2C驱动都会加上时钟拉伸处理。

注意:不是所有MCU的硬件I2C外设都支持时钟拉伸。如果你用STM32的硬件I2C,有些老型号(比如F103)的硬件I2C有bug,时钟拉伸处理不好。我的建议是:如果遇到问题,直接上软件模拟I2C,虽然慢一点,但可控性高。

2.6 速率选择:100kHz还是400kHz?

I2C有几种标准速率:

模式最大频率适用场景
标准模式100kHz大多数传感器,兼容性最好
快速模式400kHz高速传感器,如数字温度传感器
快速模式+1MHz高带宽应用,如图像传感器
高速模式3.4MHz极少用,需要特殊硬件

对于热电堆传感器,我个人建议用100kHz。为什么?因为传感器本身转换速度不快,你读一次数据可能也就几个字节,100kHz完全够用。而且速率越低,信号完整性越好,抗干扰能力越强。

你想想看,如果总线上还有别的设备,比如一个慢速的EEPROM,你用400kHz去读,EEPROM可能跟不上,导致通信失败。所以我的习惯是:除非明确需要高速,否则一律用100kHz。

2.7 避坑指南:我踩过的那些雷

  • 上拉电阻选不对:电阻太大,上升沿太慢;电阻太小,功耗大且可能拉不低。我一般先试4.7kΩ,不行再换2.2kΩ或10kΩ。
  • 地址冲突:两个传感器用了同一个地址。解决办法是用I2C多路复用器(比如TCA9548A),或者换不同地址的型号。
  • 应答位没处理好:有些传感器在写寄存器时不需要应答,但读数据时必须应答。我见过有人把应答逻辑写反了,结果数据全乱套。
  • 重复起始条件没实现:有些I2C库在读写切换时直接发了停止+起始,这在某些传感器上会出问题。一定要用重复起始条件。

好了,I2C协议的核心内容就这些。下一章咱们会结合具体的热电堆传感器,手把手教你写驱动代码。到时候你会发现,理解了这些时序细节,写驱动其实就是套模板的事。