课程设计基于I2C协议的EEPROM读写功能实现.docx
《课程设计基于I2C协议的EEPROM读写功能实现.docx》由会员分享,可在线阅读,更多相关《课程设计基于I2C协议的EEPROM读写功能实现.docx(22页珍藏版)》请在冰豆网上搜索。
课程设计基于I2C协议的EEPROM读写功能实现
基于I2C协议的EEPROM读写功能实现
一.设计要求
1)通过软件基于I2C协议对EEPROM读写功能的实现。
2)需要有EEPROM读写功能的具体实现过程。
二.设计目的
通过设计,了解I2C协议的基本原理,并对EEPROM读写功能的实现有个系统的概念,对其实现过程比较清楚。
同时,在设计中,巩固我们所学的理论知识。
三.器件EEPROM的介绍
1.EEPROM简介
EEPROM(ElectricallyErasableProgrammableRead-OnlyMemory),电可擦可编程只读存储器--一种掉电后数据不丢失的存储芯片。
EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。
EEPROM即电可擦写可编程只读存储器,其可通过高于普通电压的作用来擦除和重编程(重写)。
不像EPROM芯片,EEPROM不需从计算机中取出即可修改。
在一个EEPROM中,当计算机在使用的时候是可频繁地重编程的,EEPROM的寿命是一个很重要的设计考虑参数。
EEPROM的一种特殊形式是闪存,其应用通常是个人电脑中的电压来擦写和重编程。
DRAM断电后存在其中的数据会丢失,而EEPROM断电后存在其中的数据不会丢失。
另外,EEPROM可以清除存储数据和再编程。
2.EEPROM24XX系列功能概述
1)每一个24XX器件都支持双向。
2)线数据传输协议:
如果器件被定义为发送器,则该器件发送数据到总线;如果器件被定义为接收器,则该器件接收来自总线的数据。
总线由主器件控制,24XX作为从器件。
主器件提供串行时钟(SCL),控制总线访问和产生起始和停止条件。
主器件和从器件皆可作为发送器或接收器,但必须由主器件决定采取何种工作模式。
四.I2C协议的介绍
1.I2C协议总线特征
总线协议定义如下:
•只有在总线空闲时才可启动数据传输。
•数据传输期间,在时钟线为高电平时,无论何时,数据线都必须保持稳定。
在时钟线为高电平时改变数据线将视为起始或停止条件。
I2C总线最主要的优点是其简单性和有效性。
由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。
总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
I2C总线的另一个优点是,它支持多主控,其中任何能够进行发送和接收的设备都可以成为主总线。
一个主控能够控制信号的传输和时钟频率。
当然,在任何时间点上只能有一个主控。
2.I2C协议工作原理
以启动信号START来掌管总线,以停止信号STOP来释放总线;每次通讯以START开始,以STOP结束;启动信号START后紧接着发送一个地址字节,其中7位为被控器件的地址码,一位为读/写控制位R/W,R./W位为0表示由主控向被控器件写数据,R/W为1表示由主控向被控器件读数据;当被控器件检测到收到的地址与自己的地址相同时,在第9个时钟期间反馈应答信号;每个数据字节在传送时都是高位(MSB)在前。
3.I2C协议总线基本状态
1)总线空闲(A)
数据线和时钟线同时为高电平。
2)启动数据传输(B)
时钟(SCL)为高电平时,SDA从高电平变为低电平表示起始条件产生。
起始条件必须先于所有的命令产生。
3)停止数据传输(C)
时钟(SCL)为高电平时,SDA从低电平变为高电平表示停止条件产生。
所有操作都必须以停止条件结束。
4)数据传送/数据有效(D)
数据线的状态表明数据何时有效。
在起始条件之后,数据线在时钟处于高电平期间保持稳定。
必须在时钟信号为低电平期间改变数据线。
一个数据位对应一个时钟脉冲。
数据的每次传输以起始条件开始,以停止条件结束。
在起始条件和停止条件之间传输的数据字节数目由主器件决定
5)确认信号(ACK)
每一个被寻址的接收器在接收到每一字节数据后,应发送一个确认位。
主器件必须提供一个额外的时钟以传输确认位。
在确认时钟脉冲内,器件确认须拉低SDA线。
在确认时钟的高电平期间,SDA线以这种方式保持稳定的低电平。
当然,还必须考虑建立时间和保持时间。
读操作期间,主器件必须发送一个结束信号给从器件,而不是在从器件输出最后一个数据字节之后产生一个确认位。
这种情况下,从器件(24XX)将释放数据线为高电平,从而使主器件能够产生停止条件。
6)无应答信号(NACK)
在时钟的第9个脉冲期间发送器释放数据总线,接收器不拉低数据总线表示一个NACK,NACK有两种用途:
a、一般表示接收器未成功接收数据字节;b、当接收器是主控器时,它收到最后一个字节后,应发送一个NACK信号,以通知被控发送器结束数据发送,并释放总线,以便主控接收器发送一个停止信号STOP。
4.寻址约定
1)不带功能性地址输入引脚的器件寻址
在起始条件之后,从主器件接收的第一个字节是控制字节。
控制字节以4位控制码开始。
对于24XX器件,这4位设置为‘1010’以便进行读/写操作。
随后的3位为存储块选择位(B2、B1、B0)。
主器件用它们来选择将要访问的大小为256字的存储块。
实际上,这些位是字地址中3个最高有效位。
控制字节的最后一位定义将要进行的操作。
设置为‘1’,选择读操作;设置为‘0’,选择写操作。
在起始条件发生后,24XX器件始终监视SDA总线。
一旦接收到‘1010’码、存储块选择位和R/W位,从器件输出确认信号到SDA总线。
在确认信号之后传输地址字节。
2)带功能性地址输入引脚的器件寻址
在起始条件之后,从主器件接收的第一个字节是控制字节。
控制字节以4位控制码开始。
在24XX
器件,这4位设置为‘1010’以便进行读/写操作。
随后的3位为片选位(A2、A1、A0)。
片选位的不同组合允许在同一条总线上使用的24XX器件达八个,并用于选择访问哪一个器件。
控制寄存器中的片选位必须与相应器件引脚A2、A1和A0上的逻辑电平保持一致。
实际上这些位是字地址中3个最高有效位。
起始条件发生之后,24XX器件始终监视SDA总线。
一旦接收到‘1010’码、器件片选位和R/W位,从器件输出确认信号到SDA总线。
在确认信号之后传输地址字节。
五.EEPROM读写功能实现
1.写操作
写数据过程:
1)主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2)发送一个控制字节(包括7位器件地址码和一位R/W);(某些器件的地址字节可能带有其他功能)
3)当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
4)主控收到ACK后开始发送第一个数据字节;
5)被控器收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束;
6)主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;写数据时序。
(1)字节写操作
字节写操作以来自于主器件的起始位开始,4位控制码紧随其后(图3)。
接下来的3位是存储块寻址位(不带地址输入引脚的器件)或片选位(带地址输入引脚的器件)。
然后主发送器将R/W位(该位为逻辑低电平)发送到总线。
从器件在第九个时钟周期产生一个确认位,主器件发送的第二个字节是地址字节或高位地址字节。
24XX器件会对每一个地址字节作出确认,并把地址位锁存进器件内部的地址计数器。
对于24XX00器件,只使用地址字节的低4位。
高4位可为任意值。
送出最后一个地址字节后,24XX器件发出确认信号ACK。
主器件在接收到该确认信号后即发送数据字,该数据字将被写入已寻址的存储器位置。
24XX器件再次发出确认信号,之后主器件产生停止条件,启动内部写周期。
如果在WP引脚保持高电平时进行存储器写操作,器件会确认命令,但不会启动写周期,也不会写入数据,而会立即接受新的命令。
写命令为一个字节,在发送写命令后,内部地址计数器增加,指向下一个要寻址的位置。
写周期期间,24XX不会对命令进行确认。
图3字节写操作
(注:
图中控制字节指器件地址码和读写控制位;地址字节指器件内部的储存器地址。
下同。
)
(2)页写入操作
页写入操作时,被控器件完成一个写字节操作后内部地址计数器自动加一。
写控制字节、字地址字节和首个数据字节以和写操作字节基本相同的方式发送给24XX器件(见图4)。
不同的是,主器件发送的是多至一整页的数据字节
(1),而不是停止条件,这些数据字节临时存储在片内页缓冲器中。
在主器件发送停止条件之后,这些数据将被写入存储器。
每接收一个字,内部地址计数器加一。
如果在停止条件产生前,主器件有超出一页的数据要发送,地址计数器将会翻转,先前写入的数据将被覆盖。
对于字节写操作,一旦接收到停止条件,内部写周期开始。
在写周期期间,24XX器件不会对命令作出确认。
页写入操作在一页内可以写入任意个数的数据(最多为一页),并且可以在此页中的任意地址开始写入。
被寻址的数据只能在该页内变动。
如果在WP引脚保持高电平时进行存储器写操作,器件会确认命令,但不会启动写周期,也不会写入数据,而是立即接受新的命令。
图4页写入操作
2.确认查询
在写周期期间器件不会对命令作出确认,这可用来确定写周期何时完成(这个特点可以优化总线的吞吐量,使其最大化)。
如果主器件已经发出写命令的停止条件,器件将启动内部定时写周期。
可以随时进行确认查询。
这包括在主器件发出起始条件后,再发送用于写命令(R/W=0)的控制字节。
如果器件仍处在写周期内,则不返回确认信号。
一旦没有返回确认信号,起始位和控制字节必须重新发送。
如果写周期结束,器件返回确认信号,主器件就可以执行下一个读或写命令。
参见流程图(图9)。
3.读操作
读数据过程:
1)主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2)发送一个控制字节(包括7位器件地址码和一位R/W);(某些器件的地址字节可能带有其他功能)
3)当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
4)主控收到ACK后释放数据总线,开始接收第一个数据字节;
5)主控收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;
6)主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;
除了控制寄存器的R/W位设置为‘1’外,读操作与写操作基本相同。
有三种基本的读操作:
当前地址的读操作、随机读操作和连续读操作。
(1)当前地址的读操作
当前地址的读操作,被控器件内部储存器地址计数器保留最后一次访问的地址。
24XX内置一个自动加‘1’地址计数器,该计数器保留最后一次访问的地址。
因此,如果先前对地址‘n’(n为任意合法地址)进行读或写操作,则下一条读操作命令将可能从地址n+1访问数据。
接收到R/W位设置为‘1’的控制字节后,24XX发出确认信号,并发送8位数据字节。
主器件不会对数据传输作出确认,但会产生停止条件,24XX即停止数据发送(图5)
图5当前地址读操作
(2)随机读操作
随机读操作允许主器件以随机方式访问任意存储器,主控器件先发写命令,然后发要读的地址,然后发读命令。
执行该指令前必须先设置地址字节。
作为写操作的一部分,通过发送字节地址给24XX来完成地址字节的设置(R/W设置为‘0’)。
字节地址发送完后,主器件一接收到确认信号即产生起始条件。
内部地址计数器设置完之后写操作即被终止。
主器件再次发送控制字节,而该字节中R/W位设置为‘1’。
之后24XX会发出确认信号,并发送8位数据字节。
主器件不会对数据传输作出确认,但会产生停止条件,24XX即停止数据发送(图6)。
在随机读取命令之后,内部地址计数器加1指向下一条地址。
图6随机读操作
(3)连续读操作
连续读操作的起动过程和随机读操作相同,只是在24XX发送完第一个数据字节后,主器件发出确认信号,而在随机读操作中发送的是停止条件,在连续读操作时被控器件完成一个读字节操作后内部地址计数器自动加一。
确认信号指示24XX器件发送下一个连续地址的数据字节(图7)。
在24器件向主器件发送完最后一个字节后,主器件不会产生确认信号,而是产生停止条件。
为了可以进行连续读操作,24XX器件内置了一个地址指针,在每次操作完成后该指针加1。
地址指针允许一次操作连续读取整个存储器的内容。
在达到最后一个地址字节后,地址指针将翻转到地址0x00。
图7连续读操作
六.具体设计过程
1.程序流程图
图9确认查询流程图
2.系统组成模块结构及功能
(1)函数定义
函数定义是对各个基础函数的定义,并且设置需要运用的信息,便于调用
#include
#include
#include
#defineDELAY_TIME60/*延时时间*/
#defineTRUE1/*设置ACK*/
#defineFALSE0/*定义0为错误*/
sbitSCL=P1^7;
sbitSDA=P1^6;/*假设由P1.7和P1.6控制*/
voidDELAY(unsignedintt)/*延时函数*/
voidI2C_Start(void)/*启动I2C总线*/
voidI2C_Stop(void)/*终止I2C总线*/
voidSEND_0(void)/*设置时钟,发送0,在SCL为高电平时使SDA信号为低*/
voidSEND_1(void)/*设置时钟,发送1,在SCL为高电平时使SDA信号为高*/
bitCheck_Acknowledge(void)/*发送完一个字节后检验设备的应答信号*/
voidWriteI2CByte(charb)reentrant/*向I2C总线写一个字节*/
charReadI2CByte(void)reentrant/*从I2C总线读一个字节*/
voidWrite_One_Byte(charaddr,charthedata)/*字节写入函数*/
voidWrite_A_Page(char*buffer,charaddr)/*页写入函数*/
charRead_One_Byte(charaddr)/*随机读函数*/
voidRead_N_Bytes(char*buffer,charn,charaddr)/*顺序读函数*/
(2)主函数main()
主函数是程序的主流程,首先定义使用到的常数、全局变量及函数原型说明,然后初始化图形系统,调用函数Write_One_Byte(charaddr,charthedata)完成字节写入,调用Write_A_Page(char*buffer,charaddr)完成页写入,调用Read_One_Byte(charaddr)完成随机读,调用Read_N_Bytes(char*buffer,charn,charaddr)/*完成顺序读*/。
voidmain()
{ inti;
charmybyte;
charmyarray[8];
charmyarray2[8];
charrdarray[16];
for(i=0;i<8;i++)
{
myarray[i]=i;
myarray2[i]=i+0x08;
}
Write_One_Byte(0x20,0x28);
Write_A_Page(myarray,0x10);
Write_A_Page(myarray2,0x18);
mybyte=Read_One_Byte(0x20);
Read_N_Bytes(rdarray,16,0x10);
}
3.源程序
/****************************************************************/
#include
#include
#include
#defineDELAY_TIME60/*经实验,不要小于50!
否则可能造成时序混乱*/
#defineTRUE1
#defineFALSE0
sbitSCL=P1^7;/*假设由P1.7和P1.6控制*/
sbitSDA=P1^6;
/**********FunctionDefinition 函数定义************/
voidDELAY(unsignedintt)/*延时函数*/
{while(t!
=0)
t--; }
voidI2C_Start(void)
{/*启动I2C总线的函数,当SCL为高电平时使SDA产生一个负跳变*/
SDA=1;
SCL=1;
DELAY(DELAY_TIME);
SDA=0;
DELAY(DELAY_TIME);
SCL=0;
DELAY(DELAY_TIME);
}
voidI2C_Stop(void)
{/*终止I2C总线,当SCL为高电平时使SDA产生一个正跳变*/
SDA=0;
SCL=1;
DELAY(DELAY_TIME);
SDA=1;
DELAY(DELAY_TIME);
SCL=0;
DELAY(DELAY_TIME);}
voidSEND_0(void) /*设置时钟*/
{ /*发送0,在SCL为高电平时使SDA信号为低*/
SDA=0;
SCL=1;
DELAY(DELAY_TIME);
SCL=0;
DELAY(DELAY_TIME);
}
voidSEND_1(void)
{/*发送1,在SCL为高电平时使SDA信号为高*/
SDA=1;
SCL=1;
DELAY(DELAY_TIME);
SCL=0;
DELAY(DELAY_TIME);
}
bitCheck_Acknowledge(void)
{/*发送完一个字节后检验设备的应答信号*/
SDA=1;
SCL=1;
DELAY(DELAY_TIME/2);
F0=SDA;
DELAY(DELAY_TIME/2);
SCL=0;
DELAY(DELAY_TIME);
if(F0==1)
returnFALSE;
returnTRUE;
}
voidWriteI2CByte(charb)reentrant
{/*向I2C总线写一个字节*/
chari;
for(i=0;i<8;i++)
if((b<
SEND_1();
Else
SEND_0();
}
charReadI2CByte(void)reentrant
{/*从I2C总线读一个字节*/
charb=0,i;
for(i=0;i<8;i++)
{ SDA=1; /*释放总线*/
SCL=1; /*接受数据*/
DELAY(10);
F0=SDA;
DELAY(10);
SCL=0;
if(F0==1)
{b=b<<1;
b=b|0x01;}
Else
b=b<<1;}
returnb;}
/**********以下为读写函数**********/
voidWrite_One_Byte(charaddr,charthedata)
{ bitacktemp=1;
/*writeabytetomem*/
I2C_Start();
WriteI2CByte(0xa0);
acktemp=Check_Acknowledge();
WriteI2CByte(addr);/*address*/
acktemp=Check_Acknowledge();
WriteI2CByte(thedata);/*thedata*/
acktemp=Check_Acknowledge();
I2C_Stop();
}
voidWrite_A_Page(char*buffer,charaddr)
{ bitacktemp=1;
bitwrtmp;
inti;
/*writeapagetoat24c02*/
I2C_Start();
WriteI2CByte(0xa0);
acktemp=Check_Acknowledge();
WriteI2CByte(addr);/*address*/
acktemp=Check_Acknowledge();
for(i=0;i<7;i++)
{ WriteI2CByte(buffer[i]);
if(!
Check_Acknowledge())
{I2C_Stop();
}
}
I2C_Stop();
}
charRead_One_Byte(charaddr)
{ bitacktemp=1;
charmydata;
/*readabytefrommem*/
I2C_S