|
12.3 EEPROM的學(xué)習(xí) 在實(shí)際應(yīng)用中,保存在單片機(jī)RAM中的數(shù)據(jù),掉電后就丟失了,使用code關(guān)鍵字保存在單片機(jī)的FLASH中的數(shù)據(jù),又不能隨意改變,也就是不能用它來(lái)記錄變化的數(shù)值。但是在某些場(chǎng)合,又確實(shí)需要記錄下某些數(shù)據(jù),而它們還時(shí)常需要改變或更新,掉電之后數(shù)據(jù)還不能丟失,比如家用電表度數(shù),電視機(jī)里邊的頻道記憶,一般都是使用EEPROM來(lái)保存數(shù)據(jù),特點(diǎn)就是掉電后不丟失。Kingst51開(kāi)發(fā)板上使用的這個(gè)器件是24C02,是一個(gè)容量大小是2Kbits,也就是256個(gè)字節(jié)的EEPROM。一般情況下,EEPROM擁有30萬(wàn)到100萬(wàn)次的壽命,也就是它可以反復(fù)寫(xiě)入30-100萬(wàn)次,而讀取次數(shù)是無(wú)限的。 24C02是一個(gè)基于I2C通信協(xié)議的器件,但要分清楚,I2C是一個(gè)通信協(xié)議,它擁有嚴(yán)密的通信時(shí)序邏輯要求,而EEPROM是一個(gè)器件,只是這個(gè)器件采用了I2C協(xié)議的接口與單片機(jī)相連而已,二者并沒(méi)有必然的聯(lián)系,EEPROM可以用其它接口,I2C也可以用在其它很多器件上。 12.3.1 EEPROM單字節(jié)讀寫(xiě)操作時(shí)序1、EEPROM寫(xiě)數(shù)據(jù)流程 (1)首先是I2C的起始信號(hào),接著跟上首字節(jié),也就是I2C的器件地址,并且在讀寫(xiě)方向上選擇“寫(xiě)”操作。 (2)發(fā)送數(shù)據(jù)的存儲(chǔ)地址。24C02一共256個(gè)字節(jié)的存儲(chǔ)空間,地址從0x00~0xFF,想把數(shù)據(jù)存儲(chǔ)在哪個(gè)位置,此刻寫(xiě)的就是哪個(gè)地址。 (3)發(fā)送要存儲(chǔ)的數(shù)據(jù)第一個(gè)字節(jié)、第二個(gè)字節(jié)……注意在寫(xiě)數(shù)據(jù)的過(guò)程中,EEPROM每個(gè)字節(jié)都會(huì)回應(yīng)一個(gè)“應(yīng)答位0”,來(lái)通知用戶寫(xiě)EEPROM數(shù)據(jù)成功,如果沒(méi)有回應(yīng)答位,說(shuō)明寫(xiě)入不成功。 在寫(xiě)數(shù)據(jù)的過(guò)程中,每成功寫(xiě)入一個(gè)字節(jié),EEPROM存儲(chǔ)空間的地址就會(huì)自動(dòng)加1,當(dāng)加到0xFF后,再寫(xiě)一個(gè)字節(jié),地址會(huì)溢出又變成了0x00。 2、EEPROM讀數(shù)據(jù)流程 (1)首先是I2C的起始信號(hào),接著跟上首字節(jié),也就是I2C的器件地址,并且在讀寫(xiě)方向上選擇“寫(xiě)”操作。明明是讀數(shù)據(jù)為何方向也要選“寫(xiě)”呢?24C02一共有256個(gè)地址,選擇寫(xiě)操作,是為了把所要讀的數(shù)據(jù)的存儲(chǔ)地址先寫(xiě)進(jìn)去,告訴EEPROM要讀取哪個(gè)地址的數(shù)據(jù)。這就如同打電話,先撥總機(jī)號(hào)碼(EEPROM器件地址),而后還要繼續(xù)撥分機(jī)號(hào)碼(數(shù)據(jù)地址),而撥分機(jī)號(hào)碼這個(gè)動(dòng)作,主機(jī)仍然是發(fā)送方,方向依然是“寫(xiě)”。 (2)發(fā)送要讀取的數(shù)據(jù)的地址,注意是地址而非存在EEPROM中的數(shù)據(jù),通知EEPROM要哪個(gè)分機(jī)的信息。 (3)重新發(fā)送I2C起始信號(hào)和器件地址,并且在方向位選擇“讀”操作。 這3步當(dāng)中,每一個(gè)字節(jié)實(shí)際上都是在“寫(xiě)”,所以每一個(gè)字節(jié)EEPROM都會(huì)回應(yīng)一個(gè)“應(yīng)答位0”。 (4)讀取從器件發(fā)回的數(shù)據(jù),讀一個(gè)字節(jié)后,如果還想繼續(xù)讀下一個(gè)字節(jié),就發(fā)送一個(gè)“應(yīng)答位ACK(0)”,如果不想讀了,通知EEPROM不想要數(shù)據(jù)了,那就發(fā)送一個(gè)“非應(yīng)答位NAK(1)”。 和寫(xiě)操作規(guī)則一樣,每讀一個(gè)字節(jié)地址會(huì)自動(dòng)加1。如果想繼續(xù)往下讀,給EEPROM一個(gè)ACK(0)低電平后,再繼續(xù)給SCL完整的時(shí)序,EEPROM會(huì)繼續(xù)往外送數(shù)據(jù)。如果不想讀了,直接給一個(gè)NAK(1)高電平。 梳理一下幾個(gè)要點(diǎn): 1、在本例中單片機(jī)是主機(jī),24C02是從機(jī); 2、無(wú)論是讀是寫(xiě),SCL始終都是由主機(jī)控制的; 3、寫(xiě)的時(shí)候應(yīng)答信號(hào)由從機(jī)給出,表示從機(jī)是否正確接收了數(shù)據(jù); 4、讀的時(shí)候應(yīng)答信號(hào)則由主機(jī)給出,表示是否繼續(xù)讀下去。 下面寫(xiě)一個(gè)程序,讀取EEPROM的0x02這個(gè)地址上的一個(gè)數(shù)據(jù),不管這個(gè)數(shù)據(jù)之前是多少都將讀出來(lái)的數(shù)據(jù)加1,再寫(xiě)到EEPROM的0x02這個(gè)地址上。此外將I2C的程序建立一個(gè)文件,寫(xiě)一個(gè)I2C.c程序文件,形成又一個(gè)程序模塊。 /*****************************I2C.c文件程序源代碼*****************************/ #include <reg52.h> #include <intrins.h> #define I2CDelay() {_nop_();_nop_();_nop_();_nop_();} sbit I2C_SCL = P3^7; sbit I2C_SDA = P3^6; /* 產(chǎn)生總線起始信號(hào) */ void I2CStart() { I2C_SDA = 1; //首先確保SDA、SCL都是高電平 I2C_SCL = 1; I2CDelay(); I2C_SDA = 0; //先拉低SDA I2CDelay(); I2C_SCL = 0; //再拉低SCL } /* 產(chǎn)生總線停止信號(hào) */ void I2CStop() { I2C_SCL = 0; //首先確保SDA、SCL都是低電平 I2C_SDA = 0; I2CDelay(); I2C_SCL = 1; //先拉高SCL I2CDelay(); I2C_SDA = 1; //再拉高SDA I2CDelay(); } /* I2C總線寫(xiě)操作,dat-待寫(xiě)入字節(jié),返回值-從機(jī)應(yīng)答位的值 */ bit I2CWrite(unsigned char dat) { bit ack; //用于暫存應(yīng)答位的值 unsigned char mask; //用于探測(cè)字節(jié)內(nèi)某一位值的掩碼變量 for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進(jìn)行 { if ((mask&dat) == 0) //該位的值輸出到SDA上 I2C_SDA = 0; else I2C_SDA = 1; I2CDelay(); I2C_SCL = 1; //拉高SCL I2CDelay(); I2C_SCL = 0; //再拉低SCL,完成一個(gè)位周期 } I2C_SDA = 1; //8位數(shù)據(jù)發(fā)送完后,主機(jī)釋放SDA,以檢測(cè)從機(jī)應(yīng)答 I2CDelay(); I2C_SCL = 1; //拉高SCL ack = I2C_SDA; //讀取此時(shí)的SDA值,即為從機(jī)的應(yīng)答值 I2CDelay(); I2C_SCL = 0; //再拉低SCL完成應(yīng)答位,并保持住總線 return (~ack); //應(yīng)答值取反以符合通常的邏輯: //0=不存在或忙或?qū)懭胧。?/font>1=存在且空閑或?qū)懭氤晒?/font> } /* I2C總線讀操作,并發(fā)送非應(yīng)答信號(hào),返回值-讀到的字節(jié) */ unsigned char I2CReadNAK() { unsigned char mask; unsigned char dat; I2C_SDA = 1; //首先確保主機(jī)釋放SDA for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進(jìn)行 { I2CDelay(); I2C_SCL = 1; //拉高SCL if(I2C_SDA == 0) //讀取SDA的值 dat &= ~mask; //為0時(shí),dat中對(duì)應(yīng)位清零 else dat |= mask; //為1時(shí),dat中對(duì)應(yīng)位置1 I2CDelay(); I2C_SCL = 0; //再拉低SCL,以使從機(jī)發(fā)送出下一位 } I2C_SDA = 1; //8位數(shù)據(jù)發(fā)送完后,拉高SDA,發(fā)送非應(yīng)答信號(hào) I2CDelay(); I2C_SCL = 1; //拉高SCL I2CDelay(); I2C_SCL = 0; //再拉低SCL完成非應(yīng)答位,并保持住總線 return dat; } /* I2C總線讀操作,并發(fā)送應(yīng)答信號(hào),返回值-讀到的字節(jié) */ unsigned char I2CReadACK() { unsigned char mask; unsigned char dat; I2C_SDA = 1; //首先確保主機(jī)釋放SDA for (mask=0x80; mask!=0; mask>>=1) //從高位到低位依次進(jìn)行 { I2CDelay(); I2C_SCL = 1; //拉高SCL if(I2C_SDA == 0) //讀取SDA的值 dat &= ~mask; //為0時(shí),dat中對(duì)應(yīng)位清零 else dat |= mask; //為1時(shí),dat中對(duì)應(yīng)位置1 I2CDelay(); I2C_SCL = 0; //再拉低SCL,以使從機(jī)發(fā)送出下一位 } I2C_SDA = 0; //8位數(shù)據(jù)發(fā)送完后,拉低SDA,發(fā)送應(yīng)答信號(hào) I2CDelay(); I2C_SCL = 1; //拉高SCL I2CDelay(); I2C_SCL = 0; //再拉低SCL完成應(yīng)答位,并保持住總線 return dat; } /****************************main.c文件程序源代碼*****************************/ #include <reg52.h> 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; dat = E2ReadByte(0x02); //讀取指定地址上的一個(gè)字節(jié) dat++; //將其數(shù)值+1 E2WriteByte(0x02, dat); //再寫(xiě)回到對(duì)應(yīng)的地址上 while (1); } /* 讀取EEPROM中的一個(gè)字節(jié),addr-字節(jié)地址 */ unsigned char E2ReadByte(unsigned char addr) { unsigned char dat; I2CStart(); I2CWrite(0x50<<1); //尋址器件,后續(xù)為寫(xiě)操作 I2CWrite(addr); //寫(xiě)入存儲(chǔ)地址 I2CStart(); //發(fā)送重復(fù)啟動(dòng)信號(hào) I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作 dat = I2CReadNAK(); //讀取一個(gè)字節(jié)數(shù)據(jù) I2CStop(); return dat; } /* 向EEPROM中寫(xiě)入一個(gè)字節(jié),addr-字節(jié)地址 */ void E2WriteByte(unsigned char addr, unsigned char dat) { I2CStart(); I2CWrite(0x50<<1); //尋址器件,后續(xù)為寫(xiě)操作 I2CWrite(addr); //寫(xiě)入存儲(chǔ)地址 I2CWrite(dat); //寫(xiě)入一個(gè)字節(jié)數(shù)據(jù) I2CStop(); } I2C.c文件提供了I2C總線底層函數(shù),包括起始、停止、字節(jié)寫(xiě)、字節(jié)讀+應(yīng)答、字節(jié)讀+非應(yīng)答。將這個(gè)程序復(fù)編譯會(huì)發(fā)現(xiàn)Keil軟件提示一個(gè)警告:*** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS,這個(gè)警告的意思是在代碼中存在沒(méi)有被調(diào)用過(guò)的變量或者函數(shù),I2C.c文件中的I2CReadACK()這個(gè)函數(shù)在本例中沒(méi)有用到。 讀取EEPROM的時(shí)候,由于只讀了一個(gè)字節(jié)就要告訴EEPROM不需要再讀數(shù)據(jù)了,讀完后直接發(fā)送一個(gè)“NAK”,因此只調(diào)用了I2CReadNAK()這個(gè)函數(shù),而并沒(méi)有調(diào)用I2CReadACK()這個(gè)函數(shù)。今后很可能讀數(shù)據(jù)的時(shí)候要連續(xù)讀幾個(gè)字節(jié),因此這個(gè)函數(shù)寫(xiě)在了I2C.c文件中,作為I2C功能模塊的一部分是必要的,方便這個(gè)文件以后移植到其他程序中使用,因此這個(gè)警告在這里就不必管它了。 將這個(gè)程序中,I2C的讀寫(xiě)EEPROM操作用邏輯分析儀抓出來(lái),并且用I2C-EEPROM協(xié)議解析出來(lái),如圖12-7所示。 圖12-7 I2C-EEPROM解析結(jié)果圖 從圖12-7能看出,第一個(gè)字節(jié)是器件地址0x50+ACK,第二個(gè)字節(jié)是數(shù)據(jù)地址0x02+ACK,第三個(gè)字節(jié)是器件地址0x50+ACK,第四個(gè)是讀取到了0x04+NAK數(shù)據(jù),第五個(gè)字節(jié)是器件地址0x50+ACK,第6個(gè)字節(jié)是數(shù)據(jù)地址0x02+ACK,第七個(gè)字節(jié)是寫(xiě)入數(shù)據(jù)0x05+ACK。 12.3.2 EEPROM多字節(jié)讀寫(xiě)操作時(shí)序讀取EEPROM的時(shí)候很簡(jiǎn)單,EEPROM根據(jù)主機(jī)的時(shí)序,直接就把數(shù)據(jù)送出來(lái)了,但是寫(xiě)EEPROM卻沒(méi)有這么簡(jiǎn)單了。給EEPROM發(fā)送數(shù)據(jù)后,先保存在了EEPROM的緩存,EEPROM必須要把緩存中的數(shù)據(jù)搬移到“非易失”的區(qū)域,才能達(dá)到掉電不丟失的效果。而往非易失區(qū)域?qū)懶枰欢ǖ臅r(shí)間,每種器件不完全一樣,ATMEL公司的24C02的這個(gè)寫(xiě)入時(shí)間最高不超過(guò)5ms。在往非易失區(qū)域?qū)懙倪^(guò)程,EEPROM是不會(huì)再響應(yīng)訪問(wèn)的,不僅接收不到數(shù)據(jù),即使用I2C標(biāo)準(zhǔn)的尋址模式去尋址,EEPROM都不會(huì)應(yīng)答,就如同這個(gè)總線上沒(méi)有這個(gè)器件一樣。數(shù)據(jù)寫(xiě)入非易失區(qū)域完畢后,EEPROM再次恢復(fù)正常。 12.2節(jié)程序中寫(xiě)數(shù)據(jù)的代碼,程序上有讀取應(yīng)答ACK,但是讀取完畢后沒(méi)有做任何處理。這是因?yàn)橐淮沃粚?xiě)一個(gè)字節(jié)的數(shù)據(jù)進(jìn)去,等到下次重新再寫(xiě)的時(shí)候,時(shí)間肯定遠(yuǎn)遠(yuǎn)超過(guò)了5ms,但是如果是連續(xù)寫(xiě)入幾個(gè)字節(jié)的時(shí)候,就必須得考慮到應(yīng)答位的問(wèn)題了。寫(xiě)入一個(gè)字節(jié)后,再寫(xiě)入下一個(gè)字節(jié)之前,必須要等待EEPROM再次響應(yīng)才可以。 先從EEPROM的0x90這個(gè)地址連續(xù)讀出4個(gè)字節(jié),然后把這4個(gè)數(shù)據(jù)分別加1,加2,加3, 加4后重新寫(xiě)入到這四個(gè)地址中去。I2C.c文件和之前是完全一樣的,因此只把main.c文件給發(fā)出來(lái)。 /****************************I2C.c文件程序源代碼******************************/ (此處省略,可參考之前章節(jié)的代碼) /****************************main.c文件程序源代碼*****************************/ #include <reg52.h> 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 main() { unsigned char i; unsigned char buf[5]; E2Read(buf, 0x90, sizeof(buf)); //從E2中讀取一段數(shù)據(jù) for (i=0; i<sizeof(buf); i++) //數(shù)據(jù)依次+1,+2,+3... { buf = buf + 1 + i; } E2Write(buf, 0x90, sizeof(buf)); //再寫(xiě)回到E2中 while(1); } /* E2讀取函數(shù),buf-數(shù)據(jù)接收指針,addr-E2中的起始地址,len-讀取長(zhǎng)度 */ void E2Read(unsigned char *buf, unsigned char addr, unsigned char len) { do { //用尋址操作查詢當(dāng)前是否可進(jìn)行讀寫(xiě)操作 I2CStart(); if (I2CWrite(0x50<<1)) //應(yīng)答則跳出循環(huán),非應(yīng)答則進(jìn)行下一次查詢 { break; } I2CStop(); } while(1); I2CWrite(addr); //寫(xiě)入起始地址 I2CStart(); //發(fā)送重復(fù)啟動(dòng)信號(hào) I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作 while (len > 1) //連續(xù)讀取len-1個(gè)字節(jié) { *buf++ = I2CReadACK(); //最后字節(jié)之前為讀取操作+應(yīng)答 len--; } *buf = I2CReadNAK(); //最后一個(gè)字節(jié)為讀取操作+非應(yīng)答 I2CStop(); } /* E2寫(xiě)入函數(shù),buf-源數(shù)據(jù)指針,addr-E2中的起始地址,len-寫(xiě)入長(zhǎng)度 */ void E2Write(unsigned char *buf, unsigned char addr, unsigned char len) { while (len--) { do { //用尋址操作查詢當(dāng)前是否可進(jìn)行讀寫(xiě)操作 I2CStart(); if (I2CWrite(0x50<<1)) //應(yīng)答則跳出循環(huán),非應(yīng)答則進(jìn)行下一次查詢 { break; } I2CStop(); } while(1); I2CWrite(addr++); //寫(xiě)入起始地址 I2CWrite(*buf++); //寫(xiě)入一個(gè)字節(jié)數(shù)據(jù) I2CStop(); //結(jié)束寫(xiě)操作,以等待寫(xiě)入完成 } } 函數(shù)E2Read:讀數(shù)據(jù)前,要查詢當(dāng)前是否允許讀寫(xiě)操作,EEPROM正常響應(yīng)才表示允許。讀最后一個(gè)字節(jié)之前的,全部給ACK,而讀完最后一個(gè)字節(jié),要給出一個(gè)NAK。 函數(shù)E2Write:寫(xiě)操作前,要查詢當(dāng)前EEPROM是否響應(yīng),正常響應(yīng)后才可以寫(xiě)數(shù)據(jù)。 將I2C多字節(jié)讀寫(xiě)EEPROM的時(shí)序部分用邏輯分析儀抓取,由于此次的讀寫(xiě)數(shù)據(jù)量特別大,因此用邏輯分析儀抓取后,直接將解析后的數(shù)據(jù)導(dǎo)出到excel表格中,如圖12-8所示。 圖12-8 連續(xù)讀寫(xiě)解析后數(shù)據(jù)示意圖 從圖12-8表格看出,第一行為讀到的4個(gè)字節(jié)的數(shù)據(jù),下面只有紅框內(nèi)為寫(xiě)入EEPROM的數(shù)據(jù),而紅框外的為檢測(cè)0x50是否響應(yīng)。由于EEPROM正在將前次寫(xiě)入的數(shù)據(jù)搬移到非易失區(qū),因此一直檢測(cè)一直等待到EEPROM響應(yīng)才能再次往里邊寫(xiě)數(shù)據(jù)。 12.3.3 EEPROM的頁(yè)寫(xiě)入在向EEPROM連續(xù)寫(xiě)入多個(gè)字節(jié)的數(shù)據(jù)時(shí),如果每寫(xiě)一個(gè)字節(jié)都要等待幾ms的話,整體上的寫(xiě)入效率就太低了。因此EEPROM的廠商就想了一個(gè)辦法,把EEPROM分頁(yè)管理。24C01、24C02這兩個(gè)型號(hào)是8個(gè)字節(jié)一個(gè)頁(yè),而24C04、24C08、24C16是16個(gè)字節(jié)一頁(yè)。Kingst51開(kāi)發(fā)板上用的型號(hào)是24C02,一共是256個(gè)字節(jié),8個(gè)字節(jié)一頁(yè),一共有32頁(yè)。 分配好頁(yè)之后,同一個(gè)頁(yè)內(nèi)連續(xù)寫(xiě)入幾個(gè)字節(jié)后再發(fā)送停止位,EEPROM檢測(cè)到停止位后,就會(huì)一次性把這一頁(yè)的數(shù)據(jù)寫(xiě)到非易失區(qū)域,不需要寫(xiě)一個(gè)字節(jié)檢測(cè)一次了,并且頁(yè)寫(xiě)入的時(shí)間也不會(huì)超過(guò)5ms。如果寫(xiě)入的數(shù)據(jù)跨頁(yè)了,寫(xiě)完了一頁(yè)之后,要發(fā)送一個(gè)停止位,然后等待并且檢測(cè)EEPROM的空閑模式,一直等到把上一頁(yè)數(shù)據(jù)完全寫(xiě)到非易失區(qū)域后,再進(jìn)行下一頁(yè)的寫(xiě)入,這樣就可以在很大程度上提高數(shù)據(jù)的寫(xiě)入效率,程序如下。 /****************************I2C.c文件程序源代碼******************************/ (此處省略,可參考之前章節(jié)的代碼) /***************************eeprom.c文件程序源代碼****************************/ #include <reg52.h> extern void I2CStart(); extern void I2CStop(); extern unsigned char I2CReadACK(); extern unsigned char I2CReadNAK(); extern bit I2CWrite(unsigned char dat); /* E2讀取函數(shù),buf-數(shù)據(jù)接收指針,addr-E2中的起始地址,len-讀取長(zhǎng)度 */ void E2Read(unsigned char *buf, unsigned char addr, unsigned char len) { do { //用尋址操作查詢當(dāng)前是否可進(jìn)行讀寫(xiě)操作 I2CStart(); if (I2CWrite(0x50<<1)) //應(yīng)答則跳出循環(huán),非應(yīng)答則進(jìn)行下一次查詢 { break; } I2CStop(); } while(1); I2CWrite(addr); //寫(xiě)入起始地址 I2CStart(); //發(fā)送重復(fù)啟動(dòng)信號(hào) I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作 while (len > 1) //連續(xù)讀取len-1個(gè)字節(jié) { *buf++ = I2CReadACK(); //最后字節(jié)之前為讀取操作+應(yīng)答 len--; } *buf = I2CReadNAK(); //最后一個(gè)字節(jié)為讀取操作+非應(yīng)答 I2CStop(); } /* E2寫(xiě)入函數(shù),buf-源數(shù)據(jù)指針,addr-E2中的起始地址,len-寫(xiě)入長(zhǎng)度 */ void E2Write(unsigned char *buf, unsigned char addr, unsigned char len) { while (len > 0) { //等待上次寫(xiě)入操作完成 do { //用尋址操作查詢當(dāng)前是否可進(jìn)行讀寫(xiě)操作 I2CStart(); if (I2CWrite(0x50<<1)) //應(yīng)答則跳出循環(huán),非應(yīng)答則進(jìn)行下一次查詢 { break; } I2CStop(); } while(1); //按頁(yè)寫(xiě)模式連續(xù)寫(xiě)入字節(jié) I2CWrite(addr); //寫(xiě)入起始地址 while (len > 0) { I2CWrite(*buf++); //寫(xiě)入一個(gè)字節(jié)數(shù)據(jù) len--; //待寫(xiě)入長(zhǎng)度計(jì)數(shù)遞減 addr++; //E2地址遞增 if ((addr&0x07) == 0) //檢查地址是否到達(dá)頁(yè)邊界,24C02每頁(yè)8字節(jié), { //所以檢測(cè)低3位是否為零即可 break; //到達(dá)頁(yè)邊界時(shí),跳出循環(huán),結(jié)束本次寫(xiě)操作 } } I2CStop(); } } 遵循模塊化的原則,把EEPROM的讀寫(xiě)函數(shù)單獨(dú)寫(xiě)成一個(gè)eeprom.c文件。其中E2Read函數(shù)和上一節(jié)是一樣的,因?yàn)樽x操作與分頁(yè)無(wú)關(guān)。重點(diǎn)是E2Write函數(shù),在寫(xiě)入數(shù)據(jù)的時(shí)候,要計(jì)算下一個(gè)要寫(xiě)的數(shù)據(jù)的地址是否是一個(gè)頁(yè)的起始地址,如果是的話,則必須跳出循環(huán),等待EEPROM把當(dāng)前這一頁(yè)寫(xiě)入到非易失區(qū)域后,再進(jìn)行后續(xù)頁(yè)的寫(xiě)入。 /****************************main.c文件程序源代碼*****************************/ #include <reg52.h> extern void E2Read(unsigned char *buf, unsigned char addr, unsigned char len); extern void E2Write(unsigned char *buf, unsigned char addr, unsigned char len); void main() { unsigned char i; unsigned char buf[5]; E2Read(buf, 0x8E, sizeof(buf)); //從E2中讀取一段數(shù)據(jù) for (i=0; i<sizeof(buf); i++) //數(shù)據(jù)依次+1,+2,+3... { buf = buf + 1 + i; } E2Write(buf, 0x8E, sizeof(buf)); //再寫(xiě)回到E2中 while(1); } 同樣數(shù)量的多字節(jié)寫(xiě)入時(shí)間和頁(yè)寫(xiě)入的時(shí)間到底差別多大呢?現(xiàn)在把兩次寫(xiě)入時(shí)間用邏輯分析儀給抓了出來(lái),并且用時(shí)間標(biāo)簽A1和A2標(biāo)注了開(kāi)始位置和結(jié)束位置,如圖12-9和圖12-10所示,右側(cè)顯示的|A1-A2|就是最終寫(xiě)入5個(gè)字節(jié)所耗費(fèi)的時(shí)間。多字節(jié)一個(gè)一個(gè)寫(xiě)入,每次寫(xiě)入后都需要再次通信檢測(cè)EEPROM是否在“忙”,因此耗費(fèi)了大量的時(shí)間,同樣的寫(xiě)入5個(gè)字節(jié)的數(shù)據(jù),一個(gè)一個(gè)寫(xiě)入用了8.4ms左右的時(shí)間,而使用頁(yè)寫(xiě)入,并且還跨頁(yè)操作,只用了3.5ms左右的時(shí)間。 圖12-9 多字節(jié)寫(xiě)入時(shí)間 圖12-10 跨頁(yè)寫(xiě)入時(shí)間 12.4練習(xí)題1、徹底理解I2C的通信時(shí)序。 2、能夠獨(dú)立完成EEPROM任意地址的單字節(jié)讀寫(xiě)、多字節(jié)的跨頁(yè)連續(xù)寫(xiě)入讀出。 3、將前邊學(xué)的交通燈進(jìn)行改進(jìn),使用EEPROM保存紅燈和綠燈倒計(jì)時(shí)的時(shí)間,并且可以通過(guò)UART改變紅燈和綠燈倒計(jì)時(shí)時(shí)間。
|