1. 24C16 E2PROM简介 24C16是一个16K位串行CMOS的E2PROM,内部含有2048个8位字节,它有一个16字节的页写缓冲器,该器件通过I2C总线接口进行操作,有一个专门的写保护功能引脚。在本实验系统中,24C16的接口电路如图4-4所示。 file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.gif 图4-4 24C16的接口电路 从图4-4可看出,24C16的接口电路非常简单,只引出了2根控制线SDA和SCL到J6。各引脚功能简单描述如下: SCL:串行时钟。24C16串行时钟输入管脚,用于产生器件数据收发的时钟; SDA:串行数据/地址。24C16双向串行数据/地址管脚用于器件所有的收据收发。SDA是一个开漏输出管脚,可与其它开漏输出或集电极开路输出进行线或(wire-OR); A0、A1、A2:器件地址输入。这些输入脚用于多个器件挂接在同一对I2C总线上时设置器件地址,以便主控器件识别。当这些引脚悬空时其默认值为0。 WP:写保护。当WP脚连接到VCC时,所有内存变成写保护(只能读)。当WP引脚连接到VSS或悬空时,允许器件进行读/写操作。 2. I2C总线接口的特性 I2C接口的信息传输仅需要SDA和SCL两条线,均为双向I/O口,通过上拉电阻接正电源。当总线空闲时,两根线都是高电平。接入总线器件的输出必须是集电极或漏极开路方式的,即具有线“与”功能。 I2C总线是一个半双工、多主器件的总线,即总线上可以连接多个可控制总线的器件。总线上发送数据的发送器(主器件)与接收数据的接收器(从器件)的角色不是一成不变的,而是取决于当时数据传送的方向。当一个器件开始一个总线周期,寻址其它器件并发送数据时,它就是发送器,其他被寻址器件均作为接收器存在。 I2C总线进行数据传送时,每一位数据都与时钟脉冲相对应,在时钟信号高电平期间,数据线上必须保持稳定的逻辑电平。只有在时钟线为低电平时,才允许数据线的电平发生变化。 3. I2C总线的时序 一次完整的I2C总线时序过程由起始信号、从器件地址信号、应答信号ACK、字节数据信号和停止信号等几部分组成。 (1) 起始和停止信号 在一次通信过程中,应该有一个起始信号和一个停止信号。在I2C总线协议中,起始信号(S)和停止信号(P)都是由主器件产生的。起始信号表明一次I2C总线传送的开始,停止信号则表明I2C总线通信结束。当SCL线为高电平时,SDA线由高电平到低电平的负跳变被定义为起始信号,而SDA由低电平到高电平的正跳变为停止信号。I2C总线的起始和停止信号时序如图4-5所示。 file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.gif 图4-5 I2C总线起始信号和停止信号的时序 当总线上出现起始信号后,就认为总线处于工作状态;总线上出现停止信号,总线就被认为是处在空闲状态。如果连接到总线上的设备具有I2C的接口硬件,那么检测起始和停止信号的过程将由硬件自动完成。但是,如果微处理器没有I2C硬件接口电路,则必须由软件检测电平的跳变判断起始与停止信号。 (2) 器件地址 I2C总线上的每一个器件均有一个唯一的地址。每次发送器发出起始信号后,必须接着发出一个字节的地址信息,以选取连接在总线上的某一从机。地址字节用“从器件地址+ R/W(____)”表示。从器件地址是7bit的器件地址编码,占用字节的高7位(D7~D1);D0位是数据的传送方向位,又称读/写选择位,用R/W(____)表示,当R/W(____)=0时,表示主器件向从器件写数据(发送数据);R/W(____)=1时,表示主器件从从器件读取数据(接收数据)。 从器件地址由一个固定部分和一个可编程部分组成。固定部分为器件的标识,表明器件类型,在出厂时设置。可编程部分为器件的地址,用以区分连接在同一I2C总线上的同类器件。器件的地址由硬件接线而定,只有主器件送来的地址信息中的可编程部分和从器件的地址引脚状态一致,该器件才会响应总线的操作。例如E2PROM器件24C16的地址格式如下: 其中:高四位1010为E2PROM器件标识类型,A2~A0为引脚地址,对应于该芯片引脚A2~A0的接线,最低位为读写选择比特。当A2~A0引脚均接低电平时,该器件的地址为A0H或A1H,主器件访问地址0xA0表示写数据到该器件,访问0xA1表示从该器件读数据。 (3) 应答信号ACK I2C总线上的发送器发送完地址字节和每一个字节数据后,接收器都必须产生一个应答信号,应答的器件在第9个时钟周期时将SDA线拉低,表示已收到一个8位数据。 与应答信号相对应的第9个时钟由发送器产生,发送器必须在输出该时钟时释放数据线SDA,使其处于高阻状态,以便接收器在SDA线上输出低电平应答信号(ACK),表示继续接收。若接收器输出高电平则为非应答信号(NO ACK),表示结束接收。 如果是主器件在接收数据,例如当从器件为存储器,主器件读从器件中的数据时,它收到最后一个数据字节后,必须向从器件发送一个非应答信号(NO ACK),使从器件释放SDA线,以便主器件产生终止信号,停止数据传送。 I2C总线的数据应答时序如图4-6所示。 file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.gif 图4-6 I2C总线应答时序 (4) 数据字节信号 利用I2C总线进行数据传送时,传送的字节数是没有限制的,但是每一个字节必须保证是8位长度,并且首先发送数据的最高位,每传送一个字节数据后都必须跟随一位应答脉冲,即接收器发回的应答信号ACK。然后由发送器继续发送数据字节或发出停止信号P后结束数据的传送。如果接收器不能接收下一个字节,例如正在处理一个外部中断,可以把SCL线拉成低电平,迫使发送器处于等待状态。当从机准备好接收下一个字节时再释放时钟线SCL,使数据传输继续进行。连续发送多字节数据的格式如图4-7所示。 file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image008.gif 图4-7 I2C总线多字节操作时序 4. 单片机通过IO端口模拟I2C总线时序操作24Cxx系列E2PROM的接口程序 参考I2C总线的时序以及24Cxx芯片的数据手册,可设计出单片机通过IO端口模拟I2C总线时序控制24Cxx系列E2PROM的接口程序。程序分为24Cxx.H和24Cxx.C两部分。24Cxx.H文件用来做函数说明和常量定义等,可供最终的调用程序包含,24Cxx.C文件为各功能函数的具体实现。程序列表如下: 文件:24Cxx.H #ifndef __24Cxx__ #define __24Cxx__ #define ERRORCOUNT 10 enum E2PROMType{M2401,M2402,M2404,M2408,M2416,M2432,M2464,M24128,M24256}; void Delay(unsigned char); void I2CStart(void); void I2CStop(void); bit I2CRecAck(void); void I2CNoAck(void); void I2CAck(void); unsigned char I2CReceiveByte(void); void I2CSendByte(unsigned char); bit RWE2PROM(unsigned char *, unsigned char, unsignedint, unsigned char, enum E2PROMType); #endif 文件:24Cxx.C #include <reg51.h> #include <intrins.h> #include "24Cxx.h" sbit SDA =P1^0; sbit SCL =P1^1; // 模拟总线接口引脚,可根据实际电路修改 enum EEPROMTypeEepromType; /************************************************************************ 函数说明:24C01~24C256共9种E2PROM的读写操作函数。 参数说明:读写数据缓冲区指针,读写的字节数,E2PROM首址,EEPROM控制字节,E2PROM类型 ************************************************************************/ bit RWE2PROM( unsignedchar *Buf, unsignedchar Bytes, unsignedint Address, unsignedchar ControlByte, enumE2PROMType EepromType) { unsigned chardata j,i = ERRORCOUNT; bit errorflag= 1; while(i--) { I2CStart(); // 首先选择器件及设置器件的内部地址 I2CSendByte(ControlByte&0xfe); if(I2CRecAck())continue; if(EepromType>M2416) { I2CSendByte((unsignedchar)(Address>>8)); if(I2CRecAck())continue; } I2CSendByte((unsignedchar)Address); if(I2CRecAck())continue; if(!(ControlByte&0x01)) // 判断进行的是否是写操作 { // 是写操作 j =Bytes; errorflag= 0; // 清除错误标志 while(j--) { I2CSendByte(*Buf++); if(!I2CRecAck())continue; errorflag= 1; break; } if(errorflag==1)continue; break; } else // 是读操作 { I2CStart(); I2CSendByte(ControlByte); if(I2CRecAck())continue; while(--Bytes) { *Buf++= I2CReceiveByte(); I2CAck(); } *Buf= I2CReceiveByte(); // 读最后一个字节 I2CNoAck(); errorflag= 0; break; } } I2CStop(); // 向总线送停止信号 if(!(ControlByte&0x01)) // 判断刚才进行的是否为写操作 { Delay(255); // 如果是写操作,延时以等待E2PROM Delay(255); //完成擦写过程。具体延时长度可参考 } // 数据手册并根据实际情况加以调整。 return(errorflag); } /********************以下是模拟I2C总线操作时序的子程序********************/ // 启动I2C总线 void I2CStart(void) { SCL=0; SDA=1;SCL=1; // 参见I2C总线的Start状态 _nop_(); SDA=0; // 少许延时,等待总线信号稳定 _nop_(); SCL=0; SDA=1; } // 停止I2C总线 void I2CStop(void) { SCL=0; SDA=0;SCL=1; _nop_(); SDA=1; _nop_(); SCL=0; } // 检查ACK信号 bit I2CRecAck(void) { SCL=0; SDA=1;SCL=1; _nop_(); CY=SDA; // 结果放入进位位:PSW的一位 SCL=0; return(CY); } // 在I2C总线上产生ACK信号 void I2CACK(void) { SDA=0; SCL=1; _nop_(); SCL=0; _nop_(); SDA=1; } // 在I2C总线上产生NOACK信号 void I2CNoAck(void) { SDA=1; SCL=1; _nop_(); SCL=0; } // 向I2C总线写数据 void I2CSendByte(unsigned char sendbyte) { unsigned chardata j = 8; for(;j>0;j--) { SCL=0; sendbyte<<=1; SDA=CY; SCL=1; } SCL=0; } // 从I2C总线读数据 unsigned char I2CReceiveByte(void) { registerreceivebyte,i=8; SCL=0; while(i--) { SCL=1; //延时4.7us稳定数据 receivebyte=(receivebyte<<1)|SDA; SCL = 0; } return(receivebyte); } // 循环延时函数 void Delay(unsigned char DelayCount) { while(DelayCount--); } 在实际项目中使用24Cxx系列E2PROM接口函数时,主控程序只要包含24Cxx.H,并将24Cxx.C加入到工程中即可。 一、 实验过程 1. 电路连接 将CPU板上的单片机P1.0(J2或J6的1号引脚)和模拟总线接口IO板上24C16的SDA(J6的1号引脚)相连; 将CPU板上的单片机P1.1(J2或J6的2号引脚)和模拟总线接口IO板上24C16的SCL(J6的2号引脚)相连; 将CPU板上的COM1和PC机的串行口相连。 2. 程序设计 根据实验要求,设计实验代码如下: #include <reg51.h> #include <stdio.h> #include <string.h> #include "24Cxx.h" #define OSC 11059200 #define BAUDRATE 9600 void main(void) { char Buf[32]; int i; bit bb; TMOD = 0x20; SCON = 0x50; PCON |= 0x80; TL1 =256-(OSC/12/16/BAUDRATE); TH1 =256-(OSC/12/16/BAUDRATE); TR1 = 1; TI = 1; printf("\r\nTesting24Cxx..."); bb =RW24XX(Buf,16,0,0xA1,M2416); if(bb == 0) { printf("\r\nReaded!\r\n"); for(i=0;i<16;i++)printf("%02bX ",Buf); printf("| "); for(i=0;i<16;i++)printf("%c",Buf); } elseprintf("\r\nRead failed!"); printf("\r\nEnterto write to 24Cxx..."); scanf("%s",Buf); bb =RW24XX(Buf,strlen(Buf),0,0xA0,M2416); if(bb == 0)printf("\r\nWrite success..."); elseprintf("\r\nWrite failed..."); while(1); } 3. 验证结果 按实验要求连接好电路,在Keil中建立新工程,将上述程序代码加入工程,编译链接后,将生成的HEX文件烧写到单片机中; 将PC机的串行口和单片机的串行口相连,打开超级终端并设置波特率等参数; 复位单片机即可接收到单片机输出的24C16的数据。根据提示在PC机输入数据,按回车发送后可观察到单片机写24C16的结果。如果写入正确,再次复位单片机后即可接收到单片机读出的上次写入24C16的数据。
|