嵌入式I2C协议栈实战:时序拆解、模拟与硬件选型及调试避坑指南

发布时间:2026/10/29 1:10:51
嵌入式I2C协议栈实战:时序拆解、模拟与硬件选型及调试避坑指南

《嵌入式通讯协议栈实战》写到第四篇,I2C这个协议在嵌入式里太常用了。只要你手头有OLED屏、EEPROM、温湿度传感器或者气压计,十有八九都逃不开I2C。说实在的,I2C相比UART和SPI,最大的魅力就是两根线能挂几十个设备,靠一个地址区分,极省钱也极容易踩坑。这篇我从协议栈的分层思路、时序拆分、模拟与硬件外设选型,再到实际调试踩过的坑,一次性讲清楚,适合正在调SSD1306、AT24C02、各种传感器驱动,或者被硬件I2C折磨得想砸板子的开发者。

1. 先看清楚I2C协议栈的整体设计思路

1.1 为什么I2C只要两根线就能带起一堆设备

I2C总线只有两根信号线:SDA(数据线)和SCL(时钟线)。主设备负责产生时钟、发起通信,从设备靠自己的设备地址来响应。总线上一旦挂上多个从机,只要地址不冲突,谁都能和主机单独对话,不用像SPI那样每个设备配一个独立的片选信号。

这就像一栋楼的快递柜,每个柜门上有编号,快递员(主机)按编号投递和取件。只要编号不重复,一个快递员就能管理整栋楼的柜子。UART是快递员直接敲你家门,SPI是快递员挨家挨户派送还每家配一个专属门铃,而I2C是所有人共享一根门铃线、用门牌号区分。

这种设计带来的好处很实在:布线少、成本低、扩展方便。坏处是半双工,同一时刻只能单向传输,速度上不去,而且一旦总线上有一个设备把SDA线拉死,整条总线都瘫痪。这也是很多人调I2C时最头疼的问题之一。

我在项目里最常见的用法是:一个MCU的I2C1挂OLED、气压计、EEPROM三个设备,I2C2挂一个数字电位器,总共用了不到10根飞线,比SPI方案省了至少三分之一引脚。

1.2 从协议栈分层看I2C,调试思路会清晰很多

很多人学I2C是只看时序图,看完还是一团浆糊。我习惯把I2C拆成三层理解,这样任何一次通信失败,都能快速判断问题到底出在哪一层。

  • 物理层:SDA和SCL的电平特性、开漏输出、上拉电阻、电平匹配。
  • 数据链路层:起始条件、停止条件、字节格式、地址、应答位(ACK/NACK)、时钟延展。
  • 应用层:挂在I2C上的具体器件协议,比如SSD1306的命令字、AT24C02的页写入规则、气压计的校准寄存器格式。

打个比方:物理层是铺好的马路,链路层是红绿灯和交通规则,应用层是快递单上写的收件信息和货物内容。你在调试时如果发现SDA一直是低电平,那是物理层出问题了,不用去怀疑应用层地址写没写对;如果地址发出去之后没有ACK响应,那就是链路层通信没建立起来,先别急着看寄存器定义。

我见过不少新人调OLED不亮,一上来就翻SSD1306的驱动手册改初始化命令,改了半天还是黑屏。最后拿示波器一看,SDA线压根没上拉电阻,物理层就没通,应用层改再久也没用。

1.3 推挽模式、开漏模式,以及电平转换那些事

I2C的信号线必须用开漏输出,这是协议一个硬性约束。很多人第一次写GPIO模拟I2C时,习惯把SDA和SCL配成推挽输出,结果发现总线上接了多个设备后动不动就逻辑混乱。原因很简单:推挽输出同时又拉高又拉低,如果两个设备一个输出高、一个输出低,直接相当于短路。

开漏输出的本质是“只能拉低,不能主动拉高”。想输出高电平时,引脚处于高阻态,靠外部上拉电阻把电平拉到高。这样多个设备可以“线与”:只要有一个设备拉低,总线就是低;所有设备都释放,总线才回到高。这就像多人共用一个开关,谁都能按下,但松手之后靠弹簧(上拉电阻)弹回去。

因为I2C是靠上拉电阻提供高电平的,设备本身不自带上拉,所以每一条I2C总线都要在SDA和SCL上各接一个上拉电阻。常用的阻值是4.7kΩ,总线速度快或者挂的设备多、总线电容大的时候,可以换成2.2kΩ甚至1kΩ。

