这一篇介绍I2C存储器的使用。主要是介绍AT24CXX系列器件,它分为两类,主要是通过被存储容量地址来分的,一类是AT24C02-AT24C16,它的存储容量从256字节到2048字节。另一类是AT24C32-AT24C1024,容量从4K-128K。(理论上好像可以达到最高512K字节容量,但现在网上最高也就能看到AT24C1024也就是128K字节容量)
原理:
I2C总线是一种用于IC器件之间连接的二线制总线。它通过SDA(串行数据线)及SCL(串行时钟线)两根线在连到总线上的器件之间传送信息,并根据地址识别每个器件:不管是单片机、存储器、LCD驱动器还是键盘接口。
I2C总线接口电路结构如图所示。
SDA和SCL均为双向I/O线,通过上拉电阻接正电源。当总线空闲时,两根线都是高电平。连接总线的器件的输出级必须是集电极或漏极开路,以具有线“与”功能。I2C总线的数据传送速率在标准工作方式下为100kbit/s,在快速方式下,最高传送速率可达400kbit/s。
在I2C总线技术规范中,开始和结束信号(也称启动和停止信号)的定义如图所示。当时钟线SCL为高电平时,数据线SDA由高电平跳变为低电平定义为“开始”信号;当SCL线为高电平时,SDA线发生低电平到高电平的跳变为“结束”信号。开始和结束信号都是由主器件产生。在开始信号以后,总线即被认为处于忙状态;在结束信号以后的一段时间内,总线被认为是空闲的。
I2C总线的数据传送格式是:在I2C总线开始信号后,送出的第一个字节数据是用来选择从器件地址的,其中4-7位为器件码,如1010就是代表串行E2PROM器件。1-3位为存储器的片选地址或存储器内的块地址码,如何区分?后面再做详细说明,第8位为方向位(R/W)。方向位为“0”表示发送,即主器件把信息写到所选择的从器件;方向位为“1”表示主器件将从从器件读信息。开始信号后,系统中的各个器件将自己的地址和主器件送到总线上的地址进行比较,如果与主器件发送到总线上的地址一致,则该器件即为被主器件寻址的器件,其接收信息还是发送信息则由第8位(R/W)确定。
一个字节的写操作的过程:首先器件发出起始信号后,发送器件识别控制字节,即1010A2A1A00(最低位置0,即R/W读写控制位为低电平0),然后等待应答信号指示从器件被寻址。再发送一个AT24CXX存储器将要写入的位置地址。再次等待AT24CXX应答信号以后,将发送数据字节,AT24CXX接收到后写入到刚刚指定的存储器地址。然后主器件再次等待AT24CXX的应答信号。主器件最后发出停止信号。
在I2C总线上每次传送的数据字节数不限,但每一个字节必须为8位,而且每个传送的字节后面必须跟一个认可位(第9位),也叫应答位(ACK)。数据的传送过程如图所示。每次都是先传最高位,通常从器件在接收到每个字节后都会作出响应,即释放SCL线返回高电平,准备接收下一个数据字节,主器件可继续传送。如果从器件正在处理一个实时事件而不能接收数据时,(例如正在处理一个内部中断,在这个中断处理完之前就不能接收I2C总线上的数据字节)可以使时钟SCL线保持低电平,从器件必须使SDA保持高电平,此时主器件产生1个结束信号,使传送异常结束,迫使主器件处于等待状态。当从器件处理完毕时将释放SCL线,主器件继续传送。
当主器件发送完一个字节的数据后,接着发出对应于SCL线上的一个时钟(ACK)认可位,在此时钟内主器件释放SDA线,一个字节传送结束,而从器件的响应信号将SDA线拉成低电平,使SDA在该时钟的高电平期间为稳定的低电平。从器件的响应信号结束后,SDA线返回高电平,进入下一个传送周期。
I2C总线还具有广播呼叫地址用于寻址总线上所有器件的功能。若一个器件不需要广播呼叫寻址中所提供的任何数据,则可以忽略该地址不作响应。如果该器件需要广播呼叫寻址中提供的数据,则应对地址作出响应,其表现为一个接收器。
5.总线竞争的仲裁 总线上可能挂接有多个器件,有时会发生两个或多个主器件同时想占用总线的情况。例如,多单片机系统中,可能在某一时刻有两个单片机要同时向总线发送数据,这种情况叫做总线竞争。I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,其仲裁原则是这样的:当多个主器件同时想占用总线时,如果某个主器件发送高电平,而另一个主器件发送低电平,则发送电平与此时SDA总线电平不符的那个器件将自动关闭其输出级。总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位的比较,从而确保了竞争仲裁的可靠性。由于是利用I2C总线上的信息进行仲裁,因此不会造成信息的丢失。
器件说明:
AT24CXXX系列引脚图如下
现在我先来说说AT24CXX的具体使用
假设用AT89S51的P0.0做SDA总线,P0.1做SCL总线。有若干个I2C器件挂接在SDA和SCL总线上。
现在要对E2PROM_01存储器进行写字节操作看看它是如何找到的。
上面说过在发送完一个开始信号后接着发送一个字节的器件识别信号。这一个字节的4-7位就是器件识别码。1010就是对应E2PROM器件,其它器件就不再理会了。1-3位是器件的物理地址,也就是说如果是E2PROM,它可以在I2C总线上挂接(000-111)8个E2PROM。在这里就得详细说说AT24CXX上的A0,A1,A2和这个器件识别字节之间的关系了。上面说过存储器的寻址范围是一个字节,也就256个,AT24C02的存储容量为256字字,刚刚好将一个字节的地址用完。器件电路上A0,A1,A2三个管脚通过接高电平或低电平来和AT89S51发送过来的器件识别控制字节相匹配,从而得以识别出AT89S51将要操作的那个存储器。现在AT24C04的容量是512个字节,那不是一个字节的地址不够用了吗?其实它是将512个字节为成两个页,每页256字节,而页地址就是器件识别控制字节的1位。前面说了这个1-3位不是和器件上的A0,A1,A2匹配来识别器件的吗?是的,但存储器容量超过256字节情况就有变了。AT24C04上的A0这时就废弃不用了,只用A1和A2,这样就只能接(00-11)四个AT24C04了,同样AT24C08容量为1K字节分为4页了,于是页地址就是器件识别控制字节的1-2位,器件上的A0,A1废弃不用,只用A2,就只能接两个AT24C08了。AT24C16容量为2K字节,分为8页。页地址是器件识别控制字节的1-3位,全用了。器件上的A0,A1,A2,就无效了,只能接一只AT24C16。
我这么说能明白吗?
对于大容量AT24C32-1024的存储器。器件的存储寻址地址为两个字节,所以它的一页为65536(64K)。AT24C32-64的容量为4K字节-8K字节,在一页范围,可以接8只器件。从AT24C128-1024的器件代号也由1010改为10100,多了一位,识别控制字节的器件物理地址就少了一位,变为1-2位,相应的在器件管脚上A2也废弃空着了,因此最多只能接四只器件。AT24C128-512只有两位器件地址所以最多只能接四只器件。而AT24C1024的容量为128K,分为两页,识别控制字节的的1位为页地址,器件的A0脚废弃不用,只用了A1。因此只能接2只器件。
下面我们进行具体的制做先准备好器件如下图 ,我用的是AT24C16
原先的板子如下图
焊好后。
好!下面我们进行调试:插上主电源。但AT24C16的电源短路帽不接,在短路帽两个针之间接上万用表的电流档检查是否有短路和静态电流的大小。实测静态电流几乎为零,改变两个数据线的电平时,电流会有所上升,说明电路基本正常。
现在我们接上电源短路帽把AT24C16电源接好。将两个数据线用跳线接到P1.6和P1.7口上(接到哪个口上可以自己选的)。注意分清哪个是SDA哪个是SCL。别弄错了。
把LCD12864装上,后面我们就要进行软件的调试了。
后面一篇,我们进行软件件部分的调试。
先将源代码附上。前面部分LCD12864的代码基本不变,添加上AT24C16的代码。
//LCD12864 //********************************************************** //连线表: CPU=89S51 SysClock=12MHz * //RS=P2.0 R/W=P2.1 E=P2.2 CS1=P2.3 CS2=P2.4 * //DB0-DB7=P3.0-P3.7 /Reset=InBoard * //********************************************************** //24c16 //********************************************************** //连线表: CPU=89S51 SysClock=12MHz * //SDA=P1.6 SCL=P1.7 //********************************************************** #include <reg52.h> #include <stdlib.h> #include <intrins.h> #include <stdio.h> #include <math.h> #define uchar unsigned char #define uint unsigned int /****************LCD12864引脚定义*****************/ #define DataPort P3 //LCD128*64 I/O 信号管脚 sbit RS =P2^; //数据指令 sbit RW =P2^; //读写 sbit E =P2^; //使能 sbit CSL =P2^; //左片选 sbit CSR =P2^; //右片选 uchar Page; //页 地址 uchar Col; //列 地址 uchar code BMP1[]; //一幅图 uchar code HZK_12[]; //12×12阵点字模 uchar code ASC_5x7[]; //5×7阵点字模 uchar str[]; /**************AT24C16引脚定义*****************/ sbit SDA =P1^; //数据 sbit SCL =P1^; //时钟 /***********************************************/ /****************LCD12864函数定义*******************/ void BusyL(void); //左屏检测忙 void BusyR(void); //右屏检测忙 void CheckBusy(void); //读取忙信号 void Delay(uint MS); //延时 void Locatexy(void); //将屏幕横向0-12纵向0-7转换成左、右屏的的X、Y void WriteCommandL( uchar CommandByte ); //向左屏写入指令 void WriteCommandR( uchar CommandByte ); //向右屏写入指令 uchar ReadData( void ); //读数据 void WriteData( uchar DataByte ); //写数据 void LcmClear( void ); //清屏 void LcmInit( void ); //初始化 void LcmPutBMP( uchar *puts ); //显示一幅图 void LcmReverseBMP( void ); //将整屏反显 void LcmPutHZ_12( uchar x,uchar y,uchar HZcode ); //在屏幕上任意点显示一个12×12汉字 uchar * uchartostr(unsigned char unm); //将值转成字符串 void LcmPutAsc( uchar asc ); //显示一个5×7的ASC字符 void LcmPutstr( uchar row,uchar y,uchar * str ); //在设定位置显示字符串 void LcmPutpoint( uchar ro,uchar lie,uchar colour ); //在设定位置显示一个点 /****************AT24C16函数定义*******************/ /****向总线上发送n字节数***/ bit write_nbyte(uchar block_addr,uchar addr,uchar *s,uchar numb); /****从总线上读取n个字节数******/ bit read_nbyte(uchar block_addr,uchar addr,uchar *s,uchar numb); /***************************** 说明: block_addr 存储器的块地址选0-8 addr 存储器的存储地址 *s 一般是用在数组的首地址 numb 要写入的字节数 ********************************/ /***************************/ /*检查Busy */ /***************************/ void BusyL(void) { CSL= ; CSR= ; CheckBusy(); } void BusyR(void) { CSL= ; CSR= ; CheckBusy(); } void CheckBusy(void) { RS = ; //指令 RW = ; DataPort= 0xFF; //输出0xff以便读取正确 E = ; _nop_(); );//DataPort & 0x80); //Status Read Bit7 = BUSY E = ; _nop_(); } /********************************************************/ /*根据设定的坐标数据,定位LCM上的下一个操作单元位置 */ /********************************************************/ void Locatexy(void) { uchar x,y; switch (Col&0xc0) /* col.and.0xC0 */ { /*条件分支执行 */ : {BusyL();break;}/*左区 */ case 0x40: {BusyR();break;}/*右区 */ } x = Col&0x3F|0x40; /* col.and.0x3f.or.Set Y Address*/ y = Page&0x07|0xB8; /* row.and.0x07.or.set Page */ CheckBusy(); /* waitting for enable */ RS = ; //指令 RW = ; //写 DataPort = y; //设置页面地址 E = ; _nop_(); E = ; _nop_(); CheckBusy(); /* waitting for enable */ RS = ; RW = ; DataPort = x; //设置列地址 E = ; _nop_(); E = ; _nop_(); } /***************************/ /*写指令 */ /***************************/ void WriteCommandL( uchar CommandByte ) { BusyL(); DataPort = CommandByte; RS = ; //指令 RW = ; E = ; _nop_(); E = ; _nop_(); } void WriteCommandR( uchar CommandByte ) { BusyR(); DataPort = CommandByte; RS = ; //指令 RW = ; E = ; _nop_(); E = ; _nop_(); } /***************************/ /*读数据 */ /***************************/ uchar ReadData( void ) { uchar DataByte; Locatexy(); /*坐标定位,返回时保留分区状态不变 */ RS = ; /*数据输出*/ RW = ; /*读入 */ DataPort = 0xFF; //输出0xff以便读取正确 E = ; /*读入到LCM*/ _nop_(); DataByte = DataPort; /*数据读出到数据口P1 */ E = ; _nop_(); return DataByte; } /***************************/ /*写数据 */ /***************************/ void WriteData( uchar DataByte ) { Locatexy(); /*坐标定位,返回时保留分区状态不变 */ RS = ; /*数据输出*/ RW = ; /*写输出 */ DataPort = DataByte; /*数据输出到数据口 */ E = ; /*写入到LCM*/ _nop_(); E = ; _nop_(); } void LcmClear( void ) { Page = ; Col = ; ;Page<;Page++) ;Col<;Col++) WriteData(); } void LcmInit( void ) { Delay(); //等待复位 WriteCommandL(0x3f); //开显示 WriteCommandR(0x3f); WriteCommandL(0xc0); //设置起始地址=0 WriteCommandR(0xc0); WriteCommandL(0x3f); //开显示 WriteCommandR(0x3f); LcmClear(); Col = ; Page= ; Locatexy(); } void LcmPutBMP( uchar *puts ) { ; Page = ; Col = ; ;Page<;Page++) { ;Col<;Col++) { WriteData( puts[X] ); X++; } } } void LcmReverseBMP( void ) { uchar temp; Page = ; Col = ; ;Page<;Page++) { ;Col<;Col++) { temp = ReadData(); //空读一次 temp = ReadData(); temp = ~temp; WriteData(temp); } } } void LcmPutHZ_12( uchar x,uchar y,uchar HZcode ) { uchar offset,Rd,Wt,m,tmp,i; uint n; &y<) { Page=(y & ; Col=x; n = 0x18*HZcode; offset=y&0x07; ) { ;i>;i--) { Rd=ReadData(); Rd=ReadData(); m=HZK_12[n]; Wt=Rd&(-offset))|(m<<offset); WriteData(Wt); Page++; n++; tmp=m; m=HZK_12[n]; Rd=ReadData(); Rd=ReadData(); Wt=tmp>>(-offset)|(m<<offset)|(Rd&())); WriteData(Wt); Col++; Page--; n++; } } else { ;i>;i--) { Rd=ReadData(); Rd=ReadData(); m=HZK_12[n]; Wt=Rd&(-offset))|(m<<offset); WriteData(Wt); Page++; n++; tmp=m; m=HZK_12[n]; Wt=tmp>>(-offset)|(m<<offset); WriteData(Wt); Page++; n++; Rd=ReadData(); Rd=ReadData(); Wt=m>>(-offset)|(Rd&())); WriteData(Wt); Page=Page-;//恢复位置 Col++; //修正下一个汉字的起始位置 } } } } uchar * uchartostr(uchar unm) { uchar x00,xx,x0,x,n; x00=unm/; xx=unm%; x0=xx/; x=xx%; n=; ) { str[n]=x00+; //值加48即为字符 n++; } &x0==)) { str[n]=x0+; n++; } str[n]=x+; n++; str[n]='\0'; return str; } void LcmPutAsc( uchar asc ) { uchar j; uint x; x = *(asc-); ;j<;j++) { WriteData(ASC_5x7[x]); x++; Col++; } WriteData(0x00); Col++; } void LcmPutstr( uchar row,uchar y,uchar * str ) { unsigned char * x; x=str; Page=row; Col=y; while(*x!='\0') { LcmPutAsc( *x ); x++; } } void LcmPutpoint( uchar ro,uchar lie,uchar colour ) //画点函数 { )&(lie<)) { uchar modbyte,outByte; uchar offsetbit; Col=lie; //列等于lie Page=(ro & ; //页等于行数row与00111000B再右移3位 offsetbit=ro&0x07; //偏移量为行数与00000111 modbyte=; modbyte<<= offsetbit; //输出位的模00000001左移offsetbit位 outByte=ReadData(); outByte=ReadData(); ) { modbyte=~modbyte; outByte=modbyte&outByte; //输出位不影响其它位 } else outByte=modbyte|ReadData(); //输出位不影响其它位 WriteData(outByte); } } void Delay(uint MS) { uchar us,usn; ) { usn = ; //for 12M ) { us=0xf6; ){us--;}; usn--; } MS--; } } //下面是24c016的函数 //********************************************************** //连线表: CPU=89C51 SysClock=12MHz * //SDL=P1.6 SCL=P1.7 //********************************************************** /********起始信号*********/ void start_iic() { SDA=; SCL=; _nop_(); _nop_(); SDA=; _nop_(); _nop_(); SCL=; } /*************************/ /********停止信号*********/ void stop_iic() { SDA=; SCL=; _nop_(); _nop_(); SDA=; _nop_(); _nop_(); SCL=; } /***********************/ /*******产生应答信号********/ void ack_iic() { SDA=; SCL=; _nop_(); _nop_(); SCL=; SDA=; } /*************************/ /*****产生非应答信号******/ void nack_iic() { SDA=; SCL=; _nop_(); _nop_(); SCL=; SDA=; } /************************/ /****向总线上发送一个字节数据****/ write_byte(uchar c) { uchar i; ;i<;i++) { ; ; SCL=; _nop_(); _nop_(); SCL=; c=c<<; } SDA=; SCL=; _nop_(); _nop_(); ) F0=; ; SCL=; } /*************************/ /*****从总线上读取一个字节数据*******/ uchar read_byte() { uchar i; uchar r=; SDA=; ;i<;i++) { r=r<<; SCL=; _nop_(); _nop_(); ) r++; SCL=; } return r; } /*******************************/ /****向总线上发送n字节数据*****/ bit write_nbyte(uchar block_addr,uchar addr,uchar *s,uchar numb) /***************************** block_addr 存储器的块地址选0-8 addr 存储器的存储地址 *s 一般是用在数组的首地址 numb 要写入的字节数 ********************************/ { uchar i,slave; start_iic(); slave=); write_byte(slave); ) ; write_byte(addr); ) ; ;i<numb;i++) { write_byte(*s); ) ; s++; } stop_iic(); ); } /***************************/ /****从总线上读取n个字节数据***/ bit read_nbyte(uchar block_addr,uchar addr,uchar *s,uchar numb) /******变量说明: block_addr 存储器的块地址选0-8 addr 存储器的存储地址 *s 一般是用在数组的首地址 numb 要写入的字节数 *******/ { uchar i,slave; start_iic(); slave=); write_byte(slave); ) ; write_byte(addr); ) ; start_iic(); slave=); write_byte(slave); ) ; ;i<numb-;i++) { *s=read_byte(); ack_iic(); s++; } *s=read_byte(); nack_iic(); stop_iic(); ); } /**************************/ void Main( void ) { uchar x,i; uchar *ascii; uchar abcd[]; LcmInit(); LcmClear(); ascii=abcd; //write_nbyte(0,0,ascii,8); read_nbyte(,,ascii,); ) { LcmClear(); LcmPutBMP(BMP1); Delay(); LcmClear(); //LcmReverseBMP(); Delay(); //LcmClear(); /* x=0; for(i=0;i<8;i++) { LcmPutHZ_12(x,i*7,i); x=x+16; } */ LcmPutstr( ,,ascii ); LcmPutstr( ,,"AT24C16!" ); Delay(); } } unsigned char code BMP1[]={ //字节倒序 //-- 调入一幅图像:E:!Program!BmpSample12864.bmp //宽度x高度=1264 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x80,0xE0,0xF0,0xFC,0xFE,0xFE, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0x9F,0xCF,0xDF,0x9F, 0x9E,0x3E,0xFF,0xFE,0xFE,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFA,0xFC,0xF8,0xE0,0xC0,0x80, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x80,0xC0,0xF0,0xF8,0xFC,0xFE, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xEF, 0xCF,0xDF,0x9F,0x0F,0x1F,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFE,0xFC,0xF0,0xC0,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xC0,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xF0,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x10,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xFB,0xFF, 0xFF,0xFF,0xFF,0xFE,0xF8,0xFE,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF,0x3F, 0x37,0x37,0x37,0x27,0x63,0x43,0x03,0x03, 0x03,0x03,0x03,0x07,0x67,0x27,0x0F,0x0F, 0x1F,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x80,0x80,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x10,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x7F,0x7F, 0x7F,0x7F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, 0x3F,0x3F,0x3F,0x3F,0x3F,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x3F, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x80,0xE0,0xFC,0xFE,0xFE,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xDF,0x0F,0x07,0x07,0x03, 0x03,0x03,0x03,0x02,0xC0,0xAC,0xBF,0xA0, 0x80,0x00,0x00,0x00,0x02,0x02,0x06,0x06, 0x04,0x00,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFE,0xFC,0xF0,0xE0, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x3F, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xBF,0x3F,0x3F,0x3D, 0x7D,0xF8,0xF0,0xF0,0xC0,0x00,0x00,0x00, 0x08,0x8C,0xFC,0xFE,0xEE,0xE6,0xC2,0xC0, 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x01, 0x1F,0x1F,0x87,0x0D,0x7D,0x70,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0, 0xF0,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0xE0,0x00, 0x00,0x00,0x02,0xE7,0xE7,0xE7,0xE7,0xC3, 0xC3,0xC1,0x82,0x87,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0xE3,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFC,0xF8,0xE0,0xC0,0x80,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07, 0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xDF,0x1F,0x1F,0x1F,0x1B,0xF9, 0xF9,0xFF,0xFF,0xFF,0xFF,0x80,0x00,0x00, 0x00,0x01,0x01,0x03,0x03,0x83,0x83,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x01,0x03,0x00,0x00,0x00,0x00, 0xF8,0xFC,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFE,0xFC,0xF8,0xFE,0x8F,0x8F,0x0E,0x06, 0x0E,0x0C,0x0C,0x00,0x01,0x00,0x00,0x80, 0xC0,0xF0,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFC, 0xF8,0xF0,0xF0,0xE0,0x00,0x00,0x00,0x00, 0x00,0x03,0x07,0x0F,0x1F,0x1F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFE,0xFC,0xFC,0xFE,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xCF,0xDE, 0xD4,0xC2,0x82,0x80,0x80,0x83,0xC7,0xC7, 0x0F,0x04,0x00,0x00,0x00,0x00,0x00,0x00, 0xC0,0xF4,0xFC,0xFC,0xFC,0xFC,0xF8,0xF8, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x7F,0x3F,0x1F,0x1F,0xDF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3F,0x1E, 0x1E,0x0E,0x0C,0x04,0x02,0x06,0x1F,0xFF, 0xFF,0xCF,0x0F,0x0F,0x0F,0x1F,0x3F,0x7F, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0x7F,0x1F,0x1F,0x03,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x80,0xC1,0xE7, 0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xFB, 0xF3,0xF9,0x71,0x31,0x00,0x00,0x00,0x01, 0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0x03,0x07,0x0F,0x1F,0x1F,0x3F,0x3F,0x1F, 0x1F,0x1F,0x0F,0x4F,0x67,0x27,0x33,0x31, 0x38,0x38,0x78,0x7C,0x7E,0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,0x7F,0x3F,0x3F,0x7F,0x7F, 0x7F,0x7F,0x7D,0x79,0x79,0x70,0x70,0x70, 0x70,0x70,0x70,0x78,0x78,0x3C,0x5E,0x6F, 0x3F,0x77,0x0F,0x0C,0x7C,0x78,0x78,0x40, 0x00,0x01,0x01,0x03,0x07,0x0F,0x1F,0x1F, 0x1F,0x1F,0x1F,0x1F,0x0F,0x07,0x03,0x00, 0x00,0x00,0x00,0x40,0x60,0x70,0x70,0x78, 0x78,0x7C,0x7E,0x7F,0x7F,0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7C, 0x7C,0x7C,0x78,0x78,0x70,0x58,0x00,0x00, 0x40,0x70,0x78,0x7C,0x7F,0x7F,0x7F,0x7F, 0x7F,0x7F,0x07,0x07,0x0F,0x1F,0x3F,0x7F }; uchar code ASC_5x7[]={ 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00, 0x00, 0x4F, 0x00, 0x00, // 0x00, 0x07, 0x00, 0x07, 0x00, // 0x14, 0x7F, 0x14, 0x7F, 0x14, // 0x24, 0x2A, 0x7F, 0x2A, 0x12, // 0x23, 0x13, 0x08, 0x64, 0x62, // 0x36, 0x49, 0x55, 0x22, 0x50, // 0x00, 0x05, 0x03, 0x00, 0x00, // 0x00, 0x1C, 0x22, 0x41, 0x00, // 0x00, 0x41, 0x22, 0x1C, 0x00, // 0x14, 0x08, 0x3E, 0x08, 0x14, // 0x08, 0x08, 0x3E, 0x08, 0x08, // 0x00, 0x50, 0x30, 0x00, 0x00, // 0x08, 0x08, 0x08, 0x08, 0x00, // 0x00, 0x60, 0x60, 0x00, 0x00, // 0x20, 0x10, 0x08, 0x04, 0x02, // 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0x00, 0x42, 0x7F, 0x40, 0x00, // 0x42, 0x61, 0x51, 0x49, 0x46, // 0x21, 0x41, 0x45, 0x4B, 0x31, // 0x18, 0x14, 0x12, 0x7F, 0x10, // 0x27, 0x45, 0x45, 0x45, 0x39, // 0x3C, 0x4A, 0x49, 0x49, 0x30, // 0x01, 0x01, 0x79, 0x05, 0x03, // 0x36, 0x49, 0x49, 0x49, 0x36, // 0x06, 0x49, 0x49, 0x29, 0x1E, // 0x00, 0x36, 0x36, 0x00, 0x00, // 0x00, 0x56, 0x36, 0x00, 0x00, // 0x08, 0x14, 0x22, 0x41, 0x00, // 0x14, 0x14, 0x14, 0x14, 0x14, // 0x00, 0x41, 0x22, 0x14, 0x08, // 0x02, 0x01, 0x51, 0x09, 0x06, // 0x32, 0x49, 0x79, 0x41, 0x3E, // 0x7E, 0x11, 0x11, 0x11, 0x7E, // 0x41, 0x7F, 0x49, 0x49, 0x36, // 0x3E, 0x41, 0x41, 0x41, 0x22, // 0x41, 0x7F, 0x41, 0x41, 0x3E, // 0x7F, 0x49, 0x49, 0x49, 0x49, // 0x7F, 0x09, 0x09, 0x09, 0x01, // 0x3E, 0x41, 0x41, 0x49, 0x7A, // 0x7F, 0x08, 0x08, 0x08, 0x7F, // 0x00, 0x41, 0x7F, 0x41, 0x00, // 0x20, 0x40, 0x41, 0x3F, 0x01, // 0x7F, 0x08, 0x14, 0x22, 0x41, // 0x7F, 0x40, 0x40, 0x40, 0x40, // 0x7F, 0x02, 0x0C, 0x02, 0x7F, // 0x7F, 0x06, 0x08, 0x30, 0x7F, // 0x3E, 0x41, 0x41, 0x41, 0x3E, // 0x7F, 0x09, 0x09, 0x09, 0x06, // 0x3E, 0x41, 0x51, 0x21, 0x5E, // 0x7F, 0x09, 0x19, 0x29, 0x46, // 0x26, 0x49, 0x49, 0x49, 0x32, // 0x01, 0x01, 0x7F, 0x01, 0x01, // 0x3F, 0x40, 0x40, 0x40, 0x3F, // 0x1F, 0x20, 0x40, 0x20, 0x1F, // 0x7F, 0x20, 0x18, 0x20, 0x7F, // 0x63, 0x14, 0x08, 0x14, 0x63, // 0x07, 0x08, 0x70, 0x08, 0x07, // 0x61, 0x51, 0x49, 0x45, 0x43, // 0x00, 0x7F, 0x41, 0x41, 0x00, // 0x02, 0x04, 0x08, 0x10, 0x20, // 0x00, 0x41, 0x41, 0x7F, 0x00, // 0x04, 0x02, 0x01, 0x02, 0x04, // 0x40, 0x40, 0x00, 0x40, 0x40, // 0x01, 0x02, 0x04, 0x00, 0x00, // 0x20, 0x54, 0x54, 0x54, 0x78, // 0x7F, 0x48, 0x44, 0x44, 0x38, // 0x38, 0x44, 0x44, 0x44, 0x28, // 0x38, 0x44, 0x44, 0x48, 0x7F, // 0x38, 0x54, 0x54, 0x54, 0x18, // 0x00, 0x08, 0x7E, 0x09, 0x02, // 0x0C, 0x52, 0x52, 0x4C, 0x3E, // 0x7F, 0x08, 0x04, 0x04, 0x78, // 0x00, 0x44, 0x7D, 0x40, 0x00, // 0x20, 0x40, 0x44, 0x3D, 0x00, // 0x00, 0x7F, 0x10, 0x28, 0x44, // 0x00, 0x41, 0x7F, 0x40, 0x00, // 0x7C, 0x04, 0x78, 0x04, 0x78, // 0x7C, 0x08, 0x04, 0x04, 0x78, // 0x38, 0x44, 0x44, 0x44, 0x38, // 0x7E, 0x0C, 0x12, 0x12, 0x0C, // 0x0C, 0x12, 0x12, 0x0C, 0x7E, // 0x7C, 0x08, 0x04, 0x04, 0x08, // 0x58, 0x54, 0x54, 0x54, 0x64, // 0x04, 0x3F, 0x44, 0x40, 0x20, // 0x3C, 0x40, 0x40, 0x3C, 0x40, // 0x1C, 0x20, 0x40, 0x20, 0x1C, // 0x3C, 0x40, 0x30, 0x40, 0x3C, // 0x44, 0x28, 0x10, 0x28, 0x44, // 0x1C, 0xA0, 0xA0, 0x90, 0x7C, // 0x44, 0x64, 0x54, 0x4C, 0x44, // 0x00, 0x08, 0x36, 0x41, 0x00, // 0x00, 0x00, 0x77, 0x00, 0x00, // 0x00, 0x41, 0x36, 0x08, 0x00, // 0x02, 0x01, 0x02, 0x04, 0x02, // 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // }; unsigned char code HZK_12[]={ //字节颠倒 //-- 文字: 鹰 --* //- 宋体11; 此字体下对应的点阵为:宽x高=11x 0x42,0x00,0x22,0x00,0x12,0x00,0xFA,0x07,0x4E,0x01,0x4B,0x01,0x4A,0x01,0x4A,0x05, 0xFA,0x07,0x02,0x00,0x02,0x00,0x00,0x00,/*"有",{0}*/ 0x42,0x04,0xAE,0x04,0x93,0x04,0x9A,0x02,0xA6,0x01,0xC0,0x00,0xA6,0x04,0x9A,0x04, 0x8A,0x04,0x96,0x03,0x20,0x00,0x00,0x00,/*"努",{1}*/ 0x00,0x04,0x08,0x04,0x08,0x02,0x08,0x01,0xC8,0x00,0x3F,0x00,0x08,0x04,0x08,0x04, 0x08,0x04,0xFC,0x03,0x08,0x00,0x00,0x00,/*"力",{2}*/ 0x02,0x02,0xFA,0x01,0x4B,0x04,0xCA,0x07,0x7A,0x01,0x02,0x04,0x08,0x03,0xFF,0x00, 0xC8,0x07,0x0E,0x04,0x08,0x07,0x00,0x00,/*"就",{3}*/ 0x42,0x00,0x22,0x00,0x12,0x00,0xFA,0x07,0x4E,0x01,0x4B,0x01,0x4A,0x01,0x4A,0x05, 0xFA,0x07,0x02,0x00,0x02,0x00,0x00,0x00,/*"有",{4}*/ 0x00,0x04,0xFC,0x03,0x24,0x01,0x24,0x02,0xE4,0x01,0x04,0x04,0x7F,0x02,0x84,0x01, 0x45,0x02,0x36,0x04,0x04,0x07,0x00,0x00,/*"成",{5}*/ 0x02,0x01,0x02,0x01,0xFE,0x00,0x82,0x04,0x82,0x04,0x08,0x02,0x88,0x01,0x7F,0x04, 0x08,0x04,0x08,0x04,0xF8,0x03,0x00,0x00,/*"功",{6}*/ 0x00,0x00,0x1C,0x00,0x7E,0x03,0x7E,0x03,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00/*"!",{7}*/ }; 上面代码的各个函数部分我就不说了,只说说AT24C16的调试,也就是主函数部分: void Main( void ) { uchar x,i; uchar *ascii; //先定义一个字符型指针变量 uchar abcd[]="iic test"; //定义一个字符数组内容为“iic test” LcmInit(); LcmClear(); ascii=abcd; //将字符数组的首地址交给指针变量 write_nbyte(,,ascii,); //向AT24C16写进以ascii为首地址的八个字节数据。 //read_nbyte(0,0,ascii,8); //读AT24C16语句补注释掉了 ) { LcmClear(); LcmPutBMP(BMP1); Delay(); LcmClear(); //LcmReverseBMP(); Delay(); //LcmClear(); /* x=0; for(i=0;i<8;i++) { LcmPutHZ_12(x,i*7,i); x=x+16; } */ LcmPutstr( ,,ascii ); //向LCD12864显示所定义的字符数组(这时还不是从AT2416内读取的) LcmPutstr( ,,"AT24C16!" ); Delay(); } } 对代码进行编译成HEX文件写入S51后运行: 好!数据已写入AT24C16内,下面修改程序,看看读取出的内容是否正确。 void Main( void ) { uchar x,i; uchar *ascii; uchar abcd[]; //定义一个字符数组,内容为空 LcmInit(); LcmClear(); ascii=abcd; //将字符数组的首地址交给指针变量 //write_nbyte(0,0,ascii,8); //写AT24C16语句被注释掉 read_nbyte(,,ascii,); //从AT24C16读取8个字节的数据存入以ascii为首地址的abcd变量内 ) { LcmClear(); LcmPutBMP(BMP1); Delay(); LcmClear(); //LcmReverseBMP(); Delay(); //LcmClear(); /* x=0; for(i=0;i<8;i++) { LcmPutHZ_12(x,i*7,i); x=x+16; } */ LcmPutstr( ,,ascii ); //显示从AT24C16内读取的内容 LcmPutstr( ,,"AT24C16!" ); Delay(); } } 重新编译后写入S51芯片运行: 第一幅: 第二幅: OK,成功!这次"iic test"这行字符是从AT24C16内读取的。你可以试着拔掉那两根SDA,SCL的数据线,再按下复位键重新运行,你会发现那行“iic test”没有显示了。说明没有读取到数据。再装上数据线,重按复位键运行,又正常了。后面就可以将存储在S51代码段的字模,图形数据等,都写到AT24C16里了。 在网上找到一个写的不错的读写AT24CXXX的通用程序函数,也很好用,程序见下面: #include <AT89X52.H> #include <intrins.h> #define uchar unsigned char #define uint unsigned int #define ERRORCOUNT 10 sbit SDA=P1^; sbit SCL=P1^; enum eepromtype {M2401,M2402,M2404,M2408,M2416,M2432,M2464,M24128,M24256}; enum eepromtype mType; bit RW24XX(unsigned char *DataBuff,unsigned char ByteQuantity,unsigned int Address, unsigned char ControlByte,enum eepromtype mType); //DataBuff为读写数据输入/输出缓冲区的首址 //ByteQuantity 为要读写数据的字节数量 //Address 为EEPROM的片内地址 //ControlByte 为EEPROM的控制字节,具体形式为(1)(0)(1)(0)(A2)(A1)(A0)(R/W),其中R/W=1, //表示读操作,R/W=0为写操作,A2,A1,A0为EEPROM的页选或片选地址; //EepromType为枚举变量,需为M2401至M24256中的一种,分别对应24C01至24C256; //函数返回值为一个位变量,若返回1表示此次操作失效,0表示操作成功; //ERRORCOUNT为允许最大次数,若出现ERRORCOUNT次操作失效后,则函数中止操作,并返回1 //SDA和SCL由用户自定义,这里暂定义为P0^0和P0^1; //其余的用户不用管,只要把只子程序放在你的程序中并调用它就可以了; /***********************************************************************************/ bit RW24XX(unsigned char *DataBuff,unsigned char ByteQuantity,unsigned int Address, unsigned char ControlByte,enum eepromtype mType) { void Delay1(uchar DelayCount); void IICStart(void); void IICStop(void); bit IICRecAck(void); void IICNoAck(void); void IICAck(void); unsigned char IICReceiveByte(void); void IICSendByte(unsigned char sendbyte); unsigned char data j,i=ERRORCOUNT; bit errorflag=; while(i--) { IICStart(); IICSendByte(ControlByte&0xfe); if(IICRecAck()) continue; if(mType>M2416) { IICSendByte((unsigned )); if(IICRecAck()) continue; } IICSendByte((unsigned char)Address); if(IICRecAck()) continue; if(!(ControlByte&0x01)) { j=ByteQuantity; errorflag=; //********clr errorflag while(j--) { IICSendByte(*DataBuff++); if(!IICRecAck()) continue; errorflag=; break; } ) continue; break; } else { IICStart(); IICSendByte(ControlByte); if(IICRecAck()) continue; while(--ByteQuantity) { *DataBuff++=IICReceiveByte(); IICAck(); } *DataBuff=IICReceiveByte(); //read last byte data IICNoAck(); errorflag=; break; } } IICStop(); if(!(ControlByte&0x01)) { Delay1(); Delay1(); Delay1(); Delay1(); } return(errorflag); } /*****************以下是对IIC总线的操作子程序***/ /*****************启动总线**********************/ void IICStart(void) { SCL=; // SDA=; SCL=; _nop_(); _nop_(); _nop_(); SDA=; _nop_(); _nop_(); _nop_(); _nop_(); SCL=; SDA=; // } /*****************停止IIC总线****************/ void IICStop(void) { SCL=; SDA=; SCL=; _nop_(); _nop_(); _nop_(); SDA=; _nop_(); _nop_(); _nop_(); SCL=; } /**************检查应答位*******************/ bit IICRecAck(void) { SCL=; SDA=; SCL=; _nop_(); _nop_(); _nop_(); _nop_(); CY=SDA; //因为返回值总是放在CY中的 SCL=; return(CY); } /***************对IIC总线产生应答*******************/ void IICACK(void) { SDA=; SCL=; _nop_(); _nop_(); _nop_(); _nop_(); SCL=; _nop_(); SDA=; } /*****************不对IIC总线产生应答***************/ void IICNoAck(void) { SDA=; SCL=; _nop_(); _nop_(); _nop_(); _nop_(); SCL=; } /*******************向IIC总线写数据*********************/ void IICSendByte(unsigned char sendbyte) { unsigned ; ;j--) { SCL=; sendbyte<<=; //无论C51怎样实现这个操作,始终会使CY=sendbyte^7; SDA=CY; SCL=; } SCL=; } /**********************从IIC总线上读数据子程序**********/ unsigned char IICReceiveByte(void) { register receivebyte,i=; SCL=; while(i--) { SCL=; receivebyte=(receivebyte<<)|SDA; SCL=; } return(receivebyte); } /***************一个简单延时程序************************/ void Delay1(uchar DelayCount) { while(--DelayCount); }
但实际使用中发现它有个严重的错误。就是没有考虑到不同的I2C器件在写页操作时,是有一页数据限制的,AT24C01是8个字节,AT24C02-16是16个字节,AT24C33-64是32个字节。AT24C128-256是64个字节。AT24C512是128个字节。AT24C1024是256个字节。
这是器件说明书上的资料,我手上只有AT24C02、AT24C16和AT24C256,实际使用了一下,AT24C02的页限制是8个字节。另两个是和说明书上一致的。当我写的数据量超出页限制时,地址指针就会回到起始点覆盖原来的数据。例如我以页方式写10个字节的数据到AT24C02时。前8个字节的数据正常写入,第9、第10个数据就回头覆盖掉了第一、第二个数据。
你如果把265个字节的数据往AT24C02进行写页操作,它也不报错,就一个劲地反复覆盖你8个字节的空间,再比如你往AT24C256里输入一个字节的地址,或往AT24C02里输入两个字节的地址,它也不报错,就这么胡乱地把地址当数据或把数据当成地址往里一阵乱写。当你读取数据时才发现里面混乱不堪。这使得上面那个读写AT24CXXX器件的程序怎么运行都不会出错。我觉得这是AT24CXX器件设计上比较弱智的地方。
所以上面的程序得修改。