我已经掌握了足够的信息。让我验证一下关键点:你的延时函数按11.0592MHz编写,但AI8051U实际可能运行在24MHz甚至更高。延时严重不足导致I2C时序错误。
让我给你一个完整的分析和修正方案。
问题诊断:为什么 LCD 显示 "0127"
你写的 AT24C02_WriteByte(1, 5) 期望写入地址1的值为5,但读回来 Data = 0x7F = 127,说明 I2C 读写时序错误,AT24C02 无法正确识别数据。
根本原因:延时函数与系统主时钟不匹配
看你的延时函数:
void Delay5us(void) //@11.0592MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 12UL;
while (i) i--;
}
这个延时是为 11.0592MHz 编写的。但 AI8051U 上电默认工作在内部高速IRC,出厂校准频率通常为 24MHz(或更高)。在 24MHz 下,这个 Delay5us 实际只有约 2.3us,比标称的5us少了一半以上!
结果就是 I2C 的 SCL 高/低电平持续时间太短,AT24C02 无法稳定采样 SDA 数据,导致:
- 写入时数据未正确锁存
- 读取时 SDA 悬浮/noise 被读成高电平,得到
0x7F
这正是你看到 127 的原因。
次要因素
- I2C 引脚建议用「开漏」模式(你现在是准双向口,虽理论上也能用,但开漏+I2C规范更匹配)
- 写操作后等待时间不够:AT24C02 内部写周期约5ms,你的
Delay1ms(20) 若同样基于11.0592MHz,实际时间不足,可能读的是未写完的值。但读0x7F更偏向时序错误。
解决方案
我给你一个修正后的完整方案,主要做三件事:
- 在 main 开头明确配置系统主时钟(设为24MHz,或者把延时函数改成适配实际时钟)
- 把延时函数改成能精确保证I2C时序的版本
- I2C引脚改为开漏模式 + 内部/外部上拉
方案A:建议你直接使用 AI8051U 官方实验箱的软件模拟 I2C 例程(SDA=P3.3, SCL=P3.2)
官方例程(上面论坛 TID 24590)用的是 SDA=P3^3, SCL=P3^2。如果你板子上AT24C02接的是P2.3/P2.4,那你要用你自己的引脚,但要修好延时。
方案B:修正你的代码(推荐,思路清晰)
我基于你的代码,修正时钟和延时。关键是把 Delay5us 改成一个与系统时钟无关的、能保证至少几微秒的延时,或者直接改用 _nop_() 计数方式配合实际频率。
不过最稳妥、最推荐的还是把系统时钟明确设为 11.0592MHz,这样你的所有延时函数(Delay5us、Delay1ms 等)都和你原本写的一致。让我给你一个综合修正的 main.c:
修正后的参考代码
main.c(关键修改:时钟设置 + 开漏配置 + 写后等待):
#include
#include "LCD1602.h"
#include "Key.h"
#include "Delay.h"
#include "I2C.h"
#include "at24c02.h"
void main()
{
unsigned char Data;
// ===== 1. 允许访问扩展寄存器 XFR =====
P_SW2 |= 0x80; // 8位模式访问XFR
// ===== 2. 明确设置系统主时钟 =====
// AI8051U 出厂校准频率可能是24MHz。
// 如果你希望所有延时函数都按11.0592MHz工作,
// 就显式把系统时钟切到 11.0592MHz 频段。
// 但更简单的做法:这里不改时钟,而是把延时函数按实际时钟重写。
// 这里假设你选择保持当前默认时钟(比如24MHz),延时函数按实际频率精度写。
// 若你的Delay.h按11.0592MHz写,则强烈建议把主频设成11.0592MHz,见下面注释掉的代码。
/*
// --- 若要把系统时钟设为 11.0592MHz(可选)---
CLKDIV = 0x04;
IRTRIM = CHIPID11; // 22.1184MHz 校准值
// 注意:11.0592MHz 不是出厂校准的10个频率之一,
// 出厂校准的是 22.1184MHz。建议用内部IRC自动,不手动改。
*/
// ===== 3. I/O 配置 =====
P0M0 |= 0xff; P0M1 &= ~0xff; // P0 推挽(接LCD,驱动能力强)
// P2.3(SDA)、P2.4(SCL) 配置为 开漏模式 + 内部上拉
P2M0 |= 0x18; // P2.3、P2.4 M0=1
P2M1 |= 0x18; // P2.3、P2.4 M1=1 → 开漏模式
P2PU |= 0x18; // P2.3、P2.4 使能内部上拉
// P2.7、P2.6 推挽(你原来配置)
P2M0 |= 0xc0; P2M1 &= ~0xc0;
// P3 推挽(LCD数据等)
P3M0 |= 0xff; P3M1 &= ~0xff;
LCD_Init();
AT24C02_WriteByte(1, 5);
Delay1ms(20); // 等待写完成(AT24C02内部写周期约5ms,20ms足够)
Data = AT24C02_ReadByte(1);
LCD_ShowNum(1, 1, Data, 4);
while(1)
{
}
}
I2C.c(关键修改:延时重新基于实际需求,把SCL高/低保持时间拉长):
#include
sbit I2C_SCL = P2^4;
sbit I2C_SDA = P2^3;
// 延时函数:不管系统主频多少,保证足够的I2C时序。
// 这里用_nop_()计数,为了在不同主频下都够用,
// 建议延时偏大一些(稳妥)。下面这个约等于 24MHz 下 ~10us。
void I2C_Delay(void)
{
unsigned char i;
for(i=0; i<20; i++)
{
_nop_();
}
}
void I2C_start(void)
{
I2C_SDA = 1;
I2C_Delay();
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 0;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
}
void I2C_stop(void)
{
I2C_SDA = 0;
I2C_Delay();
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 1;
I2C_Delay();
}
void I2C_sendbyte(unsigned char Byte)
{
unsigned char i;
for(i=0; i<8; i++)
{
I2C_SDA = (Byte & (0x80 >> i)) ? 1 : 0;
I2C_Delay();
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
}
}
unsigned char I2C_receivebyte(void)
{
unsigned char i, Byte = 0x00;
I2C_SDA = 1; // 释放SDA,准备读
for(i=0; i<8; i++)
{
I2C_SCL = 1;
I2C_Delay();
if(I2C_SDA) { Byte |= (0x80 >> i); }
I2C_SCL = 0;
I2C_Delay();
}
return Byte;
}
void I2C_SendAck(unsigned char Ackbit)
{
I2C_SDA = Ackbit;
I2C_Delay();
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
}
unsigned char I2C_Receivebit(void)
{
unsigned char Ackbit;
I2C_SDA = 1;
I2C_Delay();
I2C_SCL = 1;
I2C_Delay();
Ackbit = I2C_SDA;
I2C_SCL = 0;
I2C_Delay();
return Ackbit;
}
at24c02.c(建议读操作时也加入ACK检查和适当时序):
#include
#include "I2C.h"
#define AT24C02_ADDRESS 0xa0
void AT24C02_WriteByte(unsigned char WordAddress, unsigned char Data)
{
I2C_start();
I2C_sendbyte(AT24C02_ADDRESS);
I2C_Receivebit(); // 应答
I2C_sendbyte(WordAddress);
I2C_Receivebit();
I2C_sendbyte(Data);
I2C_Receivebit();
I2C_stop();
}
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_start();
I2C_sendbyte(AT24C02_ADDRESS);
I2C_Receivebit(); // 应答
I2C_sendbyte(WordAddress);
I2C_Receivebit(); // 应答
I2C_start(); // 重复起始
I2C_sendbyte(AT24C02_ADDRESS | 0x01); // 读命令
I2C_Receivebit(); // 应答
Data = I2C_receivebyte();
I2C_SendAck(1); // 主机发送非应答(结束)
I2C_stop();
return Data;
}
重点提醒
-
最关键的修改是延时:你原来的 Delay5us 是按11.0592MHz算的,AI8051U实际主频高得多,导致时序不足。我把延时改成了一个对主频不那么敏感、且偏大更稳妥的版本。如果你的 Delay.h 里的 Delay1ms 也是按11.0592MHz写的,在24MHz下也会偏快——建议要么统一把主频设成11.0592MHz,要么把所有延时函数都改成根据实际主频计算。
-
如果改了延时还不行,请检查:
- AT24C02 的 VCC、GND、A0/A1/A2 是否接对(地址应为 0xA0)
- SCL/SDA 是否有 上拉电阻(虽然你使能了内部上拉,但如果板上有外部上拉更好)
- 确认你的板子 AT24C02 是接在 P2.3/P2.4,还是 P3.2/P3.3(如果是后者,请把
sbit 引脚和I2C脚切换改对)
-
如果方便,建议直接参考AI8051U官方实验箱例程(论坛TID 24590),它用的是 SDA=P3.3, SCL=P3.2,并配好了正确的主频(24MHz)和延时。
需要我帮你把完整的 Delay.h/.c 也按24MHz改好吗?或者你告诉我你板子上 AT24C02 实际接在哪些引脚,我帮你把引脚配置也一起调对。
🔍 搜索 "#include ai805…"<
📌 相关论坛讨论
- 求教
- 12-1 AT24C02数据存储,江协科技讲普中51单片机开发板 STC89C52学习板 配套程序, A2标
- 不断电下载疑问
⏱️ 查找用时 31.9s | 总回答用时 54.7s