上拉电阻的计算可以从RC时间常数来理解。假设总线电容是100pF,用4.7kΩ电阻,上升沿时间常数τ = R × C = 4.7k × 100p = 470ns,对应从0.3VCC升到0.7VCC的时间大约是0.847 × 470ns ≈ 398ns,小于I2C标准模式要求的1000ns,没问题。如果总线上挂了8个设备,总线电容涨到400pF以上,4.7kΩ就不够看了,上升沿会明显变缓,此时就得换2.2kΩ甚至1kΩ。

至于“I2C需要电平转换吗”,答案是:同电压域不需要,跨电压域必须处理。比如MCU是3.3V,从设备是3.3V,直接连就行。如果一边3.3V一边5V,直接把引脚怼上去,轻则逻辑电平不识别,重则长期过压损伤芯片。常用的方案是双向电平转换芯片如PCA9306,或者用两个MOS管搭一个双向电平转换电路,原理是利用I2C的开漏特性实现双向传输,两边都要接上拉电阻。

2. 核心时序与读写流程拆解

2.1 五个基础时序的判定与实现

I2C通信的所有数据交换,都建立在这几个基础时序之上:起始条件、停止条件、数据位、应答位、空闲状态。学I2C必须先把这几个烂熟于心。

起始条件(START):SCL保持高电平期间,SDA产生一个下降沿。停止条件(STOP):SCL保持高电平期间,SDA产生一个上升沿。这两个条件的变化都发生在SCL高电平时段,和普通数据位“SCL高电平期间SDA必须保持稳定”的规则正好区分开,所以从设备能识别出这是一个边界信号而不是数据。

数据位的规则是:SDA只能在SCL低电平时变化,SCL一旦拉高到采样点,SDA必须保持稳定。主机在SCL高电平期间采集SDA的状态,得到当前的二进制位。

应答位(ACK)是第9个时钟周期。默认情况下,发送方在第9个时钟释放SDA,接收方如果正常工作,就把SDA拉低,表示“我收到了”。主机在读从机数据时,最后一个字节要回NACK(不拉低),告诉从机“别发了,我够了”,然后发停止条件。

我放一个GPIO模拟I2C的关键代码,这是最底层的基础。实际项目里可以直接封装成函数用:

#define I2C_SCL_H() gpio_set_level(SCL_PIN, 1) #define I2C_SCL_L() gpio_set_level(SCL_PIN, 0) #define I2C_SDA_H() gpio_set_level(SDA_PIN, 1) #define I2C_SDA_L() gpio_set_level(SDA_PIN, 0) static void i2c_start(void) { I2C_SDA_H(); I2C_SCL_H(); delay_us(5); I2C_SDA_L(); // SCL高电平时SDA下降沿 => 起始 delay_us(5); I2C_SCL_L(); // 拉低SCL,准备传数据 } static void i2c_stop(void) { I2C_SDA_L(); I2C_SCL_H(); delay_us(5); I2C_SDA_H(); // SCL高电平时SDA上升沿 => 停止 delay_us(5); } static void i2c_ack(uint8_t ack) { if (ack) { I2C_SDA_L(); // 主机发ACK } else { I2C_SDA_H(); // 主机发NACK } I2C_SCL_H(); delay_us(5); I2C_SCL_L(); I2C_SDA_H(); } static uint8_t i2c_write_byte(uint8_t dat) { for (uint8_t i = 0; i < 8; i++) { // SCL低电平时改变SDA if (dat & 0x80) I2C_SDA_H(); else I2C_SDA_L(); dat <<= 1; I2C_SCL_H(); // SCL高电平,从机采样 delay_us(5); I2C_SCL_L(); delay_us(2); } // 第9个时钟,从机回ACK gpio_set_dir(SDA_PIN, GPIO_MODE_INPUT); I2C_SCL_H(); delay_us(5); uint8_t ack = gpio_get_level(SDA_PIN); I2C_SCL_L(); gpio_set_dir(SDA_PIN, GPIO_MODE_OUTPUT); return ack == 0; }

写字节的时候要注意:把SDA从输出模式切换成输入模式之前,最好先把SDA拉高一下,避免引脚直接转变方向时产生一个意外的下降沿。这个细节在高速调试时尤其重要,我就是吃过这个亏。很多模拟I2C的代码不处理这个,导致从设备识别到假的起始条件。

