从零开始:51单片机模拟IIC时序驱动AT24C02全攻略
1. IIC协议与AT24C02基础认知
IIC(Inter-Integrated Circuit)总线是Philips公司开发的一种简单、双向二线制同步串行总线,仅需SDA(数据线)和SCL(时钟线)即可实现设备间通信。AT24C02是Atmel推出的2Kbit(256×8位)串行EEPROM,采用IIC接口,具有以下关键特性:
- 工作电压:1.8V~5.5V宽范围
- 存储结构:256字节,支持页写(8字节/页)
- 地址分配:7位设备地址(前4位固定为1010,后3位可配置)
- 通信速率:标准模式100kHz,快速模式400kHz
典型电路连接:
51单片机 AT24C02 Px.x (SDA) —— SDA (加4.7K上拉电阻) Px.x (SCL) —— SCL (加4.7K上拉电阻) VCC —— VCC GND —— GND注意:WP引脚接地可禁用写保护,A0-A2引脚决定设备地址
2. 模拟IIC时序核心实现
2.1 关键信号模拟(51单片机无硬件IIC时)
起始信号(START)时序:
void I2C_Start() { SDA = 1; // 先拉高数据线 SCL = 1; // 时钟线高电平 Delay_us(5); // 保持时间 SDA = 0; // 数据线下降沿 Delay_us(5); SCL = 0; // 准备数据传输 }停止信号(STOP)时序:
void I2C_Stop() { SDA = 0; // 先拉低数据线 SCL = 1; // 时钟线高电平 Delay_us(5); SDA = 1; // 数据线上升沿 Delay_us(5); }应答检测(ACK)实现:
bit I2C_CheckACK() { SDA = 1; // 释放数据线(51单片机需设置为输入模式) SCL = 1; Delay_us(2); if(SDA) { // 检测从机是否拉低 SCL = 0; return 1; // 无应答 } SCL = 0; return 0; // 有应答 }2.2 数据收发基础函数
字节发送函数:
void I2C_SendByte(uint8_t dat) { uint8_t i; for(i=0; i<8; i++) { SDA = (dat & 0x80) ? 1 : 0; // 先发高位 dat <<= 1; SCL = 1; Delay_us(5); SCL = 0; Delay_us(5); } }字节接收函数:
uint8_t I2C_RecvByte() { uint8_t i, dat = 0; SDA = 1; // 设置为输入模式 for(i=0; i<8; i++) { SCL = 1; Delay_us(2); dat <<= 1; if(SDA) dat |= 0x01; SCL = 0; Delay_us(5); } return dat; }3. AT24C02驱动开发实战
3.1 设备初始化与地址配置
AT24C02的7位设备地址格式:
1 0 1 0 A2 A1 A0 R/W当A2-A0全部接地时,写地址为0xA0,读地址为0xA1
初始化函数示例:
void AT24C02_Init() { SDA = 1; // 初始状态拉高 SCL = 1; WP = 0; // 禁用写保护 }3.2 单字节写入操作
操作时序:
- 发送START
- 发送设备地址(写模式)
- 发送存储地址
- 发送数据
- 发送STOP
代码实现:
void AT24C02_WriteByte(uint8_t addr, uint8_t dat) { I2C_Start(); I2C_SendByte(0xA0); // 设备地址+写 I2C_CheckACK(); I2C_SendByte(addr); // 存储地址 I2C_CheckACK(); I2C_SendByte(dat); // 数据 I2C_CheckACK(); I2C_Stop(); Delay_ms(10); // 等待写入完成(tWR典型5ms) }3.3 页写入操作(提升效率)
AT24C02支持页写入(8字节/页),显著提高连续写入效率:
void AT24C02_PageWrite(uint8_t addr, uint8_t *buf, uint8_t len) { uint8_t i; if(len > 8) len = 8; // 不超过页限制 I2C_Start(); I2C_SendByte(0xA0); I2C_CheckACK(); I2C_SendByte(addr); I2C_CheckACK(); for(i=0; i<len; i++) { I2C_SendByte(buf[i]); I2C_CheckACK(); } I2C_Stop(); Delay_ms(10); }3.4 读取操作实现
随机读取时序:
- 发送START(伪写操作)
- 发送设备地址(写模式)
- 发送存储地址
- 发送START(重启)
- 发送设备地址(读模式)
- 接收数据
- 发送NACK
- 发送STOP
代码实现:
uint8_t AT24C02_ReadByte(uint8_t addr) { uint8_t dat; I2C_Start(); I2C_SendByte(0xA0); // 写模式 I2C_CheckACK(); I2C_SendByte(addr); // 指定地址 I2C_CheckACK(); I2C_Start(); // 重启 I2C_SendByte(0xA1); // 读模式 I2C_CheckACK(); dat = I2C_RecvByte(); I2C_SendNACK(); // 非应答 I2C_Stop(); return dat; }4. 典型问题排查与调试技巧
4.1 常见故障现象分析
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取全0xFF | 通信失败/芯片未响应 | 检查设备地址、上拉电阻、电源电压 |
| 读取全0x00 | SDA被主机持续拉低 | 检查读操作时是否释放SDA |
| 偶发读取失败 | 时序不符合要求 | 用逻辑分析仪捕获波形,调整延时 |
| 写入后读取错误 | 未等待写入完成 | 增加tWR延时(建议10ms) |
4.2 逻辑分析仪调试实战
使用Saleae逻辑分析仪捕获的典型问题波形:
正常写入时序:
START | 0xA0 | ACK | Addr | ACK | Data | ACK | STOP典型错误案例:
- ACK缺失:从机未响应,检查设备地址和连接
- SCL频率过高:降低时钟速度(建议<100kHz)
- 停止条件不完整:确保STOP后SCL和SDA都为高
4.3 代码优化建议
- 延时调整:
// 根据实际晶振频率调整(11.0592MHz示例) #define I2C_DELAY 5 // 微秒级延时 void Delay_us(uint8_t us) { while(us--) { _nop_(); _nop_(); _nop_(); } }- 错误重试机制:
uint8_t AT24C02_ReadWithRetry(uint8_t addr, uint8_t retry) { uint8_t dat; while(retry--) { dat = AT24C02_ReadByte(addr); if(dat != 0xFF) break; // 假设0xFF为无效值 Delay_ms(10); } return dat; }- 端口模式切换(针对51单片机):
#define SDA_IN() {PxM1 |= (1<<x); PxM0 &= ~(1<<x);} // 设置为输入 #define SDA_OUT() {PxM1 &= ~(1<<x); PxM0 &= ~(1<<x);} // 设置为推挽输出