4、I2C读写操作:单字节读写、多字节读写、重复起始条件、组合传输格式

各位工程师朋友,今天我们来聊聊I2C通信中最核心的部分——读写操作。说实话,很多初学者看了协议规范觉得懂了,一上逻辑分析仪就懵了。我当年也是这样,第一次抓波形时对着屏幕看了半天,愣是分不清哪个是地址、哪个是数据。

这一节,我会结合逻辑分析仪的波形图,把单字节读写、多字节读写、重复起始条件、组合传输格式这几个概念彻底讲透。嗯,咱们直接上干货。

4.1 单字节写入:最基础的通信单元

先看最简单的场景。你要往一个I2C从设备(比如温度传感器)的某个寄存器里写一个字节数据。整个流程是这样的:

  1. 主机发送起始条件(S)
  2. 主机发送7位从机地址 + 写位(0)
  3. 从机回复ACK
  4. 主机发送寄存器地址(1字节)
  5. 从机回复ACK
  6. 主机发送要写入的数据(1字节)
  7. 从机回复ACK
  8. 主机发送停止条件(P)

你看,一共就8个步骤。我习惯把这个过程叫做「三段式」:地址段、寄存器段、数据段。每个段后面跟一个ACK。

逻辑分析仪波形解析要点:

  • 起始条件:SCL高电平时,SDA从高变低
  • 地址字节:前7位是地址,第8位是R/W位(0表示写)
  • ACK位:第9个SCL时钟,SDA被从机拉低
  • 停止条件:SCL高电平时,SDA从低变高

我在项目中遇到过一个问题:写操作总是失败,但读操作正常。后来用逻辑分析仪一看,发现是寄存器地址写错了。从机期待的是16位地址,我只发了8位。嗯,这个坑我踩过,大家注意看数据手册。

4.2 单字节读取:先写后读的套路

单字节读取稍微复杂一点。为什么?因为你要先告诉从机「我要读哪个寄存器」,然后才能读数据。说白了就是「先写后读」。

流程是这样的:

  1. 主机发送起始条件(S)
  2. 主机发送从机地址 + 写位(0)
  3. 从机回复ACK
  4. 主机发送寄存器地址(1字节)
  5. 从机回复ACK
  6. 主机再次发送起始条件(Sr) —— 注意,这里是重复起始条件
  7. 主机发送从机地址 + 读位(1)
  8. 从机回复ACK
  9. 从机发送数据(1字节)
  10. 主机发送NACK(表示不再接收数据)
  11. 主机发送停止条件(P)

你想想看,为什么中间要加一个重复起始条件?直接发停止条件再发起始条件不行吗?当然可以,但那样效率低。重复起始条件的好处是:总线不会被释放,其他主机抢不走控制权。

个人经验: 我建议在读取操作中尽量使用重复起始条件,而不是Stop+Start。有些从设备对Stop+Start的间隔时间有要求,处理不好容易出问题。用重复起始条件更可靠。

4.3 多字节读写:批量传输的艺术

实际项目中,很少只读写一个字节。比如你要读取一个加速度计的X、Y、Z三轴数据,每个轴2个字节,一共6个字节。如果单字节读取,你得发6次,效率太低了。

多字节写入的规则很简单:在发送完第一个数据字节后,从机回复ACK,主机继续发送第二个数据字节,以此类推。直到主机发送停止条件。

多字节读取也是类似的:从机连续发送数据,主机每收到一个字节就回复ACK,直到主机想停止时回复NACK,然后发停止条件。

这里有个关键点:地址自动递增。大多数I2C从设备在读写多字节时,内部地址会自动加1。比如你写寄存器0x10,写完第一个字节后,地址自动变成0x11,再写就是0x12了。

注意: 不是所有从设备都支持地址自动递增!我遇到过一款EEPROM,它的地址递增只在一个page内有效,跨page需要重新发送地址。这种细节数据手册里会写,千万别想当然。

4.4 重复起始条件:为什么它如此重要

重复起始条件(Repeated Start,简称Sr)是I2C协议里一个非常巧妙的设计。它的波形特征和起始条件一模一样:SCL高电平时,SDA从高变低。区别在于:它前面没有停止条件。

