1. I2C通信基础与STM32硬件配置
I2C(Inter-Integrated Circuit)是一种同步、半双工的串行通信协议,只需要两根信号线(SCL时钟线和SDA数据线)就能实现设备间的数据交互。在STM32F407上使用HAL库配置I2C时,首先要理解几个关键参数:
- 时钟速度:标准模式100kHz,快速模式400kHz
- 地址模式:7位或10位设备地址(AT24C02使用7位地址0xA0)
- 引脚配置:必须设置为开漏输出模式(GPIO_MODE_AF_OD)
实际项目中遇到过一个问题:如果忘记配置GPIO为开漏模式,会导致总线冲突。有一次调试时发现SCL线始终为低电平,最后发现是因为GPIO模式配置错误。
CubeMX配置示例:
hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; // 400kHz hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; // 主机地址可设为0 hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;2. AT24C02特性与硬件连接
AT24C02是2Kbit(256字节)的EEPROM,具有以下关键特性:
- 页写机制:每页8字节,跨页写入需要特殊处理
- 写周期时间:典型值5ms(写入后需延时)
- 地址编排:A2/A1/A0引脚决定设备地址
硬件连接注意事项:
- SCL/SDA必须接4.7kΩ上拉电阻
- WP引脚接地禁用写保护
- 地址引脚连接方式决定设备地址(通常全接地为0xA0)
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取全FF | 通信失败 | 检查上拉电阻、地址配置 |
| 写入不生效 | 未等待写周期 | 写入后加5ms延时 |
| 数据错位 | 跨页写入 | 确保单次写入不跨页 |
3. HAL库高效读写实现
3.1 单字节读写
基础读写函数封装:
// 单字节写入(带写周期等待) HAL_StatusTypeDef EEPROM_WriteByte(uint16_t addr, uint8_t data) { HAL_StatusTypeDef status; status = HAL_I2C_Mem_Write(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); HAL_Delay(5); // 必须的写周期等待 return status; } // 单字节读取 HAL_StatusTypeDef EEPROM_ReadByte(uint16_t addr, uint8_t *data) { return HAL_I2C_Mem_Read(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, data, 1, 100); }3.2 页写入优化
AT24C02页大小为8字节,高效写入策略:
void EEPROM_PageWrite(uint16_t addr, uint8_t *data, uint8_t len) { uint8_t chunk; while(len > 0) { chunk = (addr % 8) ? (8 - (addr % 8)) : 8; chunk = (chunk > len) ? len : chunk; HAL_I2C_Mem_Write(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, data, chunk, 100); HAL_Delay(5); addr += chunk; data += chunk; len -= chunk; } }3.3 连续读取技巧
AT24C02支持连续读取,无需分页处理:
HAL_StatusTypeDef EEPROM_SequentialRead(uint16_t addr, uint8_t *buf, uint16_t len) { return HAL_I2C_Mem_Read(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, buf, len, 1000); }4. 实战优化技巧
4.1 总线错误恢复
I2C总线锁死是常见问题,可通过以下代码恢复:
void I2C_Recovery() { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 临时配置SCL为普通输出 GPIO_InitStruct.Pin = GPIO_PIN_6; // SCL引脚 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 产生9个时钟脉冲 for(int i=0; i<9; i++) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); } // 重新初始化I2C MX_I2C1_Init(); }4.2 DMA传输优化
大数据量传输建议使用DMA:
// DMA写配置示例 HAL_I2C_Mem_Write_DMA(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, data, len); // DMA完成回调函数 void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c) { if(hi2c == &hi2c1) { // 写入完成处理 HAL_Delay(5); // 仍需等待写周期 } }4.3 错误处理增强
建议添加以下状态检查:
HAL_StatusTypeDef status = HAL_I2C_GetState(&hi2c1); if(status == HAL_I2C_STATE_READY) { // 总线就绪 } else if(status == HAL_I2C_STATE_BUSY) { // 总线忙,需要处理 }5. 性能对比测试
通过优化前后的对比测试(写入256字节数据):
| 方法 | 耗时(ms) | 代码复杂度 | 稳定性 |
|---|---|---|---|
| 单字节写入 | 1285 | 低 | 高 |
| 页写入优化 | 165 | 中 | 高 |
| DMA页写入 | 160 | 高 | 中 |
实测发现,合理使用页写入可以将速度提升近8倍。但要注意DMA方式虽然速度快,但在复杂电磁环境下可能需要额外的错误处理机制。