先搞懂它是什么
- I2C(集成电路间总线):只用 SCL(时钟)+ SDA(数据)两根线就能挂很多设备的串行总线,靠“地址”区分设备,常用于传感器、EEPROM、显示屏。
- 半双工:数据线只有一根,同一时刻只能一个方向传输。
- 开漏 + 上拉:两根线都是开漏输出加外部上拉电阻,这是 I2C 能“多设备共线”的物理基础。
层层拆解原理
1. 为什么只有两根线还能挂多个设备
每个从设备有唯一的 7 位地址(如 OLED 常见 0x3C)。主机先广播“我要找地址 X 的设备”,只有地址匹配的从设备才响应,其余保持静默。所以“设备地址对不对”是 I2C 调试第一检查项。
2. 开漏 + 上拉:线与(Wired-AND)
SDA/SCL 都是开漏输出:设备只能把线拉低,拉高靠上拉电阻。因此:
- 多个设备都可以拉低总线,只要有一个拉低,总线就是低电平(“线与”)。
- 设备空闲时必须释放总线(不拉低),否则会“占住”总线导致别人通信不了。
3. 一次完整传输的时序
关键概念:
- START/STOP:通信开始与结束的特殊电平组合,是“帧”的边界。
- ACK/NACK:每收一个字节,接收方拉低 SDA 表示“收到”(ACK);不拉低(NACK)表示“出错/结束/地址找不到”。
- 读写位:地址字节最低位是读写方向(0 = 写,1 = 读),所以 7 位地址 0x3C 写操作实际发送 0x3C,读操作发送 0x3D。
4. 时钟与速率
I2C 速率由 SCL 频率决定,常见三档:标准模式 100kbps、快速模式 400kbps、高速模式 1Mbps 以上。时钟由主机产生,从机可通过**时钟拉伸(Clock Stretching)**把 SCL 拉低要求主机等待,用于“我还没准备好”。
结合 SDK 看实现
- 相关例程页:I2C 协议
SDK 操作 I2C 同样遵循“初始化 → 配置速率/地址 → 写/读”的套路:
struct bflb_device_s *i2c0 = bflb_device_get_by_name("i2c0");
/* 主机模式、400kHz、7 位地址 */
bflb_i2c_init(i2c0, I2C_MODE_HOST, 0x3C, 400000);
/* 向从机寄存器地址 0x00 写 1 字节 */
bflb_i2c_send(i2c0, 0x00, data, 1);
/* 从寄存器地址 0x00 读 n 字节 */
bflb_i2c_recv(i2c0, 0x00, rx_buf, n);很多传感器是“先写寄存器地址,再读数据”的流程,这两步合起来就是一次完整的 I2C 传输。
常见考点 / 面试题
I2C 为什么用开漏输出加外部上拉,而不是推挽输出?
开漏加“线与”允许挂多个设备而不会互相短路冲突;推挽输出两个设备一个发高一个发低会直接短路。这也是 I2C 天然支持多设备共线的原因。
7 位地址的设备,为什么读地址和写地址不一样?
地址字节 = 7 位设备地址 + 1 位读写位(0 写 / 1 读),所以写地址 0x3C、读地址 0x3D,本质是同一个设备。
扫描不到 I2C 设备,先查什么?
先查接线(SDA/SCL 是否接反)、上拉电阻是否缺失、设备地址是否写对、电平是否匹配(3.3V 设备接 5V 上拉可能损坏或通信异常)。
什么是 ACK/NACK?
每收到一字节后,接收方拉低 SDA 表示 ACK(确认收到);不拉低为 NACK,通常表示地址不存在、数据出错或通信即将结束。
I2C 和 SPI 各适合什么场景?
I2C:引脚少(2 线)、可挂多设备、速率中等,适合传感器/EEPROM;SPI:速率高、全双工,适合 Flash/屏幕等高速外设,但引脚多且片选管理复杂。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

