以下內(nèi)容,將解釋以下幾個問題
1.IIC協(xié)議是什么?
2.IIC協(xié)議用來干什么?
3.IIC協(xié)議的通信過程?
1.IIC協(xié)議是什么?
IIC,即I2C,全稱 Inter-Integrated Circuit,字面上的意思是集成電路之間,它其實是I2C Bus簡稱,所以中文應該叫 集成電路總線 ,它是一種串行通信總線,使用多主從架構(gòu),由飛利浦公司在1980年代為了讓主板、嵌入式系統(tǒng)或手機用以連接低速周邊設備而發(fā)展。(百度百科)
2.IIC協(xié)議用來干什么?
簡單地說,IIC就是一種通信協(xié)議,是為了能讓主板,或嵌入式系統(tǒng)等與其他外設模塊進行通信而進行開發(fā)的。玩過stm32開發(fā)板的同學都知道,對于一塊stm32核心開發(fā)板而言,要想使用其他的外設模塊,就肯定要經(jīng)過接線,寫代碼,燒錄運行的這個過程。
其實這個過程,就是一個stm32與外設模塊通信的過程。接線,就是搭建通信的線路。寫代碼,就是制定通信的傳輸協(xié)議。燒錄運行,就是正式的通信過程。只不過有的模塊通信過程很簡單,大家感覺不出來。
外設和芯片間的通信可以形象地比喻成兩個人講話:
你說的別人得能聽懂:雙方約定信號的協(xié)議
你的語速別人得能接受:雙方滿足時序要求
但是隨著科技的發(fā)展,模塊越來越多,總不可能,每個模塊都要制定一種通信協(xié)議,這樣不現(xiàn)實。所以,總要有一些代表性的協(xié)議能夠適應大部分的模塊的通信。IIC這是這樣一種協(xié)議,一個IIC總線上,可以掛載多個外接設備。
常用的串行通信協(xié)議有:
①UART串口通信
②IIC協(xié)議
③SPI協(xié)議
④USB協(xié)議(很難)
常用的并行通信協(xié)議有:
①8080
②6800
3 .IIC協(xié)議的通信過程( 此處重點 )
接線:要搭建IIC的通信線路,出除去電源之外,還需要兩條線,分別是SDA和SCLK
SDA:數(shù)據(jù)信號線,用于傳輸數(shù)據(jù)
SCLK:時鐘信號線,用于產(chǎn)生時鐘頻率,控制時序,實現(xiàn)協(xié)議過程
由此可以看出,由于是單總線進行數(shù)據(jù)傳輸,所以IIC協(xié)議是半雙工的。
搭建好線路之后,就要進行具體的通信了。
要通信,總得先發(fā)個開始信號吧。就像你要和別人說話,總要先喊他一聲一樣。如下圖所示,協(xié)議規(guī)定,當SCLK時鐘信號一直處于高電平狀態(tài)時,SDA線由高電平跳變到低電平這個動作,表示起始信號。注意此時就算SDA數(shù)據(jù)線的電平跳變完,SCLK線依然是高電平哦。當連接在IIC總線上的外設模塊檢測到這個信號時,就知道數(shù)據(jù)要開始傳輸了。對于結(jié)束信號同理,協(xié)議規(guī)定,當SCLK時鐘信號一直處于高電平狀態(tài)時,SDA線由低電平跳變到高電平這個動作,表示結(jié)束信號。
在明白如何開始之后,就要開始進行數(shù)據(jù)的傳輸了。
協(xié)議規(guī)定,在數(shù)據(jù)的傳輸過程中,SCLK為高電平時,外設模塊開始采集SDA數(shù)據(jù)線上的數(shù)據(jù),此時要求SDA數(shù)據(jù)線上的電平狀態(tài)必須穩(wěn)定(不然鬼知道這一位數(shù)據(jù)是0還是1),當SCLK為低電平時才允許SDA線上的數(shù)據(jù)跳變成另外一種狀態(tài)。
以下以傳輸1個bit的數(shù)據(jù)為例,如下圖所示:
現(xiàn)在,我想傳輸1bit數(shù)據(jù),該位數(shù)據(jù)為1,從上文知道,我們在發(fā)完開始信號之后,此時SDA數(shù)據(jù)線的電平狀態(tài)為低電平,SCLK信號依然是高電平。難道這個時候外設就要開始讀取數(shù)據(jù)了嗎?
這顯然不是的,從發(fā)完開始信號到真正的數(shù)據(jù)傳輸之間,會有一段緩沖時間,讓我們?nèi)蕚鋽?shù)據(jù),在準備數(shù)據(jù)階段,先將SCLK信號拉低一段時間,在這期間將SDA數(shù)據(jù)線拉高一段時間(即數(shù)據(jù)1),然后再將SCLK信號拉高,此時這個時鐘信號的高電平被外設檢測到的話,外設就知道要讀取數(shù)據(jù)了,從而SDA上的數(shù)據(jù)就會被外設讀到了。依次類推,傳輸下一位數(shù)據(jù)。
一般,傳輸完1個字節(jié)(即8bit,高位先入)的數(shù)據(jù),才算做一次完整的數(shù)據(jù)傳輸,因為對存儲單元而言,最小的單位便是字節(jié)。那如何確定,每次都完好地傳輸了一個字節(jié)呢?
這種情況就需要外設來做出回應了,就像打電話一樣,如果對方不在,或不想聽,說再多也沒用啊。那么外設如何做出回應呢?
協(xié)議規(guī)定,主機每傳完一個字節(jié)的數(shù)據(jù)即外設每收到一個字節(jié)的數(shù)據(jù),外設就要在第9個時鐘脈沖到來的時候,將SDA數(shù)據(jù)線拉低進行應答(ACK),且必須是穩(wěn)定的低電平,表示已經(jīng)收到了一個字節(jié)的數(shù)據(jù),拉高表示不進行應答(NACK;注意這里是外設將SDA數(shù)據(jù)線拉低,不是主機了哦。如下圖所示:
所以在主機傳完一個字節(jié)的數(shù)據(jù)之后,就應該釋放總線(協(xié)議規(guī)定,當SDA和SCLK同時為高時,表示空閑狀態(tài))然后把SDA數(shù)據(jù)線連接的IO口從輸出模式轉(zhuǎn)換成輸入模式,這樣才能拿到SDA數(shù)據(jù)線上的應答信號。這樣,一個字節(jié)的數(shù)據(jù)就從主機到外設傳輸完畢了。
既然IIC是雙向通信的,那主機肯定也是需要從外設讀取數(shù)據(jù)的,那這個讀取的過程又是怎么實現(xiàn)的呢?畢竟外設對于我們而言是不能直接操作的,我們能操作的只有stm32。我們知道,一個IIC總線上,可以掛載多個設備,那么stm32如何確定是哪個外設正在跟我進行通信呢。對于此,那些生產(chǎn)外設模塊的廠商們就約定,要是這個設備使用IIC協(xié)議進行通信,那么就要給這個設備指定一個器件地址,以供芯片訪問。這個器件地址會在你購買其模塊的時候在使用手冊上注明。所以,要跟哪個模塊通信,就一定要通過查閱其使用手冊,找到它的器件地址。
所以,在上文所述的最開始的一個字節(jié)的數(shù)據(jù)傳輸過程中,這一個數(shù)據(jù)往往是器件地址。這樣,對應的外設才知道,是要跟我進行通信。讀取數(shù)據(jù),也是同理,要想從外設中讀取到數(shù)據(jù),主機要明確三點:從哪個外設中的哪個地方讀取數(shù)據(jù),讀取到的數(shù)據(jù)要存到哪里。
所以主機,在開始讀數(shù)據(jù)之前,主機必須要先給外設發(fā)器件地址,數(shù)據(jù)所在的地址,外設才會知道你要從該地址讀取數(shù)據(jù),從而把數(shù)據(jù)通過SDA線傳出來。至于具體的每個字節(jié)的傳輸過程,和上面所講的從主機到外設的過程差不多,只不過反了一個反向而已,并且主機的等待應答變成了主動應答。
/* 設置SDA總線為輸出模式 參數(shù)值:NULL 返回值:NULL*/ void IIC_setSDAMode_Out(){ GPIO_InitTypeDef GPIO_IIC; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_IIC.GPIO_Mode = GPIO_Mode_OUT; //輸出 GPIO_IIC.GPIO_OType = GPIO_OType_PP; //推挽 GPIO_IIC.GPIO_Pin = GPIO_Pin_15; //引腳 GPIO_IIC.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_IIC.GPIO_Speed = GPIO_Speed_25MHz; //輸出 GPIO_Init(GPIOE, &GPIO_IIC);} /* 設置SDA總線為輸入模式 參數(shù)值:NULL 返回值:NULL*/ void IIC_setSDAMode_In(){ GPIO_InitTypeDef GPIO_IIC; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_IIC.GPIO_Mode = GPIO_Mode_IN; //輸出 GPIO_IIC.GPIO_Pin = GPIO_Pin_15; //引腳 GPIO_IIC.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOE, &GPIO_IIC); } /* IIC開始信號 參數(shù)值:NULL 返回值:NULL*/ void IIC_Start(){ IIC_setSDAMode_Out(); IIC_SDA_OUT(1); //總線釋放狀態(tài) IIC_SCL_OUT(1); delay_us(5); IIC_SDA_OUT(0); //SDA跳變?yōu)榈碗娖? delay_us(5); IIC_SCL_OUT(0); delay_us(5); } /* IIC停止信號 參數(shù)值:NULL 返回值:NULL*/ void IIC_Stop(){ IIC_setSDAMode_Out(); IIC_SDA_OUT(0); IIC_SCL_OUT(0); delay_us(5); IIC_SCL_OUT(1); //SDA跳變?yōu)楦唠娖? delay_us(5); IIC_SDA_OUT(1); delay_us(5); } /* 主機寫入數(shù)據(jù)到外設中 參數(shù)值: data 要寫入的一個字節(jié) 返回值:NULL*/ void IIC_writeByte(u8 data){ IIC_setSDAMode_Out(); IIC_SCL_OUT(0); //只有時鐘線拉低,SDA上的數(shù)據(jù)才允許寫入 delay_us(5); //將數(shù)據(jù)一位一位的發(fā)出去 for(int i =0;i<8;i++) { if(data&(0x1<<(7-i))) //高位先入 { IIC_SDA_OUT(1); } else { IIC_SDA_OUT(0); } IIC_SCL_OUT(1); //讓外設讀取數(shù)據(jù) delay_us(5); IIC_SCL_OUT(0); //重新拉低,準備寫入下一位數(shù)據(jù) delay_us(5); }} /* 主機從外設中讀取一個字節(jié)的數(shù)據(jù) 參數(shù)值:NULL 返回值:NULL*/ u8 IIC_readByte(){ u8 data = 0; IIC_setSDAMode_In(); IIC_SCL_OUT(0); //先拉低,為讀取數(shù)據(jù)做準備 delay_us(5); for(int i=0;i<8;i++) { IIC_SCL_OUT(1); // SCL為高期間才可以讀取數(shù)據(jù) delay_us(5); if(IIC_SDA_IN) { data|=(0x01<<(7-i)); }else{ data &= ~(0x1<<(7-i)); } IIC_SCL_OUT(0); delay_us(5); } return data; } /* 主機等待應答 參數(shù)值:NULL 返回值:ack 0 應答 1 不應答*/ u8 IIC_waitAck(){ u8 ack =0; IIC_setSDAMode_In(); IIC_SCL_OUT(0); //準備時序 delay_us(5); IIC_SCL_OUT(1); delay_us(5); if(IIC_SDA_IN) { ack =1; } else { ack =0; } IIC_SCL_OUT(0); //拉低,表示應答完成 delay_us(5); return ack; } /* 主機主動應答 參數(shù)值: ack 0 應答 1 不應答 返回值:NULL*/ void IIC_Ack(u8 ack){ IIC_setSDAMode_Out(); IIC_SCL_OUT(0); delay_us(5); if(ack) { IIC_SDA_OUT(1); } else { IIC_SCL_OUT(0); } IIC_SCL_OUT(1); delay_us(5); IIC_SCL_OUT(0); delay_us(5); }
審核編輯:湯梓紅
-
通信協(xié)議
+關注
關注
28文章
883瀏覽量
40310 -
總線
+關注
關注
10文章
2881瀏覽量
88090 -
IIC
+關注
關注
11文章
300瀏覽量
38339
原文標題:IIC協(xié)議超詳細解釋(適合小白入門)
文章出處:【微信號:傳感器與檢測技術,微信公眾號:傳感器與檢測技術】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論