【自学嵌入式:51单片机】用I2C通信读写AT24C02芯片,用串口通信获取AT24C02芯片存储的数据
所有自学嵌入式:51单片机系列的操作相关器件的代码都汇总到了:https://gitee.com/qin-ruiqian/Qin51
AT24C02
AT24C02是EEPROM芯片(读做E方P ROM芯片),可读可写,掉电不丢失,共256B
STC89C52RC芯片有ROM也有RAM,ROM掉电不丢失但不可写,RAM可读可写但掉电丢失

E0 E1 E2都是其地址线,图中把这3个引脚均接地了,也就是都是低电平0,AT24C02的设备地址高4位是固定的,1010,低3位由E0 E1 E2这3根地址线来决定,如果全部都接地,它的低3位都是000,则这个模块的设备地址是1010 000,也就是十六进制的0x50
WE 写入使能端,接GND默认为0,低电平触发(WE上面画了一个横线,表示低电平开启)
VCC、GND的电压范围在1.8V到5.5V
其内部结构图如下:

数据网格左侧连着一个译码器,SERIAL MUX是串行数据选择端,通过Y译码器将数据一位一位地输出出去,DATA RECOVERY数据恢复(擦除用),DATA WORD ADDR/COUNTER(数据字地址/计数器),用于I2C通信的模块里有START STOP LOGIC通信启动停止逻辑,DEVICE ADDRESS COMPARATOR器件地址比较器,SERIAL CONTROL LOGIC串行控制逻辑,它是把I2C通信的数据拿过来进行操作。
AT24C02数据帧