2.2 读时序的三种模式,以EEPROM为例

I2C读操作在实际器件里不是只有一种“读数据”的流程。以EEPROM AT24C02为例,读操作有三种模式:当前地址读、随机读、连续读。理解这三种,就基本拿下了I2C读时序。

当前地址读最简单:主机发送设备地址+读位,从机收到后,把内部当前地址指针指向的那个字节直接送出来。这个模式适合连续读取上一次操作结束时的地址,一般用的不多。

随机读是实际项目里最常用的。主机先发送设备地址+写位,然后发送要读的字节地址,再从机收到这个地址后,主机再发一次起始条件,接着发送设备地址+读位,然后从机开始输出数据。整个过程两次起始条件、一次伪写、一次真读,很多新手第一次写随机读的时候会漏掉中间的重新起始条件,导致读到的数据永远是0。

连续读则是读完一个字节后,主机返回ACK,从机收到ACK后自动把地址指针加1,继续输出下一个字节。主机想结束就回一个NACK,再发停止条件。这种模式适合一次性读取多字节数据,比如读取传感器的一大块缓冲数据,或者连续读一整页EEPROM。

24C02有一个容易踩的坑:页写入。它的每一页是8字节,如果一次写入超过页边界,地址会回卷,把数据写到这一页的开头去,而不是自动翻页到下一页。所以跨页写数据时,要么一页一页写,要么把写入长度控制在页内。这个坑和I2C协议本身没关系,但和EEPROM这个从设备的内部实现有关,也算应用层的一部分。

2.3 七位地址、八位地址,和控制字节的关系

I2C设备地址分7位和10位两种,绝大多数消费级芯片用的是7位地址。7位地址在总线上发送时,会被左移一位,空出来的最低位是读写标志:0表示写,1表示读。也就是说,地址0x50在发送时的第一个字节是0xA0(写)或0xA1(读)。

这个“左移一位”是新手最容易懵的地方。有人拿到一个模块说地址是0x3C,扫描程序里直接写了0x3C,结果怎么都找不到设备。原因就是0x3C是7位地址,实际发送的原始字节是0x78(写)或0x79(读)。我在调试SSD1306 OLED时,经常看到有人把0x78当成7位地址写进去,然后发现找不到设备,其实0x78是已经左移过的地址,直接用是可以的,但对应的7位地址就是0x3C。

更直观的理解是:I2C的第一个字节的高7位是设备地址,最低1位是方向位。你用逻辑分析仪抓到的波形如果第一个字节是0xA0,那对应的7位地址就是0x50。

EEPROM的写操作在地址字节之后还要跟一个字节地址,所以在AT24C02上写一个字节,实际上要发三个帧:设备地址、字节地址、数据。如果用示波器抓波形,会看到起始条件后连续三组9个时钟。没有掌握这个结构的人,往往会漏掉中间那个字节地址,直接把数据塞给设备地址,导致写入失败。

2.4 影响I2C稳定性的几个参数

I2C通信速率分几档:标准模式100kHz、快速模式400kHz、快速+模式1MHz、高速模式3.4MHz。绝大多数传感器和存储芯片只支持到400kHz,不要以为主控支持1MHz就可以随便上,得看从设备的极限。

很多人觉得I2C速度快才好,其实I2C这种半双工同步协议对时序的规范性要求很高。模拟I2C时钟频率一旦超过从设备支持范围,最典型的症状就是“有时能读到数据,有时读到0xFF”,或者从设备根本没有ACK。我建议先按100kHz跑通功能,确认驱动逻辑没问题之后,再逐步提高速率。

时钟延展(clock stretching)也是一个容易被忽略的机制。某些从设备(尤其是老式传感器)在处理数据时,会把SCL拉低一段时间,意思是“我忙着呢,你等我一下”。主机在发送每个字节的时钟周期时,必须检测SCL是不是被从设备拉住了,如果SCL在主机释放后没有在约定时间内变高,就要继续等待。硬件I2C外设有些对时钟延展支持不好,软件模拟I2C反而更容易处理,只要在每发一个位之前检查SCL是否恢复高电平。

模拟I2C的延时也不是越大越好。延时长,SCL频率低,兼容性确实更好,但用户体验很差,刷一块12864的OLED屏幕要卡顿好几秒。正确的做法是让延时略大于从设备允许的最小SCL高/低电平时间,既保证稳定又不拖节奏。

