I2C+E2PROM再回顾ing~

第二更~~~~


今天是准备学习A/D,D/A转换的,然后看了一下代码发现用到的都是I2C的底层,那就先回顾一下I2C叭。


1. I2C时序

首先明确I2C总线是由时钟总线SCL数据总线SDA构成的,另外需要注意I2C总线的“线与“”关系,也就是说只要任何一个器件输出低电平,那么总线就会被占用(所以说为了防止总线被占用,我们一定要记得拉高电平)。下面我们来看I2C的时序图:
I2C+E2PROM再回顾ing~_第1张图片
注意!!!这里要特别注意最后的应答位 ACK ,它的作用是什么呢?
可以把它理解为I2C的一个应答信号,如果ack = 1,表示I2C并没有收到数据,即主机写入不成功,我们一般用NAK来表示,相反,如果ack = 0,则表示写入成功,用ACK表示。

我们现在来分析时序图:
(要注意一点:在SCL为低电平期间才能入数据,在SCL为高电平期间才能取数据)
从时序图上我们可以清楚地看到I2C通信流程中有开始部分数据传输部分,还有结束部分,那么相对应的我们的底层就应该分成这三个部分来写。

开始部分:分析时序图,I2C通信的开始信号是在SCL以及SDA都是高电平期间,SDA产生一个由高到低的变化,代码如下:

void I2CStart()
{
	I2C_SCL = 1;
	I2C_SDA = 1;
	I2CDelay();
	I2C_SDA = 0;
	I2CDelay();
	I2C_SCL = 0;//拉低SCL,方便下次写入数据
	I2CDelay();
}

对了!I2C里还用到了延时函数(5us延时),因为我们规定I2C通信中SCL以及SDA的高低电平持续时间不得短于5us,这个5us的延时函数是可以用STC的软件生成的。

#define I2CDelay() Delay5us()

void Delay5us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 11;
	while (--i);
}

数据传输部分: 这里的数据写入的方法与我之前写的DS1302(从低位到高位传输)是不是有相似之处呢,需要注意的是I2C通信是 从高位到低位 传输的,代码的解释部分可以看一下我写的注释

bit I2CWrite(u8 dat)//注意这里函数有返回值,故不能定义为void形式
{
	bit ack;
	u8 mask;
	
	for(mask = 0x80; mask != 0x00; mask >>= 1)
	{
		if((mask & dat) == 0)//运算符优先级的问题!!!!!
			I2C_SDA = 0;
		else
			I2C_SDA = 1;
		I2CDelay();
		I2C_SCL = 1;//拉高SCL
		I2CDelay();
		I2C_SCL = 0;//再拉低SCL,完成一个位的操作
		I2CDelay();
	}
	I2C_SDA = 1;//拉高SDA,便于主机读取应答位ack的值,注意是主机读!!!
	I2CDelay();
	I2C_SCL = 1;//拉高SCL,主机读取ack
	ack = I2C_SDA;
	I2CDelay();
	I2C_SCL= 0;//拉低SCL,方便下次写入
	I2CDelay();
	
	return(~ack);//这里的返回值是~ack,是为了符合正常的逻辑,即ack=1,表示写入成功,ack=0,表示写入失败
}

注意一点!!! 主机读取应答位时,一定要把SDA拉高,即释放总线,如果总线被占用,主机就无法从从机中读取数据!!!!

有了写操作(这个写,是主机也就是单片机向从机也就是I2C写入数据),自然也有读操作(这个读,是主机读,主机从从机里读数据),主机读取完信息后也会给从机发一个应答信号,这个应答信号是为了告诉从机,“我”(主机)还要不要再读取信息了,与ack一样,代码如下:

u8 I2CReadACK()//连续读
{
	u8 mask;
	u8 dat;
	
	I2C_SDA = 1;//只有在sda为高电平时主机才能进行读操作
	I2CDelay();//这里不要忘记加延时函数
	
	for(mask = 0x80; mask != 0x00; mask >>= 1)
	{
		I2C_SCL = 1;//scl在高电平期间才能读
		I2CDelay();
		
		if(I2C_SDA == 0)
			dat &= ~mask;
		else
			dat |= mask;
	    I2CDelay();
		I2C_SCL = 0;//拉低scl,使从机发送出下一位
		I2CDelay();
	}
	I2C_SDA = 0; //ack=0,表示还要继续读数据                                                                              
	I2CDelay();
	I2C_SCL = 1;//拉高SCL
	I2CDelay();
	I2C_SCL = 0;//再拉低SCL,完成一个位的操作
	I2CDelay();
	
	return dat;
}

u8 I2CReadNAK()//只读一次
{
	u8 mask;
	u8 dat;
	
	I2C_SDA = 1;
	I2CDelay();//这里不要忘记加延时函数
	
	for(mask = 0x80; mask != 0x00; mask >>= 1)
	{
		I2C_SCL = 1;//scl在高电平期间才能读
		I2CDelay();
		
		if(I2C_SDA == 0)
			dat &= ~mask;
		else
			dat |= mask;
	    I2CDelay();
		I2C_SCL = 0;
		I2CDelay();
	}
	I2C_SDA = 1;//ack=1,表示不要读了
	I2CDelay();
	I2C_SCL = 1;
	I2CDelay();
	I2C_SCL = 0;
	I2CDelay();
	
	return dat;
}

结束部分: 分析时序图,I2C通信的结束信号是在SCL以及SDA都是低电平期间,先把SCL拉高,然后SDA产生一个由低到高的变化,代码如下:

void I2CStop()
{
	I2C_SCL = 0;
	I2C_SDA = 0;
	I2CDelay();
	I2C_SCL = 1;
	I2CDelay();
	I2C_SDA = 1;
	I2CDelay();
//	I2C_SCL = 0;//停止后scl不必再置0
}

2. E2PROM

既然提到了I2C怎么能不提到E2PROM呢,这里我们直接从E2PROM的多字节读写开始说吧,这里的写我们也直接开始讲解页写入,多字节的理解了单字节的还不好理解嘛,哈哈~~
直接上代码吧:

/*多字节读,*buf用来存储读到的数据,addr为E2起始地址,len字节长度*/
void E2Read(u8 *buf, u8 addr, u8 len)//多字节读
{
	do{
		I2CStart();//开启I2C
		if(I2CWrite(0x50 << 1))//寻址器件,后续写,I2Cwrite返回值为~ack故若为1,则表示应答,跳出循环,执行read操作
		{
			break;
		}	
		I2CStop();
	}while(1);
	I2CWrite(addr);//写入起始地址
	I2CStart();//重复启动,因为接下来要执行读操作了
	I2CWrite((0x50 << 1) | 0x01);//写入寻址器件,后续读
	
	while(len > 1)//注意这里是len>1,也就是说读完倒数第二个字节的数据后就跳出这个循环了,开始执行下面的程序
	{
		*buf++ = I2CReadACK();//最后字节之前为连续读取
		len--;
	}
	*buf = I2CReadNAK();//最后一个字节读
	I2CStop();	
}

void E2Write(u8 *buf, u8 addr, u8 len)//页写入
{
	while(len > 0)//页循环,循环的是页数
	{
		do{
			I2CStart();
			if(I2CWrite(0x50 << 1))//判断E2prom是否处于响应状态,若ack = 0,表示处于响应状态
			{
				break;
			}
			I2CStop();
		}while(1);
		I2CWrite(addr);
		while(len > 0)//循环写入1页数据
		{
			I2CWrite(*buf++); //buf为源数据指针
			addr++;
			len--;
			if((addr & 0x70) == 0)//判断地址是否达到页边界,24C02每页8字节
			{
				break;//如果达到一页的边界,就跳出该页写数据的循环,然后写下一页的内容
			}
		}
		I2CStop();
	}
}

做几点解释:

  1. 关于do,while 这个循环:作用是什么呢?
    判断E2PROM是否处于响应状态,因为我们这是多字节读写操作,而E2PROM在每次读取或者写入一个字节的数据后,都需要一定的时间把数据搬移到“非易失区域”,在这个过程中,E2PROM是不作出响应的,所以我们在写入下一个字节的数据之前,一定要判断一下E2PROM是否处于响应状态。
  2. 判断E2PROM是否响应的原理是什么呢?
    寻址器件,也就是if条件中的代码,(注意if条件句中非0才为真,也就是说为1才能进入if循环)这里涉及到了I2C寻址模式,我们只要记住24C02(E2PROM的器件型号)的7位地址位为:0b1010000(也就是0x50),而第8位是读写标志位,0表示接下来是写操作1表示,那么如果要实现寻址24C02并且表明后续操作为写,我们应该向I2C内部写入什么呢?我们只需要让第8位为0就好了呀,那么就让0x50左移1位,不就可以实现低位补0了嘛,明白了这些我们再来看这段代码:
do{
		I2CStart();//开启I2C
		if(I2CWrite(0x50 << 1))//寻址器件,后续写,I2Cwrite返回值为~ack故若为1,则表示应答,跳出循环,执行read操作
		{
			break;
		}	
		I2CStop();
	}while(1);

明白是怎么判断了的吗?其实就是向I2C中写入E2PROM的地址,如果存在这个器件或者E2PROM处于响应状态,那么应答位(即ack)就是0,那么I2CWrite这个函数的返回值就是1,if条件为真,执行break语句,跳出do,while循环,执行读写操作。下面再附上24C02的原理图,方便大家理解:
I2C+E2PROM再回顾ing~_第2张图片
解释一下为什么24C02的7位地址是0b1010000呢?24C02的高四位地址是固定的(0b1010),低三位的地址是由A0,A1,A2,这三个引脚决定的,从原理图上我们可以看出,这三个引脚都是接地的,所以低三位000,合在一起就是0b1010000啦~
好了~~~这些就是我对于I2C的理解了,晚上就开始学习A/D,D/A啦~

你可能感兴趣的:(蓝桥备赛)