856 字
2 分钟
I2C通信原理与实战指南
I2C通信原理与实战指南
I2C(Inter-Integrated Circuit)是由 Philips 公司开发的两线式串行通信总线,广泛应用于传感器、EEPROM、OLED 屏幕等外设与主控芯片之间的通信。
I2C 基础概念
两根线搞定一切
I2C 只需要两根信号线:
| 信号线 | 名称 | 功能 |
|---|---|---|
| SDA | Serial Data | 数据线,双向传输 |
| SCL | Serial 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 ACKSCL: ╱╲__╱╲__╱╲__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();}
// 发送一个字节,返回 ACKunsigned 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 或 NACKunsigned 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;}
// 初始化 MPU6050void 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, ®, 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, ®, 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 |
| 时序是否满足 | 软件模拟注意延时 |
| 总线冲突 | 多主机需要仲裁机制 |
示波器调试技巧
- 观察起始/停止条件:SDA 在 SCL 高电平时的跳变
- 确认地址字节:第一个字节应该是从机地址 + R/W
- 检查 ACK:第 9 个时钟 SDA 应该被从机拉低
- 测量频率:确认 SCL 频率是否在允许范围内
I2C 速度等级
| 模式 | 速率 | 适用场景 |
|---|---|---|
| 标准模式 | 100 kbps | 通用传感器 |
| 快速模式 | 400 kbps | EEPROM、OLED |
| 高速模式 | 3.4 Mbps | 高速数据采集 |
| 超快模式 | 5 Mbps | 特殊应用 |
总结
I2C 是嵌入式开发中最常用的通信协议之一,掌握它能让你轻松连接各种传感器和外设:
- 两根线即可连接多个设备
- 软件模拟简单灵活,适合没有硬件 I2C 的单片机
- 硬件 I2C 效率更高,适合高速场景
- 注意上拉电阻和地址格式
建议从软件模拟开始理解时序,再过渡到硬件 I2C 提高效率。
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
I2C通信原理与实战指南
https://mizuki.mysqil.com/posts/i2c-communication/ 部分信息可能已经过时





