尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

从波形到中断:一篇看懂 I2C 通信原理、地址、ACK 与调试方法

从波形到中断:一篇看懂 I2C 通信原理、地址、ACK 与调试方法
📅 发布时间:2026/7/1 10:53:49

从波形到中断:一篇看懂I2C通信原理、地址、ACK与调试方法

一、本文适用场景

本文适用于以下学习和调试场景:

  • 正在学习STM32、GD32或其他单片机的I2C外设;
  • 会调用I2C库函数,但看不懂底层通信过程;
  • 不清楚SCL和SDA应该在什么时候变化;
  • 不理解START、STOP、ACK和NACK的波形特征;
  • 不清楚7位地址、8位地址、读地址和写地址之间的关系;
  • 不明白I2C发送一个字节后为什么会产生第9个时钟;
  • 不清楚SBSEND、ADDSEND、TBE、RBNE、BTC和AERR等标志的含义;
  • 使用逻辑分析仪抓取I2C波形,但不知道应该按照什么顺序分析;
  • 遇到地址后NACK、总线BUSY、SDA被拉低、接收中断频繁触发等问题。

本文将从I2C的基础采样规则开始,逐步讲解一帧通信的完整流程,并把代码中的状态标志与逻辑分析仪上的实际波形对应起来。


二、I2C总线基础

I2C是一种同步串行通信协议,常用于单片机与传感器、EEPROM、电源管理芯片、风扇控制器、显示屏等器件之间的通信。

I2C总线主要由两根信号线组成:

  • SCL:Serial Clock,串行时钟线;
  • SDA:Serial Data,串行数据线。

其中,主机通常负责产生SCL时钟,SDA用于传输地址、读写方向、数据以及ACK/NACK应答信号。

I2C最核心的采样规则是:

SCL低电平期间,SDA可以变化。 SCL高电平期间,SDA应保持稳定。

发送方一般会在SCL为低电平时修改SDA,为下一位数据做好准备。

接收方则在SCL上升沿附近,或者SCL高电平的稳定区域内读取SDA。

因此,在分析I2C波形时,应该重点观察每一个SCL上升沿附近的SDA电平:

SDA为高电平:表示逻辑1 SDA为低电平:表示逻辑0

图1 I2C基础与采样原则

从图中可以看到,SCL低电平期间,SDA可以发生变化;SCL进入高电平后,SDA需要保持稳定,接收方根据SDA的高低电平还原数据。

需要注意的是,START和STOP属于特殊情况。它们允许SDA在SCL高电平期间发生变化,用来表示一次通信的开始和结束。


三、I2C总线空闲状态

在没有设备进行通信时,I2C总线通常处于空闲状态。

总线空闲时:

SCL = 高电平 SDA = 高电平

这是因为I2C的SCL和SDA通常采用开漏输出结构,信号线依靠外部上拉电阻恢复为高电平。

主机在发起一次新的通信之前,通常需要先检查总线是否空闲。

在部分MCU中,可以通过BUSY标志判断I2C总线状态。

示例:

while(i2c_flag_get(I2C0,I2C_FLAG_I2CBSY)){/* 等待I2C总线空闲 */}

如果总线一直处于BUSY状态,可能存在以下问题:

  • 上一次通信没有正常发送STOP;
  • SDA被某个从机持续拉低;
  • SCL被某个设备持续拉低;
  • 主机在通信过程中异常复位;
  • GPIO复用配置错误;
  • I2C外设没有正确初始化;
  • BUSY状态没有被正确恢复。

四、START起始信号

当主机准备发起一次I2C通信时,需要先产生START起始信号。

START的产生条件是:

SCL保持高电平时,SDA由高电平变为低电平。

也就是:

SCL = 1 SDA:1 → 0

因为正常数据传输时,SDA不应该在SCL高电平期间变化,所以从机一旦检测到这种特殊变化,就知道一次新的通信开始了。

START产生后,主机通常会继续发送:

7位从机地址 + R/W读写方向位

在部分GD32或STM32系列MCU中,程序设置START位后,I2C硬件会自动产生START波形。

示例:

i2c_start_on_bus(I2C0);

然后等待START发送完成标志:

while(!i2c_flag_get(I2C0,I2C_FLAG_SBSEND)){}

其中,SBSEND通常表示:

START起始条件已经成功发送。

五、STOP停止信号

当主机完成地址和数据传输后,通常会产生STOP停止信号,释放I2C总线。

STOP的产生条件是:

SCL保持高电平时,SDA由低电平变为高电平。

也就是:

SCL = 1 SDA:0 → 1

START和STOP可以通过下面的方法快速记忆:

SCL高电平时,SDA高变低:START SCL高电平时,SDA低变高:STOP

在程序中,主机通常通过设置STOP位,让I2C硬件自动产生停止波形。

示例:

i2c_stop_on_bus(I2C0);

产生STOP以后,SCL和SDA最终都应该恢复为高电平,总线重新进入空闲状态。


六、ACK和NACK是什么

I2C每发送8位地址或数据后,还会额外产生第9个时钟。

前8个时钟用于传输一个完整字节,第9个时钟用于传输ACK或NACK应答信号。

1. ACK应答

ACK表示接收方已经正确接收到前面的地址或数据。

在第9个时钟期间,接收方主动将SDA拉低:

第9个时钟期间: SDA = 0

这就是ACK。

ACK可以理解为接收方告诉发送方:

当前字节已经收到,可以继续通信。

2. NACK非应答

NACK表示接收方没有应答,或者接收方不准备继续接收后续数据。

在第9个时钟期间,如果没有设备把SDA拉低,SDA保持高电平:

第9个时钟期间: SDA = 1

这就是NACK。

3. ACK和NACK由谁产生

ACK或NACK由当前字节的接收方产生。

主机向从机写数据时:

发送方:主机 接收方:从机 ACK/NACK:由从机产生

主机从从机读取数据时:

发送方:从机 接收方:主机 ACK/NACK:由主机产生

连续读取多个字节时,主机通常对前面的数据返回ACK,表示还要继续读取。

读取最后一个字节后,主机返回NACK,表示:

当前字节是最后一个字节,不需要继续发送。

随后主机产生STOP,结束本次通信。

图2 I2C的START、STOP、ACK与NACK

从图中可以看到,START和STOP都发生在SCL高电平期间。每发送8位地址或数据后,第9个时钟用于传输ACK或NACK。


七、为什么发送8位数据后还有第9个时钟

I2C以字节为基本传输单位。

一个完整字节包含8位数据:

D7 D6 D5 D4 D3 D2 D1 D0

发送完D0后,主机还会产生第9个时钟,用于让接收方返回应答。

完整过程如下:

第1个时钟:D7 第2个时钟:D6 第3个时钟:D5 第4个时钟:D4 第5个时钟:D3 第6个时钟:D2 第7个时钟:D1 第8个时钟:D0 第9个时钟:ACK或NACK

因此,第9位不是数据位,而是应答位。

在分析逻辑分析仪波形时,不要把第9个时钟误认为下一个字节的数据。


八、7位地址和8位地址的关系

I2C地址是开发过程中最容易混淆的部分之一。

很多芯片手册给出的是7位地址,但驱动代码、寄存器或者逻辑分析仪中显示的可能是8位地址字节。

一个完整的I2C地址字节由以下两部分组成:

A6 A5 A4 A3 A2 A1 A0 R/W

其中:

A6~A0:7位从机地址 R/W:读写方向位

读写方向位的定义为:

R/W = 0:写操作 R/W = 1:读操作

因此,总线上真正发送的地址字节,本质上是:

7位从机地址左移1位,再拼接R/W位。

九、地址0x20为什么会变成0x40和0x41

假设某个I2C设备的7位地址为:

0x20

1. 写地址字节

写操作时,R/W位为0。

计算方式:

0x20<<1

计算结果:

0x40

所以写地址字节为:

0x40

2. 读地址字节

读操作时,R/W位为1。

计算方式:

(0x20<<1)|0x01

计算结果:

0x41

所以读地址字节为:

0x41

三者之间的关系如下:

7位设备地址:0x20 写地址字节:0x40 读地址字节:0x41

对应的二进制形式为:

0x20 = 010 0000 0x40 = 0100 0000 0x41 = 0100 0001

代码示例:

uint8_tslave_addr=0x20;uint8_twrite_addr=slave_addr<<1;uint8_tread_addr=(slave_addr<<1)|0x01;

运行结果:

write_addr = 0x40 read_addr = 0x41

图3 I2C的7位地址、写地址和读地址

从图中可以看到,0x40和0x41通常不是两个不同的I2C设备地址,而是同一个7位地址0x20在写操作和读操作下的两种总线表示。


十、地址到底要不要左移

地址是否需要左移,取决于具体驱动接口的要求。

有些驱动接口要求调用者传入7位地址。

例如:

i2c_write(0x20,data,length);

这种情况下,驱动内部可能会自动完成:

地址左移1位 拼接R/W方向位

有些底层接口则要求调用者直接传入完整的地址字节。

例如:

i2c_master_addressing(I2C0,0x40,I2C_TRANSMITTER);

如果接口内部已经会自动处理地址,而调用者又提前左移一次,就会导致地址错误。

例如,原始7位地址为:

0x20

调用者错误地先转换为:

0x40

驱动内部又执行一次左移:

0x40 << 1 = 0x80

这样总线上发送的地址就会错误,从机无法返回ACK。

调试地址问题时,需要确认以下内容:

  • 芯片手册给出的是7位地址还是8位地址字节;
  • 驱动函数要求传入7位地址还是完整地址字节;
  • 驱动内部是否会自动左移;
  • 驱动内部是否会自动拼接R/W位;
  • 逻辑分析仪显示的是7位地址还是原始地址字节。

十一、主机写一帧数据的完整流程

一次基本的主机写操作通常包含以下步骤:

1. 等待总线空闲 2. 产生START 3. 发送从机地址和写方向位 4. 等待从机ACK 5. 发送第1个数据字节 6. 等待从机ACK 7. 继续发送后续数据字节 8. 每个字节后等待ACK 9. 产生STOP

假设从机7位地址为0x20,主机准备发送两个数据字节。

通信过程可以表示为:

START 0x40 ACK DATA1 ACK DATA2 ACK STOP

其中:

0x40 = 0x20 << 1

0x40的最低位为0,表示当前为写操作。

每发送一个字节后,从机都会通过ACK告诉主机:

当前字节已经接收完成,可以继续发送。

如果地址发送后立即出现NACK,应优先检查:

  • 地址是否正确;
  • 是否混淆7位地址和8位地址;
  • 地址是否被重复左移;
  • 读写方向位是否正确;
  • 从机是否上电;
  • 从机是否完成初始化;
  • SDA和SCL是否接反;
  • 总线上是否存在上拉电阻;
  • 从机是否处于可通信状态。

十二、主机读一帧数据的完整流程

一次基本的主机读操作通常包含以下步骤:

1. 等待总线空闲 2. 产生START 3. 发送从机地址和读方向位 4. 等待从机ACK 5. 从机发送数据 6. 主机返回ACK或NACK 7. 产生STOP

从地址为0x20的设备读取一个字节时,可以表示为:

START 0x41 ACK DATA NACK STOP

其中:

0x41 = (0x20 << 1) | 0x01

主机读取最后一个字节后,需要返回NACK,告诉从机:

读取已经结束,不需要继续发送数据。

随后主机产生STOP。

连续读取多个字节

假设主机连续读取3个字节,通信过程通常为:

START 读地址 ACK DATA1 主机ACK DATA2 主机ACK DATA3 主机NACK STOP