3. 实战:从裸机模拟I2C到硬件I2C选型

3.1 为什么我建议先写一遍软件模拟I2C

很多初学者学I2C,第一件事就是去调MCU的硬件I2C外设,结果被复杂的状态机、事件标志、错误标志按在地上摩擦,调了三天屏幕不亮,自信心全没了。我的意见是:第一次做I2C项目,老老实实先用GPIO软件模拟把协议走通。

软件模拟的好处是可控:每一步做什么、在哪个时钟沿采样、哪里需要延时,你都清清楚楚。万一出问题,你可以在代码里加断点,用变量观察每个字节的发送流程,一眼就能看出是哪一步没走对。而硬件I2C外设一旦初始化状态不对,总线直接锁死,可调试性极差。

我经常拿SSD1306 OLED作为I2C练手项目,因为它的驱动逻辑简单,反馈直观,屏幕亮不亮一眼就能看到。0.9寸OLED模组的I2C地址多半是0x3C,也有个别是0x3D,具体看模块硬件配置。初始化流程大致是:关显示、设置显示时钟分频、设置复用率、设置显示偏移、设置起始行、设置列地址、打开显示。

写SSD1306的时候,核心思路是区分“命令字节”和“数据字节”。I2C模式下,主机发完设备地址之后,要发送一个控制字节,最高位是连续标志Co,次高位是D/C位。D/C为0表示后续字节是命令,D/C为1表示后续是数据。通过这个控制字节,SSD1306才能知道接下来写入的是控制寄存器还是显存内容。

