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