前两个字节后,主机返回ACK,表示继续读取。

最后一个字节后,主机返回NACK,表示当前读取结束。


十三、为什么读取寄存器时经常要先写后读

很多I2C从机内部包含多个寄存器。

主机在读取某个寄存器之前,必须先告诉从机:

我要读取哪个寄存器。

因此,常见的寄存器读取流程分为两个阶段。

1. 阶段A:发送寄存器地址

START 从机写地址 ACK 寄存器地址或命令字 ACK

2. 阶段B:读取寄存器数据

Repeated START 从机读地址 ACK 从机返回数据 主机NACK STOP

完整流程如下:

START 从机写地址 ACK 寄存器地址 ACK Repeated START 从机读地址 ACK 数据 NACK STOP

Repeated START称为重复起始信号。

它可以在不释放总线的情况下,重新发起一次地址传输,并将通信方向从写切换为读。

部分设备也允许下面的通信方式:

START 写地址 寄存器地址 STOP 延时一段时间 START 读地址 读取数据 STOP

到底使用STOP还是Repeated START,需要查看具体从机芯片的数据手册和通信协议。

图4 一帧I2C通信的完整流程

从总线空闲开始,主机依次产生START、发送地址、等待ACK、发送或接收数据,最后产生STOP。对于寄存器查询类命令,通常需要先写入寄存器地址,再发起读取。


十四、一帧主机写通信的代码流程

下面以主机发送数据为例,说明代码中的主要步骤。

/* 等待总线空闲 */while(i2c_flag_get(I2C0,I2C_FLAG_I2CBSY)){}/* 产生START */i2c_start_on_bus(I2C0);/* 等待START发送完成 */while(!i2c_flag_get(I2C0,I2C_FLAG_SBSEND)){}/* 发送从机地址,方向为写 */i2c_master_addressing(I2C0,slave_addr,I2C_TRANSMITTER);/* 等待地址发送完成 */while(!i2c_flag_get(I2C0,I2C_FLAG_ADDSEND)){}/* 清除地址发送完成标志 */i2c_flag_clear(I2C0,I2C_FLAG_ADDSEND);/* 发送数据 */for(uint8_ti=0;i<length;i++){while(!i2c_flag_get(I2C0,I2C_FLAG_TBE)){}i2c_data_transmit(I2C0,buffer[i]);}/* 等待最后一个字节传输完成 */while(!i2c_flag_get(I2C0,I2C_FLAG_BTC)){}/* 产生STOP */i2c_stop_on_bus(I2C0);

需要注意,不同MCU和不同固件库的函数名、标志名以及清除方式可能不同,应以当前芯片的参考手册和库函数说明为准。


十五、I2C中断是怎么产生的

I2C中断并不是总线上每出现一次电平变化,CPU就进入一次中断。

SCL时钟、地址移位和数据移位通常由I2C硬件外设自动完成。

CPU是否进入中断,一般取决于两个条件:

对应状态标志位置位 并且 对应中断使能位已经打开

可以简化理解为:

中断请求 = 状态标志有效 && 对应中断已使能

例如,发送缓冲区为空时,TBE标志可能置位。

只有在TBE中断被使能的情况下,才会向CPU请求进入对应的I2C中断服务程序。

因此,不应该简单理解成:

SCL每跳变一次,CPU就进入一次中断。

SCL的每一个高低电平变化,一般都是由I2C硬件自动产生的。


十六、常见I2C状态和中断标志

在GD32、STM32等MCU中,经常可以看到以下I2C状态标志:

SBSEND ADDSEND TBE RBNE BTC AERR

不同芯片中的名称和具体定义可能略有区别,最终应以当前MCU参考手册为准。

1. SBSEND

SBSEND通常表示:

START起始条件已经发送完成。

对应波形为:

SCL高电平时,SDA由高变低。

主机检测到SBSEND后,通常可以继续发送从机地址。

代码示例:

