【蓝桥杯】EEPROM保存数据并正确读取

在学习蓝桥杯的时候,经常会用的EEPROM来保存调用数据。我在写程序的过程中,会出现读取失败或者是写入数据不准确的情况。在这里我总结了一种可以很稳定读写EEPROM的方法

蓝桥杯开发板上使用的存储芯片为24C02,可以把它简单理解为一个专门用来保存数据用的芯片,里面有256个字节的空间(00-FF)。它通过IIC协议与单片机进行通信(收发数据),且保存在里面的数据掉电也不会丢失。想要向它写入或者读取数据,我们需要简单了解一下IIC通信协议,想要写好与各种通信协议有关的程序,必须严格按照时序要求

IIC协议是一种双向、两线串行通讯接口,分别是串行数据线SDA和串行时钟线SCL.两根线都必须通过一个上拉电阻接到电源

请添加图片描述
总线上发送数据的器件被称作发送器,接收数据的器件被称作接收器。控制信息交换的器件被称作主器件,受主器件控制的器件则被称作从器件。主器件产生串行时钟SCL,控制总线的访问状态、产生START和STOP条件。24C02在IIC总线中作为从器件工作。只有当总线处于空闲状态时才可以启动数据传输。每次数据传输均开始于START条件,结束于STOP条件,二者之间的数据字节数是没有限制的,由总线上的主器件决定。信息以字节(8位)为单位传输,第9位时由接收器产生应答。

IIC是一种通信协议,由主器件(单片机)控制两条线:时钟线SCL,数据线SDA进行数据的收发,24C02作为从器件接收

我们要使用IIC协议来进行数据的收发,我们只需要记住读写的时序即可。

写时序

字节写

请添加图片描述

字节写是常用的写操作。我简单概括一下,有关字节写具体的描述见手册

1.发送起始命令
2.发送器件地址和写命令
0xa0*
请添加图片描述

高四位为器件地址固定,A2-A0接地,最后一位为读写控制,写是0读是1,所以是0xa0;
3.等待应答信号 (即拉低SDA)
4.发送字节地址(你数据想要保存的位置0x00-0xff)
5.等待应答信号
6.发送保存的数据
7.等待应答信号
8.发送停止信号

以上每个步骤都对应一个函数,这些函数在比赛会提供的数据包里的驱动文件里(IIC.c,IIC.h)

请添加图片描述

代码示例

//按照上面步骤写一个往EEPROM写数据的函数
void Write_eep(uchar addr,uchar dat) //定义两个形参,一个代表想要写入的位置,一个代表想要写入的数据。引入形参,我们可以再调用此函数时设定任意的地址和数据,如果我想把我测的温度数据保存在0x00位置上,那么就可以*Write_eep(0x00,temp)*
{
	IIC_Start();              //起始信号
	IIC_SendByte(0xa0);       //发送器件地址和写命令
	IIC_WaitAck();            //等待应答信号
	IIC_SendByte(addr);       //发送你想要保存的位置
	IIC_WaitAck();            
	IIC_SendByte(value);      //发送要保存的数据
	IIC_WaitAck();
	IIC_Stop();               //发送停止信号
}

读时序

随机读

请添加图片描述

随机读就是读取指定位置的数据,需要提供地址,是比较常用的读操作。我还是简单概括一下,感兴趣的伙伴可以查看24C02的读操作。

由上图可以看出,前半段就是写一个地址(你要读取的数据的位置),和写操作完全一样,后半段是读操作。

1.发送起始命令
2.发送器件地址和写指令(0xa0)
3.等待应答
4.发送想要读取的数据所在地址
5.等待应答

6.发送起始命令
7发送器件地址和读操作(0xa1)
8.等待应答
9.调用读取函数,读出数据
10.发送一个非应答信号(即拉高SDA,NO ACK)
11.发送停止信号

代码示例

//接上一个示例,把保存的温度数据给读出来
uchar Read_eep(uchar adde)    //此函数有返回值,返回value
{
	unsigned char value;      //定义一个变量,用来保存读取的数据,并返回出去
	IIC_Start();              //起始信号
	IIC_SendByte(0xa0);       //发送器件地址和写命令
	IIC_WaitAck();            //等待应答信号
	IIC_SendByte(addr);       //发送你想要读取的位置
	IIC_WaitAck();

	IIC_Start();
	IIC_SendByte(0xa1);       //发送器件地址和读命令
	IIC_WaitAck();
	value=IIC_RecByte();      //调用函数读取数据
	IIC_SendAck(1);           //发送一个非应答信号
	IIC_Stop();               

	return value;             //返回value
}	

实际练习

使用蓝桥杯开发板,模拟显示温度值(小数点后保留两位),按下S5保存当前温度数据,按下S6回显保存的数据,再次按下S6返回温度显示。
请添加图片描述
请添加图片描述

[[include]] "stc15f2k60s2.h"
[[include]] "iic.h"
[[include]] "intrins.h"

[[define]] uchar unsigned char
[[define]] uint unsigned int
	
sbit S6=P3^1;
sbit S5=P3^2;

