mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3
856 字
2 分钟
I2C通信原理与实战指南
2026-06-18

I2C通信原理与实战指南#

I2C(Inter-Integrated Circuit)是由 Philips 公司开发的两线式串行通信总线,广泛应用于传感器、EEPROM、OLED 屏幕等外设与主控芯片之间的通信。

I2C 基础概念#

两根线搞定一切#

I2C 只需要两根信号线:

信号线名称功能
SDASerial Data数据线,双向传输
SCLSerial Clock时钟线,由主机产生

两根线都需要通过上拉电阻(通常 4.7kΩ)连接到 VCC,空闲时保持高电平。

主从架构#

VCC
|
[R] 4.7kΩ
|
┌───────┴───────┐
│ │ │
SDA SDA SDA
SCL SCL SCL
│ │ │
┌─┴─┐ ┌─┴─┐ ┌─┴─┐
│MCU│ │传感器│ │EEPROM│
│主机│ │从机│ │从机│
└───┘ └───┘ └───┘
  • 主机(Master):发起通信,产生时钟信号
  • 从机(Slave):被动响应,每个从机有唯一地址
  • 支持多主机多从机架构

通信时序详解#

起始条件(START)#

SCL 为高电平时,SDA 从高变低:

SCL: ──────╲____
SDA: ──╲________
↑ 起始条件

停止条件(STOP)#

SCL 为高电平时,SDA 从低变高:

SCL: ______╱────
SDA: ╱─────────
↑ 停止条件

数据传输规则#

  • SCL 高电平时,SDA 必须保持稳定(读取数据)
  • SCL 低电平时,SDA 才能变化(准备数据)
  • 数据以字节为单位传输,高位在前(MSB First)
SCL: __╱╲__╱╲__╱╲__╱╲__╱╲__╱╲__╱╲__╱╲__
SDA: ══D7══D6══D5══D4══D3══D2══D1══D0══

应答信号(ACK/NACK)#

每传完一个字节,接收方在第 9 个时钟周期回应:

  • ACK(应答):SDA 拉低,表示接收成功
  • NACK(非应答):SDA 保持高电平,表示接收失败或无数据
D0 ACK
SCL: ╱╲__╱╲__╱╲__
SDA: ══D0══ ╲__
ACK(低电平)

完整通信流程#

一次典型的 I2C 写操作:

[START] → [从机地址 + W] → [ACK] → [寄存器地址] → [ACK] → [数据] → [ACK] → [STOP]

一次典型的 I2C 读操作:

[START] → [从机地址 + W] → [ACK] → [寄存器地址] → [ACK]
[RESTART] → [从机地址 + R] → [ACK] → [数据] → [NACK] → [STOP]

地址格式#

7 位地址 + 1 位读写方向:

| A6 | A5 | A4 | A3 | A2 | A1 | A0 | R/W |
  • R/W = 0:写操作
  • R/W = 1:读操作

例如 MPU6050 地址 0x68,写地址为 0xD0,读地址为 0xD1

51 单片机软件模拟 I2C#

51 单片机没有硬件 I2C 外设,需要用 GPIO 软件模拟:

#include <reg52.h>
sbit SDA = P2^0;
sbit SCL = P2^1;
// 延时约 5us(12MHz 晶振)
void I2C_Delay() {
unsigned char i = 5;
while (i--);
}
// 起始信号
void I2C_Start() {
SDA = 1;
SCL = 1;
I2C_Delay();
SDA = 0; // SCL 高电平时 SDA 拉低
I2C_Delay();
SCL = 0;
I2C_Delay();
}
// 停止信号
void I2C_Stop() {
SDA = 0;
SCL = 1;
I2C_Delay();
SDA = 1; // SCL 高电平时 SDA 拉高
I2C_Delay();
}
// 发送一个字节,返回 ACK
unsigned char I2C_WriteByte(unsigned char dat) {
unsigned char i, ack;
for (i = 0; i < 8; i++) {
SDA = (dat & 0x80) ? 1 : 0; // 高位在前
dat <<= 1;
SCL = 1;
I2C_Delay();
SCL = 0;
I2C_Delay();
}
// 读取 ACK
SDA = 1; // 释放 SDA
SCL = 1;
I2C_Delay();
ack = SDA; // 0=ACK, 1=NACK
SCL = 0;
I2C_Delay();
return ack;
}
// 读取一个字节,发送 ACK 或 NACK
unsigned char I2C_ReadByte(unsigned char ack) {
unsigned char i, dat = 0;
SDA = 1; // 释放 SDA
for (i = 0; i < 8; i++) {
dat <<= 1;
SCL = 1;
I2C_Delay();
if (SDA) dat |= 0x01;
SCL = 0;
I2C_Delay();
}
// 发送 ACK/NACK
SDA = (ack) ? 0 : 1; // 0=ACK, 1=NACK
SCL = 1;
I2C_Delay();
SCL = 0;
SDA = 1;
I2C_Delay();
return dat;
}

实战:读取 MPU6050 传感器#