if(i2c_flag_get(I2C0,I2C_FLAG_SBSEND)){i2c_master_addressing(I2C0,slave_addr,I2C_TRANSMITTER);}

2. ADDSEND

在主机模式下,ADDSEND通常表示:

地址阶段已经完成,并且从机已经返回ACK。

在从机模式下,ADDSEND也可能表示:

从机地址已经匹配。

主机地址发送后,如果从机没有返回ACK,程序通常不会正常进入ADDSEND流程,而可能产生AERR应答错误。

3. TBE

TBE通常表示:

发送数据寄存器为空,可以写入下一个字节。

代码示例:

if(i2c_flag_get(I2C0,I2C_FLAG_TBE)){i2c_data_transmit(I2C0,tx_buffer[index]);index++;}

需要注意,TBE主要表示发送数据寄存器可以继续写入。

它不一定表示当前字节连同ACK阶段都已经完全结束。

4. BTC

BTC通常表示:

当前字节以及对应的应答阶段已经完成。

发送最后一个字节后,程序经常等待BTC,再产生STOP。

代码示例:

while(!i2c_flag_get(I2C0,I2C_FLAG_BTC)){}i2c_stop_on_bus(I2C0);

TBE和BTC的含义不能完全等同:

TBE:发送数据寄存器为空,可以装入下一个字节。 BTC:当前字节以及应答阶段已经完成。

5. RBNE

RBNE通常表示:

接收数据寄存器非空,已经收到一个新字节。

程序需要及时读取接收数据寄存器。

代码示例:

if(i2c_flag_get(I2C0,I2C_FLAG_RBNE)){rx_buffer[index]=i2c_data_receive(I2C0);index++;}

读取数据寄存器后,RBNE通常会被清除。

如果RBNE置位后一直不读取数据,可能导致:

  • 中断持续触发;
  • 接收缓冲区溢出;
  • CPU频繁进入中断;
  • 其他任务无法及时运行;
  • 系统表现为卡死或响应变慢。

6. AERR

AERR通常表示:

地址或数据发送后,没有收到接收方的ACK。

地址阶段出现AERR时,应重点检查:

  • 从机地址是否正确;
  • 地址是否被重复左移;
  • 读写方向位是否正确;
  • 从机是否已经上电;
  • 从机是否在线;
  • SDA和SCL接线是否正确;
  • 总线上拉电阻是否存在;
  • 从机是否处于可响应状态。

图5 I2C状态标志与实际波形阶段的对应关系

SBSEND对应START发送完成,ADDSEND对应地址阶段完成,TBE表示发送寄存器可以写入新数据,RBNE表示接收到新字节,BTC表示字节传输完成,AERR通常表示没有收到ACK。


十七、发送一个字节是否一定进入一次中断

答案是不一定。

在很多简单的I2C中断发送程序中,可能表现为:

发送一个字节 TBE置位 进入I2C中断 装载下一个字节

因此,看起来像是发送一个字节就进入一次中断。

但是实际中断次数还与以下因素有关:

  • MCU的I2C外设结构;
  • 启用了哪些中断源;
  • 是否使用发送FIFO;
  • 是否使用接收FIFO;
  • 是否使用DMA;
  • 多个状态是否共用一个中断入口;
  • 中断服务程序一次处理几个状态;
  • 中断进入之前是否已经有多个标志同时置位。

CPU进入一次I2C中断后,可能会同时检查多个状态标志。

例如:

voidI2C0_EV_IRQHandler(void){if(i2c_flag_get(I2C0,I2C_FLAG_SBSEND)){/* 发送地址 */}if(i2c_flag_get(I2C0,I2C_FLAG_ADDSEND)){/* 处理地址发送完成 */}if(i2c_flag_get(I2C0,I2C_FLAG_TBE)){/* 发送下一个字节 */}if(i2c_flag_get(I2C0,I2C_FLAG_RBNE)){/* 读取接收数据 */}}

因此,更准确的理解是:

I2C硬件在不同通信阶段设置状态标志。 当对应中断被使能时,CPU才可能进入中断服务程序。

