目录
[一、I2C 核心原理(彻底看懂)](#一、I2C 核心原理(彻底看懂))
[二、标准时序解析(面试 + 排错必考)](#二、标准时序解析(面试 + 排错必考))
[三、软件 I2C 完整工程代码(通用所有芯片)](#三、软件 I2C 完整工程代码(通用所有芯片))
[四、EEPROM 标准读写量产代码(最常用)](#四、EEPROM 标准读写量产代码(最常用))
[五、硬件 I2C 使用要点(避坑)](#五、硬件 I2C 使用要点(避坑))
[六、I2C 九大经典故障 + 根治方案](#六、I2C 九大经典故障 + 根治方案)
[1、读出来全是 0xFF](#1、读出来全是 0xFF)
[4、卡死在等待 ACK](#4、卡死在等待 ACK)
[8、中断里调用 I2C](#8、中断里调用 I2C)
[9、EEPROM 写完不延时](#9、EEPROM 写完不延时)
[七、软件 I2C vs 硬件 I2C 最终选型](#七、软件 I2C vs 硬件 I2C 最终选型)
前言
上一篇我们搞定了高速 SPI 总线,本篇补齐嵌入式两大同步总线之二:I2C。
SPI 主打高速 ,I2C 主打极简布线、多设备组网。 几乎所有传感器、EEPROM、RTC、气压、触控、温湿度全部依赖 I2C。
但 I2C 也是玄学 Bug 最多的总线: 卡死、总线忙、应答丢失、读出来全是 0/0xFF、偶尔通信失败、上电第一次不通。
本篇带你彻底吃透 I2C 底层时序、软硬件驱动、EEPROM 量产代码、所有经典坑点根治方案。
一、I2C 核心原理(彻底看懂)
两根线:
- SCL:时钟线
- SDA:数据线
特性:
- 半双工通信:同一时刻只能收或发
- 设备地址寻址:一条总线可挂几十种设备
- 带应答机制 ACK/NACK,出错可排查
- 开漏输出、上拉电平,支持多设备线与
I2C 四大时序单元: 起始信号、停止信号、发送字节、接收字节、ACK 应答
二、标准时序解析(面试 + 排错必考)
- 起始信号:SCL 高电平 → SDA 拉低
- 停止信号:SCL 高电平 → SDA 拉高
- 发送数据:SCL 低电平变数据,高电平采样
- 接收应答:主机释放 SDA,从机拉低为 ACK
90% I2C 卡死原因:缺少停止信号、没有等待总线空闲、时序延时不匹配
三、软件 I2C 完整工程代码(通用所有芯片)
可移植、无需硬件外设、任意 GPIO
// 自行修改引脚
#define I2C_SCL_PIN GPIO_PIN_6
#define I2C_SDA_PIN GPIO_PIN_7
#define I2C_GPIO_PORT GPIOB
void SoftI2C_Init(void)
{
GPIO_InitTypeDef gpio_cfg = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = I2C_SCL_PIN | I2C_SDA_PIN;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD; //开漏输出
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(I2C_GPIO_PORT, &gpio_cfg);
HAL_GPIO_WritePin(I2C_GPIO_PORT,I2C_SCL_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(I2C_GPIO_PORT,I2C_SDA_PIN,GPIO_PIN_SET);
}
//起始
void SoftI2C_Start(void)
{
SDA_H;SCL_H;
HAL_Delay(1);
SDA_L;
HAL_Delay(1);
SCL_L;
}
//停止
void SoftI2C_Stop(void)
{
SDA_L;SCL_H;
HAL_Delay(1);
SDA_H;
}
//发送一字节
void SoftI2C_WriteByte(uint8_t dat)
{
uint8_t i;
for(i=0;i<8;i++)
{
SCL_L;
HAL_Delay(1);
if(dat & 0x80) SDA_H;
else SDA_L;
dat <<= 1;
HAL_Delay(1);
SCL_H;
HAL_Delay(1);
}
SCL_L; //等待ACK
SDA_H; //释放SDA
}
//读取一字节
uint8_t SoftI2C_ReadByte(uint8_t ack)
{
uint8_t i,dat=0;
SDA_H;
for(i=0;i<8;i++)
{
dat <<= 1;
SCL_H;
HAL_Delay(1);
if(HAL_GPIO_ReadPin(I2C_GPIO_PORT,I2C_SDA_PIN)) dat |= 1;
SCL_L;
HAL_Delay(1);
}
//发送应答
if(ack) SDA_L;
else SDA_H;
SCL_H;
HAL_Delay(1);
SCL_L;
return dat;
}
四、EEPROM 标准读写量产代码(最常用)
//写一个字节
void EEPROM_Write(uint16_t addr,uint8_t dat)
{
SoftI2C_Start();
SoftI2C_WriteByte(0xA0);
SoftI2C_WriteByte(addr>>8);
SoftI2C_WriteByte(addr&0xFF);
SoftI2C_WriteByte(dat);
SoftI2C_Stop();
HAL_Delay(5); //EEPROM固化等待
}
//读一个字节
uint8_t EEPROM_Read(uint16_t addr)
{
uint8_t dat;
SoftI2C_Start();
SoftI2C_WriteByte(0xA0);
SoftI2C_WriteByte(addr>>8);
SoftI2C_WriteByte(addr&0xFF);
SoftI2C_Start();
SoftI2C_WriteByte(0xA1);
dat = SoftI2C_ReadByte(0);
SoftI2C_Stop();
return dat;
}
关键点: EEPROM 写完必须延时 5ms,不延时直接连读会读错数据。
五、硬件 I2C 使用要点(避坑)
HAL 硬件 I2C 新手最大问题:
- 容易卡死、总线忙
- 重启不恢复
- 多设备冲突
硬件 I2C 推荐工程写法:
//等待总线空闲
while(HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
硬件 I2C:
**● 优势:**速度快、不占用 CPU
●**缺点:容错极差 ,时序错乱直接卡死,产品级多数人反而优先用软件 I2C**稳跑。
六、I2C 九大经典故障 + 根治方案
1、读出来全是 0xFF
- 原因:设备没应答、地址写错、硬件没上拉
- 解决:检查设备 7 位地址、检查 4.7K 上拉电阻
2、第一次上电不通,复位就好
- 原因:上电 I2C 总线处于忙状态
- 解决:初始化增加总线复位时序
3、偶尔丢数据、偶尔不通
- 原因:缺少延时、时序过快、中断打断 I2C
- 解决:I2C 读写期间关闭全局中断或加长时序
4、卡死在等待 ACK
- 原因:从机未响应、SDA 被拉低卡死
- 解决:增加超时退出机制(量产必须加)
5、连续读写错乱
- 原因:缺少停止信号、重复起始信号错误
6、不用上拉直接跑
- 绝对错误!I2C 开漏必须外部上拉,单片内部上拉电流太小不稳定
7、高低速混用传感器
- 不同传感器 I2C 速率不同,统一 100K 标准最稳
8、中断里调用 I2C
- 极易总线冲突死机,禁止!
9、EEPROM 写完不延时
- 数据未固化,读取旧数据 / 乱数据
七、软件 I2C vs 硬件 I2C 最终选型
- 产品量产、追求稳定:软件 I2C(容错高、不卡死)
- 高速大批量采集:硬件 I2C
- 多传感器混杂设备:一律软件 I2C
小结
SPI 拼速度,I2C 拼稳定。 学会本篇 I2C 时序 + EEPROM 读写 + 故障排查,你已经搞定嵌入式 90% 外设通信 。
I2C 所有玄学 Bug 全部来自:时序不标准、总线不复位、缺少超时、缺少上拉、写完不等待 。