I²C通信总线协议详解

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. 数据帧格式
  1. 地址帧

    | 7位地址  | R/W位     |  
    | 从机地址 | 0=写, 1=读 |
    
  2. 数据帧

    • 每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均为高)时发起。

四、典型应用场景

  1. 传感器读取
    • 温度传感器(BMP280)、加速度计(MPU6050)通过I²C输出数据。
  2. 存储器访问
    • EEPROM(AT24C02)的读写操作。
  3. 显示控制
    • OLED屏幕(SSD1306)的初始化与刷新。
  4. 多设备管理
    • 微控制器同时控制多个外设(如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、硬件配置要求
  1. MCU型号:STM32F103C8T6(其他型号需调整引脚)
  2. I²C接口:I2C1(PB6-SCL, PB7-SDA)
  3. 外设地址
    • 24C02 EEPROM:0xA0(7位地址)
    • BMP280传感器:0x76(地址由SDO引脚电平决定)
  4. 开发环境: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、常见问题解决
  1. 通信失败(返回HAL_ERROR)

    • 检查设备地址是否正确(示波器捕捉实际地址)
    • 验证SCL/SDA线是否正常拉高(测量电压)
    • 确保从设备已正确供电
  2. 数据错误

    • 检查I²C时钟速度是否超过从设备支持的最大速率
    • 确认寄存器地址和字节顺序是否符合数据手册
  3. 总线锁死

    • 执行总线恢复操作:

      __HAL_I2C_CLEAR_FLAG(&hi2c1, I2C_FLAG_BERR); // 清除错误标志
      HAL_I2C_Init(&hi2c1); // 重新初始化
      

8、扩展功能
  1. DMA传输

    // DMA写
    HAL_I2C_Master_Transmit_DMA(&hi2c1, addr, data, len);
    
    // DMA读
    HAL_I2C_Master_Receive_DMA(&hi2c1, addr, data, len);
    
  2. 多主模式

    hi2c1.Init.OwnAddress1 = 0x20; // 设置本机地址
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_ENABLE;
    

通过上述代码可实现STM32的I²C基础通信功能。实际开发中建议:

  1. 使用 STM32CubeMX 生成初始化代码
  2. 配合逻辑分析仪调试I²C波形
  3. 为关键操作添加超时检测和重试机制

七、与其他协议的对比

特性I²CSPIUART
信号线数2(SDA/SCL)3-4(SCK/MOSI/MISO/CS)2(TX/RX)
通信模式半双工全双工全双工
寻址方式硬件地址片选信号
最大速率3.4Mbps50Mbps+1Mbps
多设备支持多主多从主从点对点

八、常见问题与解决方案

问题现象可能原因解决方案
从机无响应地址配置错误检查从机地址(示波器捕捉实际地址)
数据错误总线电容过大减小上拉电阻值或缩短走线
总线死锁从设备未释放SDA硬件复位或发送额外时钟脉冲
通信速度慢未启用高速模式配置设备支持的最高速率

九、总结

I²C凭借极简的硬件连接灵活的多设备管理能力,成为嵌入式系统中芯片级通信的首选协议。其核心优势在于:

  • 仅需两根信号线即可实现多设备通信。
  • 支持动态切换主从角色(多主模式)。
  • 标准化地址分配简化设备集成。

实际开发中需特别注意:

  • 上拉电阻优化以平衡速度和信号完整性。
  • 错误恢复机制防止总线死锁。
  • 协议严格性(如时序公差需<10%)。

对于需要更高速度或更长距离的场景,可考虑SPI或CAN总线,但I²C在低复杂度、多设备场景中仍不可替代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值