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
实验
写程序,单片机上电后,不断读取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; //波特率4800TL1 = 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; //先拉高SCLdelayI2C();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还是0if(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,释放I2CdelayI2C();SCL = 1; // 把SCL拉高,第9个时钟信号delayI2C();Ack_bit = SDA; //读取应答信号delayI2C();SCL = 0; //拉低SCL,结束接收应答return Ack_bit;
}//I2C发送应答位
void writeAckI2C(bit Ack_bit)
{//根据应答状态变量来确定SDA是1还是0if(Ack_bit){SDA = 1;}else{SDA = 0;}//第9个时钟周期//拉高SCL,再拉低SCL,完成一次发送应答信号SCL = 1; SCL = 0;
}//向EEPROM中写入一个字节,addr是地址
void writeByteE2PROM(unsigned char addr, unsigned char dat)
{bit isNotAck = 0; //接收应答位用的变量//启动信号initI2C();//写入设备地址和写数据位writeByteI2C(0x50<<1); //寻址器件,后续为写操作,左移1位,右侧低位多出来的0是读写位的写入0//接收应答位isNotAck = readAckI2C();//接收不正确则退出函数if(isNotAck){return;}//写入寄存器地址writeByteI2C(addr);//接收应答位isNotAck = readAckI2C();//接收不正确则退出函数if(isNotAck){return;}//写入数据writeByteI2C(dat);//接收应答位isNotAck = readAckI2C();//接收不正确则退出函数if(isNotAck){return;}//停止信号closeI2C();
}//读取EEPROM中的一个字节,addr是地址
unsigned char readByteE2PROM(unsigned char addr)
{//判断是否读写正确的变量unsigned char isOK = 0;unsigned char dat = 0;bit isNotAck = 0; //接收应答位用的变量//没应答成功就循环重复发while(!isOK){//写入地址部分//启动信号initI2C();//写入设备地址和写数据位writeByteI2C(0x50<<1);//接收应答位isNotAck = readAckI2C();//接收不正确则设置为0,进行下一次循环if(isNotAck){sentUART(0x01);isOK = 0;break;}//写入寄存器地址writeByteI2C(addr);//接收应答位isNotAck = readAckI2C();//接收不正确则设置为0,进行下一次循环if(isNotAck){sentUART(0x02);isOK = 0;break;}//停止信号closeI2C();//读取数据部分//启动信号initI2C();//写入设备地址和写数据位(最低位为1,也就是读写位为1,读数据)writeByteI2C((0x50<<1)|0x01);//接收应答位isNotAck = readAckI2C();//接收不正确则设置为0,进行下一次循环if(isNotAck){sentUART(0x03);isOK = 0;break;}dat = readByteI2C(); //读取一个字节数据//发送应答位writeAckI2C(0);//停止信号closeI2C();isOK = 1; //此时说明传输成功,下次循环直接跳出}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也行,我懒得写)