I2C(Inter-Integrated Circuit)总线是飞利浦公司在1980年代推出的同步串行通信协议,现已成为嵌入式领域最常用的外设通信方式之一。在实际项目中,I2C因其简单的两线制结构(SDA数据线和SCL时钟线)和多主多从的拓扑特性,被广泛应用于传感器、EEPROM、RTC等低速外设的连接。
我曾在智能家居项目中用I2C同时连接了温湿度传感器、光照传感器和OLED显示屏,这种"一主多从"的架构极大简化了PCB布线。但要注意,标准模式下I2C的通信速率只有100kbps,快速模式可达400kbps,高速模式3.4Mbps,选择时需权衡速度和抗干扰能力。
关键特性速览:
- 工作电压:通常3.3V/5V(部分器件支持1.8V)
- 通信距离:一般不超过1米(长距离需加缓冲器)
- 节点容量:7位地址制支持112个设备(保留地址除外)
下图展示STM32与AT24C02 EEPROM的标准连接方式:
code复制STM32F103 AT24C02
PB6(SCL) ---- SCL
PB7(SDA) ---- SDA
3.3V -- VCC
GND --- GND
上拉电阻取值是关键,根据总线电容(Cb)计算:
Rp(min) = (Vdd - Volmax)/(Iol)
Rp(max) = tr/(0.8473×Cb)
以3.3V系统为例:
7位地址格式如下:
code复制[MSB] 7 6 5 4 3 2 1 [R/W]
常见设备地址:
地址冲突解决方案:
- 使用地址引脚(如A0/A1/A2)
- 切换不同I2C总线
- 软件模拟I2C(GPIO模拟)
以STM32F103C8T6为例:
生成代码后重点检查i2c.c中的初始化结构体:
c复制hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
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;
读取BMP180校准参数(0xAA地址)的完整过程:
c复制uint8_t cmd = 0xAA;
uint8_t data[2];
HAL_I2C_Master_Transmit(&hi2c1, 0xEE, &cmd, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, 0xEF, data, 2, 100);
int16_t ac1 = (data[0] << 8) | data[1];
超时设置经验:
- 简单操作:100ms足够
- 复杂时序:300-500ms
- 故障排查:先增大超时,再查硬件
当硬件I2C不稳定时(特别是STM32F1系列),可采用GPIO模拟:
c复制#define I2C_DELAY 5 // μs级延时
void I2C_Start(void) {
SDA_HIGH();
SCL_HIGH();
Delay_us(I2C_DELAY);
SDA_LOW();
Delay_us(I2C_DELAY);
SCL_LOW();
}
void I2C_WriteByte(uint8_t byte) {
for(int i=0; i<8; i++) {
(byte & 0x80) ? SDA_HIGH() : SDA_LOW();
byte <<= 1;
SCL_HIGH();
Delay_us(I2C_DELAY);
SCL_LOW();
Delay_us(I2C_DELAY);
}
SDA_INPUT();
SCL_HIGH();
// 检查ACK...
}
典型表现:
解决方案:
c复制__HAL_RCC_I2C1_FORCE_RESET();
__HAL_RCC_I2C1_RELEASE_RESET();
HAL_I2C_Init(&hi2c1);
c复制for(int i=0; i<16; i++) {
SCL_LOW();
Delay_us(10);
SCL_HIGH();
Delay_us(10);
}
排查步骤:
当多个主机同时发起传输时:
代码实现示例:
c复制// 发送前检测总线忙
while(HAL_I2C_IsDeviceReady(&hi2c1, TARGET_ADDR, 3, 100) != HAL_OK) {
// 退避随机时间
HAL_Delay(rand() % 50);
}
启用400kHz快速模式:
c复制hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.Timing = 0x00310309; // 实测稳定值
c复制// 配置I2C唤醒中断
HAL_I2CEx_EnableWakeUp(&hi2c1);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
初始化序列:
c复制uint8_t init_cmd[] = {
0xAE, 0xD5, 0x80, 0xA8, 0x3F,
0xD3, 0x00, 0x40, 0x8D, 0x14,
0x20, 0x00, 0xA1, 0xC8, 0xDA,
0x12, 0x81, 0xCF, 0xD9, 0xF1,
0xDB, 0x30, 0xA4, 0xA6, 0xAF
};
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, 1, init_cmd, sizeof(init_cmd), 100);
六轴数据采集:
c复制uint8_t buf[14];
HAL_I2C_Mem_Read(&hi2c1, 0xD0, 0x3B, 1, buf, 14, 100);
int16_t ax = (buf[0]<<8)|buf[1];
int16_t ay = (buf[2]<<8)|buf[3];
int16_t az = (buf[4]<<8)|buf[5];
// 陀螺仪数据同理...
页写入函数:
c复制void EEPROM_WritePage(uint16_t addr, uint8_t *data, uint8_t len) {
uint8_t buf[len+1];
buf[0] = addr & 0xFF;
memcpy(buf+1, data, len);
HAL_I2C_Master_Transmit(&hi2c1, 0xA0, buf, len+1, 100);
HAL_Delay(5); // 等待写入完成
}
启用DMA提升吞吐量:
c复制// CubeMX配置I2C TX/RX DMA流
HAL_I2C_Mem_Write_DMA(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, data, len);
建议添加以下保护措施:
c复制for(int retry=0; retry<3; retry++) {
if(HAL_I2C_IsDeviceReady(&hi2c1, addr, 3, 100) == HAL_OK) {
break;
}
Hardware_Reset(); // 硬件复位
}
实测有效的改进方案:
使用i2c-dev接口的典型操作:
c复制int fd = open("/dev/i2c-1", O_RDWR);
ioctl(fd, I2C_SLAVE, 0x50);
i2c_smbus_write_byte_data(fd, reg, value);
uint8_t val = i2c_smbus_read_byte_data(fd, reg);
共享I2C总线的保护措施:
c复制pthread_mutex_lock(&i2c_mutex);
HAL_I2C_Master_Transmit(...);
pthread_mutex_unlock(&i2c_mutex);
STM32硬件BUG记录:
上拉电阻选择误区:
地址对齐陷阱:
电源干扰案例:
极端环境适配: