|
從這一貼開始,和大家一起聊聊有關(guān)通訊協(xié)議,通訊協(xié)議是指單片機(jī)和外部設(shè)備進(jìn)行數(shù)據(jù)交互的基本準(zhǔn)則.先說(shuō)說(shuō)物理連接吧! 地球上的通訊端口,從根本上來(lái)講只有兩種:一種是串行接口,即指數(shù)據(jù)按照高地位的一定順序,一位一位分時(shí)進(jìn)行傳輸;一種是并行接口,即指所有數(shù)據(jù),一次性同時(shí)進(jìn)行傳輸;由此可以看來(lái),并行接口理論上的速度會(huì)比串行接口快.為了簡(jiǎn)化單片機(jī)(主)和外部設(shè)備(從)的物理連接復(fù)雜度,一般情況下會(huì)使用串行接口.而單片機(jī)內(nèi)部的總線,一般就是并行的結(jié)構(gòu).俺們這里主要聊聊串行通信,再說(shuō)協(xié)議,這個(gè)是非常重要的通信手段. 舉個(gè)栗子,咱(主機(jī))和女神(從機(jī))聊天,首先得確認(rèn)兩個(gè)原則性的東西:第一,相同的語(yǔ)言.咱說(shuō)國(guó)語(yǔ),女神說(shuō)那美克星語(yǔ),直接后果就是雙方完全不能溝通.類比下來(lái),就是通訊中的信號(hào)協(xié)議,必須遵循一定的信號(hào)發(fā)送接收的格式.還記得韓梅梅和李雷嗎? 韓:hi,lilei,how are you 李:fine,thank you,and you? 韓:I’m fine too,goodbye 李:goodbye 韓用英語(yǔ)先喊名字打招呼,李聽到喊自己名字且聽懂后,才會(huì)有下面的交流.這就是一次完整的通信過(guò)程,以hi開始,以goodbye結(jié)束. 第二,相同的語(yǔ)速.雖然咱和女神都說(shuō)國(guó)語(yǔ).但是咱每秒說(shuō)10個(gè)字,女神每秒只能聽見并理解5個(gè)字,這樣下來(lái),明顯也無(wú)法正常溝通.不信你試試飛快地讀“西安”一詞,會(huì)不會(huì)有人理解成“先”一字呢?呵呵,速度快了,意思完全就不通了 拋開上面兩點(diǎn),接下來(lái)就是一些細(xì)節(jié)內(nèi)容了,譬如,咱向女神闡述長(zhǎng)達(dá)1分鐘的故事.女神在這1分鐘內(nèi),會(huì)隔一段時(shí)間回復(fù)一個(gè)“嗯”,來(lái)表示正在聆聽,并且做好準(zhǔn)備繼續(xù)聆聽.咱才能繼續(xù)講下去哈 反過(guò)來(lái)女神講事情給咱也是一樣,咱也得時(shí)不時(shí)回復(fù)一個(gè)“嗯”.好讓女神繼續(xù)講下去,如果換個(gè)場(chǎng)景,咱一個(gè)人(主機(jī))給多個(gè)人(多從機(jī))吹水.也基本上就是上面的過(guò)程,個(gè)人認(rèn)為一個(gè)串行通信比較重要的方面也就這么些了,下面來(lái)看看今天咱聊的具體對(duì)象IIC. IIC,有些寫成I2C,簡(jiǎn)單讀成i方c,全稱Inter-Integrated Circuit,是由飛利浦在上世紀(jì)80年代設(shè)計(jì)推廣的一種通信總線協(xié)議,呃,現(xiàn)在應(yīng)該叫恩智浦(NXP)了.它只需要兩個(gè)IO口即可構(gòu)成,一根是SCL時(shí)鐘線,有些也標(biāo)為SCK之類,說(shuō)法不一;另外一根是SDA數(shù)據(jù)線,SCL是一個(gè)單向的IO,由主機(jī)向從機(jī)發(fā)送.SDA則是條雙向IO,主機(jī)需要對(duì)其進(jìn)行讀寫操作,每次8位數(shù)據(jù).讀的時(shí)候接收數(shù)據(jù),寫的時(shí)候發(fā)送數(shù)據(jù). IIC的速度分為三個(gè)等級(jí):標(biāo)準(zhǔn)模式100kbit/s,快速模式400kbit/s,高速模式3.4Mbit/s.由SCL的頻率來(lái)決定.一般情況下,需要參照外圍器件的要求來(lái)決定SCL頻率.在硬件上,SDA和SCL是需要有上拉電阻,從機(jī)設(shè)備需要是OC/OD狀態(tài),集電極開路或者漏極開路,且這個(gè)上拉電阻的取值,會(huì)對(duì)速度產(chǎn)生比較大的影響.10kohm以下比較常見,個(gè)人喜歡4.7k,IIC要求的細(xì)則很多,有興趣可以下載英文原版的手冊(cè)研讀.這里聊聊一些基本要求: 1、基本原則 SCL為高時(shí),SDA不能亂動(dòng).SCL為低時(shí),SDA隨便玩.SCL必須由主機(jī)控制.在尋址過(guò)程中,一次發(fā)送數(shù)據(jù)8位,其中高7位為從機(jī)地址,最后一位為讀寫標(biāo)志位.So,每個(gè)從機(jī)一個(gè)地址,一條IIC總線理論上最多掛載127個(gè)從設(shè)備.主機(jī)呼叫,從機(jī)聽到叫自己才作出回應(yīng) 2、起始信號(hào) 類似于打招呼hi,告訴別人,咱要發(fā)話了 時(shí)序圖 
SCL在高電平器件,SDA出現(xiàn)一次下降沿,也就是SDA從高電平跳變到低電平,看代碼: void start() { SDA=1; //SDA拉高 delay(); SCL=1; //SCL拉高 delay(); SDA=0; //SDA拉低,出現(xiàn)一次下降沿,形成起始信號(hào) delay(); SCL=0; //SCL拉低 delay(); }
3、停止信號(hào) 時(shí)序圖 
類似于再見goodbye,告訴別人交流結(jié)束,SCL高電平期間,SDA由低電平跳變到高電平 ,也就是出現(xiàn)一次上升沿 ,看代碼: void stop() { SDA=0; //SDA拉低 delay(); SCL=1; //SCL拉高 delay(); SDA=1; //SDA拉高,出現(xiàn)一次上升沿 delay(); }
4、應(yīng)答信號(hào) 應(yīng)答信號(hào)有兩類:一類是主機(jī)發(fā)送給從機(jī)的,另一類是從機(jī)發(fā)送給主機(jī)的,出現(xiàn)在8位數(shù)據(jù)傳輸結(jié)束后,第九個(gè)時(shí)鐘到來(lái)之時(shí),應(yīng)答信號(hào)一般稱為ACK信號(hào).至于NACK(非應(yīng)答信號(hào)),其實(shí)就是ACK的另一種說(shuō)法,時(shí)序圖: 
表示從機(jī)已經(jīng)收到之前傳輸?shù)?位數(shù)據(jù),簡(jiǎn)單點(diǎn)理解,就是女神的“嗯”(邪惡了!!!!!!!) .這個(gè)需要由主機(jī)讀取SDA的值,來(lái)判斷是否有ACK信號(hào).看代碼: bit respons() { bit temp=0; SDA=1; //主機(jī)將SDA拉高,釋放SDA控制權(quán)(SDA是上拉到電源的喲) delay(); SCL=1; //SCL拉高 delay(); temp = SDA; //讀取SDA的值,賦給temp SCL=0; //SCL拉低 delay(); return temp; //返回temp值 }
So,temp=1的話,是個(gè)NACK信號(hào),從機(jī)無(wú)響應(yīng) ,temp=0的話,則是個(gè)ACK信號(hào)咯,那就可以認(rèn)為,從機(jī)已經(jīng)接收到主機(jī)發(fā)送過(guò)來(lái)的數(shù)據(jù)了. 5、數(shù)據(jù)發(fā)送過(guò)程 每次傳遞8位的數(shù)據(jù),這些數(shù)據(jù)啥時(shí)候發(fā)送呢 ?看時(shí)序圖: 
So easy ,SCL高電平期間,保持SDA數(shù)據(jù)穩(wěn)定,就是1位數(shù)據(jù)傳過(guò)去了 .SCL低電平期間,SDA可以進(jìn)行數(shù)據(jù)變化...瞧代碼: void wr_byte(uchar date) { uchar i; for(i=0;i<> { date=date<1;>1;> SDA=CY; //將左移進(jìn)位信號(hào)1或0賦給SDA,詳見REG51.h中SY寄存器 delay(); SCL=1; //SDA穩(wěn)定后,SCL拉高 delay(); SCL=0; //SCL拉低 delay(); } }
上面2-4肢解了IIC一次數(shù)據(jù)通信過(guò)程,就這么些.但是不同的IIC設(shè)備,讀寫時(shí)序略有不同,咱來(lái)看些具體的例子吧!正好手頭上有塊24C08 看看這貨的地址情況
8位尋址數(shù)據(jù),最后一位為讀寫標(biāo)志位,0寫1讀,D7-D1為地址位,其中高四位固定為1010,B2、B1、B0可操作.但是注意一下,對(duì)于24C08而言,B2位是由外部管腳確定的,寫無(wú)效.也就是說(shuō),一條IIC總線上可以掛2個(gè)24C08,B2為0或者1.而其他,特別是24C02,B0、B1、B2都是由外部管腳確定,可掛8片.不過(guò)24c08可以軟件操作B1和B0.B1B0=00時(shí),指向block0的256個(gè)字節(jié)空間.后面依次類推.總共有4x256=1024k字節(jié)=8kbit.所以叫做24c08,呵呵。在寫尋址的時(shí)候,地址為0xa1.在讀尋址的時(shí)候,地址為0xa0.看看這貨的操作過(guò)程

其他細(xì)節(jié),可以參考數(shù)據(jù)手冊(cè) ,動(dòng)手?jǐn)]代碼: #include#include#define uint unsigned int /*宏定義*/ #define uchar unsigned char /*宏定義*/ uchar WRDADDS= 0xa0 ; /*I2C器件地址,尋址寫*/ uchar RDDADDS =0xa1 ; /*I2C器件地址,尋址讀*/ sbit SDA = P1^0; /*數(shù)據(jù)線*/ sbit SCL = P1^1; /*時(shí)鐘線*/ sbit LED = P1^3; void Write(uchar address,uchar date); /*向24c02的地址address中,寫入一字節(jié)數(shù)據(jù)date*/ uchar Read(uchar address); /*從24c02的地址address中,讀取一個(gè)字節(jié)數(shù)據(jù)(返回值)*/ void wr_byte(uchar date); /*I2C總線寫一個(gè)字節(jié)(有參數(shù))*/ uchar rd_byte(); /*I2C總線讀一個(gè)字節(jié)(返回值)*/ void start(); /*啟動(dòng)I2C總線*/ void stop(); /*停止I2C總線*/ bit respons(); /*I2C總線應(yīng)答信號(hào)檢查*/ void delay(); /*延時(shí)微秒*/ void delayms(uint xms); /*延時(shí)毫秒*/ /*向24c02的地址address中,寫入一字節(jié)數(shù)據(jù)date*/ void Write(uchar address,uchar date) { start(); //啟動(dòng)信號(hào) wr_byte(WRDADDS); //I2C器件地址,尋址寫 while(respons()); //等待應(yīng)答信號(hào) wr_byte(address); //選擇單元地址 while(respons()); //等待應(yīng)答信號(hào) wr_byte(date); //寫數(shù)據(jù) while(respons()); //等待應(yīng)答信號(hào) stop(); //停止信號(hào) } /*從24c02的地址address中讀取一個(gè)字節(jié)數(shù)據(jù)*/ uchar Read(uchar address) { uchar temp; start(); //開始信號(hào) wr_byte(WRDADDS); //I2C器件地址,尋址寫 while(respons()); //等待應(yīng)答信號(hào) wr_byte(address); //選擇單元地址 信號(hào) while(respons()); //等待應(yīng)答信號(hào) start(); //重發(fā)啟動(dòng)信號(hào) wr_byte(RDDADDS); //I2C器件地址,尋址讀 while(respons()); //等待應(yīng)答信號(hào) temp = rd_byte(); //讀數(shù)據(jù)(函數(shù)返回值) stop(); //停止信號(hào) return temp; } /*啟動(dòng)I2C總線*/ void start() { SDA=1; delay(); SCL=1; delay(); SDA=0; delay(); SCL=0; delay(); } /*停止I2C總線*/ void stop() { SDA=0; delay(); SCL=1; delay(); SDA=1; delay(); } /*I2C總線應(yīng)答信號(hào)檢查*/ bit respons() { bit temp=0; SDA=1; delay(); SCL=1; delay(); temp = SDA; SCL=0; delay(); return temp; } /*I2C總線寫一個(gè)字節(jié)*/ void wr_byte(uchar date) { uchar i; for(i=0;i<> { date=date<> SDA=CY; delay(); SCL=1; delay(); SCL=0; delay(); } } /*I2C總線讀一個(gè)字節(jié)*/ uchar rd_byte() { uchar i,temp; for(i=0;i<> { SCL=1; delay(); temp=(temp<> delay(); SCL=0; delay(); } return temp; } /*NOP延時(shí)函數(shù)*/ void delay() { _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); } /*延時(shí)函數(shù)*/ void delayms(uint xms) { uint i,j; for(i=0;i<> for(j=0;j<> } void main() { uchar num,x; uint i,count1,count2; LED = 1; for(i=0;i<1024;i++)>1024;i++)> count2++; if(i<256)>256)> Write(i,i); delayms(20); num = Read(i); if(num==i) { count1++; LED = ~LED; } } if((256<><=512))>=512))> x = (i-256); WRDADDS = 0xa2; RDDADDS = 0xa3; Write(x,x); delayms(20); num = Read(x); if(num==x) { count1++; LED = ~LED; } } if((512<><=768))>=768))> x = (i-512); WRDADDS = 0xa4; RDDADDS = 0xa5; Write(x,x); delayms(20); num = Read(x); if(num==x) { count1++; LED = ~LED; } } if((768<><1024))>1024))> WRDADDS = 0xa6; RDDADDS = 0xa7; x = (i-768); Write(x,x); delayms(20); num = Read(x); if(num==x) { count1++; LED = ~LED; } } } if(count1==count2) {LED=0;} else{LED = 1;} while (1) { } }
可以參照對(duì)比上面的時(shí)序,把代碼簡(jiǎn)單讀一下.寫得比較隨意,了解一下大概過(guò)程即可.尤其是Read和Write兩個(gè)函數(shù).大致功能就是把24C08所有的存儲(chǔ)單元都進(jìn)行一次讀寫同時(shí)blink.如果每個(gè)單元寫入和讀出的數(shù)據(jù)相同,最后LED點(diǎn)亮.上個(gè)GIF 
有些時(shí)候咱操作的不一定是EEPROM,還有一些其他傳感器器件 ,正好手頭上有塊TMP75B:

很早以前向TI申請(qǐng)的樣片,溫度傳感器 ,看看這貨的地址和讀寫時(shí)序吧 
A0-A2均可由外部管腳確定,不過(guò)高4位固定為1001,tmp75b地址寫為0x90,地址讀為0x91 因?yàn)門MP75B需要16位來(lái)完成溫度的讀取,所以需要連續(xù)讀2個(gè)字節(jié)來(lái)獲得溫度值.注意紅圈內(nèi)的時(shí)序要求.在多字節(jié)傳輸?shù)臅r(shí)候,每個(gè)字節(jié)傳輸結(jié)束后,主機(jī)需要發(fā)送一個(gè)ACK.也就是SCL高電平期間,SDA保持低電平.表示接收到了一個(gè)8位數(shù)據(jù).然后將SDA拉高.簡(jiǎn)單修改一下上面的代碼,完成連續(xù)讀的操作.
#define WRDADDS 0x90 ; /*宏定義I2C器件tmp75b地址,尋址寫*/ #define RDDADDS 0x91 ; /*宏定義I2C器件tmp75b地址,尋址讀*/ void ack_master() { SDA=0; delay(); SCL=1; delay(); SCL=0; delay(); SDA=1; delay(); } uint Read_temprature(uchar address)//連續(xù)讀2個(gè)字節(jié)的溫度數(shù)據(jù) { uint temp; start(); //開始信號(hào) wr_byte(WRDADDS); //I2C器件地址,尋址寫 while(respons()); //應(yīng)答信號(hào) wr_byte(address); //選擇單元地址 信號(hào) while(respons()); //應(yīng)答信號(hào) stop(); start(); //重發(fā)啟動(dòng)信號(hào) wr_byte(RDDADDS); //I2C器件地址,尋址讀 while(respons()); //應(yīng)答信號(hào) temp = rd_byte()<8;>8;> ack_master(); temp |= rd_byte(); //低8位數(shù)據(jù) ack_master(); stop(); //停止信號(hào) return temp; } void main() { float temprature; LED = 1; //熄滅LED while (1) { Write(0x00,0x00); delayms(20); nums = Read_temprature(0x00); //獲取16位溫度HEX temprature = (nums>>4)*0.0625; //換算溫度值 if(temprature>25.0){LED = 0;} //如果溫度大于25,點(diǎn)LED else {LED = 1;} //否則熄滅 } }
看看 Read_temprature
函數(shù),是不是和貼圖的時(shí)序一致呢?代碼基本功能就是,通過(guò)IIC與傳感器TMP75B通信.獲取溫度信息 如果超過(guò)25度就點(diǎn)亮LED, 否則熄滅LED!上GIF: 
好累!這次就先到這里. 了解更多51系列教程,請(qǐng)關(guān)注“云漢電子社區(qū)”官方微信公眾號(hào)ickeybbs,或者登錄云漢電子社區(qū)官方網(wǎng)站(bbs.ickey.cn)
|