搞嵌入式开发的朋友都知道,I2C 这协议说起来简单,就两根线(SDA 和 SCL),真用起来能把你折腾得怀疑人生。设备不响应、数据读不对、时序有问题……今天我就用逻辑分析仪实战演示一下,怎么把这些奇奇怪怪的 I2C 问题一个个揪出来。
需要准备什么?
| 物品 | 型号/规格 | 价格 |
|---|---|---|
| 逻辑分析仪 | Saleae Logic 8 / 国产克隆版 | ¥80-300 |
| I2C 设备 | MPU6050 加速度计 | ¥15 |
| 开发板 | CH559 开发板 | ¥25 |
| 杜邦线 | 公对母 10cm | ¥5 |
| 面包板 | 迷你型 | ¥8 |
| **总计** | **¥133-253** |
逻辑分析仪不一定要买原版 Saleae,国产克隆版几十块钱就能搞定,日常调试完全够用。我手上这台是 8 通道的,测 I2C 绰绰有余。
步骤 1:搭建测试环境
先把硬件连起来。I2C 接线很简单:
CH559 MPU6050
───── ───────
3.3V → VCC
GND → GND
P1.4(SCL) → SCL
P1.5(SDA) → SDA
注意事项: ⚠️
- I2C 需要上拉电阻!大多数模块已经内置了 4.7kΩ上拉,如果没有,记得在 SDA 和 SCL 上各接一个 4.7kΩ电阻到 VCC
- 电压要匹配!CH559 是 3.3V 逻辑,MPU6050 也是 3.3V,如果接 5V 设备需要电平转换
- 逻辑分析仪的地线一定要和被测电路共地,不然抓不到信号
接下来安装逻辑分析仪软件。国产克隆版通常用 Sigrok/PulseView:
# Ubuntu/Debian
sudo apt-get install pulseview
# Fedora
sudo dnf install pulseview
# 或者下载 Saleae 官方软件(原版用户)
# https://www.saleae.com/downloads
步骤 2:编写 I2C 测试代码
我们用 CH559 写个简单的 I2C 读写程序。CH559 的硬件 I2C 在 P1.4 和 P1.5 引脚:
#include
#include
// I2C 引脚定义
sbit I2C_SDA = P1^5;
sbit I2C_SCL = P1^4;
// MPU6050 地址(7 位地址,0x68)
#define MPU6050_ADDR 0x68
#define WHO_AM_I_REG 0x75
// I2C 延时(根据实际时钟调整)
void I2C_Delay() {
_nop_();
_nop_();
}
// I2C 起始信号
void I2C_Start() {
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 0;
I2C_Delay();
I2C_SCL = 0;
}
// I2C 停止信号
void I2C_Stop() {
I2C_SDA = 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 1;
I2C_Delay();
}
// I2C 发送一个字节
uint8_t I2C_SendByte(uint8_t dat) {
uint8_t i, ack;
for (i = 0; i < 8; i++) {
I2C_SDA = (dat >> 7) & 0x01;
dat <<= 1;
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
}
// 读取应答
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay();
ack = I2C_SDA;
I2C_SCL = 0;
I2C_Delay();
return ack;
}
// I2C 接收一个字节
uint8_t I2C_RecvByte(uint8_t ack) {
uint8_t i, dat = 0;
I2C_SDA = 1;
for (i = 0; i < 8; i++) {
dat <<= 1;
I2C_SCL = 1;
I2C_Delay();
dat |= I2C_SDA;
I2C_SCL = 0;
I2C_Delay();
}
// 发送应答
I2C_SDA = ack ? 1 : 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
I2C_SDA = 1;
return dat;
}
// I2C 写寄存器
void I2C_WriteReg(uint8_t reg, uint8_t dat) {
I2C_Start();
I2C_SendByte(MPU6050_ADDR << 1); // 写地址
I2C_SendByte(reg);
I2C_SendByte(dat);
I2C_Stop();
}
// I2C 读寄存器
uint8_t I2C_ReadReg(uint8_t reg) {
uint8_t dat;
I2C_Start();
I2C_SendByte(MPU6050_ADDR << 1); // 写地址
I2C_SendByte(reg);
I2C_Start(); // 重复起始
I2C_SendByte((MPU6050_ADDR << 1) | 0x01); // 读地址
dat = I2C_RecvByte(1); // NACK
I2C_Stop();
return dat;
}
void main() {
uint8_t who_am_i;
// 配置时钟(12MHz)
CCLK_CFG = 0x00;
// 配置 GPIO(开漏输出)
P1_MOD_OC |= 0x30; // P1.4/P1.5 开漏
P1_DIR_PU |= 0x30; // P1.4/P1.5 输入
// 初始化串口(用于调试输出)
mInitSTDIO();
printf("I2C Test Starting...\n");
while (1) {
// 读取 WHO_AM_I 寄存器(应该是 0x68)
who_am_i = I2C_ReadReg(WHO_AM_I_REG);
printf("WHO_AM_I: 0x%02X\n", who_am_i);
if (who_am_i == 0x68) {
printf("MPU6050 detected!\n");
} else {
printf("Device not found!\n");
}
DelayMs(1000);
}
}
编译烧录:
# 安装 SDCC
sudo apt-get install sdcc
# 编译
sdcc --code-loc 0x0000 --xram-loc 0x0000 main.c
# 烧录(使用 wchisp)
wchisp flash main.bin
步骤 3:逻辑分析仪抓包
硬件和代码都准备好了,现在重头戏来了——抓包分析!
-
连接探头:把逻辑分析仪的通道 0 接 SCL,通道 1 接 SDA,GND 接电路地线
-
配置采样率:I2C 标准模式是 100kHz,快速模式 400kHz。采样率至少是信号频率的 10 倍,我一般设 1MHz 或更高
-
设置触发:选"下降沿触发",通道 0(SCL),这样能从起始信号开始抓
-
开始捕获:运行程序,等几秒后停止捕获
在 PulseView 里你会看到这样的波形:
SCL: ──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──
└──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘
SDA: ──┐ ┌────────────────────────────┐ ┌──
START └──┘ 地址 + 读写位 + 应答 + 数据 └──┘ STOP
PulseView 有 I2C 协议解码器,加载后会自动解析出:
- 起始/停止条件
- 设备地址(0x68)
- 读写方向
- 寄存器地址
- 数据内容
- ACK/NACK 状态
步骤 4:分析波形找问题
正常波形特征
正常的 I2C 通信应该满足:
- SCL 高电平和低电平时间基本对称(50% 占空比)
- SDA 只在 SCL 低电平时变化(除了起始/停止条件)
- 每个字节后有 ACK(SDA 被拉低)
- 起始条件:SCL 高电平时 SDA 下降沿
- 停止条件:SCL 高电平时 SDA 上升沿
常见问题波形
问题 1:没有 ACK
波形表现:第 9 个时钟周期 SDA 保持高电平(没有被拉低)
可能原因:
- 设备地址错误(MPU6050 是 0x68,不是 0x69)
- 设备没供电或损坏
- 上拉电阻缺失或阻值太大
- 接线松动
问题 2:数据读回来全是 0xFF 或 0x00
波形表现:SDA 一直高或一直低
可能原因:
- 上拉电阻太大/太小
- 总线电容太大(线太长)
- 设备处于错误状态,需要复位
问题 3:时序错乱
波形表现:SDA 在 SCL 高电平时变化
可能原因:
- 代码延时不对
- 中断干扰了 I2C 时序
- 使用了错误的 GPIO 模式(应该用开漏)
常见问题排查
问题 1:设备地址不对,一直 NACK
- 原因: I2C 地址有 7 位和 8 位两种表示法。MPU6050 的 7 位地址是 0x68,但有些库用 8 位地址(左移一位 + 读写位)
- 解决: 确认你的代码用的是 7 位地址。写操作时左移一位(0xD0),读操作时左移一位 +1(0xD1)
问题 2:波形毛刺很多
- 原因: 接地不良、线太长、附近有干扰源
- 解决: 缩短杜邦线、确保共地、远离电机/继电器等干扰源
问题 3:逻辑分析仪抓不到信号
- 原因: 探头没接好、触发电平设置不对、采样率太低
- 解决: 检查 GND 连接、调整阈值电压(3.3V 逻辑设 1.5V 左右)、提高采样率
问题 4:偶尔能读到数据,偶尔读不到
- 原因: 时序余量不足、电源不稳定
- 解决: 增加 I2C 延时、加去耦电容、检查电源质量
总结
I2C 调试的核心就是"看到"信号。没有逻辑分析仪的时候,我们只能猜:"是不是地址错了?""是不是时序有问题?"有了逻辑分析仪,一切都在眼前,问题一目了然。
几点经验总结:
- 先抓波形,再改代码——不要盲目改代码,先看波形对不对
- 从简单开始——先读 WHO_AM_I 这种固定值的寄存器,确认通信正常再读其他
- 注意上拉电阻——这是最容易被忽视的问题
- 保存波形截图——遇到问题截图保存,方便对比分析
最后提醒一句,逻辑分析仪不一定要买贵的。淘宝上几十块的国产克隆版,日常调试 I2C/SPI/UART 完全够用。让它发热吧,别发光就好——稳定工作比什么都重要。
希望这篇博客文章对您有所帮助!
相关资源:



