AT24C02 I2C 读取总是 0xFF

在I2C通信中,调整了I2CStop函数的SCL和SDA信号顺序后,设备能正常读写。原函数中SDA在SCL变为高电平后改变,而调整后的函数确保了SCL先升高再改变SDA,这一改动使得包括BMP280在内的其他设备工作正常,但AT24C02出现不兼容情况。

我是因为停止信号时序有问题

本来是这样

void I2CStop(void)
{
    I2C_SDA(0);
    I2C_SCL(0);
    HAL_Delay_us(5);//SCL保持4.7us,拉高SDA
    I2C_SDA(1);
    HAL_Delay_us(5);
    I2C_SCL(1);
    HAL_Delay_us(5);
}

调整了SCL SDA 顺序之后的

void I2CStop(void)
{
    I2C_SDA(0);
    I2C_SCL(0);
    HAL_Delay_us(5);//SCL保持4.7us,拉高SDA
    I2C_SCL(1);
    HAL_Delay_us(5);
    I2C_SDA(1);//起始终止信号始终都要是在SCL高电平期间变化SDA,这是与其他时序的根本区别,所以一定要先拉高SCL再变化SDA
    HAL_Delay_us(5);
}

然后就能正常读写了
不直到为啥其他IIC设备都能用这个时序,包括我上午才用的BMP280都能正常读写,AT24C02就不行。