void Display_temp();
void Display_dat();
void IintTimer0();
void keyscan();

uchar code SEG_duan[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xc6,0x89};//后两位为C和H的段码
uchar SEG_buf[8]={0};//定义一个数组作为缓存区

uint dat=2340;  //模拟温度值为2340,在3的位置加上小数点就可以了	
uint temp;      //保存读取的温度值     

bit flag=0;     //S6按下的标志位

void main()
{
	IintTimer0();
	while(1)
	{
		keyscan();
		if(flag==0)        //初始为0,显示初始温度数据
			Display_dat();	
		else
			Display_temp();//S6按下了,显示读取的温度数据
	}
}

void Delay10ms()		//@12.000MHz
{
	unsigned char i, j;

	i = 117;
	j = 184;
	do
	{
		while (--j);
	} while (--i);
}

void hc138(uchar n)
{
	switch(n)
	{
		case 4: P2= (P2&0x1f) | 0x80;
			break;
		case 5: P2= (P2&0x1f) | 0xa0;
			break;
		case 6: P2= (P2&0x1f) | 0xc0;
			break;
		case 7: P2= (P2&0x1f) | 0xe0;
			break;
	}

}

void SEG_bit(uchar pos,uchar value)
{
	hc138(6);
	P0= 0x01 << pos;
	hc138(7);
	P0=value;
}

/*因为IIC对时序要求较为严格,所以在执行有关RRPROM函数的时候关闭中断,
有关IIC的函数里有延时函数,所以我们在延时里面打开中断,这样写更有效率*/

uchar Read_epp(uchar addr)
{
	uchar value=0;
	EA=0;
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	IIC_SendByte(addr);
	IIC_WaitAck();
	
	IIC_Start();
	IIC_SendByte(0xa1);
	IIC_WaitAck();
	value=IIC_RecByte();
	IIC_SendAck(1);
	IIC_Stop();
	EA=1;
	return value;
}

void Write_eep(uchar addr,uchar value)
{
	EA=0;
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	IIC_SendByte(addr);
	IIC_WaitAck();
	IIC_SendByte(value);
	IIC_WaitAck();
	IIC_Stop(); 
	EA=1;
}

void baocun()
{
	uchar i,j;
	j=2350/100;           	//因为温度值是四位数,超过了一个字节,分为两个											    
	i=2350%100;				//字节逐个保存,为验证是否保存了数据,这里假设温度为23.50	
	Write_eep(0x00,i);      //保存地址选择0x00,把dat的高八位先保存(保存顺序可随意)
	Delay10ms();            //手册说明,当接受完一个字节的数据后,EEPROM进入内部写周期
	Write_eep(0x01,j);      //此时任何输入无效,所以我们要等待这个周期过去,经过我的测试
	Delay10ms();		    //这个周期的时间大概在几个微妙,具体时间大家可以去测试一下。
	
}

void duqu()
{
	uchar i,j;
	j=Read_epp(0x00);       //读出放在这个位置的高八位数据
	i=Read_epp(0x01);	
	temp=i*100+j;   	
}

void Display_dat()       
{	
	SEG_buf[0]=SEG_duan[10];
	
	SEG_buf[1]=0xff;
	SEG_buf[2]=0xff;
	SEG_buf[3]=0xff;
	SEG_buf[4]=SEG_duan[dat/1000];           //提取个十百千位
	SEG_buf[5]=SEG_duan[(dat/100)%10]-0x80;  //因为要显示小数点,所以把最高位置低。
	SEG_buf[6]=SEG_duan[(dat/10)%10];
	SEG_buf[7]=SEG_duan[dat%10];	
}

void Display_temp()       
{	
	SEG_buf[0]=SEG_duan[11];
	SEG_buf[1]=SEG_duan[10];
	
	SEG_buf[2]=0xff;
	SEG_buf[3]=0xff;
	SEG_buf[4]=SEG_duan[temp/1000];           //提取个十百千位
	SEG_buf[5]=SEG_duan[(temp/100)%10]-0x80;  
	SEG_buf[6]=SEG_duan[(temp/10)%10];
	SEG_buf[7]=SEG_duan[temp%10];	
}

void keyscan()
{
	if(S5==0)
	{
		Delay10ms();
		if(S5==0)
		{
			while(S5==0);                     //等待弹起,避免重复执行
			baocun();                         //按键弹起,保存温度值
		}
	}
	if(S6==0)
	{
		Delay10ms();
		if(S6==0)
		{
			while(S6==0);                     
			duqu(); 
            flag=~flag;                       //S6按下一次,标志位取反即0变1或者1变零			
		}
	}
}

void IintTimer0()
{
	TMOD=0x00;
	TH0=(65536-2000)/255;
	TL0=(65536-2000)%255;
	ET0=1;
	EA=1;
	TR0=1;
}

void Timer0() interrupt 1  
{
	static uchar i=0;
	SEG_bit(i,SEG_buf[i]);//通过改变SEG_buf内的数据,可以显示你想要的内容
	i++;
	i&=0x07;		
}
  • 10
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值