4、I2C读写操作:单字节读写、多字节读写、重复起始条件、组合传输格式
各位工程师朋友,今天我们来聊聊I2C通信中最核心的部分——读写操作。说实话,很多初学者看了协议规范觉得懂了,一上逻辑分析仪就懵了。我当年也是这样,第一次抓波形时对着屏幕看了半天,愣是分不清哪个是地址、哪个是数据。
这一节,我会结合逻辑分析仪的波形图,把单字节读写、多字节读写、重复起始条件、组合传输格式这几个概念彻底讲透。嗯,咱们直接上干货。
4.1 单字节写入:最基础的通信单元
先看最简单的场景。你要往一个I2C从设备(比如温度传感器)的某个寄存器里写一个字节数据。整个流程是这样的:
- 主机发送起始条件(S)
- 主机发送7位从机地址 + 写位(0)
- 从机回复ACK
- 主机发送寄存器地址(1字节)
- 从机回复ACK
- 主机发送要写入的数据(1字节)
- 从机回复ACK
- 主机发送停止条件(P)
你看,一共就8个步骤。我习惯把这个过程叫做「三段式」:地址段、寄存器段、数据段。每个段后面跟一个ACK。
逻辑分析仪波形解析要点:
- 起始条件:SCL高电平时,SDA从高变低
- 地址字节:前7位是地址,第8位是R/W位(0表示写)
- ACK位:第9个SCL时钟,SDA被从机拉低
- 停止条件:SCL高电平时,SDA从低变高
我在项目中遇到过一个问题:写操作总是失败,但读操作正常。后来用逻辑分析仪一看,发现是寄存器地址写错了。从机期待的是16位地址,我只发了8位。嗯,这个坑我踩过,大家注意看数据手册。
4.2 单字节读取:先写后读的套路
单字节读取稍微复杂一点。为什么?因为你要先告诉从机「我要读哪个寄存器」,然后才能读数据。说白了就是「先写后读」。
流程是这样的:
- 主机发送起始条件(S)
- 主机发送从机地址 + 写位(0)
- 从机回复ACK
- 主机发送寄存器地址(1字节)
- 从机回复ACK
- 主机再次发送起始条件(Sr) —— 注意,这里是重复起始条件
- 主机发送从机地址 + 读位(1)
- 从机回复ACK
- 从机发送数据(1字节)
- 主机发送NACK(表示不再接收数据)
- 主机发送停止条件(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寄存器。
波形上的关键节点:
- 起始条件:SDA在SCL高时下降
- 地址字节:0x48左移1位,加上写位0,得到0x90(二进制1001 0000)
- ACK:第9个时钟,SDA被拉低
- 寄存器地址:0x00(二进制0000 0000)
- ACK:同上
- 重复起始条件:又一个SDA在SCL高时下降
- 地址字节:0x48左移1位,加上读位1,得到0x91(二进制1001 0001)
- ACK:同上
- 数据字节:从机发送的温度值,比如0x1A(二进制0001 1010)
- NACK:主机在第9个时钟不拉低SDA
- 停止条件:SDA在SCL高时上升
波形分析小技巧:
- 先找起始条件和停止条件,确定一帧数据的范围
- 然后数ACK位(第9个时钟),确认通信是否正常
- 再解析地址字节,确认是不是你要操作的设备
- 最后看数据字节,确认读写内容是否正确
我个人的习惯是:先用逻辑分析仪的协议解析功能自动解码,然后手动对照波形验证一遍。自动解码偶尔会出错,尤其是信号质量不好的时候。手动验证能帮你发现很多隐藏问题。
4.7 常见问题与避坑指南
最后,分享几个我在项目中遇到的实际问题:
- ACK丢失:从机没拉低SDA。原因可能是地址错了、从机忙、或者从机不支持该操作。用逻辑分析仪看第9个时钟的SDA电平就知道了。
- 时钟拉伸:从机把SCL拉低,表示「我还没准备好」。有些主控不支持时钟拉伸,会超时报错。我建议选主控时确认一下这个功能。
- 数据错位:读回来的数据顺序不对。通常是地址自动递增的边界问题,或者读写方向搞反了。
- 总线死锁:SDA一直被拉低。我曾经遇到过一次,是从机在发送数据时主机复位了,从机还在等时钟,结果总线卡死了。解决办法是给从机断电或者发9个时钟脉冲让它释放总线。
避坑指南: 我曾经在调试一个电池管理芯片时,发现读回来的电压值偶尔跳变。用逻辑分析仪抓了上百次波形,终于发现问题:主机在发送停止条件时,SCL和SDA的时序不满足保持时间要求。加了一个小延时后,问题彻底解决。所以,时序余量一定要留够。
好了,这一节的内容就到这里。单字节读写是基础,多字节读写是效率,重复起始条件是技巧,组合传输是实战。把这些搞明白了,I2C通信的波形你基本就能看懂了。下一节我们聊聊I2C的时序参数和电气特性,那才是真正考验硬件设计功底的地方。