I²C(Inter-Integrated Circuit)是由飞利浦(现恩智浦)开发的一种同步、多主多从、串行通信协议,广泛应用于芯片间通信(如传感器、存储器、显示器等)。以下从协议层、物理层、技术特点和代码示例四个维度展开说明。
一、物理层
1. 硬件结构
I²C总线仅需两根信号线:
- SDA(Serial Data Line):双向数据线,用于传输数据。
- SCL(Serial Clock Line):时钟线,由主设备控制。
- 上拉电阻:必须为SDA和SCL线添加(典型值:4.7kΩ @ 3.3V),确保总线空闲时为高电平。
2. 电气特性
参数 | 值/特性 |
---|---|
电平标准 | 开漏输出(Open-Drain) |
逻辑高电平 | 由上拉电阻拉高(3.3V/5V) |
逻辑低电平 | 设备主动拉低(接近0V) |
最大总线电容 | 400pF(标准模式) |
最大传输距离 | <1米(高速模式下更短) |
3. 设备地址
- 7位地址:支持128个地址(0x000x7F),实际可用地址范围为0x080x77。
- 10位地址:扩展模式,支持1024个地址(需特殊协议支持)。
4. 总线拓扑
主设备1 主设备2
| |
→ SCL → SCL
→ SDA → SDA
\ /
\ /
I²C总线(SDA/SCL,多个设备共用的信号线,在一个I2C通信总线中,可连接多个I2C通信设备)
/ | \
/ | \
从机1 从机2 从机3
二、协议层
1. 基本通信流程
[起始条件] → [地址帧 + R/W位] → [ACK/NACK] → [数据帧] → [ACK/NACK] → ... → [停止条件]
2. 关键时序定义
- 起始条件(Start Condition):
SCL为高电平时,SDA产生下降沿。 - 停止条件(Stop Condition):
SCL为高电平时,SDA产生上升沿。 - 重复起始条件(Repeated Start):
在未发送停止条件时,再次发送起始条件以切换通信目标。
3. 数据帧格式
-
地址帧:
| 7位地址 | R/W位 | | 从机地址 | 0=写, 1=读 |
-
数据帧:
- 每8位数据后跟随1位ACK/NACK。
- ACK:从机拉低SDA(确认接收成功)。
- NACK:SDA保持高电平(接收失败或结束传输)。
4. 时序图示例
三、技术特点
1. 核心特性
特性 | 说明 |
---|---|
多主多从架构 | 支持多个主设备,通过仲裁机制避免冲突 |
同步通信 | 时钟由主设备控制,从设备根据SCL同步采样 |
半双工 | 同一时刻只能发送或接收数据 |
速率模式 | 标准模式(100kbps)、快速模式(400kbps)、高速模式(3.4Mbps) |
时钟拉伸 | 从设备可拉低SCL以延长时钟周期(主设备等待) |
2. 仲裁机制
- 原理:当多个主设备同时发送时,若某设备发送高电平但检测到SDA为低,则退出竞争。
- 实现:基于线与特性(任何设备拉低SDA,总线即为低电平)。
3. 时钟同步
- 多主场景:所有主设备SCL线进行“线与”,最终时钟频率由最慢的设备决定。
4. 错误检测
- ACK/NACK机制:从机通过ACK确认每字节接收成功。
- 总线忙检测:起始条件只能在总线空闲(SCL和SDA均为高)时发起。
四、典型应用场景
- 传感器读取:
- 温度传感器(BMP280)、加速度计(MPU6050)通过I²C输出数据。
- 存储器访问:
- EEPROM(AT24C02)的读写操作。
- 显示控制:
- OLED屏幕(SSD1306)的初始化与刷新。
- 多设备管理:
- 微控制器同时控制多个外设(如RTC、GPIO扩展芯片)。
五、开发注意事项
1. 硬件设计要点
-
上拉电阻计算:
R = VDD / (IOL × N) 其中:IOL为设备最大拉低电流,N为总线设备数量。 示例:3.3V系统,IOL=3mA,N=3 → R ≈ 360Ω(需选择标准值4.7kΩ并验证时序)。
-
总线电容控制:
长走线或设备过多时,需降低上拉电阻值或分段总线。
2. 软件实现关键
- 状态机设计:
处理起始条件、地址匹配、数据传输、错误恢复等状态。 - 超时机制:
防止因从设备故障导致总线死锁。
六、代码示例
以下是一个基于 STM32 HAL库 的I²C应用示例代码,包含 初始化配置、EEPROM读写 和 传感器数据读取 的实现(以24C02 EEPROM和BMP280传感器为例)。
1、硬件配置要求
- MCU型号:STM32F103C8T6(其他型号需调整引脚)
- I²C接口:I2C1(PB6-SCL, PB7-SDA)
- 外设地址:
- 24C02 EEPROM:0xA0(7位地址)
- BMP280传感器:0x76(地址由SDO引脚电平决定)
- 开发环境:STM32CubeIDE + HAL库
2、代码实现
1). I²C初始化配置
// 在 main.c 中添加
I2C_HandleTypeDef hi2c1;
void I2C_Init(void) {
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 标准模式100kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 50%占空比
hi2c1.Init.OwnAddress1 = 0; // 主设备无需地址
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
}
2.) GPIO与时钟配置(CubeMX自动生成)
// 在 stm32f1xx_hal_msp.c 中添加:
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (hi2c->Instance == I2C1) {
__HAL_RCC_I2C1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// PB6-SCL, PB7-SDA
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP; // 必须上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 启用I2C中断(可选)
HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
}
}
3、EEPROM 24C02 读写示例
1). 写入单个字节
#define EEPROM_ADDR 0xA0 // 7位地址左移1位后的值(实际发送0xA0)
HAL_StatusTypeDef EEPROM_WriteByte(uint16_t mem_addr, uint8_t data) {
uint8_t buffer[2] = {mem_addr, data};
return HAL_I2C_Master_Transmit(
&hi2c1,
EEPROM_ADDR, // 设备地址
buffer, // 数据:地址+数据
2, // 数据长度
100 // 超时时间(ms)
);
}
2). 读取单个字节
HAL_StatusTypeDef EEPROM_ReadByte(uint16_t mem_addr, uint8_t *data) {
// 先发送要读取的地址
if (HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDR, (uint8_t*)&mem_addr, 1, 100) != HAL_OK) {
return HAL_ERROR;
}
// 再读取数据
return HAL_I2C_Master_Receive(&hi2c1, EEPROM_ADDR, data, 1, 100);
}
4、BMP280传感器数据读取
1). 读取校准参数(示例)
#define BMP280_ADDR 0x76 // SDO接地时的地址
typedef struct {
uint16_t dig_T1;
int16_t dig_T2, dig_T3;
} BMP280_Calib;
HAL_StatusTypeDef BMP280_ReadCalibData(BMP280_Calib *calib) {
uint8_t calib_data[6];
// 读取温度校准参数(寄存器0x88~0x8D)
if (HAL_I2C_Mem_Read(
&hi2c1,
BMP280_ADDR,
0x88, // 起始寄存器地址
I2C_MEMADD_SIZE_8BIT,
calib_data,
6,
100
) != HAL_OK) return HAL_ERROR;
calib->dig_T1 = (uint16_t)(calib_data[1] << 8) | calib_data[0];
calib->dig_T2 = (int16_t)(calib_data[3] << 8) | calib_data[2];
calib->dig_T3 = (int16_t)(calib_data[5] << 8) | calib_data[4];
return HAL_OK;
}
2). 读取温度原始数据
HAL_StatusTypeDef BMP280_ReadRawTemp(int32_t *raw_temp) {
uint8_t temp_data[3];
// 读取温度数据(寄存器0xFA~0xFC)
if (HAL_I2C_Mem_Read(
&hi2c1,
BMP280_ADDR,
0xFA,
I2C_MEMADD_SIZE_8BIT,
temp_data,
3,
100
) != HAL_OK) return HAL_ERROR;
*raw_temp = (temp_data[0] << 12) | (temp_data[1] << 4) | (temp_data[2] >> 4);
return HAL_OK;
}
5、主函数调用示例
int main(void) {
HAL_Init();
SystemClock_Config();
I2C_Init();
// EEPROM测试
EEPROM_WriteByte(0x00, 0x55); // 向地址0写入0x55
uint8_t read_data;
EEPROM_ReadByte(0x00, &read_data); // 读取验证
// BMP280测试
BMP280_Calib calib;
BMP280_ReadCalibData(&calib);
int32_t raw_temp;
BMP280_ReadRawTemp(&raw_temp);
while (1) {
// 主循环任务
}
}
6、关键功能说明
功能 | 实现方式 | 特点 |
---|---|---|
初始化配置 | HAL_I2C_Init + MSP配置 | 自动生成引脚和时钟初始化代码 |
EEPROM写操作 | HAL_I2C_Master_Transmit | 需先发送内存地址 |
EEPROM读操作 | 先发送地址再接收数据 | 分两步操作 |
传感器寄存器读 | HAL_I2C_Mem_Read | 直接指定寄存器地址 |
校准数据处理 | 数据结构解析 | 需参考传感器数据手册 |
7、常见问题解决
-
通信失败(返回HAL_ERROR)
- 检查设备地址是否正确(示波器捕捉实际地址)
- 验证SCL/SDA线是否正常拉高(测量电压)
- 确保从设备已正确供电
-
数据错误
- 检查I²C时钟速度是否超过从设备支持的最大速率
- 确认寄存器地址和字节顺序是否符合数据手册
-
总线锁死
-
执行总线恢复操作:
__HAL_I2C_CLEAR_FLAG(&hi2c1, I2C_FLAG_BERR); // 清除错误标志 HAL_I2C_Init(&hi2c1); // 重新初始化
-
8、扩展功能
-
DMA传输
// DMA写 HAL_I2C_Master_Transmit_DMA(&hi2c1, addr, data, len); // DMA读 HAL_I2C_Master_Receive_DMA(&hi2c1, addr, data, len);
-
多主模式
hi2c1.Init.OwnAddress1 = 0x20; // 设置本机地址 hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_ENABLE;
通过上述代码可实现STM32的I²C基础通信功能。实际开发中建议:
- 使用 STM32CubeMX 生成初始化代码
- 配合逻辑分析仪调试I²C波形
- 为关键操作添加超时检测和重试机制
七、与其他协议的对比
特性 | I²C | SPI | UART |
---|---|---|---|
信号线数 | 2(SDA/SCL) | 3-4(SCK/MOSI/MISO/CS) | 2(TX/RX) |
通信模式 | 半双工 | 全双工 | 全双工 |
寻址方式 | 硬件地址 | 片选信号 | 无 |
最大速率 | 3.4Mbps | 50Mbps+ | 1Mbps |
多设备支持 | 多主多从 | 主从 | 点对点 |
八、常见问题与解决方案
问题现象 | 可能原因 | 解决方案 |
---|---|---|
从机无响应 | 地址配置错误 | 检查从机地址(示波器捕捉实际地址) |
数据错误 | 总线电容过大 | 减小上拉电阻值或缩短走线 |
总线死锁 | 从设备未释放SDA | 硬件复位或发送额外时钟脉冲 |
通信速度慢 | 未启用高速模式 | 配置设备支持的最高速率 |
九、总结
I²C凭借极简的硬件连接和灵活的多设备管理能力,成为嵌入式系统中芯片级通信的首选协议。其核心优势在于:
- 仅需两根信号线即可实现多设备通信。
- 支持动态切换主从角色(多主模式)。
- 标准化地址分配简化设备集成。
实际开发中需特别注意:
- 上拉电阻优化以平衡速度和信号完整性。
- 错误恢复机制防止总线死锁。
- 协议严格性(如时序公差需<10%)。
对于需要更高速度或更长距离的场景,可考虑SPI或CAN总线,但I²C在低复杂度、多设备场景中仍不可替代。