#define MPU6050_ADDR 0x68
#define MPU6050_W (MPU6050_ADDR << 1) // 0xD0
#define MPU6050_R ((MPU6050_ADDR << 1) | 1) // 0xD1
// 写寄存器
void MPU6050_WriteReg(unsigned char reg, unsigned char dat) {
I2C_Start();
I2C_WriteByte(MPU6050_W);
I2C_WriteByte(reg);
I2C_WriteByte(dat);
I2C_Stop();
}
// 读寄存器
unsigned char MPU6050_ReadReg(unsigned char reg) {
unsigned char dat;
I2C_Start();
I2C_WriteByte(MPU6050_W);
I2C_WriteByte(reg);
I2C_Start(); // 重复起始
I2C_WriteByte(MPU6050_R);
dat = I2C_ReadByte(0); // NACK 结束
I2C_Stop();
return dat;
}
// 初始化 MPU6050
void MPU6050_Init() {
MPU6050_WriteReg(0x6B, 0x00); // 唤醒,退出睡眠模式
MPU6050_WriteReg(0x19, 0x07); // 采样率分频
MPU6050_WriteReg(0x1A, 0x06); // 低通滤波
MPU6050_WriteReg(0x1B, 0x18); // 陀螺仪量程 ±2000°/s
MPU6050_WriteReg(0x1C, 0x01); // 加速度计量程 ±2g
}
// 读取加速度数据
void MPU6050_ReadAccel(int *ax, int *ay, int *az) {
*ax = (MPU6050_ReadReg(0x3B) << 8) | MPU6050_ReadReg(0x3C);
*ay = (MPU6050_ReadReg(0x3D) << 8) | MPU6050_ReadReg(0x3E);
*az = (MPU6050_ReadReg(0x3F) << 8) | MPU6050_ReadReg(0x40);
}
// 读取温度
float MPU6050_ReadTemp() {
int temp;
temp = (MPU6050_ReadReg(0x41) << 8) | MPU6050_ReadReg(0x42);
return temp / 340.0 + 36.53;
}
void main() {
int ax, ay, az;
float temp;
MPU6050_Init();
while (1) {
MPU6050_ReadAccel(&ax, &ay, &az);
temp = MPU6050_ReadTemp();
// 将数据通过串口发送或 LCD 显示
}
}

STM32 硬件 I2C 示例#

STM32 有硬件 I2C 外设,使用 HAL 库更简洁:

#include "stm32f1xx_hal.h"
extern I2C_HandleTypeDef hi2c1;
#define MPU6050_ADDR 0x68
void MPU6050_WriteReg(uint8_t reg, uint8_t dat) {
uint8_t buf[2] = {reg, dat};
HAL_I2C_Master_Transmit(&hi2c1, MPU6050_ADDR << 1, buf, 2, 100);
}
uint8_t MPU6050_ReadReg(uint8_t reg) {
uint8_t dat;
HAL_I2C_Master_Transmit(&hi2c1, MPU6050_ADDR << 1, &reg, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, (MPU6050_ADDR << 1) | 1, &dat, 1, 100);
return dat;
}
void MPU6050_Init() {
MPU6050_WriteReg(0x6B, 0x00); // 唤醒
}
// 读取多个寄存器(连续读取效率更高)
void MPU6050_ReadAccel(int16_t *ax, int16_t *ay, int16_t *az) {
uint8_t buf[6];
uint8_t reg = 0x3B;
HAL_I2C_Master_Transmit(&hi2c1, MPU6050_ADDR << 1, &reg, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, (MPU6050_ADDR << 1) | 1, buf, 6, 100);
*ax = (buf[0] << 8) | buf[1];
*ay = (buf[2] << 8) | buf[3];
*az = (buf[4] << 8) | buf[5];
}

常见问题排查#

通信失败检查清单#

检查项说明
上拉电阻SDA/SCL 需要 4.7kΩ 上拉到 VCC
地址是否正确7 位地址左移 1 位才是写地址
波特率标准模式 100kHz,快速模式 400kHz
时序是否满足软件模拟注意延时
总线冲突多主机需要仲裁机制

示波器调试技巧#

  1. 观察起始/停止条件:SDA 在 SCL 高电平时的跳变
  2. 确认地址字节:第一个字节应该是从机地址 + R/W
  3. 检查 ACK:第 9 个时钟 SDA 应该被从机拉低
  4. 测量频率:确认 SCL 频率是否在允许范围内

I2C 速度等级#

模式速率适用场景
标准模式100 kbps通用传感器
快速模式400 kbpsEEPROM、OLED
高速模式3.4 Mbps高速数据采集
超快模式5 Mbps特殊应用

总结#

I2C 是嵌入式开发中最常用的通信协议之一,掌握它能让你轻松连接各种传感器和外设:

  • 两根线即可连接多个设备
  • 软件模拟简单灵活,适合没有硬件 I2C 的单片机
  • 硬件 I2C 效率更高,适合高速场景
  • 注意上拉电阻地址格式

建议从软件模拟开始理解时序,再过渡到硬件 I2C 提高效率。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

I2C通信原理与实战指南
https://mizuki.mysqil.com/posts/i2c-communication/
作者
まつざか ゆき
发布于
2026-06-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时