实验
写程序,单片机上电后,不断读取EEPROM芯片0x02地址的的数据,将该数据通过串口传送给上位机(电脑端),利用电脑端串口调试助手进行显示,然后数据自动加1,之后再回写到EEPROM芯片中,回写的地址还是0x02
代码如下:
#include <REGX52.H>
#include <intrins.h> // 延时空语句
sbit SCL = P2^1;
sbit SDA = P2^0;
//延时用(测试)
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
//初始化串口通信
void initScon()
{
SCON = 0x50; //0101 0000,串口通信方式1,由定时器控制,高4位的最低位REN为1,允许接收控制位,允许接收
TMOD = 0x20; //设置T1工作方式2,8位自动重载
TH1 = 0xFA; //波特率4800
TL1 = 0XFA;
ES = 1; //串口中断
EA = 1; //开总中断
TR1 = 1; //打开波特率发生器(定时器1)
}
//UART串口通信需要发送的字节
void sentUART(unsigned char Byte)
{
TI = 1; //打开发送中断,准备发送数据
SBUF = Byte; //待发送数据写入缓存
while(TI == 0); //TI变成1才算发送完成
TI = 0; //软件复位
}
//I2C通信专用延时程序
void delayI2C()
{
_nop_();
_nop_();
_nop_();
_nop_();
}
//初始化I2C通信
void initI2C()
{
//先都拉高成高电平
SCL = 1;
SDA = 1;
//下降沿
delayI2C();
SDA = 0;
delayI2C();
SCL = 0;
}
//关闭I2C通信
void closeI2C()
{
//先都拉低
SCL = 0;
SDA = 0;
delayI2C();
SCL = 1; //先拉高SCL
delayI2C();
SDA = 1; //SCL高电平期间,再拉高SDA,这样就产生了一个上升沿
delayI2C();
}
//I2C写入一个字节,I2C_Byte是待传送的数据
void writeByteI2C(unsigned char I2C_Byte)
{
unsigned char i = 0;
for(i = 0; i<8; i++)
{
//I2C_Byte & 0x80(1000 0000)
//获取最高位,看是1还是0
if(I2C_Byte & 0x80)
{
SDA = 1;
}
else
{
SDA = 0;
}
delayI2C();
SCL = 1; //拉高电平,发送数据
delayI2C();
SCL = 0; //拉低,发送完成
I2C_Byte <<= 1; //左移,循环读取每一位,让每一位都成为首位
}
}
// I2C读一个字节
unsigned char readByteI2C()
{
unsigned char i, Byte = 0x00;
for(i = 0; i<8; i++)
{
SDA = 1; //拉高SDA,释放I2C总线
//读总线之前一定要将SDA拉高
SCL = 1; //然后将SCL拉高,准备接收SDA状态
delayI2C();
if(SDA)
{
Byte |= (0x80>>i); //依次循环右移,得到一个字节(0x00分别或每一位得到,如果是低电平,不需要操作,该位就是0)
}
SCL = 0;
}
return Byte;
}
//读取I2C总线应答信号
bit readAckI2C()
{
bit Ack_bit; //应答信号
SDA = 1; //拉高SDA,释放I2C
delayI2C();
SCL = 1; // 把SCL拉高,第9个时钟信号
delayI2C();
Ack_bit = SDA; //读取应答信号
delayI2C();
SCL = 0; //拉低SCL,结束接收应答
return Ack_bit;
}
//I2C发送应答位
void writeAckI2C(bit Ack_bit)
{
//根据应答状态变量来确定SDA是1还是0
if(Ack_bit)
{
SDA = 1;
}
else
{
SDA = 0;
}
//第9个时钟周期
//拉高SCL,再拉低SCL,完成一次发送应答信号
SCL = 1;
SCL = 0;
}
//向EEPROM中写入一个字节,addr是地址
void writeByteE2PROM(unsigned char addr, unsigned char dat)
{
//启动信号
initI2C();
//写入设备地址和写数据位
writeByteI2C(0x50<<1); //寻址器件,后续为写操作,左移1位,右侧低位多出来的0是读写位的写入0
//接收应答位
readAckI2C();
//写入寄存器地址
writeByteI2C(addr);
//接收应答位
readAckI2C();
//写入数据
writeByteI2C(dat);
//接收应答位
readAckI2C();
//停止信号
closeI2C();
}
//读取EEPROM中的一个字节,addr是地址
unsigned char readByteE2PROM(unsigned char addr)
{
unsigned char dat = 0;
//启动信号
initI2C();
//写入设备地址和写数据位
writeByteI2C(0x50<<1);
//接收应答位
readAckI2C();
//写入寄存器地址
writeByteI2C(addr);
//接收应答位
readAckI2C();
//这里不要有停止信号,看上面的示意图
//closeI2C();
//启动信号
initI2C();
//写入设备地址和写数据位(最低位为1,也就是读写位为1,读数据)
writeByteI2C((0x50<<1)|0x01);
//接收应答位
readAckI2C();
dat = readByteI2C(); //读取一个字节数据
//发送应答位
writeAckI2C(1);//这里应答位要发送1
//停止信号
closeI2C();
///////
return dat;
}
void main()
{
unsigned char dat = 0;
initScon();
dat = readByteE2PROM(0x02); //读取E2PROM 0x02地址中的一个字节
dat++;
if(dat>=10)
{
dat = 0;
}
sentUART(dat); //送串口
writeByteE2PROM(0x02, dat); //回写到对应地址
while(1)
{
}
}
其效果是每次EEPROM自动记录上次的值,每次单片机开机后都加1,多次反复复位单片机,最后在串口助手中能看到以下效果(其实用LCD1602也行,我懒得写)

补充
24C02芯片手册列给了当前地址读的功能
内部地址计数器(指针)保存着上次访问最后一个地址加1的值。只要芯片有电,该地址就一直保存。当读到最后页的最后字节,地址会回转到0;当写到某页尾的最后一个字节,地址会回转到该页的首字节。
接收器件地址(读/写选择位为1)、EEPROM应答ACK后,当前地址的数据就随时钟送出。主器件无需应答0,但需发送停止条件,见下图。

也就是不用指定地址了,它自动按递增顺序存进去
【注】可以把 EEPROM 里的 “页” 理解成 “固定大小的存储小分区” :
想象 EEPROM 是个 “小仓库”,为了方便 批量读写数据 ,把仓库分成很多 “小格子组” ,每个 “小格子组” 就是 1 页 。
比如:假设 1 页有 16 个字节(具体大小看芯片型号),就像 1 页纸上画了 16 个格子,写数据时,填满这页最后 1 个格子后,再写就会 绕回这页第 1 个格子重新写 ;读数据时,也会按 “页” 的边界管理地址,避免乱套~
简单说:“页” 是 EEPROM 里为了规范读写、防止地址乱跑,预先划好的「固定长度小存储块」 ,类似笔记本里 “一页纸有固定行数” 的逻辑 。
浙公网安备 33010602011771号