为什么要用重复起始条件?三个原因:

  • 原子操作:从写地址切换到读地址的过程中,总线一直被当前主机控制,不会被其他主机打断
  • 避免总线冲突:如果先发Stop再发Start,中间会有总线空闲时间,其他主机可能趁机发起通信
  • 兼容性:有些从设备要求读写操作必须在一次传输中完成,不能有Stop

我记得有一次调试一个摄像头模组,它的寄存器读取必须用重复起始条件。我用Stop+Start的方式,读回来的数据全是错的。折腾了两天,最后看数据手册才发现——人家明确写了「Repeated start condition must be used for read operations」。

4.5 组合传输格式:灵活应对复杂场景

组合传输格式,说白了就是把上面几种操作组合起来。常见的组合有:

组合类型 格式 典型应用
写-读组合 S + Addr(W) + RegAddr + Sr + Addr(R) + Data + P 读取寄存器值
写-写组合 S + Addr(W) + RegAddr + Data1 + Data2 + ... + P 连续写入多个寄存器
读-读组合 S + Addr(R) + Data1 + Data2 + ... + P 连续读取数据(地址已预设)
多地址组合 S + Addr1(W) + Data + P + S + Addr2(W) + Data + P 操作不同从设备

实际项目中,最常用的是第一种——写-读组合。几乎所有传感器的寄存器读取都是这个格式。

4.6 实战:用逻辑分析仪解析波形

好了,理论说完了,咱们看看实际波形长什么样。假设我们要读取一个I2C温度传感器(地址0x48)的0x00寄存器。

波形上的关键节点:

  1. 起始条件:SDA在SCL高时下降
  2. 地址字节:0x48左移1位,加上写位0,得到0x90(二进制1001 0000)
  3. ACK:第9个时钟,SDA被拉低
  4. 寄存器地址:0x00(二进制0000 0000)
  5. ACK:同上
  6. 重复起始条件:又一个SDA在SCL高时下降
  7. 地址字节:0x48左移1位,加上读位1,得到0x91(二进制1001 0001)
  8. ACK:同上
  9. 数据字节:从机发送的温度值,比如0x1A(二进制0001 1010)
  10. NACK:主机在第9个时钟不拉低SDA
  11. 停止条件:SDA在SCL高时上升

波形分析小技巧:

  • 先找起始条件和停止条件,确定一帧数据的范围
  • 然后数ACK位(第9个时钟),确认通信是否正常
  • 再解析地址字节,确认是不是你要操作的设备
  • 最后看数据字节,确认读写内容是否正确

我个人的习惯是:先用逻辑分析仪的协议解析功能自动解码,然后手动对照波形验证一遍。自动解码偶尔会出错,尤其是信号质量不好的时候。手动验证能帮你发现很多隐藏问题。

4.7 常见问题与避坑指南

最后,分享几个我在项目中遇到的实际问题:

  • ACK丢失:从机没拉低SDA。原因可能是地址错了、从机忙、或者从机不支持该操作。用逻辑分析仪看第9个时钟的SDA电平就知道了。
  • 时钟拉伸:从机把SCL拉低,表示「我还没准备好」。有些主控不支持时钟拉伸,会超时报错。我建议选主控时确认一下这个功能。
  • 数据错位:读回来的数据顺序不对。通常是地址自动递增的边界问题,或者读写方向搞反了。
  • 总线死锁:SDA一直被拉低。我曾经遇到过一次,是从机在发送数据时主机复位了,从机还在等时钟,结果总线卡死了。解决办法是给从机断电或者发9个时钟脉冲让它释放总线。

避坑指南: 我曾经在调试一个电池管理芯片时,发现读回来的电压值偶尔跳变。用逻辑分析仪抓了上百次波形,终于发现问题:主机在发送停止条件时,SCL和SDA的时序不满足保持时间要求。加了一个小延时后,问题彻底解决。所以,时序余量一定要留够。

好了,这一节的内容就到这里。单字节读写是基础,多字节读写是效率,重复起始条件是技巧,组合传输是实战。把这些搞明白了,I2C通信的波形你基本就能看懂了。下一节我们聊聊I2C的时序参数和电气特性,那才是真正考验硬件设计功底的地方。