wordpress 基础建站,网站开发需要文章写的好吗,网站后台上传图片失败,网站规划与建设需求分析总结下上篇博文的结论#xff1a;
1#xff1a;ACK信号在SCL为高电平期间会一直保持。
2#xff1a;在字节数据传输过程中如果发送电平跳变#xff0c;那么电平信号就会变成重复起始或者结束的信号。#xff08;上篇博文的测试方法还是不能够明确证明这个结论#xff0…总结下上篇博文的结论
1ACK信号在SCL为高电平期间会一直保持。
2在字节数据传输过程中如果发送电平跳变那么电平信号就会变成重复起始或者结束的信号。上篇博文的测试方法还是不能够明确证明这个结论因为笔者的开发版晶振已经固定是11.0592M而改变电平信号是用赋值语句比如SDA 0它是需要1个机器周期的即1us。而24C02是可以工作在400KB的速率下的。因此笔者由程序引起电平跳动时序过程是必然满足起始或者结束条件的时序时间的因此这个结论只能是可能。 在字节数据传输过程中如果发送电平跳变那么电平信号就会可能变成重复起始或者结束的信号。所以如果电平信号的变化持续时间很短I2C器件最终会读到什么信号依然不是很明确。笔者手上的板子应是没有办法做实验测试了,笔者在6T的模式下即1个机器周期为0.5us的模式下测试跳变电平依然被识别成起始或者结束的信号400KB模式下高电平最小值是0.6us。 当然这只是软件工具对电平信号的识别与实体24C02对电平信号的判读笔者觉得应是有区别的。怎么理解呢每个I2C通信器件都有自己的时序要求因此某个跳变信号被器件A认为是起始信号但对于器件B它可能没达到起始信号的时序要求器件内部某项功能的开启都是使能内部功能电路工作再快速它都是需要时间的如果时间没达到文档给出的时序要求那该功能可能开启可也可能没开启就不被认为是起始信号。但它确实发生了一个跳变电平信号那么对于器件B它读到的会是什么信号呢依然是一个跳变信号只是这个跳变信号无法使能起始信号这个信号在软件工具里被捕捉为起始信号标识)因此这个信号如果功能使能了就会变成功能信号如果这个信号没被使能并且没干扰其他信号那该信号就是一个无用的“废”信号它“来”了又好像没“来”过。 E2PROM的学习 在实际的应用中保存在单片机RAM中的数据掉电后就丢失了保存在单片机的FLASH中的数据又不能随意改变也就是不能用它来记录变化的数值。但是在某些场合又确实需要记录下某些数据而且它们还时常需要改变或跟新掉电之后数据还不能丢失比如家用电表度数电视机里边的频道记忆一般都是使用E2PROM来保存数据特点是掉电后不丢失。开发板上使用的这个器件是24C02一个容量大小是2KB也就是256个字节的E2PROM。一般情况下E2PROM拥有30~100万次的寿命。 24C02是一个基于I2C通信协议的E2PROM器件。
E2PROM单字节读写操作时序 上篇博文对E2PROM器件进行寻址并且检测了ACK本篇将读取E2PROM的0x02这个地址上的一个数据不管这个数据之前是多少都将读出来的数据加1再写到E2PROM的0x02这个地址上。并用LCD1602显示出来。
看程序
main.c
# includereg52.hextern void InitLcd1602();
extern void LcdShowStr(unsigned char x,unsigned char y, unsigned char *str);
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
unsigned char E2ReadByte(unsigned char addr);
void E2WriteByte(unsigned char addr, unsigned char dat);void main()
{unsigned char dat;unsigned char str[10];InitLcd1602(); //初始化液晶dat E2ReadByte(0x02);//读取指定地址上的一个字节str[0] (dat/100) 0; //转换为十进制字符串格式str[1] (dat/10%10) 0;str[2] (dat %10) 0;str[3] \0;LcdShowStr(0,0,str); //显示在液晶上dat; //dat值加1E2WriteByte(0x02,dat); //再将其写回到24C02对应的地址上while(1);}/* 读取EEPROM中的一个字节addr为字节地址 */
unsigned char E2ReadByte(unsigned char addr)
{unsigned char dat;I2CStart();I2CWrite(0x501); //寻址器件后续为写操作I2CWrite(addr); //写入储存地址I2CStart(); //发送重复启动信号I2CWrite(0x501 | 0x01); //寻址器件后续为读操作dat I2CReadNAK(); //读取1个字节数据I2CStop();return dat;}
/*向EEPROM中写入一个字节addr为字节地址 */
void E2WriteByte(unsigned char addr,unsigned char dat)
{I2CStart();I2CWrite(0x501); //寻址器件后续为写操作I2CWrite(addr); //写入存储地址I2CWrite(dat); //写入一个字节数据I2CStop();}
I2C.C
# includereg52.h
# includeintrins.h# define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
// # define I2CDelay() {_nop_();}
sbit I2C_SCL P3^7;
sbit I2C_SDA P3^6; /* 产生总线起始信号 */
void I2CStart()
{I2C_SDA 1; //首先确保SDASCL都是高电平I2C_SCL 1;I2CDelay();I2C_SDA 0; //先拉低SDAI2CDelay();I2C_SCL 0; //再拉低SCL}/* 产生总线停止信号 */
void I2CStop()
{I2C_SCL 0; //首先确保SDASCL都是低电平I2C_SDA 0;I2CDelay();I2C_SCL 1; //先拉高SCL的电平I2CDelay();I2C_SDA 1; //再拉高SDA的电平I2CDelay();}/*I2C总线写操作dat为待写入字节返回值为从机的应答位的值 */
bit I2CWrite(unsigned char dat)
{bit ack; //用于暂存应带位的值unsigned char mask; //用于探测字节内一位值的掩码变量for(mask 0x80; mask ! 0; mask 1)//从高位依次进行{if((maskdat) 0)I2C_SDA 0;elseI2C_SDA 1; //通过上述语句把dat的8位电平信息从最高位开始依次发出I2CDelay();I2C_SCL 1;I2CDelay();I2C_SCL 0; //再拉低SCL完成一个位周期}I2C_SDA 1; //8位数据发送完后主机释放SDA以检测从机应答I2CDelay();I2C_SCL 1; //拉高SCLack I2C_SDA;//读取此时的SDA的值即为从机的应答值I2CDelay();I2C_SCL 0; //再拉低SCL完成应答位并保持住总线return(~ack); //应答值取反符合通常的逻辑0 不纯在//或忙或写入失败1 纯在且空闲或者写入成功}
/* I2C总线读操作并发送非应答信号返回值为读到的字节 */
unsigned char I2CReadNAK()
{unsigned char mask;unsigned char dat;I2C_SDA 1; //首先确保主机释放SDAfor(mask 0x80; mask ! 0; mask 1) //从高位到低位依次进行{I2CDelay();I2C_SCL 1; //拉高SCLif(I2C_SDA 0) //读取SDA的值dat ~mask; //为0时dat中对应位清零elsedat | mask; //为1时dat中对应位置1I2CDelay();I2C_SCL 0; //再拉低SCL以使从机发送下一位}I2C_SDA 1; //8位数据发送完后拉高SDA发送非应答信号I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成非应答位并保持住总线return dat;}/* I2C总线操作并发送应答信号返回值为读到的字节 */
unsigned char I2CReadACK()
{unsigned char mask;unsigned char dat;I2C_SDA 1;for(mask 0x80; mask ! 0; mask 1){I2CDelay();I2C_SCL 1;if(I2C_SDA 0)dat ~mask;elsedat | mask;I2CDelay();I2C_SCL 0;//再拉低SCL以使从机发送出下一位}I2C_SDA 0; //8位数据发送完后拉低SDA。发送应答信号I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成应答并保持住总线return dat;}
1602LCD.C
#includereg52.h#define LCD1602_DB P0sbit LCD1602_RS P1^0;
sbit LCD1602_RW P1^1;
sbit LCD1602_E P1^5;/*等待液晶准备好“忙”判断 */
void LcdWaitReady()
{unsigned char sta;LCD1602_DB 0xFF;LCD1602_RS 0;LCD1602_RW 1;do{LCD1602_E 1;sta LCD1602_DB; //read the status of bit 7 postionLCD1602_E 0;} while(sta 0x80);// bit 7 equal 1,indicating that LCD is busy.Repeat the detection until it equal 0.
}
/*向LCD1602液晶写入一字节命令,cmd为待写入命令值 */
void LcdWriteCmd(unsigned char cmd)
{LcdWaitReady();LCD1602_RS 0;LCD1602_RW 0;LCD1602_DB cmd;//High Pulse operation ,Default state is low levelLCD1602_E 1;LCD1602_E 0;}
/*向LCD1602液晶写入一字节数据dat为待写入数据值 */
void LcdWriteDat(unsigned char dat)
{LcdWaitReady();LCD1602_RS 1;LCD1602_RW 0;LCD1602_DB dat;//High Pulse operation ,Default state is low levelLCD1602_E 1;LCD1602_E 0;
}
/*设置显示RAM的起始地址亦即光标位置(x,y) 为对于屏幕上的字符坐标 */
void LcdSetCursor(unsigned char x, unsigned char y)
{unsigned char addr;if(y 0) addr 0x00 x; //The first line adress starts from 0x00;elseaddr 0x40 x; //The second line adress starts from 0x40;LcdWriteCmd(addr|0x80);//this operation is actually adding 0x80 to the addr.}
/*在液晶上显示字符串(x,y)为对应屏幕上的起始坐标str为字符指针len为需要显示的字符长度 */
void LcdShowStr(unsigned char x,unsigned char y, unsigned char *str)
{LcdSetCursor(x,y); //Set the starting position of the cursorwhile(*str ! \0){LcdWriteDat(*str);// Continuously write len character data}}
/*初始化1602液晶 */
void InitLcd1602()
{LcdWriteCmd(0x38);//0x38 0011 1000 16*2显示5*7点阵8位数据接口LcdWriteCmd(0x08);//显示关闭LcdWriteCmd(0x01);//清屏LcdWriteCmd(0x06);//0x04 0000 0100 文字不动地址自动加1LcdWriteCmd(0x0C);//显示器开 光标关闭}看下程序结果 开关一次电源开关地址0x02储存的数值显示在液晶上然后把数值加1储存到0x02地址上。即该次显示的数值是上次开关动作的时候储存在0x02地址里面的信息。液晶分别显示了177、178、179程序工作正常。这个程序在编译的会有个警告是因为没有调用I2CReadACK()这个函数。
看一下24C02单字节的读写操作时序图 上图是24C02写字节时序图因此这个E2PROM写数据的流程是
首先是I2C的起始信号接着跟上首字节也就是I2C的器件地址本案是24C02的器件地址并且读写方向上选择“写”操作。 发送数据的存储地址。24C02一共256个字节的存储空间。地址从0x00~0xFF想把数据存储在哪个位置此刻写的就是哪个地址。 发送要存储的数据第一个字节 、第二个字节......注意在写数据的过程中E2PROM每个字节都会回应一个“应答位0”来告诉我们写E2PROM数据成功如果没有应答位说明写入不成功。 在写数据的过程中每成功写入一个字节E2PROM存储空间的地址就会自动加1当加到0xFF后在写入一个字节地址就会溢出又变成0x00。
E2PROM读数据流程 首先I2C的起始信号接着跟上首字节也就是I2c的器件地址并且在读写功能选择“写”操作。这地方可能会有异议明明是读数据为何方向也要选“写”呢24C02一个256个地址选择写操作是为了把所要读的数据的储存地址先写进去告诉E2PROM要读取哪个地址的数据。发送要读取的数据的地址注意是地址而非存在E2PROM中的数据通知E2PROM要哪个分机的信息。重新发送I2C起始信号和器件地址并且在方向上选择“读”操作在这三步当中每一个字节实际上都是在“写”所以每一个字节E2PROM都会回应一个“应答位0”。即ACK信号来自从机读取从器件发回的数据读一个字节如果还想继续读下一个字节就发送一个“应答位ACK0”如果不想读了告诉E2PROM不想要数据了别再发数据了那就发送1个“非应答位NAK(1)”.和写操作规则一样每读取一个字节地址会自动加1如果想继续往下读给E2PROM一个ACK0那再继续给SCL完整的时序E2PROM会继续往外送数据。如果不想读了要告诉E2PROM不要数据了直接给1个MAK(1)高电平即可。梳理几个要点1在本例中主机是单片机24C02是从机。2无论读写SCL始终都是由主机控制的。3写的时候应答信号是由从机发出表示从机是否正确接收了数据 .4)读的时候应答信号由主机给出表示是否继续读下去除了读地址的时候
看一下主程序程序是如何编写的 InitLcd1602(); //初始化液晶dat E2ReadByte(0x02);//读取指定地址上的一个字节
初始化液晶后E2ReadByte0x02函数返回值赋值给变量dat看一下“读”这个函数怎么 编写的。
/* 读取EEPROM中的一个字节addr为字节地址 */
unsigned char E2ReadByte(unsigned char addr)
{unsigned char dat;I2CStart();I2CWrite(0x501); //寻址器件后续为写操作I2CWrite(addr); //写入储存地址I2CStart(); //发送重复启动信号I2CWrite(0x501 | 0x01); //寻址器件后续为读操作dat I2CReadNAK(); //读取1个字节数据I2CStop();return dat;}
编写起始信号写入首字节器件地址与方向即;1010 0000 0x501读写方向为“写”ACK为从机写入内存地址ACK为从机重复起始信号写入器件地址方向为“读”。ACK为从机即读地址是写模式使能。读取确认的地址的字节数据赋值给变量dat并发送NAK给从机。发送停止信号变量dat的内容作为函数返回值 str[0] (dat/100) 0; //转换为十进制字符串格式str[1] (dat/10%10) 0;str[2] (dat %10) 0;str[3] \0;LcdShowStr(0,0,str); //显示在液晶上
dat的值是8位的因此最大的值就是255.即3位就可以表达完全部可能的数据。通过函数变换把百位十位个位上的值转换为字节格式最终显示在液晶1602上。 dat; //dat值加1E2WriteByte(0x02,dat); //再将其写回到24C02对应的地址上while(1);
dat的值加1
/*向EEPROM中写入一个字节addr为字节地址 */
void E2WriteByte(unsigned char addr,unsigned char dat)
{I2CStart();I2CWrite(0x501); //寻址器件后续为写操作I2CWrite(addr); //写入存储地址I2CWrite(dat); //写入一个字节数据I2CStop();}
开始信号写入首字节器件地址读写方向是“写”0ACK来自从机写入字节存储地址0x02写入数据dat写入结束信号
本函数至此结束上述流程是符合时序图给出的流程。前文提到在写数据的时候ACK不是即刻响应的这里也没有作判断。主要是这边只写入1个字节后续就不再写入了。而且开发板使用的是机械开关而写入的最大时间是5ms因此无论怎么操作开关24C02都有足够的时间把数据搬到“非易失区”。 E2PROM多字节读写操作时序 读取E2PROM的时候很简单E2PROM根据所送的时序直接就把数据送出来了但是写E2PROM却没有这么简单。给E2PROM发送数据后先保存在E2PROM的缓存中E2PROM必须要把缓存中的数据搬移到“非易失”的区域才能达到掉电不丢失的效果。而往非易失区写需要一定的时间每种器件不完全一样ATMEL公司的24C02的这个写入时间最高不超过5ms。在往非易失区域写的过程E2PROM都不会应答就如同这个总线上没有这个器件一样。数据写入非易失区域完毕后E2PROM再次恢复正常可以正常读写。
多字节写入并在LCD1602液晶上显示看程序
main.c
#include reg52.hextern void InitLcd1602();
extern void LcdShowStr(unsigned char x,unsigned char y, unsigned char *str);
extern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);
void E2Read(unsigned char* buf,unsigned char addr,unsigned char len);
void E2Write(unsigned char* buf,unsigned char addr,unsigned char len);
void MemToStr(unsigned char* str,unsigned char* src,unsigned char len); void main()
{unsigned char i;unsigned char buf[5];unsigned char str[20];InitLcd1602(); //初始化液晶E2Read(buf,0x90,sizeof(buf)); //从E2中读取一段数据MemToStr(str,buf,sizeof(buf)); //转换为16进制字符串LcdShowStr(0,0,str); //显示到液晶上for(i 0; i sizeof(buf);i) //数据依次123{buf[i] buf[i]1i; //即buf[0]加1buf[1]加2是我们要求该式子即是满足这个要求}E2Write(buf,0x90,sizeof(buf));//再写回到E2中while(1);}/* 将一段内存数据转换为十六进制格式的字符串str为字符串指针src为源数据buf数组地址len为buf数组数据长度 */void MemToStr(unsigned char *str,unsigned char *src,unsigned char len)
{unsigned char tmp;while(len--){tmp *src 4; //先取高4位if(tmp 9 ) //转换为0-9或A-F*str tmp 0;else*str tmp - 10 A;tmp *src 0x0F; //再取低4位if(tmp 9) //转换为0-9或A-F*str tmp0;else*str tmp - 10 A;*str ; //转换完1个字节添加一个空格src;}}/*E2读取函数buf为数据接收指针addr位E2中的起始地址len位读取长度 */
void E2Read(unsigned char *buf,unsigned char addr, unsigned char len)
{do{I2CStart(); //用寻址操作查询当前是否可进行读写操作if(I2CWrite(0x50 1)) //应答则跳出循环非应答则进行下一次查询 0x501 1010 0000{break;}I2CStop(); }while(1); I2CWrite(addr); //写入起始地址I2CStart(); //发送重复启动信号I2CWrite((0x50 1)|0x01); //寻址器件后续为读操作while(len 1) //连续读取len-1个字节{*buf I2CReadACK(); //最后字节之前为读取操作应答len--;}*buf I2CReadNAK(); //最后一个字节为读取操作非应答I2CStop(); }/* E2写入函数buf为数据指针addr为E2中的起始地址len为写入长度*/
void E2Write(unsigned char *buf,unsigned char addr,unsigned char len)
{while(len--){do{I2CStart(); //寻址操作查询当前是否可进行读写操作if(I2CWrite(0x50 1))//应答则跳出循环非应答则进行下一次查询 0x501 1010 0000{break;}I2CStop();}while(1);I2CWrite(addr); //写入起始地址I2CWrite(*buf); //写入一个字节数据I2CStop(); //结束写操作以等待写入完成}}I2C.c
LCD1602.c
这两个文件见前文 给出的文档。
分析下主函数 InitLcd1602(); //初始化液晶E2Read(buf,0x90,sizeof(buf)); //从E2中读取一段数据
/*E2读取函数buf为数据接收指针addr位E2中的起始地址len位读取长度 */
void E2Read(unsigned char *buf,unsigned char addr, unsigned char len)
{do{I2CStart(); //用寻址操作查询当前是否可进行读写操作if(I2CWrite(0x50 1)) //应答则跳出循环非应答则进行下一次查询 0x501 1010 0000{break;}I2CStop(); }while(1); I2CWrite(addr); //写入起始地址I2CStart(); //发送重复启动信号I2CWrite((0x50 1)|0x01); //寻址器件后续为读操作while(len 1) //连续读取len-1个字节{*buf I2CReadACK(); //最后字节之前为读取操作应答len--;}*buf I2CReadNAK(); //最后一个字节为读取操作非应答I2CStop(); }
液晶初始化起始信号寻址器件地址0x50方向为“写”(0),对函数I2CWrite(0x50 1)返回值进行判断如果是1执行break跳出循环。因为对返回值进行了取反即ACK是“0”的时候跳出循环。类似1602液晶的“忙”判断。写入内存地址重复起始信号写入器件地址读写方向选择“读”把24C02器件从0x90地址开始往后共4个存储空间包括它自己的数据按顺序赋值给buf[]数组前4个数组元素(ACK来自主机)0x90地址开始往后第5个存储空间0x94的数据赋值给buf[4],即buf[]数组的最后一个元素。并发送NAK发送结束信号
接着是 MemToStr(str,buf,sizeof(buf)); //转换为16进制字符串
/* 将一段内存数据转换为十六进制格式的字符串str为字符串指针src为源数据buf数组地址len为buf数组数据长度 */
void MemToStr(unsigned char *str,unsigned char *src,unsigned char len)
{unsigned char tmp;while(len--){tmp *src 4; //先取高4位if(tmp 9 ) //转换为0-9或A-F*str tmp 0;else*str tmp - 10 A;tmp *src 0x0F; //再取低4位if(tmp 9) //转换为0-9或A-F*str tmp0;else*str tmp - 10 A;*str ; //转换完1个字节添加一个空格src;}}
以buf[0]0x2D为例当执行完第1遍这个函数的时候STR数组里的数组元素是 5次循环结束后跳出while函数buf[]数组的5个元素分别转化为字节格式存储在str数组里。共计15个元素。
接着 LcdShowStr(0,0,str); //显示到液晶上for(i 0; i sizeof(buf);i) //数据依次123{buf[i] buf[i]1i; //即buf[0]加1buf[1]加2是我们要求该式子即是满足这个要求}E2Write(buf,0x90,sizeof(buf));//再写回到E2中while(1);
buf[]相对应的元素加上相应的值最后存到以0x90开头往后共5个地址中。
/* E2写入函数buf为数据指针addr为E2中的起始地址len为写入长度*/
void E2Write(unsigned char *buf,unsigned char addr,unsigned char len)
{while(len--){do{I2CStart(); //寻址操作查询当前是否可进行读写操作if(I2CWrite(0x50 1))//应答则跳出循环非应答则进行下一次查询 0x501 1010 0000{break;}I2CStop();}while(1);I2CWrite(addr); //写入起始地址I2CWrite(*buf); //写入一个字节数据I2CStop(); //结束写操作以等待写入完成}}1写入开始信号对器件寻址检测其ACK。检测到ACK后跳出循环
2 写入器件地址
3写入buf数据
4写入结束信号进行下一个循环
看下程序结果 总结1下
1函数E2Read在读之前要查询一下当前是否可以进行读写操作E2PROM正常相应才可以进行。进行后读最后一个字节前全部给出ACK而读完最后1个字节要给出NAK。(其实对本程序来说是不需要进行类似“忙”判断的写法直接写地址就可以了但是如果作为模块化比如先进行“写”数据的操作马上接“读”的操作或者工作比较复杂的时候那么就需要进行ACK判断了)
2函数E2Write每次操作之前都要进行查询判断当前E2PROM是否响应正常响应后才可以写数据。 E2PROM的页写入
在向E2PROM连续写入多个字节的数据时如果每写一个字节都要等待几ms的话整体上的写入效率就太低了。因此E2PROM的厂商就想了一个办法把E2PROM分页管理。24C02、24C01这两个信号是8个字节一页。 24C02一共是256个字节8个字节1页那么一共就32页。 分配好页之后如果在同一个页连续写入几个字节后最后再发送停止位的时序。E2PROM检测到这个停止位后就会一次性把这一页的数据写入非易失区域就不需要像上节那样写一个字节检测一次并且页写入的时间也不会超过5ms。 如果写入的数据跨页了那么写完一页之后要发送一个停止位然后等待并检测E2PROM的空闲模式一直等到把上一页数据完全写到非易失区域后再进行下一页的写入这样就可以很大程度提高数据的写入效率。
如图24C02页写入 在看一下单字节写入 可以看到似乎没什么太大的区别页写入的时候字节是连续写入的但ACK好像是即刻反馈似乎和前文说的不一样笔者之前以为ACK是数据搬运到非易失区后再发送的ACK事实上是存储在缓冲区后就发送了那上篇博文的描述可能有误前文说道写入过程是缓冲区的数据搬运到“非易失区“的过程那么ACK是什么时候发出的搬运到非易失区是什么时候发生的通过逻辑分析仪抓到的时序图联系前后文可知这么一个情况在连续写入数据的时候依然会即刻检测到从机发出的ACK但这个ACK只代表数据转移到了缓冲区这个缓冲区我觉得可以理解为存到了RAM中。再写入一个数据从机依然会即刻发送一个ACK这个时候缓冲区里存了两个数据这两个数据都没有转移到非易失区。那什么时后转移到非易失区当主机发出STOP时序信号的时候这个时候缓冲区中的数据会开始搬到非易失区这个时候如果你马上去检测器件地址是检测不到ACK的因为此时24C02它还处在“搬运”状态。因此在完成一次页写入进行第二次页写入的时候就需要重新对器件地址寻址以判断E2PROM器件是否响应即数据搬运结束或者处于空闲状态由上述描述可知其实单字节写入就是页写入的一个变种而已页写入但只写一个字节就发送stop信号不就是单字节写入了那就出现一个问题缓冲区一次可以存储多少个字节24C02应该是8个即1页如果写9个进去会发生什么暂时不知后续如果有精力笔者可以写个程序测试一下。
贴一下页写入的程序
main.c
# includereg52.hextern void InitLcd1602();
extern void LcdShowStr(unsigned char x,unsigned char y, unsigned char *str);
extern void E2Read(unsigned char* buf,unsigned char addr,unsigned char len );
extern void E2Write(unsigned char* buf,unsigned char addr, unsigned char len);
extern void MemToStr(unsigned char* str,unsigned char* src,unsigned char len);void main()
{unsigned char i;unsigned char buf[5];unsigned char str[20];InitLcd1602(); //初始化液晶E2Read(buf,0x8E,sizeof(buf)); //从E2中读取一段数据MemToStr(str,buf,sizeof(buf)); //转换为字符串格式LcdShowStr(0,0,str); //显示到液晶上for(i0;isizeof(buf);i) //数据依次123{buf[i] buf[i]1i;}E2Write(buf,0x8E,sizeof(buf)); //再写回到E2中while(1);}
/*将一段内存数据转换为十六进制格式的字符串 str为字符串指针src为源数据地址len为数据长度 */
void MemToStr(unsigned char *str,unsigned char *src,unsigned char len)
{unsigned char tmp;while(len--){tmp *src 4; //先取高4位if(tmp 9 ) //转换为0-9或A-F*str tmp 0;else*str tmp - 10 A;tmp *src 0x0F; //再取低4位if(tmp 9) //转换为0-9或A-F*str tmp0;else*str tmp - 10 A;*str ; //转换完1个字节添加一个空格src;}}
E2PROM.c
# includereg52.hextern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);/*E2读取函数buf为数据接收指针addr为E2中的起始地址len为读取长度 */void E2Read(unsigned char* buf,unsigned char addr,unsigned char len )
{do{I2CStart(); //用寻址操作查询当前是否可以进行读写操作if(I2CWrite(0x501)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();}while(1);I2CWrite(addr); //写入起始地址I2CStart(); //发送重复启动信号I2CWrite((0x501) | 0x01);//寻址器件后续为读操作while(len 1) //连续读取len-1个字节{*buf I2CReadACK(); //最后字节前为读取操作应答len--;}*buf I2CReadNAK(); //最后一个字节为读操作非应答I2CStop();
}/* E2写入函数buf为数据指针addr为E2中的起始地址len为写入长度 */
void E2Write(unsigned char* buf,unsigned char addr, unsigned char len)
{while(len 0){ //等待上次写入操作完成do{ //用寻址操作查询当前是否可以进行读写操作I2CStart(); if(I2CWrite(0x50 1)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();}while(1);
//按页写入模式连续写入字节 I2CWrite(addr); //写入起始地址while(len 0){I2CWrite(*buf); //写入一个字节数据len--; //待写入长度计数递减addr; //E2地址递增if((addr0x07) 0)//检查地址是否到达页边界24C02每页8字节{ //所以检测低3位是否为0即可break; //到达页边界时跳出循环结束本次写操作}}I2CStop();}}
LCD1602.c和I2C.c见前文给出的。
看下结果程序功能是和连续写入一样的只是写入方式现在是页写入看下结果 采用页写入的原因是因为页写入消耗的时间更短通过逻辑分析仪抓到的时序图
单字节连续写入时序图写入过程耗时8.4ms
页写入时序图写入过程耗时3.5ms可以看到整整少了5ms。
再分析一下页写入程序是怎么工作的 /* E2写入函数buf为数据指针addr为E2中的起始地址len为写入长度 */
void E2Write(unsigned char* buf,unsigned char addr, unsigned char len)
{while(len 0){ //等待上次写入操作完成do{ //用寻址操作查询当前是否可以进行读写操作I2CStart(); if(I2CWrite(0x50 1)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();}while(1);
//按页写入模式连续写入字节 I2CWrite(addr); //写入起始地址while(len 0){I2CWrite(*buf); //写入一个字节数据len--; //待写入长度计数递减addr; //E2地址递增if((addr0x07) 0)//检查地址是否到达页边界24C02每页8字节{ //所以检测低3位是否为0即可break; //到达页边界时跳出循环结束本次写操作}}I2CStop();}}
124c02器件寻址判断是否响应
2I2CWrite(addr); 写入内存地址由前后文可知是0x8E那么要重新写入的5个地址分别是 0x8E0x8F0x900x910x92这5个地址由于是分页写入那么0x8E0x8F0x90这三个地址是一页0x910x92这两个地址是一页。
3whilelen0函数如果写入地址到达边界跳出循环主机发出Stop信号。
由程序可以这个循环中它只写入了0x8E与0x8F当前页的最后一个地址它没写就跳出循环了接着进行“搬运”工作了是不是程序写错了继续读程序
4又回到上一个whilelen0循环开始24C02的器件寻址这时的ACK响应这次的ACK响应应该会发生延时
5ACK响应后I2CWrite(addr);这时的addr是0x90然后进入while循环0x910x92地址都写入数据
6主机发送Stop开始向三个内存空间搬运数据即搬运到非易失区。
由上可知的时序是如图 看下逻辑分析仪的时序图 可以看到确实是和笔者之前的示意图一样的流程。
由时序图可知进行了跨页写入即在一次写入中同时对两页进行写入。这是被允许的。就看你程序怎么写了。本质上就是24C02一次最多只能写入8个数据写满后就要进行一次缓冲区到非易失区的搬运工作。
总结一下
对E2PROM器件24C02的读写模式进行了基础探索从使用结果来说ACK基本上都是下个时序即刻响应的只在缓冲区的数据搬运到非易失区的时候需要从新对器件进行ACK检测来判断器件是否空闲或者叫有效把缓充区的数据搬运到非易失区的信号是Stop信号。这个程序本质上对ACK都是直接读写的不是把ACK作为一个判断信号进行后续的语句除了第二条搬运的情况。这可能存在风险即如果器件受干扰或者什么原因ACK没有在字节数据的下个时序发出。但程序还是依然向器件写入数据而不是等待这就可能造成错误。当然一般情况下符合时序要求就没问题因此无论什么时序下拉长ACK响应的SCL电平时间SCL为低电平的时间可能是个不错的选择。