1. STM32与EEPROM单字节读写实战解析在嵌入式开发中EEPROM因其非易失性存储特性被广泛用于参数保存。今天我将分享基于STM32硬件I2C接口实现AT24C02 EEPROM单字节读写的完整实现过程这是对前一篇I2C基础篇day18的实际应用延伸。2. 硬件设计与原理分析2.1 EEPROM芯片关键特性AT24C02是2Kbit(256x8)的串行EEPROM支持标准I2C协议工作电压1.8V~5.5V写周期时间5ms最大值100万次擦写寿命数据保存100年2.2 硬件连接示意图STM32F4 AT24C02 PB6(SCL) ---- SCL PB7(SDA) ---- SDA 3.3V | [4.7K]上拉电阻 | GND注意必须使用开漏模式配置GPIO并外接上拉电阻。I2C协议规定总线在空闲时应被上拉至高电平。3. 软件实现详解3.1 I2C外设初始化首先配置GPIO和I2C参数void I2C_EE_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; I2C_InitTypeDef I2C_InitStruct; // 1. 使能时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 2. 配置GPIO GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF; GPIO_InitStruct.GPIO_OType GPIO_OType_OD; // 开漏输出 GPIO_InitStruct.GPIO_PuPd GPIO_PuPd_UP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); // 3. 引脚复用 GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); // 4. I2C参数配置 I2C_InitStruct.I2C_ClockSpeed 100000; // 100kHz I2C_InitStruct.I2C_Mode I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 0x00; // 主机地址可任意 I2C_InitStruct.I2C_Ack I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, I2C_InitStruct); // 5. 使能I2C I2C_Cmd(I2C1, ENABLE); }3.2 单字节写入实现写入时序分为三个阶段发送设备地址写方向(0xA0)发送内存地址发送数据字节uint8_t I2C_EE_ByteWrite(uint8_t data, uint8_t addr) { // 1. 等待总线空闲 uint32_t timeout I2C_TIMEOUT; while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)){ if((timeout--) 0) return 0; } // 2. 发送起始条件 I2C_GenerateSTART(I2C1, ENABLE); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)){ if((timeout--) 0) return 0; } // 3. 发送设备地址(写) I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){ if((timeout--) 0) return 0; } // 4. 发送内存地址 I2C_SendData(I2C1, addr); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){ if((timeout--) 0) return 0; } // 5. 发送数据 I2C_SendData(I2C1, data); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){ if((timeout--) 0) return 0; } // 6. 产生停止条件 I2C_GenerateSTOP(I2C1, ENABLE); // 7. 等待写入完成 I2C_EE_WaitStandbyState(); return 1; }3.3 单字节读取实现读取时序较为复杂采用复合格式发送设备地址写方向(0xA0)发送要读取的内存地址重新发送起始条件发送设备地址读方向(0xA1)接收数据uint8_t I2C_EE_ByteRead(uint8_t addr) { uint8_t data 0; uint32_t timeout I2C_TIMEOUT; // 1. 等待总线空闲 while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)){ if((timeout--) 0) return 0; } // 2. 发送起始条件 I2C_GenerateSTART(I2C1, ENABLE); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)){ if((timeout--) 0) return 0; } // 3. 发送设备地址(写) I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){ if((timeout--) 0) return 0; } // 4. 发送内存地址 I2C_SendData(I2C1, addr); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){ if((timeout--) 0) return 0; } // 5. 发送重复起始条件 I2C_GenerateSTART(I2C1, ENABLE); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)){ if((timeout--) 0) return 0; } // 6. 发送设备地址(读) I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver); timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){ if((timeout--) 0) return 0; } // 7. 禁用应答(准备接收最后一个字节) I2C_AcknowledgeConfig(I2C1, DISABLE); // 8. 产生停止条件 I2C_GenerateSTOP(I2C1, ENABLE); // 9. 等待数据接收完成 timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)){ if((timeout--) 0) return 0; } // 10. 读取数据 data I2C_ReceiveData(I2C1); // 11. 重新使能应答 I2C_AcknowledgeConfig(I2C1, ENABLE); return data; }4. 关键问题与优化技巧4.1 写入等待处理EEPROM写入需要时间完成内部擦写操作典型值3-5ms连续写入时必须检测设备就绪状态void I2C_EE_WaitStandbyState(void) { uint32_t timeout I2C_TIMEOUT; do { // 1. 产生起始条件 I2C_GenerateSTART(I2C1, ENABLE); // 2. 等待EV5事件 timeout I2C_TIMEOUT; while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)){ if((timeout--) 0) break; } // 3. 发送设备地址 I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 4. 检测是否应答 timeout I2C_TIMEOUT; while(1){ if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){ // 设备已就绪 I2C_GenerateSTOP(I2C1, ENABLE); return; } if(I2C_GetFlagStatus(I2C1, I2C_FLAG_AF)){ // 收到NACK I2C_ClearFlag(I2C1, I2C_FLAG_AF); break; } if((timeout--) 0) break; } I2C_GenerateSTOP(I2C1, ENABLE); Delay_ms(1); // 适当延时后再重试 } while(1); }4.2 时序优化建议批量写入优化虽然本文重点在单字节操作但实际应用中建议使用页写入AT24C02页大小为8字节可减少90%以上的写入时间。中断方式改进查询方式会阻塞CPU对于实时性要求高的系统可改用中断或DMA方式。错误恢复机制增加超时处理和总线错误恢复逻辑提高鲁棒性。5. 实际测试验证完整的测试流程应包括擦除测试写入0xFF到目标地址写入测试写入递增数据模式(0x00-0xFF)读取验证回读数据并校验边界测试测试地址0x00和0xFF连续操作测试连续读写验证稳定性典型测试代码void EEPROM_Test(void) { uint8_t i, write_data 0x55, read_data; // 单地址重复读写测试 for(i0; i10; i){ I2C_EE_ByteWrite(write_datai, 0x00); read_data I2C_EE_ByteRead(0x00); if(read_data ! (write_datai)){ printf(Test failed at iteration %d\r\n, i); return; } } // 全地址范围测试 for(i0; i255; i){ I2C_EE_ByteWrite(i, i); // 写入地址值作为数据 read_data I2C_EE_ByteRead(i); if(read_data ! i){ printf(Address test failed at 0x%02X\r\n, i); return; } } printf(EEPROM test passed!\r\n); }通过本实现我们完成了STM32与EEPROM的单字节读写操作。在实际项目中根据具体需求可以在此基础上扩展页写入、多字节读写等功能。特别注意EEPROM的写入寿命限制应避免频繁写入同一地址。