<think>我们只需要修改与EEPROM相关的部分,因为AT24C02AT24C64的容量不同,但接口和基本操作是兼容的,主要区别在于: 1. AT24C02的容量为2Kbit(256字节),而AT24C64为64Kbit(8192字节),因此地址空间不同。 2. AT24C02的地址为8位(1字节),而AT24C64需要13位地址(因此需要2字节地址,高5位在器件地址中,低8位作为地址字节)。 但是,在I2C协议中,AT24C02AT24C64的器件地址格式相同(前4位固定为1010,后3位为A2/A1/A0引脚状态,最后1位是读写位)。区别在于: - AT24C02:使用一个字节表示地址(因为256字节,8位地址足够)。 - AT24C64:使用两个字节表示地址(高8位和低8位,但实际AT24C64的地址是13位,所以高字节只有低5位有效)。 因此,我们需要修改写和读函数,将原来的两个字节地址改为一个字节地址(对于AT24C02)。 具体修改如下: 1. 在写操作时,AT24C02只需要发送一个字节的地址(8位),而AT24C64需要发送两个字节(16位地址,但实际有效13位,所以高字节的前3位忽略)。 2. 读操作同理。 但是,为了兼容两种芯片,我们可以通过宏定义来选择,或者修改函数接口。由于用户要求替换为AT24C02,我们直接修改为AT24C02的地址格式。 原AT24C64的写操作(两个字节地址): I2C_Start(); I2C_WriteByte(0xA0); // 器件地址(写) I2C_WriteByte(addr_high); // 地址高字节 I2C_WriteByte(addr_low); // 地址低字节 ... // 写入数据 对于AT24C02,写操作(一个字节地址): I2C_Start(); I2C_WriteByte(0xA0); // 器件地址(写) I2C_WriteByte(addr); // 8位地址 ... // 写入数据 因此,我们需要修改EEPROM_WriteUser和EEPROM_ReadUser函数。 另外,由于AT24C02只有256字节,而每个用户数据占8字节(ID+6字节密码+1字节权限),最多可以存储256/8=32个用户(实际上我们原设计是3个普通用户,1个管理员,共4个,完全足够)。 修改后的函数: 写用户数据AT24C02): void EEPROM_WriteUser(unsigned char id, User* user) { unsigned char addr = id * 8; // 每个用户8字节,所以地址为id*8 I2C_Start(); I2C_WriteByte(0xA0); // 器件地址(写) I2C_WriteByte(addr); // 发送8位地址 I2C_WriteByte(user->id); for(int i=0; i<6; i++) I2C_WriteByte(user->password[i]); I2C_WriteByte(user->level); I2C_Stop(); delay_ms(10); // 写入周期等待 } 读用户数据AT24C02): void EEPROM_ReadUser(unsigned char id, User* user) { unsigned char addr = id * 8; I2C_Start(); I2C_WriteByte(0xA0); // 器件地址(写) I2C_WriteByte(addr); // 发送要读取的地址 I2C_Start(); // 重复启动 I2C_WriteByte(0xA1); // 器件地址(读) user->id = I2C_ReadByte(1); // 读取ID,发送ACK for(int i=0; i<6; i++) user->password[i] = I2C_ReadByte(1); // 读取密码,发送ACK user->level = I2C_ReadByte(0); // 读取权限,发送NACK I2C_Stop(); } 注意:原代码中EEPROM_ADDR定义为0xA0,我们直接使用0xA0(写)和0xA1(读)即可。 另外,由于AT24C02的页写入能力为8字节(一页),而我们的用户数据正好是8字节,所以可以一次写入一页,无需分页。 因此,我们只需要修改这两个函数,其他部分不变。 但是,原代码中在写用户数据时,地址计算是`id * 8`,这个对于AT24C02同样适用,因为地址是8位的,最大地址为255(0xFF),而id最大为31(因为32个用户,0~31),所以地址范围0~248,没有超出。 所以,我们只需要将原来发送两个字节地址改为发送一个字节地址即可。 修改后的完整代码(仅EEPROM操作部分)如下: 注意:原代码中I2C_WriteByte和I2C_ReadByte函数是已经实现的,我们假设它们是正确的。 另外,由于AT24C02的器件地址和AT24C64相同(都是0xA0作为写地址,0xA1作为读地址),所以不需要修改器件地址。 因此,我们只需要修改上述两个函数。 但是,为了确保代码的清晰,我们可以将器件地址定义为宏,但原代码已经定义了`#define EEPROM_ADDR 0xA0`,我们可以继续使用。 修改后的代码: ```c /****************** AT24C02存储操作 ******************/ // 写入用户数据(地址=用户ID*8) void EEPROM_WriteUser(unsigned char id, User* user) { unsigned char addr = id * 8; I2C_Start(); I2C_WriteByte(EEPROM_ADDR); // 器件地址(写) I2C_WriteByte(addr); // 8位地址 I2C_WriteByte(user->id); for(int i=0; i<6; i++) I2C_WriteByte(user->password[i]); I2C_WriteByte(user->level); I2C_Stop(); delay_ms(10); // 写入周期等待 } // 读取用户数据 void EEPROM_ReadUser(unsigned char id, User* user) { unsigned char addr = id * 8; I2C_Start(); I2C_WriteByte(EEPROM_ADDR); // 器件地址(写) I2C_WriteByte(addr); // 8位地址 I2C_Start(); // 重复启动 I2C_WriteByte(EEPROM_ADDR | 0x01); // 读模式 user->id = I2C_ReadByte(1); // ACK for(int i=0; i<6; i++) user->password[i] = I2C_ReadByte(1); user->level = I2C_ReadByte(0); // NACK I2C_Stop(); } ``` 这样,我们就将存储芯片从AT24C64替换为AT24C02,并且代码能够正常工作。 注意:由于AT24C02容量较小,我们在添加用户时需要注意不要超过最大用户数(32个,包括管理员)。原设计最多3个普通用户和1个管理员,所以没有问题。 另外,原代码中创建默认管理员的代码不需要修改,因为地址计算方式相同。 总结:主要修改就是去掉了地址的高字节(因为AT24C02只需要8位地址),其他不变。 最后,需要确保I2C的时序满足AT24C02的要求(如启动、停止、应答等),由于AT24C02AT24C64的I2C协议兼容,所以时序部分不需要修改。 如果之前I2C的驱动是通用的,那么现在应该可以正常工作。 注意:AT24C02的页写入缓冲区为8字节,而我们每次写入正好8字节,所以不会跨页,可以一次写入。 如果用户数据超过8字节,则需要分页写入,但这里我们不需要。 因此,替换完成。</think># AT24C64替换为AT24C02I2C通信实现 将AT24C64替换为AT24C02主要涉及地址空间调整和存储管理优化。AT24C02容量为2Kb(256字节),而AT24C64为64Kb(8192字节),因此需要修改存储地址结构和相关操作函数。 ## 关键修改点 ### 1. 地址空间差异 - **AT24C64**:使用16位地址(2字节地址) $$ \text{地址范围} = 0x0000 \sim 0x1FFF $$ - **AT24C02**:使用8位地址(1字节地址) $$ \text{地址范围} = 0x00 \sim 0xFF $$ ### 2. 器件地址 两种芯片使用相同的7位器件地址格式: ```c #define EEPROM_ADDR 0xA0 // 1010 + A2A1A0 + R/W ``` 其中A2A1A0由硬件引脚决定,通常接地(000) ## 修改后的I2C通信代码 ```c /****************** AT24C02存储操作 ******************/ // 写入单字节数据 void EEPROM_WriteByte(unsigned char addr, unsigned char data) { I2C_Start(); I2C_WriteByte(EEPROM_ADDR); // 器件地址+写模式 I2C_WriteByte(addr); // 8位地址 I2C_WriteByte(data); // 写入数据 I2C_Stop(); delay_ms(5); // 写入周期等待 } // 读取单字节数据 unsigned char EEPROM_ReadByte(unsigned char addr) { unsigned char data; I2C_Start(); I2C_WriteByte(EEPROM_ADDR); // 器件地址+写模式 I2C_WriteByte(addr); // 设置读取地址 I2C_Start(); // 重复启动 I2C_WriteByte(EEPROM_ADDR | 0x01); // 器件地址+读模式 data = I2C_ReadByte(0); // 读取数据(发送NACK) I2C_Stop(); return data; } // 写入用户数据(地址=用户ID*8) void EEPROM_WriteUser(unsigned char id, User* user) { unsigned char addr = id * 8; // 每个用户8字节 I2C_Start(); I2C_WriteByte(EEPROM_ADDR); // 器件地址+写模式 I2C_WriteByte(addr); // 起始地址 I2C_WriteByte(user->id); for(int i=0; i<6; i++) I2C_WriteByte(user->password[i]); I2C_WriteByte(user->level); I2C_Stop(); delay_ms(10); // 页写入等待 } // 读取用户数据 void EEPROM_ReadUser(unsigned char id, User* user) { unsigned char addr = id * 8; I2C_Start(); I2C_WriteByte(EEPROM_ADDR); // 器件地址+写模式 I2C_WriteByte(addr); // 设置读取地址 I2C_Start(); // 重复启动 I2C_WriteByte(EEPROM_ADDR | 0x01); // 器件地址+读模式 user->id = I2C_ReadByte(1); // ACK for(int i=0; i<6; i++) user->password[i] = I2C_ReadByte(1); user->level = I2C_ReadByte(0); // NACK I2C_Stop(); } ``` ## 存储管理优化 由于AT24C02容量减小,需要优化用户存储结构: ```c // 用户数据结构(8字节/用户) typedef struct { unsigned char id; // 用户ID (1字节) unsigned char password[6]; // 密码 (6字节) unsigned char level; // 权限级别 (1字节) } User; // 存储布局 #define ADMIN_ADDR 0x00 // 管理员起始地址 #define USER_ADDR 0x08 // 普通用户起始地址 #define USER_SIZE 8 // 每个用户占8字节 #define MAX_USERS 31 // (256-8)/8 = 31个普通用户 ``` ## 使用注意事项 1. **页写入限制**: AT24C02页大小为8字节,跨页写入需要分多次操作 $$ \text{页边界} = \text{地址} \mod 8 = 0 $$ 2. **地址空间计算**: 管理员固定使用地址0x00-0x07,普通用户从0x08开始: ```c unsigned char user_addr = USER_ADDR + (id - 1) * USER_SIZE; ``` 3. **写入等待时间**: AT24C02写入周期为5ms,连续写入需要添加延时 4. **用户ID管理**: ```c // 获取下一个可用用户ID unsigned char GetNextUserID() { for(unsigned char id = 1; id <= MAX_USERS; id++) { User user; EEPROM_ReadUser(id, &user); if(user.id == 0xFF) // 空槽标记 return id; } return 0; // 无可用ID } ``` ## 性能优化建议 1. **批量读取优化**: ```c void EEPROM_ReadBlock(unsigned char addr, unsigned char *buf, unsigned char len) { I2C_Start(); I2C_WriteByte(EEPROM_ADDR); I2C_WriteByte(addr); I2C_Start(); I2C_WriteByte(EEPROM_ADDR | 0x01); for(int i=0; i<len-1; i++) buf[i] = I2C_ReadByte(1); // ACK buf[len-1] = I2C_ReadByte(0); // NACK I2C_Stop(); } ``` 2. **写入验证机制**: ```c bit EEPROM_VerifyWrite(unsigned char addr, unsigned char data) { EEPROM_WriteByte(addr, data); return (EEPROM_ReadByte(addr) == data); } ``` 3. **磨损均衡策略**: ```c // 使用循环缓冲区存储频繁变更的数据 #define LOG_START 0xF0 unsigned char log_ptr = 0; void WriteLogEntry(unsigned char event) { EEPROM_WriteByte(LOG_START + log_ptr, event); log_ptr = (log_ptr + 1) % 16; // 16字节日志区 } ``` ## 相关问题 1. 如何实现AT24C02的页写入功能以提高写入效率?[^1] 2. I²C通信中如何处理总线冲突和从设备无响应的情况?[^2] 3. 在有限存储空间下,如何优化用户密码的存储结构?[^3] 4. AT24C02的写入寿命约100万次,如何实现磨损均衡?[^4] 5. 如何添加I²C通信超时检测机制提高系统鲁棒性?[^5] > 本实现将AT24C64替换为AT24C02,主要修改包括地址空间调整(16位→8位)、存储管理优化和页写入处理。AT24C02虽然容量较小,但通过合理的存储结构设计,仍可支持管理员+31个普通用户的门禁系统需求[^1][^3]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值