void oled_write_cmd(uint8_t cmd) { i2c_start(); i2c_write_byte(0x78); // 写地址,0x3C左移一位 i2c_write_byte(0x00); // 控制字节,命令类型 i2c_write_byte(cmd); i2c_stop(); } void oled_write_data(uint8_t dat) { i2c_start(); i2c_write_byte(0x78); // 写地址 i2c_write_byte(0x40); // 控制字节,数据类型 i2c_write_byte(dat); i2c_stop(); }

这里有一个“0.9寸OLED对I2C兼容问题”的热词经常被问到。市面上有些0.9寸OLED模组只引出了GND、VCC、SCL、SDA四个脚,没有RES复位脚,芯片内部上电复位又不够可靠,导致偶尔上电后屏幕不显示。解决办法有两种:一是把RES脚飞线到MCU的GPIO,初始化前手动拉低再拉高复位;二是初始化命令结束后,连续写几遍清屏和设置命令,让屏幕从异常状态中恢复过来。我遇到过几次莫名其妙显示雪花的问题,最后都是靠手动复位解决的。

3.2 硬件I2C外设的坑,以及什么时候非用它不可

软件模拟I2C虽好,但MCU的硬件I2C外设仍然是工程上绕不开的东西。STM32F4的硬件I2C被吐槽过很多次,主要问题集中在Busy标志误置、EV5/EV6事件顺序复杂等。实际用下来,新版本HAL库已经稳了很多,只要严格按状态机流程操作,大多数时候是没问题的。翻车案例通常有两个共同点:一是初始化时没有正确配置GPIO复用功能,二是中断里没有清除标志位就进入下一轮传输。

CH32V307这类国产芯片也有I2C外设,官方例程很多都带OLED例程,可以直接参考。需要注意的是,这类芯片I2C时钟和GPIO复用的配置方式和STM32有差异,不要直接移植STM32的初始化代码就跑,至少要把GPIO复用寄存器重新对一遍。

硬件I2C真正的优势是高效率和低CPU占用。软件模拟I2C虽然灵活,但每个字节都要用GPIO翻转模拟波形,MCU在传输期间几乎干不了别的。如果项目里需要持续传输大量数据,比如I2C接口的TFT屏刷显存,或者从传感器连续采集数百字节数据,那硬件I2C配合DMA是更好的方案。

我之前做过一个项目,用I2C接口的OLED显示实时波形,软件模拟I2C刷新一屏要80ms,肉眼可见地闪烁;切到硬件I2C+DMA之后,刷新时间降到十几毫秒,CPU还能同时处理ADC采集和按键扫描。所以我现在的选型原则很简单:低速少量数据,先用软件模拟;需要持续批量传输,直接上硬件外设。

3.3 用I2C扩展MCU的IO和模拟量调节能力

I2C不只是连传感器和存储芯片,它做“能力扩展”特别好用。最典型的就是I2C扩展GPIO,芯片如PCAL9535、MCP23017,一颗芯片就能扩展出16个IO口,适合MCU引脚紧张、需要控制大量LED或读取开关状态的场景。

更高级的玩法是用I2C数字电位器去调节电源电路的输出电压。比如MCU通过I2C写数字电位器AD5252,改变电阻值,从而改变DC-DC反馈引脚FB的分压,实现输出电压的软件调节。这种方案在数字电源里很常见,用一个数字电位器加I2C总线,代替了传统的手动电位器,也避免了用MCU的DAC/PWM直接控制反馈引脚带来的纹波干扰。

用数字电位器时要注意几个应用层的细节:端到端电阻、分辨率位数、滑动端电流限制。比如一个10kΩ、256抽头的数字电位器,每一档就是约39Ω。如果你把它接在反馈电阻上,算输出电压时要按这个精度去估算调节步进。

I2C编码器这类设备也很有意思,它把旋转编码器的计数值直接暴露给主机,MCU通过I2C读寄存器就能知道旋转了多少格,还能支持多路编码器挂在同一条总线上。这种方案非常适合需要多旋钮的调音台、仪器面板之类的场景,省掉了每个编码器单独接外部中断引脚的问题。

3.4 一个驱动实例:GZP6891D气压计的I2C驱动思路

GZP6891D是常见的压阻式气压传感器,I2C接口,内部会存有校准系数。写这类传感器的驱动,不能只把它当成EEPROM那样死板地读写,而是要理解它的寄存器布局和数据处理流程。

驱动分四步:上电后等待芯片稳定,然后通过I2C读取芯片内部校准系数,再读取ADC原始压力数据和温度数据,最后套入芯片手册给出的公式计算出真实气压值。

读取校准系数时,通常在初始化阶段一次读完,存到全局变量里。之后每次获取气压,不需要重复读校准系数,只要读原始ADC值就行。这能大量减少I2C总线上的传输时间。还有一个细节:这类传感器在每次上电后可能需要几百毫秒的稳定时间,如果上电后立刻去读ADC,读到的可能是无效值。我一般是在驱动初始化里加一个至少50ms的延时,有些压力传感器甚至要求200ms以上。

如果你在做FPGA平台,想用Verilog实现I2C读写EEPROM,那算是另一条路。FPGA里没有现成的GPIO模拟环境,一般用状态机实现I2C时序,SDA引脚要设计成三态控制,等效开漏。核心步骤和软件模拟一样,只是把“延时函数”换成了“计数器等待”,把“GPIO翻转”换成了“三态门控制”。难点在于所有信号都要按时钟周期精确对齐,但反过来因为不用理会MCU的中断和调度,时序反而比软件模拟更可预测。

4. 我踩过的坑和排查技巧

4.1 高频问题速查表

把I2C调试中高频出现的问题整理成一张表,方便直接对照。

现象可能原因排查方向
发送设备地址后无ACK地址写错、设备未上电、SDA被拉低、从设备不在总线上先地址扫描,再用万用表量SDA电平
总线上SDA一直被拉低某个从设备输出异常锁死、上拉电阻缺失、SCL空接断开所有从设备,逐个挂回
数据读出来全是0xFF从机没响应、主机读时序错误、时钟频率过高降到100kHz,检查停止条件是否完整
数据错位、多一位或少一位ACK时序不对、第9个时钟被跳过、SDA在SCL高电平时变化用逻辑分析仪逐位比对
OLED花屏或闪烁初始化命令不完整、电源不稳、缺少复位初始化后清屏,确认RES复位时序
连续读时数据顺序错乱从机页边界回卷、主机ACK/NACK顺序反了检查连续读模式下ACK和NACK的位置

4.2 用示波器抓I2C时序的要点

I2C调试最有效的工具是示波器或逻辑分析仪。很多人一上来就把探头往SDA上一夹,看到一堆波形就开始瞎猜,实际上要抓对信号才有效。

第一,SDA和SCL两个通道必须同时抓,单看一路信号你根本分不清那是一条数据还是一个地址字节。第二,触发方式建议用下降沿触发,电平设置成略低于高电平一半,这样能抓住起始条件的下降沿,展开波形后逐位分析。第三,看波形不能只数脉冲个数,要标注出设备地址字节、ACK位、数据字节的位置,对照你代码里发送的内容去核对。

逻辑分析仪更直观一些,可以直接解码出START、STOP、ACK、地址和数据的语义,不用手工数脉冲,特别适合初学者快速验证时序。我的习惯是先用逻辑分析仪确认整包数据是正确,再用示波器看具体信号边沿质量,比如上升沿是不是太缓、电平是不是达到阈值。

4.3 快速定位设备地址的扫描法

如果你不确定挂在总线上的设备到底是什么地址,最直接的办法是写一个I2C地址扫描程序。逐个试探0x08到0x77的所有7位地址,只要从设备回了ACK,就说明这个地址有设备。

void i2c_scan(void) { for (uint8_t addr = 0x08; addr < 0x78; addr++) { i2c_start(); if (i2c_write_byte((addr << 1) | 0)) { printf("Found device at 0x%02X\n", addr); } i2c_stop(); delay_ms(10); } }

这个程序在总线上挂了多个设备时很有用,能一次性把所有活跃设备都列出来。如果扫描结果为空,基本可以断定物理层有问题:SDA、SCL没接上拉、线序接反、电源没供上,或者两个信号线互相接反了。扫描也是验证总线是否复位的快速手段,每次扫描前先发一个停止条件,再发9个SCL时钟,能把一些挂在异常状态的从设备复位。

在Linux主板上调试I2C设备时,也可以用现成的工具。i2c-tools里的i2cdetect -y 1可以直接扫描总线,python-smbus库可以在脚本里直接读写从设备寄存器。遇到总线上的设备行为奇怪时,先用这些工具隔离硬件和上层驱动的问题,比在嵌入式代码里反复编译下载快得多。

4.4 几个只有实操才知道的小技巧

I2C调试越到后面,越发现很多问题是数据手册里不会写的。这里分享几个我踩过之后才懂的技巧。

第一个是上电后先复位总线。MCU复位时,如果正好有从设备处于一个中间状态,它可能把SDA拉低,导致总线上电即锁死。解决办法是在初始化I2C之前,让SCL翻转至少9个时钟周期,同时确保SDA是释放状态,最后发一个停止条件。这段“复位序列”可以强制让大多数从设备回到空闲状态。

第二个是软件模拟I2C时,SDA引脚方向切换要格外小心。读数据前把引脚从输出切到输入,如果此时引脚电平恰好是低,切换瞬间可能产生一个下降沿,被从设备识别成新的起始条件。正确的做法是先从软件上把输出寄存器置1,再切输入模式,这样引脚始终是高电平,不会产生意外边沿。

第三个是共地问题。I2C电平是相对参考地的,多个模块如果不共地,SDA和SCL的信号电平就无法对齐,出现“时通时不通”的诡异现象。多模块接线时,GND一定要先连好。

第四个关于双向电平转换板。如果用了PCA9306这类模块,别忘了两侧都要接上拉电阻,很多转换板本身就带,但自制的MOS管方案经常只接了一边,导致某一侧高电平无法维持,波形畸变。测试时如果某一侧电平只有零点几伏,先查上拉电阻。

还有一个细节:I2C总线设备太多时,总线电容会增大,不仅影响上升沿时间,还可能让从设备误判电平。我调过一块挂了6个I2C设备的板子,SCL和SDA的上升沿都超过200ns,逻辑分析仪还能勉强解码,但示波器看波形已经明显圆润了。换成2.2k欧姆上拉后,波形立刻变得干脆。项目定型后,建议用示波器确认一下实际波形质量,别只顾着“能跑就行”。

最后再说一个经验:调I2C不要一口咬定是“从设备有问题”。我遇到过很多次,总线上设备的ACK看起来正常,数据也有,但结果还是不对。最后还是靠逻辑分析仪抓波形,发现是主机在第9个时钟之后没等从设备释放SDA就提前读写下一个字节,导致总线竞争。I2C是半双工总线,发送方和接收方在同一个字节上的角色是明确划分的,谁在哪一拍释放总线、谁在哪一拍采样,时序图上都写得清清楚楚。只要双方都严格按协议走,I2C其实是很可靠的协议。要是哪天调得不顺,不妨回去把时序图再对着数据手册逐位看一遍,往往问题就出在你以为绝对不会错的那个环节。