我們這一講簡要講解如何往EEPROM的地址0x55寫入一個(gè)數(shù)據(jù),然后讀出這個(gè)數(shù)據(jù)的內(nèi)容。本講代碼圍繞的是宋老師的lesson14_2例程代碼講解。
1.寫入一個(gè)字節(jié)內(nèi)容
如果要在EEPROM的某個(gè)地址里寫入內(nèi)容,那么步驟的實(shí)現(xiàn)歸為:
起始信號(hào)→找到這個(gè)器件是否存在(尋址),發(fā)送的字節(jié)最低位要為0意為要往這個(gè)器件寫內(nèi)容→選擇EEPROM的哪個(gè)地址去寫→寫入8位的數(shù)據(jù)→停止信號(hào)。
宋老師寫的“void E2WriteByte(unsigned char addr, unsigned char dat)”函數(shù)里面,上一講都講解過里面的函數(shù)了,寫入一個(gè)字節(jié)內(nèi)容的講解我們就介紹完了。
2.讀出一個(gè)字節(jié)內(nèi)容
在“unsigned char E2ReadByte(unsigned char addr)”中前面三個(gè)函數(shù)與“void E2WriteByte(unsigned char addr, unsigned char dat)”都是一樣的操作步驟,選定好要讀出哪個(gè)地址的內(nèi)容,然后還需再重新發(fā)送起始信號(hào),接著是把尋址的字節(jié)最低位設(shè)置為1意為要讀出EEPROM的某個(gè)地址里面的內(nèi)容,因?yàn)橹蛔x一個(gè)字節(jié),所以單片機(jī)在接收完EEPROM發(fā)送回來的數(shù)據(jù)(這個(gè)數(shù)據(jù)就是當(dāng)初寫進(jìn)去的數(shù)據(jù))之后,不產(chǎn)生拉低應(yīng)答(是單片機(jī)不產(chǎn)生應(yīng)答,不是說EEPROM不產(chǎn)生應(yīng)答),這樣EEPROM就不會(huì)再發(fā)送數(shù)據(jù)回來了,達(dá)到了只讀一個(gè)字節(jié)的功能。
所謂單片機(jī)讀EEPROM的數(shù)據(jù)出來,其實(shí)就是EEPROM在SDA線上不停地拉高拉低變化,而單片機(jī)就是不斷地判斷第一位是0或者1,第二位是0或者1······,這個(gè)細(xì)節(jié)過程大家有能力的話可以一步步去解讀宋老師寫的“unsigned char I2CReadNAK()”和“unsigned char I2CReadACK()”,其實(shí)這兩個(gè)函數(shù)只有一處不同,那就是“I2C_SDA = 1;”和“I2C_SDA = 0;”是否產(chǎn)生應(yīng)答。如果大家一句句地很難理解這個(gè)函數(shù),那么不妨直接省去理解,拿來運(yùn)用就可以了。
3.獨(dú)立代碼
在main.c里復(fù)制以下代碼,編譯下載進(jìn)開發(fā)板,我們做的實(shí)驗(yàn)就是先在EEPROM的0x55的地址里寫入數(shù)據(jù)71(也就是字符‘G’的ASCII碼值),然后再去讀出這個(gè)數(shù)據(jù),將字符‘G’顯示在液晶屏上。
之前的章節(jié)有提過為了兼容性我們不打算使用bit型的數(shù)據(jù)類型,所以修改了一下宋老師的“bit I2CWrite(unsigned char dat)”函數(shù),改為unsigned char類型。
那么我們就不能像宋老師那樣寫為“return (~ack);”,而是寫為“return (!ack);”。unsigned char類型下,ack如果是0,那么~ack就為0xFF;ack如果是1,~ack就為0xFE,所以“~ack”的書寫方式會(huì)導(dǎo)致返回值不能限定在0或者1之間,也就會(huì)使代碼功能失效。
而“!ack”的表達(dá)就是說,當(dāng)ack等于0時(shí),!ack只會(huì)等于1,不會(huì)等于0xFF這些。當(dāng)ack不等于0時(shí)(比如“ack=0xFE;”這些),那么!ack只會(huì)等于0,這樣保證了返回值只有0和1。
#include <reg52.h> #include <function.h>//詳見第六章第8講 #include <lcd.h> //詳見第十一章第3講 #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總線寫操作,dat-待寫入字節(jié),返回值-從機(jī)應(yīng)答位的值 */ u8 I2CWrite(unsigned char dat) { u8 ack; //用于暫存應(yīng)答位的值 unsigned char mask; //用于探測字節(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,以檢測從機(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ū)懭胧。?=存在且空閑或?qū)懭氤晒? } /* 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; } void main() { u8 str[2]; InitLcd1602();//初始化液晶屏 while (1) { I2CStart(); I2CWrite(0x50<<1); //尋址器件,后續(xù)為寫操作 I2CWrite(0x55); //寫入要存儲(chǔ)的地址為0x55 I2CWrite('G'); //寫入71這個(gè)數(shù)據(jù),'G'的表達(dá)更加直觀地表示要在液晶屏上顯示的內(nèi)容 I2CStop(); delay_ms(1000); //等待一秒之后,準(zhǔn)備讀出這個(gè)數(shù)據(jù) I2CStart(); I2CWrite(0x50<<1); //尋址器件,后續(xù)為寫操作 I2CWrite(0x55); //選擇要讀出內(nèi)容的地址為0x55 I2CStart(); //發(fā)送重復(fù)啟動(dòng)信號(hào) I2CWrite((0x50<<1)|0x01); //尋址器件,后續(xù)為讀操作 str[0] = I2CReadNAK(); //讀取這個(gè)器件0x55的地址里面的內(nèi)容存放在數(shù)組的0號(hào)元素里 I2CStop(); LcdShowStr_len(7, 0, str, 1);//顯示str[0]的內(nèi)容 while (1);//程序暫停運(yùn)行 } }
為了更加直觀了解這些過程,筆者并沒有像宋老師那樣給大家封裝好寫函數(shù)和讀函數(shù),這是為了大家不用在各個(gè)函數(shù)中跳來跳去地分析,在主函數(shù)里直接一步到位地講解這些操作步驟,方便大家對(duì)這些細(xì)節(jié)的了解。
C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍(lán)橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點(diǎn)擊了解:
一點(diǎn)編程也不會(huì)寫的:零基礎(chǔ)C語言學(xué)練課程
解決困擾你多年的C語言疑難雜癥特性的C語言進(jìn)階課程
從零到寫出一個(gè)爬蟲的Python編程課程
只會(huì)語法寫不出代碼?手把手帶你寫100個(gè)編程真題的編程百練課程
信息學(xué)奧賽或C++選手的 必學(xué)C++課程
藍(lán)橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競賽課入門課程
手把手講解近五年真題的藍(lán)橋杯輔導(dǎo)課程