不能简单认为I2C总线上出现一个动作,就一定对应一次固定中断。


十八、逻辑分析仪抓取I2C波形的正确顺序

拿到一段I2C波形后,不建议一开始就直接分析数据内容。

更合理的分析顺序如下。

1. 检查SCL是否正常

首先观察SCL:

  • 时钟是否连续;
  • 频率是否符合预期;
  • 高低电平是否正常;
  • 是否存在异常拉长的低电平;
  • 是否存在明显毛刺;
  • 上升沿和下降沿是否过慢。

如果SCL本身不正常,后续分析地址和数据通常没有意义。

2. 寻找START和STOP

START的特征为:

SCL高电平时,SDA由高变低。

STOP的特征为:

SCL高电平时,SDA由低变高。

先确定一帧通信从哪里开始,到哪里结束。

3. 识别地址字节和R/W位

START之后的第一个字节通常为地址字节。

需要确认:

  • 高7位是否为目标从机地址;
  • 最低位是读还是写;
  • 工具显示的是7位地址还是完整地址字节。

4. 检查第9个时钟的ACK或NACK

每发送一个地址或数据字节后,都要检查第9个时钟。

如果地址后立即NACK,应优先检查:

  • 地址;
  • 电源;
  • 接线;
  • 上拉电阻;
  • 从机状态。

5. 分析数据内容

确认地址和ACK正常后,再分析实际数据:

  • 数据顺序是否正确;
  • 字节长度是否正确;
  • 大小端是否正确;
  • 命令字是否正确;
  • 返回数据是否符合协议;
  • 是否包含CRC或PEC;
  • 字节间隔是否满足协议要求。

十九、地址后一直NACK怎么排查

如果逻辑分析仪显示地址后一直NACK,可以按照下面的顺序排查。

1. 检查地址格式

确认当前使用的是:

7位地址 还是 8位地址字节

例如设备7位地址为0x20:

7位地址:0x20 写地址:0x40 读地址:0x41

不要把0x40再次左移。

2. 检查R/W方向

如果当前准备向从机写入命令,地址最低位应该为0。

如果当前准备从从机读取数据,地址最低位应该为1。

3. 检查从机供电

确认:

  • 从机电源电压正常;
  • 从机地线与主机共地;
  • 从机复位引脚状态正常;
  • 从机已经完成上电初始化。

4. 检查SDA和SCL接线

确认:

  • SDA连接到SDA;
  • SCL连接到SCL;
  • 没有接反;
  • 引脚复用配置正确;
  • GPIO没有被其他模块占用。

5. 检查上拉电阻

确认SDA和SCL是否连接了合适的上拉电阻。

没有上拉电阻时,I2C总线可能无法正常恢复高电平。

6. 检查从机地址配置

部分从机具有地址配置引脚,例如:

A0 A1 A2

这些引脚的高低电平会影响最终I2C地址。

需要根据原理图和数据手册确认实际地址。


二十、SDA一直被拉低怎么排查

SDA一直为低电平,通常表示总线没有正常释放。

可能原因包括:

  • 从机状态机卡在未完成的通信中;
  • 上一次通信没有产生STOP;
  • 主机在通信过程中异常复位;
  • 从机异常拉住SDA;
  • SDA线路短路;
  • GPIO模式配置错误;
  • 引脚复用配置错误;
  • 从机仍在等待后续时钟。

部分情况下,可以通过GPIO手动发送若干个SCL时钟,帮助从机退出未完成的接收状态。

常见恢复思路如下:

1. 临时把SCL配置为GPIO输出。 2. 保持SDA释放。 3. 手动输出9个左右的SCL脉冲。 4. 尝试产生STOP。 5. 重新初始化I2C外设。

该方法是否适用,需要结合具体从机协议和硬件情况判断。


二十一、总线一直BUSY怎么排查

