嵌入式实验:非易失存储

/******************************************************************************************************************************/

/***补充:源码中有一部分是想要做串口通信的,后来一直在调按键之类的小问题,就耽搁了************/

/*********************************************************************************************************************/

一、目的

这一次的实验是个自选实验,刚开始选了RFID,本来以为难度没有很大,看了一下暑期用的工程感觉这个东西还是挺难的,从实验室借来的RFID识别的元件也不能用,就临时换了选题,选了这个非易失存储。

实验使用的是STC-B学习板,板上有一个非易失存储元件,要做的事情是使我们板上的芯片和这个存储元件可以“沟通”,完成存储器重要的功能:读,写,非易失

二、原理

存储部分使用的是24C02芯片,通过IIC协议与芯片实现沟通,板上共有八个24C02芯片,每个芯片有256个8bit的空间。

IIC使用了两条总线,这样说不太好,是一条总线中有两条信号线,一条代表数据线,一条代表时钟线,分别可以命名为SDA和SCL。   

数据线的功能是比较丰富的,既可以传送地址信息又可以传送数据信息,所以需要分别来看

寻址过程:每次都发送一个字节的信息到存储芯片,芯片的高四位是固定的1010,低四位的高三位代表寻址范围,所以我们可以查找000-111共八个芯片,最后一位表示方向,0代表接下来的是写操作,1代表接下来的是读操作。

三种信号

1.start

/*IIC总线启动信号
*clk为高电平时,data由高电平向低电平跳变,开始传送数据
*/
void start()
{
	dt = 1;	 
	/*先置data线为1 再置clk为1 再跳变*/
	Delay8();
	clk = 1;
	Delay8();
	dt = 0;
	Delay8();
}

其中的dt和clk分别代表数据线和时钟线,分别对应板上的P4^4和P5^0号接口,能传递的只有两种状态,高/低,所以前面说的通过数据线传送字节信息都是串行传送的。start信号的标志就是时钟线为高,数据线由高向低跳变,表示总线启动的信号,所以我们要先将数据线置一,再将时钟线置一,然后再将数据线置零,模拟启动信号需要的两条线上的情况,这里我自己写的时候是没有延时的,然后参考了一下之前的样例,发现这里有延时效果会好一点,给一个信号接收和处理的时间。

2.回复信号

/*IIC总线从机回复信号
*主设备每发送完8bit数据后等待从设备的ACK
*/
void res()
{
	/*数据位为1统计次数*/
	uchar time=0;
	/*上升沿*/
	clk	= 1;
	Delay8();
	/*某时刻dt为低电平表示从机做好准备*/
	while(dt == 1 && (time<256)) 
		time++;
	clk=0;
	Delay8();
	/*回复完毕*/
}

这里要做的事情主要是用于判断是否从机已经做好了准备,读或者写数据,当主机发送完8bit数据之后,会进入一种等待状态,当从机接收完信号,并做好了准备之后,会将数据线置零,所以如果主机发现数据线不再是高电平,或者超过了等待的限度,这个时候就将数据线拉低,后面如果需要其他操作的话再作修改。

3.终止信号

/*IIC总线终止信号
 *clk为高电平时,data由低电平向高电平跳变,结束传送数据
 */
void fin()
{
	dt = 0;	  /*先置data为0 再置clk为1  再跳变到高电平*/
	Delay8();
	clk = 1;
	Delay8();
	dt = 1;
	Delay8();
}

终止信号可以类比开始信号来看,开始信号是时钟线为高的时候,数据线由高到低,而终止信号是时钟线为高的时候,数据线由低到高。要说这几种信号有什么作用呢,由前面的介绍可以知道,IIC协议只使用了这两条线进行信号传输,又是每次只能传输1bit所以需要定义多种bit的组合来实现后续的一些动作,比如我们根据两条线上的bit状态,定义了这三种信号,后面我们就可以根据这三种信号的搭配,实现更复杂的动作

三、动作

这里的动作可以粗略地概括为:向从机写数据,从从机读数据

写数据

/*向从机写一个字节的内容
* 将要传送的内容按bit发送到data线上
*/
void wrtbyte(uchar c)
{
	uint i=0;
	uchar temp=c;
	for(i=0;i<8;i++)
	{
		temp = temp<<1;
		clk = 0;
		Delay8();
		dt = CY;/*PSW寄存器表示溢出值,原最高位的值*/
		Delay8();
		clk = 1;
		Delay8();
	}
	clk = 0;
	Delay8();
	dt = 1;
	Delay8();
}

写数据需要注意的一点是,当时钟线为低的时候,我们才能改变数据线上的值

这里我们将需要写给从机的字节信息按位传送,每次将数据左移一位,这样每次就会溢出一位,溢出的一位在PSW寄存器中的CY位中,所以我们将CY的值赋给数据线,就实现了字节信息按位传送到数据线上,这里需要注意的是传送前将时钟线拉低,传送之后立即将时钟线拉高,防止产生一些错误。之后将时钟线拉低,这里的作用和循环中的拉低是一样的,再将数据线拉高,像前面所讲的,为等待从机回复做准备。

读数据

/*从从机读取一个字节的内容
 *拆解成bit
 */
 uchar rdbyte()
 {
 	uint i=0;
	uchar result;
	clk = 0;
	Delay8();
	dt = 1;
	Delay8();
	/*按位读取出来每一位,由于从从机中弹出的bit是最高位
	 *所以左移之后,再与新来的bit合并*/
	for(i=0;i<8;i++)
	{
		clk = 1;
		Delay8();
		result = (result<<1)|dt;
		Delay8();
		clk = 0;
		Delay8();
	}
	Delay8();
	return result;
 }

前面说过,时钟线为低,才能改变数据线,所以先将时钟线拉低,然后先将数据线拉高,后面存储设备还可以对数据线做改动。

循环中,首先将时钟线拉高,保证数据线上的数不会改变了,然后将原数据与数据线做或运算,由于数据线上只有一位,所以每一次都是将数据线上的bit保存到低位,再将时钟线拉低,为后面的bit读取做准备。

四、更高级的功能

到这里只是实现了两种基本功能,与我们预期的实现非易失存储还有差距,差距在目前可以实现字节的读和取,但这字节的意义并没有明确,所以通过已有的功能,我们来完善一下。

从地址中读数据

/*从从机读取一个字节出来  执行顺序:
 *devsel  byteaddr devsel dataout
 */
 uchar bytefromadd(uchar add)
 {
 	uchar result;
 	start();
	wrtbyte(0xa0); /*1010 0000*/
	res();
	wrtbyte(add);
	res();
	start();//重新start 发送取数据指令
	wrtbyte(0xa1);
	res();
	result = rdbyte();
	fin();
	return result;
 }

注释中已经介绍了读数据的时序问题,首先我们执行一次启动信号,告诉从机 我要对你有一些操作了

然后向从机发送一个信号,这个信号是寻找芯片的,像前面说过的,有八个存储芯片,要根据这一次传输的字节信息找到所需的芯片。然后等待一下从机的回应,等待到了之后再发送一个地址信息,这个地址对应的是片内地址,也就是我们之前说的256个8bit中的某一个再等待,最后向从机发送一个0xa1,最后一位是1,我们前面说过,为1是读操作,这之后我们就可以调用前面写好的从从机读取一个字节的方法,将数据读取出来了

向地址写数据

/*写字节时序   执行顺序:
*devsel  byte addr datain
*向给定的地址写入给定的数据
*/
void byte2add(uchar adr,uchar dat)
{
	/*启动*/
	start();
	wrtbyte(0xa0);
	/*0xa0是24C02地址 1010 0000 
	*前面四位固定 最后一位:0写1读
	*其余三位表示寻址范围,共八个
	*/
	res();//从机确认
	wrtbyte(adr);
	res();//从机确认
	wrtbyte(dat);
	res();//从机确认
	fin();
	return;
}

这个地方和读数据是很相似的,相比来说要简单一点。

同样的,要给从机一个启动的信号,然后输入芯片的寻址信息,查到8个存储芯片中的某一个,然后我们将要写入的片内地址发给从机,从机这时候就应该知道是对这个位置进行修改,然后我们将需要写入的字节信息发给从机,从机就可以把这个数据存到指定芯片的指定地址了。

五、收获和不足

这次实验的收获是了解了非易失存储的一些通信方式,像IIC协议的大致内容

不足在于这个实验先看了之前案例的说明和一些博客上的IIC的介绍,然后出现了很多的bug,比如没有没有做到非易失,按键修改修改不好,这两个问题现在已经解决,还有一个问题是每次保存之后,都不能立即看到存入地址的信息,需要重新刷新之后才能看见的确是存入了,这个问题现在还没有改好,也没什么头绪

六、源码

#include <STC15F2K60S2.h>
#include<intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define SendMax 2
#define NMAX_KEY 10

sbit K1 = P3^2;
sbit K2 = P3^3;
sbit K3 = P1^7;
sbit led = P2^3;
sbit dt = P4^0;
sbit clk = P5^5;

uchar duanxuan[] = 	{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; 
uchar weixuan[] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};

uchar pos;/*位选变量*/
uchar adrs;/*写入的地址*/
uchar wrtdt;/*write data写入的数据*/
uchar rddt;/*read data读出的数据*/
bit flag_1ms;
uint cnt;/*timer0 interrupt times*/
int sendcnt=0;
int sendflag = 0;//0  空闲  1   busy
uint k1cnt;
uint k2cnt;
uint k3cnt;
uint kcnt;
bit k1p;
bit k1c;
bit k2p;
bit k2c;
bit k3p;
bit k3c;

void Delay8()		//@11.0592MHz
{
//	uchar i;
//	i = 19;
//	while (--i);
;;
}

void UartInit() 
{						
	SCON = 0x50;		
	AUXR |= 0x40;		
	AUXR &= 0xFE;		
	TMOD &= 0x0F;		
	TL1 = 0x00;		
	TH1 = 0xF7;		
	ET1 = 0;		
	TR1 = 1;		
	ES = 1;
	EA = 1;
}

void timer0_init()
{
	TMOD = 0x01;
	AUXR = 0x80;
	EA = 1;
	ET0 = 1;
	TH0 = (65535-1000)/256;
	TL0 = (65535-1000)%256;
	TR0 = 1;
}

void IIC_init()
{
	dt=1;
	//Delay8();
	clk = 1;
	//Delay8();
}

void key_init()
{
	cnt = 0;
	flag_1ms = 0;
	k1cnt = 0x80 + NMAX_KEY/3*2;
	k2cnt = 0x80 + NMAX_KEY/3*2;
	k3cnt = 0x80 + NMAX_KEY/3*2;
	kcnt = 	NMAX_KEY;
	k1p = 1;
	k1c = 1;
	k2p = 1;
	k2c = 1;
	k3p = 1;
	k3c = 1;
}

void Init()
{
	P2M0 = 0x08;
	P2M1 = 0x00;
	P0M0 = 0xff;
	P0M1 = 0x00;
	led = 0;
	P0 = 0x00;
	wrtdt = 0x00;
	adrs = 0x00;
	rddt = 0x00;
	timer0_init();
	IIC_init();
	key_init();
	UartInit();
}
/*动态扫描  
*显示地址 写入数据 当前数据
*/
void display()
{
	pos++;
	P0 = 0x00; //不加这一句会有残影
	if(pos==8)
		pos=0;
	P2 = weixuan[pos];
	if(adrs >= 0xff)
		adrs = 0x00;
	if(wrtdt>= 0xff)
		wrtdt = 0x00;
	if(rddt>= 0xff)
		rddt = 0x00;
	switch(pos)
	{
		case 0x00:P0 = duanxuan[adrs/16];break;
		case 0x01:P0 = duanxuan[adrs%16];break;
		case 0x03:P0 = duanxuan[wrtdt/16];break;
		case 0x04:P0 = duanxuan[wrtdt%16];break;
		case 0x06:P0 = duanxuan[rddt/16];break;
		case 0x07:P0 = duanxuan[rddt%16];break;
		default:P0 = 0x40;break;
	}
}


/*IIC总线启动信号
*clk为高电平时,data由高电平向低电平跳变,开始传送数据
*/
void start()
{
	dt = 1;	 
	/*先置data线为1 再置clk为1 再跳变*/
	Delay8();
	clk = 1;
	Delay8();
	dt = 0;
	Delay8();
}
/*IIC总线从机回复信号
*主设备每发送完8bit数据后等待从设备的ACK
*/
void res()
{
	/*数据位为1统计次数*/
	uchar time=0;
	/*上升沿*/
	clk	= 1;
	Delay8();
	/*某时刻dt为低电平表示从机做好准备*/
	while(dt == 1 && (time<256)) 
		time++;
	clk=0;
	Delay8();
	/*回复完毕*/
}
 /*IIC总线终止信号
 *clk为高电平时,data由低电平向高电平跳变,结束传送数据
 */
void fin()
{
	dt = 0;	  /*先置data为0 再置clk为1  再跳变到高电平*/
	Delay8();
	clk = 1;
	Delay8();
	dt = 1;
	Delay8();
}
/*向从机写一个字节的内容
* 将要传送的内容按bit发送到data线上
*/
void wrtbyte(uchar c)
{
	uint i=0;
	uchar temp=c;
	for(i=0;i<8;i++)
	{
		temp = temp<<1;
		clk = 0;
		Delay8();
		dt = CY;/*PSW寄存器表示溢出值,原最高位的值*/
		Delay8();
		clk = 1;
		Delay8();
	}
	clk = 0;
	Delay8();
	dt = 1;
	Delay8();
}
 /*从从机读取一个字节的内容
 *拆解成bit
 */
 uchar rdbyte()
 {
 	uint i=0;
	uchar result;
	clk = 0;
	Delay8();
	dt = 1;
	Delay8();
	/*按位读取出来每一位,由于从从机中弹出的bit是最高位
	 *所以左移之后,再与新来的bit合并*/
	for(i=0;i<8;i++)
	{
		clk = 1;
		Delay8();
		result = (result<<1)|dt;
		Delay8();
		clk = 0;
		Delay8();
	}
	Delay8();
	return result;
 }
/*写字节时序   执行顺序:
*devsel  byte addr datain
*向给定的地址写入给定的数据
*/
void byte2add(uchar adr,uchar dat)
{
	/*启动*/
	start();
	wrtbyte(0xa0);
	/*0xa0是24C02地址 1010 0000 
	*前面四位固定 最后一位:0写1读
	*其余三位表示寻址范围,共八个
	*/
	res();//从机确认
	wrtbyte(adr);
	res();//从机确认
	wrtbyte(dat);
	res();//从机确认
	fin();
	return;
}
 /*从从机读取一个字节出来  执行顺序:
 *devsel  byteaddr devsel dataout
 */
 uchar bytefromadd(uchar add)
 {
 	uchar result;
 	start();
	wrtbyte(0xa0); /*1010 0000*/
	res();
	wrtbyte(add);
	res();
	start();//重新start 发送取数据指令
	wrtbyte(0xa1);
	res();
	result = rdbyte();
	fin();
	return result;
 }

 void timer0() interrupt 1
{
	cnt++;
	//rddt = bytefromadd(adrs);
	
	TH0 = (65535-1000)/256;							      
	TL0 = (65535-1000)%256;
	if(cnt==10)
	{
		cnt=0;
		//rddt = bytefromadd(adrs);	  // 加入这一句之后按键无法工作
		flag_1ms = 1;
	}
	display();
}
/*80ms*/
//void Delay()		//@11.0592MHz
//{
//	unsigned char i, j, k;
//
//	_nop_();
//	_nop_();
//	i = 4;
//	j = 93;
//	k = 155;
//	do
//	{
//		do
//		{
//			while (--k);
//		} while (--j);
//	} while (--i);
//}

void Uartisr() interrupt 4 		   //串口中断函数   
{	
	if (TI) 
	{ 
		  TI = 0;                      
		  if (sendcnt >= SendMax)      
			  sendflag = 0;
		  else 		//发送当前地址 和 内容
			{ 
				 if(sendcnt == 0)
				 	SBUF = adrs;
				 else if(sendcnt == 1)
				 	SBUF = rddt;
				 sendcnt++;		  
			}					 
	}       
	else RI=0;						  
}

int main()
{
	Init();
	while(1)
	{
		while(flag_1ms)
		{
			 rddt = bytefromadd(adrs);
			 flag_1ms = 0;
			 if(K1 == 0)
			 	k1cnt--;
			 if(K2==0)
			 	k2cnt--;
			 if(K3==0)
			 	k3cnt--;
			 kcnt--;
			 if(kcnt <= 0x00)
			 {
			 	if(k3cnt < 0x80)//k3按下
				{
					k3c = 0;
					if(k3p == 1)//下降沿
					{
						k3p = 0;
						adrs++;
						if(adrs == 0xff)
						{
							adrs = 0x00;
						}
					}
				}
				if(k2cnt < 0x80) //k2按下
				{
					k2c = 0;
					if(k2p == 1)
					{
						k2p = 0;
						wrtdt++;
						if(wrtdt == 0xff)
							wrtdt = 0x00;
					}
				}
				if( k1cnt <0x80)//k 1按下
				{
					k1c = 0;
					if(k1p == 1)
					{
						k1p = 0;
						byte2add(adrs,wrtdt);
						rddt = bytefromadd(adrs);
					}
				}
				if(k3cnt >= 0x80)
				{			   
					k3c = 1;
					if(k3p == 0)
					{
						k3p = 1;
					}
				}
				if(k2cnt >= 0x80)
				{			   
					k2c = 1;
					if(k2p == 0)
					{
						k2p = 1;
					}
				}
				if(k1cnt >= 0x80)
				{			   
					k1c = 1;
					if(k1p == 0)
					{
						k1p = 1;
					}
				}
				/*错误记录,之前将计数重置放在了外面,导致按键功能无法执行*/
				 k1cnt = 0x80 + NMAX_KEY/3*2;
				 k2cnt = 0x80 + NMAX_KEY/3*2;
				 k3cnt = 0x80 + NMAX_KEY/3*2;
				 kcnt = NMAX_KEY;
			 }
			 
		}

	}
}
发布了63 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/LieberVater/article/details/89816797