I²C通信完整流程详解:START、地址、ACK、数据、Repeated START和STOP一次讲透

前言
在 STM32、GD32、ESP32 等单片机开发中,I²C 几乎是最常见的板级通信协议之一。
很多器件都会使用 I²C:
- 温度传感器
- EEPROM
- RTC
- 电源管理芯片
- GPIO 扩展芯片
- ADC/DAC
- 风扇控制芯片
- 电流/电压监控芯片
- OLED
- 各类传感器
在 STM32 HAL 库中,我们经常直接调用:
c
HAL_I2C_Master_Transmit();
HAL_I2C_Master_Receive();
HAL_I2C_Mem_Write();
HAL_I2C_Mem_Read();
于是很容易产生一种错觉:
I²C 通信就是调用一个 HAL 函数。
实际上,一个完整 I²C 事务背后会经历:
text
总线空闲
↓
START
↓
从机地址 + R/W
↓
ACK
↓
数据 / 寄存器地址
↓
ACK
↓
数据
↓
ACK / NACK
↓
STOP
如果是典型的"读取寄存器",中间还会出现:
text
Repeated START
理解这些底层过程,对排查下面的问题非常重要:
text
I²C设备不响应
HAL_I2C返回HAL_ERROR
地址总是NACK
读出来的数据全是0xFF
切换设备后通信异常
逻辑分析仪有波形但程序报错
HAL_I2C_Mem_Read到底发了什么
7位地址和8位地址到底怎么换算
本文就从最基础的物理层开始,把整个 I²C 通信过程完整讲清楚。
一、I²C是什么
I²C 全称:
text
Inter-Integrated Circuit
最初由 Philips 提出。
I²C 是一种:
两线制、同步、串行、支持多设备挂接的通信总线。
最核心的两根线是:
text
SCL:Serial Clock
串行时钟线
SDA:Serial Data
串行数据线
基本结构:
text
VCC
│
┌───┴───┐
R R
│ │
SCL ──────┼───────┼──────
│ │
SDA ──────┼───────┼──────
│ │
┌─────────┐
│ Master │
└─────────┘
│
┌──────┼──────┐
│ │ │
Slave1 Slave2 Slave3
一条 I²C 总线上可以连接多个从设备。
主机通过:
text
设备地址
选择目标从机。
二、I²C为什么只需要两根线
I²C 使用:
text
SCL
SDA
就能够完成通信。
其中:
SCL
主要由主机产生时钟。
每一个时钟周期对应一个数据位的传输节拍。
SDA
负责真正的数据传输。
地址、R/W 位、数据和 ACK/NACK 都通过 SDA 传输。
因此可以简单理解为:
text
SCL:告诉大家什么时候看数据
SDA:告诉大家数据是什么
三、I²C为什么必须有上拉电阻
I²C 的一个关键电气特性是:
SDA 和 SCL 通常采用开漏/开集输出结构。
设备通常只能主动:
text
拉低总线
而不能主动把总线强推为高电平。
当所有设备释放总线后,由外部上拉电阻将线路拉高。
因此:
text
设备想输出0:
主动拉低
设备想输出1:
释放线路
由上拉电阻拉高
可以理解为:
text
VCC
│
Rpull-up
│
SDA ───────┼────────
│
开漏输出
│
GND
这也是为什么 I²C 原理图中通常都会看到:
text
SCL上拉电阻
SDA上拉电阻
四、为什么I²C使用开漏结构
因为一条总线上可能有多个设备。
如果某个设备强制输出高电平,而另一个设备强制输出低电平:
text
高电平驱动
VS
低电平驱动
可能产生很大的短路电流。
开漏结构避免了这个问题。
总线逻辑可以理解为:
text
只要有任何设备拉低
总线就是0
只有所有设备都释放
总线才是1
这个特性也是:
text
ACK
仲裁
Clock Stretching
等机制能够实现的基础。
五、I²C总线空闲状态是什么
当没有任何设备通信时:
text
SCL = 1
SDA = 1
也就是:
text
SCL ───────────────── HIGH
SDA ───────────────── HIGH
这是 I²C 总线的空闲状态。
所以遇到 I²C 完全不能通信时,第一件事情就可以测:
text
SCL空闲是不是高电平?
SDA空闲是不是高电平?
如果某一根线长期为低:
text
SCL = 0
或者
SDA = 0
就说明总线可能已经被某个设备拉死。
六、I²C数据什么时候允许变化
I²C 有一个非常重要的时序规则:
正常数据传输期间,SDA 应该在 SCL 低电平期间变化,在 SCL 高电平期间保持稳定。
也就是:
text
SCL低:
允许修改SDA
SCL高:
接收方读取SDA
例如:
text
SCL __/‾‾\__/‾‾\__/‾‾\__
SDA ___0_____1_____0_____
↑ ↑
低电平 低电平
期间变化
为什么?
因为当 SCL 高电平时:
text
SDA发生特殊跳变
会被解释成:
text
START
或者
STOP
所以必须特别区分。
七、START起始条件是什么
I²C 通信正式开始之前,主机必须先产生:
text
START
起始条件。
START 的定义是:
当 SCL 保持高电平时,SDA 从高电平变成低电平。
即:
text
SCL = HIGH
SDA:
HIGH → LOW
波形:
text
SCL ───────────────
HIGH
SDA ────────┐
└────────
↑
START
所以 START 最核心的记忆方法:
SCL 高,SDA 高→低,就是 START。
八、为什么必须先发送START
START 相当于告诉整个 I²C 总线上的设备:
我要开始一笔新的通信了。
它有几个作用:
- 宣布总线事务开始;
- 让所有从机开始监听;
- 主机开始占用总线;
- 为后续设备地址做准备。
完整过程:
text
空闲
SCL=1
SDA=1
↓
START
SCL=1时
SDA 1→0
↓
发送地址
九、START之后第一件事是什么
START 之后,主机并不会立刻发送业务数据。
而是先发送:
text
目标从机地址
+
读写方向
典型 7 位 I²C 地址格式:
text
A6 A5 A4 A3 A2 A1 A0 R/W
一共:
text
8 bit
其中:
text
A6~A0:7位从机地址
R/W:读写方向
十、R/W位是什么意思
R/W 位决定:
这一阶段是主机写数据给从机,还是主机从从机读取数据。
通常:
text
R/W = 0
表示Write
R/W = 1
表示Read
例如从机 7 位地址:
text
0x50
二进制:
text
1010000
左移一位:
text
10100000
写操作:
text
10100000
↑
R/W=0
也就是:
text
0xA0
读操作:
text
10100001
↑
R/W=1
也就是:
text
0xA1
因此:
text
7位地址 = 0x50
写地址字节 = 0xA0
读地址字节 = 0xA1
十一、7位地址和8位地址为什么经常搞混
这是 I²C 最常见的坑之一。
假设芯片真实的 7 位地址是:
text
0x50
那么:
text
Write地址字节 = 0xA0
Read地址字节 = 0xA1
于是不同资料里可能写:
text
Slave Address = 0x50
也可能看到:
text
Write Address = 0xA0
Read Address = 0xA1
它们实际上可能是在描述同一个设备。
十二、STM32 HAL里的地址到底传哪个
在很多 STM32 HAL I²C API 中,DevAddress 参数使用的是已经左移后的地址形式。
例如:
c
#define DEVICE_ADDR (0x50U << 1)
调用:
c
HAL_I2C_Master_Transmit(
&hi2c1,
DEVICE_ADDR,
data,
length,
100
);
即:
text
0x50 << 1
=
0xA0
但工程中一定要注意:
不同库、不同API、不同芯片资料对地址表示方式可能不同。
所以遇到地址问题时,要确认三个东西:
text
器件手册写的是7位还是8位地址?
你的驱动API需要7位还是左移后的地址?
逻辑分析仪显示的是哪一种?
千万不要只凭数值判断。
十三、地址发送以后谁来判断是否匹配
START 后:
text
主机发送
7位地址 + R/W
总线上的所有从机都会监听。
例如:
text
Master发送:
0x50
Slave1地址:
0x20
Slave2地址:
0x50
Slave3地址:
0x68
只有:
text
Slave2
发现自己的地址匹配。
因此只有它应该响应。
其他设备继续保持释放 SDA。
十四、什么是ACK
ACK 全称:
text
Acknowledge
表示:
text
应答
I²C 每发送完:
text
8 bit
之后,还会有:
text
第9个时钟
这个第 9 位就是 ACK/NACK 位。
例如:
text
D7 D6 D5 D4 D3 D2 D1 D0 ACK
十五、ACK是怎么产生的
发送方发送完 8 位以后:
text
发送方释放 SDA
接收方在第 9 个时钟期间:
text
拉低 SDA
表示 ACK。
也就是:
text
第9位:
SDA = 0
→ ACK
波形概念:
text
SCL _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
1 2 3 4 5 6 7 8 9
SDA D7 D6 D5 D4 D3 D2 D1 D0 0
↑
ACK
记忆:
第9位低电平 = ACK。
十六、ACK到底是谁发的
ACK 不是永远由从机发送。
应该记住:
谁接收前面的8位数据,谁负责产生第9位应答。
场景1:主机写从机
例如:
text
Master → Slave
主机发送:
text
地址
寄存器地址
数据
那么接收方是从机。
因此:
text
Slave产生ACK
流程:
text
Master发送8bit
↓
Slave第9位拉低SDA
↓
ACK
场景2:主机读从机
此时:
text
Slave → Master
从机发送数据。
接收方变成:
text
Master
所以:
text
Master负责发送ACK/NACK
这点非常重要。
十七、什么是NACK
NACK:
text
Not Acknowledge
表示:
text
没有应答
第 9 个时钟期间:
text
SDA保持高电平
就是 NACK。
所以:
text
SDA = 0 → ACK
SDA = 1 → NACK
可以记成:
第9位:低是ACK,高是NACK。
十八、为什么会出现NACK
NACK 不一定代表硬件坏了。
常见原因包括:
1. 地址错误
主机发送:
text
0x50
但总线上没有这个设备。
于是:
text
Address NACK
2. 从机没有上电
从机完全不能响应。
3. 地址模式写错
例如:
text
本应传0xA0
却传0x50
或者反过来。
4. 从机正忙
例如 EEPROM 内部正在执行写操作。
短时间内可能不响应。
5. 从机不支持当前操作
例如:
- 寄存器地址无效;
- 当前状态不允许访问;
- 数据长度错误。
6. 主机主动发送NACK
在主机读取从机时:
主机读取最后一个字节以后,通常主动发送 NACK,表示"我不要更多数据了"。
所以:
text
读操作最后一个NACK
往往是正常行为,而不是错误。
十九、为什么每8位数据后都有ACK/NACK
因为 I²C 需要让发送方知道:
text
这个字节到底有没有被正确接收?
所以通信节奏实际上是:
text
8位数据
↓
1位应答
↓
8位数据
↓
1位应答
↓
8位数据
↓
1位应答
也就是:
text
8 + 1
8 + 1
8 + 1
...
如果发送多个字节:
text
Byte1 → ACK
Byte2 → ACK
Byte3 → ACK
Byte4 → ACK/NACK
二十、I²C数据按照什么顺序发送
I²C 数据通常:
最高位 MSB 先发送。
例如发送:
text
0xA5
二进制:
text
1010 0101
发送顺序:
text
D7 D6 D5 D4 D3 D2 D1 D0
1 0 1 0 0 1 0 1
然后进入:
text
第9位 ACK/NACK
二十一、SDA在什么时候改变
正常数据位期间:
SDA 应该在 SCL 低电平期间改变。
然后在:
text
SCL高电平期间
保持稳定。
例如:
text
SCL __/‾‾\__/‾‾\__/‾‾\__
SDA __0_____1_____0_______
接收方通常在时钟有效阶段读取 SDA。
二十二、一个完整I²C写操作是什么样
假设:
text
从机地址:0x50
寄存器地址:0x10
写入数据:0x55
典型写流程:
text
START
↓
Slave Address + W
↓
ACK
↓
Register Address
↓
ACK
↓
Data
↓
ACK
↓
STOP
具体可以表示:
text
START
0x50 + Write
ACK
0x10
ACK
0x55
ACK
STOP
二十三、完整写寄存器时序
逻辑上:
text
S
│
├─ Address + W
│
├─ ACK
│
├─ Register Address
│
├─ ACK
│
├─ Data
│
├─ ACK
│
└─ P
其中:
text
S = START
P = STOP
二十四、STM32 HAL写寄存器示例
例如设备:
text
7位地址:0x50
寄存器:0x10
数据:0x55
可以:
c
#define DEVICE_ADDR (0x50U << 1)
uint8_t value = 0x55;
HAL_StatusTypeDef ret;
ret = HAL_I2C_Mem_Write(
&hi2c1,
DEVICE_ADDR,
0x10,
I2C_MEMADD_SIZE_8BIT,
&value,
1,
100
);
if (ret != HAL_OK)
{
/* I2C error */
}
它底层概念上完成:
text
START
↓
Address + W
↓
ACK
↓
0x10
↓
ACK
↓
0x55
↓
ACK
↓
STOP
二十五、为什么HAL_I2C_Master_Transmit和Mem_Write不一样
例如:
c
HAL_I2C_Master_Transmit();
更像是:
向某个 I²C 从机发送一串普通字节。
而:
c
HAL_I2C_Mem_Write();
更加适合:
带内部寄存器地址的 I²C 器件。
例如:
text
温度传感器
风扇控制芯片
GPIO扩展芯片
EEPROM
电源管理芯片
很多设备内部都有:
text
Register Address
因此 HAL 帮你把:
text
设备地址
+
寄存器地址
+
数据
组合起来。
二十六、I²C读数据为什么比写更复杂
写操作很直观:
text
Master
↓
Slave
主机一路发送。
但是读取寄存器时存在一个问题:
从机怎么知道主机到底想读哪个寄存器?
例如从机内部:
text
0x00:状态寄存器
0x01:温度高字节
0x02:温度低字节
0x03:配置寄存器
主机想读:
text
0x02
必须先告诉从机:
text
我要访问0x02
所以常见寄存器读操作不是简单:
text
START
Address + R
而是先"写寄存器地址",再切换为读。
二十七、典型寄存器读流程
假设要读:
text
Slave = 0x50
Register = 0x10
完整流程:
text
START
↓
Address + W
↓
ACK
↓
Register Address
↓
ACK
↓
Repeated START
↓
Address + R
↓
ACK
↓
Slave发送Data
↓
Master发送NACK
↓
STOP
注意:
中间不是一定先 STOP 再 START。
很多器件推荐使用:
text
Repeated START
二十八、什么是Repeated START
Repeated START:
text
重复起始条件
本质上与 START 的时序规则相同:
SCL 高电平期间,SDA 从高变低。
区别是:
text
普通START:
总线原本处于空闲状态
Repeated START:
前一个事务还没完全释放总线
主机直接重新发起START
所以:
text
START
Address + W
ACK
Register
ACK
Repeated START
Address + R
ACK
Data
NACK
STOP
非常常见。
二十九、为什么使用Repeated START
因为主机需要:
- 先写寄存器地址;
- 保持总线控制权;
- 立即切换到读模式;
- 防止中间被其他主机抢占。
例如:
text
先告诉设备我要读0x10
↓
不释放总线
↓
重新发送设备地址+Read
↓
读取0x10内容
这就是典型的:
text
Register Read
三十、主机读一个字节时谁发ACK
例如:
text
Slave → Master
从机发送:
text
8bit Data
因为主机是接收方,所以:
text
第9位由Master决定
如果主机还想继续读取:
text
Master发送ACK
如果这个字节是最后一个:
text
Master发送NACK
表示:
数据够了,不要继续发了。
三十一、主机读取多个字节
例如读取 4 字节:
text
Slave → Byte1
Master → ACK
Slave → Byte2
Master → ACK
Slave → Byte3
Master → ACK
Slave → Byte4
Master → NACK
Master → STOP
所以:
text
前几个字节:
ACK
最后一个字节:
NACK
这是非常重要的读操作规则。
三十二、为什么最后一个字节要NACK
因为 ACK 的含义可以理解为:
我收到这个字节了,请继续发送。
那么最后一个字节之后,如果主机仍然 ACK:
text
Slave可能认为:
主机还要下一个字节
因此主机需要通过:
text
NACK
明确告诉从机:
这是最后一个字节,停止继续发送。
随后主机产生:
text
STOP
结束事务。
三十三、STM32 HAL读取寄存器示例
例如读取:
text
Slave 7位地址:0x50
Register:0x10
代码:
c
#define DEVICE_ADDR (0x50U << 1)
uint8_t value = 0;
HAL_StatusTypeDef ret;
ret = HAL_I2C_Mem_Read(
&hi2c1,
DEVICE_ADDR,
0x10,
I2C_MEMADD_SIZE_8BIT,
&value,
1,
100
);
if (ret == HAL_OK)
{
printf("value = 0x%02X\r\n", value);
}
else
{
printf("I2C read failed\r\n");
}
底层概念上类似:
text
START
↓
Address + W
↓
ACK
↓
Register 0x10
↓
ACK
↓
Repeated START
↓
Address + R
↓
ACK
↓
Read Data
↓
NACK
↓
STOP
三十四、16位寄存器地址怎么办
有些器件内部寄存器地址不是8位,而是16位。
例如:
text
Register Address = 0x1234
那么可以使用:
c
HAL_I2C_Mem_Read(
&hi2c1,
DEVICE_ADDR,
0x1234,
I2C_MEMADD_SIZE_16BIT,
&value,
1,
100
);
此时寄存器地址阶段可能发送:
text
0x12
0x34
具体字节顺序和设备协议要看器件手册。
三十五、STOP停止条件是什么
通信完成后,主机通常发送:
text
STOP
STOP 的定义是:
当 SCL 保持高电平时,SDA 从低电平变化为高电平。
即:
text
SCL = HIGH
SDA:
LOW → HIGH
波形:
text
SCL ───────────────
SDA ________┐
└──────
STOP方向相反示意
更准确记忆:
text
START:
SCL高时
SDA 高 → 低
STOP:
SCL高时
SDA 低 → 高
所以:
起始看高到低,停止看低到高。
三十六、STOP的作用是什么
STOP 表示:
text
本次I²C事务结束
并让总线重新回到:
text
SCL = 1
SDA = 1
的空闲状态。
它的作用包括:
- 结束当前事务;
- 通知从机通信完成;
- 释放总线;
- 允许下一次通信开始。
三十七、完整I²C通信流程总结
最基础的通信流程:
text
Idle
↓
START
↓
Address + R/W
↓
ACK
↓
Data
↓
ACK/NACK
↓
STOP
如果多个数据字节:
text
START
↓
Address + R/W
↓
ACK
↓
Byte1
↓
ACK
↓
Byte2
↓
ACK
↓
Byte3
↓
ACK/NACK
↓
STOP
三十八、典型写寄存器完整流程
text
START
↓
Slave Address + W
↓
ACK
↓
Register Address
↓
ACK
↓
Data
↓
ACK
↓
STOP
记成:
先找设备,再找寄存器,再写数据。
三十九、典型读寄存器完整流程
text
START
↓
Slave Address + W
↓
ACK
↓
Register Address
↓
ACK
↓
Repeated START
↓
Slave Address + R
↓
ACK
↓
Data
↓
NACK
↓
STOP
记成:
先写寄存器地址,再重新起始切换到读模式。
四十、逻辑分析仪应该怎么看I²C
工程中排查 I²C 最有效的工具之一就是:
text
逻辑分析仪
例如抓到:
text
START
0x50 Write
ACK
0x10
ACK
0x55
ACK
STOP
就可以还原为:
text
向地址0x50设备
寄存器0x10
写入0x55
四十一、逻辑分析仪排查的正确顺序
建议按照:
第一步:找START
检查:
text
SCL高时
SDA是否高→低
第二步:看设备地址
确认:
text
是不是预期地址
第三步:看R/W
确认:
text
Write还是Read
第四步:看第9位
这是最关键的位置:
text
ACK还是NACK
如果地址后立即 NACK:
重点检查:
text
设备地址
器件供电
SDA/SCL
地址脚
芯片是否存在
第五步:看数据
检查:
text
寄存器地址是否正确
数据顺序是否正确
多字节字节序是否正确
第六步:看STOP
确认:
text
事务是否正确结束
四十二、如果地址直接NACK应该检查什么
这是最常见问题。
例如:
text
START
Address 0x50
NACK
STOP
说明:
主机已经发起通信,但没有设备对这个地址应答。
重点检查:
- 从机是否上电;
- GND是否共地;
- SCL/SDA是否接反;
- 是否有上拉电阻;
- 地址是否正确;
- 7位/8位地址是否弄错;
- 地址选择脚是否正确;
- 器件是否处于复位;
- 器件是否正在忙;
- 时钟速度是否超过设备能力。
四十三、为什么读出来全是0xFF
如果数据:
text
0xFF
0xFF
0xFF
不一定说明从机真的返回了 0xFF。
需要看逻辑分析仪。
可能是:
text
SDA一直处于高电平
原因包括:
- 从机没有真正驱动 SDA;
- 地址错误;
- 读阶段没有成功;
- 从机没响应;
- 软件读取失败后使用了未更新缓冲区。
因此不能只看:
c
printf("%02X", data);
还要看真实总线波形。
四十四、为什么读出来全是0x00
类似地:
text
0x00
也不一定是正确数据。
可能是:
- SDA被某个器件拉低;
- 总线卡死;
- 缓冲区提前清零但读取失败;
- 地址或寄存器错误;
- 器件真的返回0。
所以要同时检查:
text
HAL返回值
+
逻辑分析仪
四十五、I²C总线为什么会卡死
典型现象:
text
SDA长期为0
主机无法正常产生 START。
可能原因:
- 从机在发送数据中途主机复位;
- 从机认为事务还没结束;
- 软件没有正确发送STOP;
- 时钟中途停止;
- 上电时序异常;
- 外设状态机异常。
例如:
text
Slave已经拉低SDA准备发送下一bit
↓
Master突然Reset
↓
Slave不知道主机已经重启
↓
SDA一直保持低
四十六、I²C总线恢复怎么做
常见工程办法是:
如果 SDA 被从机拉低,可以临时把 SCL 配成 GPIO,手动产生若干个时钟脉冲。
通常可以尝试:
text
9个SCL脉冲
让从机内部状态机把剩余bit走完。
然后重新产生:
text
STOP
最后重新初始化 I²C 外设。
概念流程:
text
检测SDA=0
↓
关闭I²C外设
↓
SCL改GPIO
↓
产生9个时钟
↓
尝试生成STOP
↓
恢复I²C复用
↓
重新初始化
这是很多工业嵌入式设备中非常实用的恢复策略。
四十七、为什么是9个时钟
因为:
text
一个I²C字节
=
8bit数据
+
1bit ACK/NACK
如果从机卡在某个字节中间,最多补齐若干位后让其完成当前字节和应答周期。
9个脉冲是一种非常常见的总线恢复做法。
实际是否有效还取决于从机芯片实现。
四十八、什么是Clock Stretching
虽然 SCL 通常由主机控制,但部分从机如果暂时处理不过来,可以:
text
把SCL拉低
暂缓通信。
这叫:
text
Clock Stretching
也就是时钟拉伸。
流程:
text
Master尝试释放SCL
↓
Slave继续拉低SCL
↓
Master看到SCL还没有真正变高
↓
等待
↓
Slave处理完成
↓
释放SCL
↓
SCL被上拉到高
所以:
主机"释放SCL"不一定等于线路马上变高。
四十九、I²C速度有哪些
常见模式包括:
text
Standard-mode
100 kbit/s
Fast-mode
400 kbit/s
Fast-mode Plus
1 Mbit/s
还有更高速模式,具体设备支持能力要看标准和器件规格。
工程中最常见:
text
100kHz
400kHz
五十、为什么400kHz通信失败,100kHz却正常
如果降低 I²C 频率以后通信恢复,常见原因包括:
- 上拉电阻过大;
- 总线电容过大;
- 走线过长;
- 挂载设备太多;
- 从机不支持更高频率;
- 时序参数错误;
- 电平转换器速度不足。
因为 I²C 高电平主要依赖:
text
上拉电阻
+
总线电容
形成 RC 上升。
近似:
text
上升速度与 Rpull-up × Cbus 有关
当:
text
R太大
C太大
SDA/SCL 上升沿会变慢。
100kHz 还能工作,400kHz 就可能不满足时序。
五十一、上拉电阻应该选多大
实际取值需要根据:
- 总线电压;
- 总线电容;
- 通信速度;
- 器件最大灌电流;
- 上升时间要求;
计算。
工程中经常见到:
text
1kΩ
2.2kΩ
4.7kΩ
10kΩ
但不要认为:
text
I²C上拉永远就是4.7kΩ
4.7kΩ 只是非常常见的经验值之一。
五十二、STM32如何判断设备是否在线
HAL 可以使用:
c
HAL_I2C_IsDeviceReady();
例如:
c
#define DEVICE_ADDR (0x50U << 1)
if (HAL_I2C_IsDeviceReady(
&hi2c1,
DEVICE_ADDR,
3,
100) == HAL_OK)
{
printf("I2C device online\r\n");
}
else
{
printf("I2C device offline\r\n");
}
它很适合:
- 开机检测设备;
- I²C扫描;
- 判断从机是否在线;
- 故障恢复。
五十三、实现一个简单I²C扫描器
可以扫描常见7位地址:
c
void I2C_Scan(void)
{
for (uint8_t addr = 1; addr < 0x7F; addr++)
{
if (HAL_I2C_IsDeviceReady(
&hi2c1,
(uint16_t)(addr << 1),
1,
10) == HAL_OK)
{
printf(
"Found I2C device: 0x%02X\r\n",
addr
);
}
}
}
如果输出:
text
Found I2C device: 0x20
Found I2C device: 0x4E
Found I2C device: 0x68
说明总线上检测到对应7位地址设备。
五十四、I²C扫描器也不是万能的
一些器件可能:
- 某些状态下不响应扫描;
- 有特殊通信要求;
- 存在保留地址;
- 需要先解除复位;
- 需要等待上电初始化。
所以扫描不到设备时,仍然应该:
text
看原理图
看数据手册
看逻辑分析仪
五十五、软件I²C如何产生START
如果使用 GPIO 模拟 I²C,START 可以写成:
c
void I2C_Start(void)
{
SDA_HIGH();
SCL_HIGH();
I2C_Delay();
SDA_LOW();
I2C_Delay();
SCL_LOW();
}
关键顺序:
text
SDA = 1
SCL = 1
SDA → 0
SCL → 0
真正产生 START 的关键瞬间是:
text
SCL为高时
SDA 1→0
五十六、软件I²C如何产生STOP
c
void I2C_Stop(void)
{
SDA_LOW();
SCL_HIGH();
I2C_Delay();
SDA_HIGH();
I2C_Delay();
}
关键:
text
SCL高时
SDA 0→1
即 STOP。
五十七、软件I²C发送一个字节
示例:
c
void I2C_SendByte(uint8_t data)
{
for (uint8_t i = 0; i < 8; i++)
{
SCL_LOW();
if ((data & 0x80U) != 0U)
{
SDA_HIGH();
}
else
{
SDA_LOW();
}
I2C_Delay();
SCL_HIGH();
I2C_Delay();
data <<= 1;
}
SCL_LOW();
}
可以看到:
text
每次先准备SDA
再拉高SCL
非常符合:
SDA 在 SCL 低时变化,高时保持稳定。
五十八、软件I²C读取ACK
示例:
c
uint8_t I2C_WaitAck(void)
{
uint8_t ack;
SDA_RELEASE();
I2C_Delay();
SCL_HIGH();
I2C_Delay();
ack = SDA_READ();
SCL_LOW();
return ack;
}
如果:
text
SDA = 0
说明:
text
ACK
如果:
text
SDA = 1
说明:
text
NACK
需要注意 GPIO 模拟 I²C 时,SDA 必须能正确:
text
输出低
+
释放线路
+
读取输入
五十九、I²C和UART最大的区别之一
UART:
text
TX
RX
通常是:
text
点对点
异步通信
而 I²C:
text
SCL
SDA
属于:
text
同步通信
共享总线
地址寻址
I²C 不需要每个设备单独占两根通信线。
多个从机可以共用:
text
SCL
SDA
所以特别适合板内多个低速芯片通信。
六十、I²C和SPI有什么区别
简单比较:
| 对比 | I²C | SPI |
|---|---|---|
| 典型信号线 | 2根 | 4根左右 |
| 时钟 | 有 | 有 |
| 地址机制 | 有 | 通常靠CS |
| 多从机 | 地址区分 | CS区分 |
| 全双工 | 通常否 | 可以 |
| 速度 | 中低速 | 通常更高 |
| 引脚占用 | 少 | 较多 |
| 线路结构 | 开漏+上拉 | 推挽较常见 |
所以:
text
设备多、速度一般、引脚少
→ I²C非常合适
高速数据传输
→ SPI通常更有优势
六十一、I²C故障完整排查流程
如果 I²C 通信失败,可以按照下面顺序检查。
第一步:检查供电
确认从机:
text
VCC正常
GND正常
第二步:检查SCL和SDA空闲状态
正常应为:
text
SCL = High
SDA = High
如果不是:
重点排查:
text
上拉电阻
短路
从机拉死
GPIO配置
第三步:检查START
逻辑分析仪看:
text
SCL高时
SDA是否高→低
第四步:检查设备地址
确认:
text
7位地址
8位地址
R/W
HAL传参格式
第五步:检查地址后的ACK
如果:
text
Address NACK
先不要继续分析数据。
因为设备压根还没真正响应。
第六步:检查寄存器地址
确认:
text
8位还是16位
高字节还是低字节先发
寄存器地址是否存在
第七步:检查数据ACK
如果地址有 ACK,但数据阶段出现 NACK:
重点检查:
text
寄存器是否合法
从机是否忙
当前操作是否允许
第八步:检查Repeated START
读寄存器时确认:
text
写寄存器地址
↓
Repeated START
↓
读数据
是否符合器件手册。
第九步:检查STOP
事务结束是否正常释放总线。
第十步:检查频率和波形质量
示波器检查:
text
上升沿
下降沿
SCL频率
噪声
串扰
六十二、常见现象与原因对照
| 现象 | 可能原因 |
|---|---|
| SCL/SDA一直高 | 主机没有启动I²C |
| SDA一直低 | 从机拉死/短路 |
| SCL一直低 | GPIO/I²C异常或Clock Stretch |
| 地址后NACK | 地址错、设备没上电 |
| 地址ACK,寄存器NACK | 寄存器/协议不支持 |
| 100k正常,400k失败 | 上拉/电容/时序问题 |
| 读数据全FF | 没人驱动SDA或读取失败 |
| 读数据全00 | SDA被拉低或缓冲区问题 |
| 第一次正常之后失败 | STOP/状态机/总线释放问题 |
| MCU复位后I²C卡死 | 从机仍停在旧事务状态 |
| HAL_BUSY | 总线BUSY或软件状态没恢复 |
| HAL_TIMEOUT | SCL/SDA状态不符合预期 |
六十三、看逻辑分析仪时最重要的口诀
可以记住:
text
先看START
再看地址
然后看R/W
第9位看ACK
接着看数据
最后找STOP
也就是:
text
S
↓
ADDR + R/W
↓
ACK
↓
DATA
↓
ACK/NACK
↓
P
六十四、I²C整套记忆口诀
可以这样记:
起始看高到低,停止看低到高;
先地址,后方向;
每8位后看第9位;
第9位低是ACK,高是NACK;
写操作从机应答,读操作主机决定继续还是结束。
再压缩成:
text
START:
SCL高,SDA高→低
STOP:
SCL高,SDA低→高
ACK:
第9位SDA=0
NACK:
第9位SDA=1
地址:
7bit + R/W
Write:
R/W=0
Read:
R/W=1
六十五、面试中怎么回答I²C完整通信流程
如果面试官问:
I²C完整通信流程是什么?
可以这样回答:
I²C 总线空闲时 SCL 和 SDA 都保持高电平。
主机发起通信时,在 SCL 为高电平期间将 SDA 从高拉低,产生 START 起始条件。
随后主机发送 7 位从机地址和 1 位 R/W 方向位。地址匹配的从机会在第 9 个时钟将 SDA 拉低产生 ACK。
之后开始按照每 8 位数据加 1 位 ACK/NACK 的形式传输数据。
写操作时主要由主机发送数据、从机应答;读操作时由从机发送数据、主机负责发送 ACK 或 NACK。
读取寄存器时通常先以写方向发送寄存器地址,再通过 Repeated START 重新发送从机地址并切换到读方向。
最后主机在 SCL 为高电平期间将 SDA 从低变高,产生 STOP 条件,释放总线。
六十六、最后总结
I²C 看起来只是:
text
SCL + SDA
两根线,但协议内部其实有非常清晰的通信规则。
一笔最典型的事务:
text
START
↓
Address + R/W
↓
ACK
↓
Data
↓
ACK / NACK
↓
STOP
如果是寄存器写:
text
START
↓
Address + W
↓
ACK
↓
Register
↓
ACK
↓
Data
↓
ACK
↓
STOP
如果是寄存器读:
text
START
↓
Address + W
↓
ACK
↓
Register
↓
ACK
↓
Repeated START
↓
Address + R
↓
ACK
↓
Data
↓
NACK
↓
STOP
最需要记住的几个核心规则:
text
1. 总线空闲:
SCL=1,SDA=1
2. START:
SCL高时,SDA高→低
3. STOP:
SCL高时,SDA低→高
4. 地址:
常见为7位地址 + 1位R/W
5. R/W:
0=写
1=读
6. 每8位之后:
第9位是ACK/NACK
7. ACK:
SDA=0
8. NACK:
SDA=1
9. 主机读最后一个字节:
通常发送NACK,再发送STOP
10. 读寄存器:
经常使用Repeated START
最后用一句话总结:
I²C通信的本质,就是主机通过 START 开启事务,用地址找到目标从机,通过 R/W 确定数据方向,每传输 8 位利用第 9 位 ACK/NACK 确认状态,最终通过 STOP 结束并释放总线。