总线一直BUSY可能由以下原因引起:

  • SDA没有恢复高电平;
  • SCL被某个器件拉低;
  • 上一次通信异常结束;
  • STOP没有正确产生;
  • I2C外设状态异常;
  • GPIO复用配置错误;
  • 主机初始化顺序不正确;
  • 从机处于异常状态。

建议按照下面的顺序检查:

1. 用逻辑分析仪观察SDA和SCL实际电平。 2. 检查SDA是否被持续拉低。 3. 检查SCL是否被持续拉低。 4. 检查上一次通信是否存在STOP。 5. 检查GPIO复用和开漏配置。 6. 尝试复位I2C外设。 7. 必要时执行总线恢复操作。

二十二、为什么I2C需要上拉电阻

I2C的SDA和SCL通常采用开漏输出结构。

开漏输出的特点是:

设备可以主动输出低电平。 设备通常不能主动输出高电平。

信号线恢复为高电平,需要依靠外部上拉电阻。

上拉电阻会影响:

  • 信号上升沿速度;
  • 总线最高通信频率;
  • 电平稳定性;
  • 总线功耗;
  • 设备拉低信号时的电流;
  • 多设备通信可靠性。

常见的I2C上拉电阻可能为:

2.2kΩ 4.7kΩ 10kΩ

但实际阻值不能只靠经验固定选择,需要结合以下因素:

  • I2C总线电压;
  • 总线电容;
  • PCB走线长度;
  • 从机数量;
  • 通信频率;
  • 芯片允许的低电平灌电流。

上拉电阻过大时,信号上升沿可能过慢。

上拉电阻过小时,设备把总线拉低时的电流会增大。

逻辑分析仪主要观察数字逻辑状态。

如果怀疑信号存在上升沿过慢、振铃、过冲或者电压幅值异常,建议使用示波器观察真实模拟波形。


二十三、波形正常但数据错误怎么排查

如果START、地址、ACK和STOP看起来都正常,但数据内容不符合预期,可以重点检查以下内容:

  • 命令字是否正确;
  • 寄存器地址是否正确;
  • 数据长度是否一致;
  • 字节顺序是否正确;
  • 大小端是否正确;
  • 是否存在CRC;
  • 是否存在PEC;
  • 校验和计算是否正确;
  • 数据结构是否存在对齐问题;
  • 主机和从机使用的协议版本是否一致;
  • 连续读取时寄存器是否自动递增;
  • 是否需要等待从机处理命令;
  • 写命令后是否需要延时再读取结果。

例如,一个16位数据可能按下面两种顺序发送。

大端格式:

高字节 低字节

小端格式:

低字节 高字节

如果主从双方对大小端理解不一致,就会出现波形正常但数据错误的情况。


二十四、如何把代码、标志位和波形对应起来

调试I2C时,建议同时观察以下三类信息。

1. 软件执行流程

等待BUSY清除 产生START 等待SBSEND 发送地址 等待ADDSEND 发送数据 等待TBE或BTC 产生STOP

2. I2C状态标志

SBSEND ADDSEND TBE BTC RBNE AERR

3. 逻辑分析仪波形

START 地址 ACK 数据 ACK STOP

把三者对应起来后,问题会更容易定位。

例如,程序一直等待ADDSEND,而逻辑分析仪显示地址后为NACK。

这时问题通常不在等待循环本身,而更可能是:

  • 从机地址错误;
  • 地址格式错误;
  • 从机没有上电;
  • 从机没有应答;
  • SDA或SCL连接异常;
  • 上拉电阻异常。

再例如,程序频繁进入RBNE中断,但没有读取接收数据寄存器。

这可能导致:

  • RBNE持续保持;
  • CPU频繁进入中断;
  • 接收缓冲区溢出;
  • 其他任务得不到执行;
  • 系统表现为卡死。

因此,分析I2C问题时不能只看代码,也不能只看逻辑分析仪。

最有效的方法是:

程序执行流程 + I2C状态标志 + 实际总线波形

三者同时进行对照。


二十五、I2C调试的推荐步骤

实际项目中,可以按照以下顺序排查I2C问题。

第1步:确认从机供电和共地正常。 第2步:确认SDA、SCL接线和GPIO复用正确。 第3步:确认总线上存在上拉电阻。 第4步:确认总线空闲时SDA、SCL都为高电平。 第5步:检查SCL频率是否符合从机要求。 第6步:寻找START和STOP。 第7步:确认地址字节和R/W位。 第8步:检查地址后的ACK或NACK。 第9步:检查每个数据字节后的ACK或NACK。 第10步:检查数据顺序、长度和校验。 第11步:把波形与代码中的状态标志对应起来。 第12步:必要时使用示波器检查信号质量。

不要一开始就直接分析数据内容。

如果地址阶段已经NACK,后面的数据实际上并没有被从机正常接收。


二十六、核心知识总结

学习I2C时,可以先记住以下结论:

  1. I2C主要由SCL和SDA两根信号线组成。

  2. SCL低电平期间,SDA可以变化。

  3. SCL高电平期间,SDA应保持稳定。

  4. SCL高电平时,SDA由高变低表示START。

  5. SCL高电平时,SDA由低变高表示STOP。

  6. 每发送8位地址或数据后,第9个时钟用于ACK或NACK。

  7. ACK表示接收方在第9个时钟期间把SDA拉低。

  8. NACK表示第9个时钟期间SDA保持高电平。

  9. 7位地址左移1位后,再拼接R/W位,形成总线地址字节。

  10. 7位地址0x20对应写地址字节0x40和读地址字节0x41。

  11. 主机读取最后一个字节后,通常返回NACK,再产生STOP。

  12. 查询寄存器时,通常需要先写入寄存器地址,再发起读取。

  13. SBSEND通常表示START已经发送完成。

  14. ADDSEND通常表示地址阶段已经完成。

  15. TBE表示发送数据寄存器为空,可以写入下一个字节。

  16. RBNE表示接收数据寄存器非空,需要及时读取数据。

  17. BTC通常表示当前字节及应答阶段已经完成。

  18. AERR通常表示地址或数据发送后没有收到ACK。

  19. I2C中断由状态标志和中断使能共同决定。

  20. 发送一个字节可能触发一次中断,但不是所有控制器都固定如此。

  21. 调试波形时,应按照SCL、START、地址、ACK、数据、STOP的顺序分析。

  22. 地址后立即NACK,应优先检查地址、电源、接线、上拉电阻和从机状态。


二十七、结语

真正掌握I2C,不只是会调用发送和接收函数,而是能够理解:

代码为什么停在当前状态 状态标志为什么会置位 总线上实际发生了什么波形

当程序执行流程、I2C状态标志和逻辑分析仪波形能够一一对应时,就可以快速判断问题发生在:

  • 总线空闲阶段;
  • START阶段;
  • 地址阶段;
  • ACK阶段;
  • 数据阶段;
  • STOP阶段;
  • 中断处理阶段。

这也是从“会调用I2C接口”走向“能够独立调试I2C驱动”的关键一步。


---

相关新闻

  • ASD433A评估板硬件设计解析与PowerPC MCU开发实战指南
  • 方向科技 GEO 系统与市面 AI 搜索优化软件深度横评
  • 国产大模型会回答之后,怎样用魔珐星云补齐具象交互?

最新新闻

  • 终局不是 GUI,而是 CLI、TUI 和 GUI 的重新分工
  • 当GPT-5.5 成为技术中台核心:企业智能化升级的机遇与陷阱
  • MuleSoft+LangChain企业级AI编排实战:让大模型走进生产流水线
  • Windows本地语音识别终极指南:TMSpeech让你的电脑自动记录一切对话
  • 虚拟机的安装与配置
  • 【AI论文写作生死线】:超86%用户踩雷的“伪原创”陷阱,如何用ChatGPT产出真正通过Turnitin+CNKI双审的学术文本?

日新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号