http://home.eeworld.com.cn/my/space-uid-716241-blogid-655190.html 一、I2C協議簡介 I2C是兩線式串列匯流排,用於連接微控制器及其外圍設備。兩根信號線分別是: 時鐘信號線SCL和數據信號線SDA。 二、I2C匯流排傳輸時序 2.1 I2 ...
http://home.eeworld.com.cn/my/space-uid-716241-blogid-655190.html 一、I2C協議簡介 I2C是兩線式串列匯流排,用於連接微控制器及其外圍設備。兩根信號線分別是: 時鐘信號線SCL和數據信號線SDA。 二、I2C匯流排傳輸時序 2.1 I2C傳輸協議的三種信號 I2C在數據傳輸過程中有三種信號類型,分別是:起始信號、結束信號和應答信號。 ①起始信號:在時鐘信號SCL為高電平時,數據線SDA由高電平跳變為低電平,開始傳輸數據; ②結束信號:在時鐘信號SCL為高電平時,數據線SDA由低電平跳變為高電平,數據傳輸結束; ③應答信號:接收數據的IC在接收8位(一個位元組)數據後,向發送數據的IC發出特定的低電平信號,表示已經收到數據。準確的說法是:發送設備在時鐘信號SCL的8個脈衝的驅動下發送了8個bit,即一個位元組後,在SCL第九個脈衝到來前將SDA線釋放(即將其電平拉高),由接收設備在SCL第9個脈衝期間返回一個應答信號,即將SDA電平拉低。要註意的是:在SCL第9個脈衝高電平期間,SDA的低電平信號必須保持穩定。(實際上I2C協議中,數據傳輸時,SCL每個脈衝的高電平期間,SDA上的信號都必須保持穩定,只有在SCL為低電平期間,SDA上的數據才能變化。) 2.2 I2C協議規定 ①什麼時候匯流排為空閑狀態? SDA和SCL同時為高電平時,規定匯流排為空閑狀態。即只要SDA和SCL中有一個為低電平,則匯流排為忙狀態。 ②關於應答信號說明 應答信號為低電平時,規定為有效應答,簡稱ACK,表示已接收到數據;應答信號為高電平時,規定為非應答,簡稱NACK,一般表示數據接收沒有成功,有時根據相關操作時序,也可能需要產生一個NACK,這個後面會有說明。 2.3 傳輸協議數據包構成圖示
三、以I2C讀寫EEPROM(AT24C02)為例進行說明
3.1 針對EEPROM的基本操作
①寫操作
②讀操作
針對EEPROM的寫操作和讀操作也可以細分,如:
寫操作:
①往EEPROM中寫入一個位元組的數據;
②往EEPROM中批量寫入數據;
讀操作:
①從EEPROM中指定地址讀取一個位元組數據;
②從EEPROM中批量讀取數據;
下麵結合上面4種讀寫操作來具體說明:
1、往EEPROM中寫入一個位元組數據
1)寫入時序
2)時序分析
①第一步:主機產生起始信號,開始傳輸;
②第二步:發送掛載在I2C匯流排下的從機設備地址(這裡由於是針對EEPROM,其地址是7位),註意:EEPROM的地址高四位已經固定,為1010,低三位由自己的硬體設計決定,一般都將EEPROM的這三根地址線直接接地,所以低三位地址就為000,這樣該EEPROM的設備地址就為1010 000。由於這裡是寫入數據,所以R/W位就為0,這一位與前面的7位地址組成一個位元組,所以寫入數據時,發送的從機地址就為0xA0;
③第三步:發送完從機地址後,主機會釋放SDA線,等待從機給一個低電平應答信號ACK,主機收到ACK後,進行下一步;
④第四步:發送將要寫入數據的地址,這裡要搞清楚,這個地址不是上面的從機地址,而是我們要寫入數據到EEPROM中具體地址;(兩者的關係可以打個比方:我們假設I2C匯流排就是一條叫香樟大道的馬路,EEPROM就是這條馬路上某個地方的一棟大樓,地址是香樟大道99號,而寫入數據的地址就是香樟大道99號這棟樓里的某個具體房間號。)
⑤發送完寫入數據地址後,釋放SDA匯流排並等待從機給一個低電平的應答信號ACK;
⑥主機收到應答信號ACK後,開始向指定的地址中寫入一個位元組數據,寫完一個位元組數據後釋放SDA匯流排;
⑦從機接受到一個位元組數據後,返回一個應答信號給主機,主機接受到應答信號後,發送一個結束信號來結束本次數據傳輸。
3)根據時序分析調用STM32庫函數編寫具體的程式 /* * 函數名:I2C_EEPROM_ByteWrite * 描述:向EEPROM中寫入一個位元組數據 * 輸入參數:pBuffer—指針變數,指向我們要發送的數據 * WriteAddr—要寫入數據的地址 * 說明:程式中EEPROM_ADDRESS是一個巨集定義,是從機地址,為0xA0 */ void I2C_EEPROM_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr) { /*①調用庫函數,產生起始信號 */ I2C_GenerateSTART(I2C1, ENABLE); /*檢測EV5事件(即SB=1,表示起始信號已發送)*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*②調用庫函數發送從機地址*/ I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /*檢測EV6事件(即ADDR=1,表示從機設備地址已經發送,並且收到從機的應答)*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /*③調用庫函數發送要寫入數據的地址*/ I2C_SendData(I2C1, WriteAddr); /*檢測EV8事件(即TxE=1,數據寄存器空,就是地址已經發送完成)*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); /*④調用庫函數發送要寫入的數據*/ I2C_SendData(I2C1, *pBuffer); /*檢測EV8_2事件(即TxE=1, BTF=1,請求設置停止位)*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /*⑤調用庫函數發送停止信號結束傳輸 */ I2C_GenerateSTOP(I2C1, ENABLE); } 4) 關於3)中向EEPROM中寫入一個位元組數據函數的詳細解析 看了上面的函數,可能有些人會有疑問,比如為什麼你這函數要這麼寫?發送完起始信號為什麼要檢測EV5?後面的操作中又為什麼要檢測EV6、EV8或者EV8_2?等等問題。下麵我結合《STM32中文參考手冊_V10》中有關I2C的相關內容來進行說明。 首先,我們來看看《STM32中文參考手冊_V10》P498中有關主發送器的相關時序說明:圖4中我們要看的是7位主發送的序列圖,下麵結合圖4的主發送器傳送序列圖來解釋上面寫一個位元組數據到EEPROM函數:
①主機產生起始信號,起始信號發送後,會產生EV5事件,EV5即表示SB=1,SB是I2C狀態寄存器I2C_SR1的bit0,如下圖5:
看上面的圖5可以知道,如果起始條件已發送,SB會被置“1”,即產生事件EV5,所以我們要檢測該位確認起始條件已發送;
②發送從機設備地址,從機應答後,會產生EV6事件,即ADDR=1,ADDR是I2C_SR1寄存器的bit1,說明如下圖6:
通過圖6我們可以知道,當地址發送完成,主機收到從機的應答後,ADDR位被置“1”,我們通過檢測EV6事件來判斷地址已發送完成並清除相關位;
③發送要寫入數據的地址(地址也是數據),從機收到數據後應答,通過圖4傳送序列圖可以看出,在第一個數據(第一個數據是要寫入數據的地址)發送完並收到應答後,EV8事件早已經產生,EV8表示TxE=1,移位寄存器非空,數據寄存器空,寫入DR寄存器將清除該事件。數據寄存器空,移位寄存器非空說明我們要發送的數據已經由數據寄存器轉入移位寄存器,數據一旦轉入移位寄存器就會在時鐘信號的驅動下自動一位一位的將數據發送出去,所以我們在發送完地址後檢測EV6事件,確認數據已經由數據寄存器轉移到移位寄存器中發送,可以向數據寄存器中寫入新的數據;
④發送我們要寫入到EEPROM中的數據,從機應答後,檢測EV8_2,這裡為什麼是檢測EV8_2而不是EV8?因為我們這個函數只向EEPROM中寫入一個位元組數據,所以這個位元組數據就是最後一個要發送的數據,通過圖4傳送序列圖可以看出,最後一個數據傳輸完成後產生事件EV8_2,表示TxE=1,BTF=1,請求設置停止位。至於為什麼產生的是EV8_2而不是EV8,我們來看看圖7中有關BTF的說明:BTF表示位元組發送結束,在發送時,當一個新數據被髮送且數據寄存器還未被寫入新的數據(TxE=1)時,BTF會被置“1”,由於我們這個函數只向從機發送一個位元組數據(不包括地址),所以在這個位元組數據被髮送後並沒有新的數據寫入到數據寄存器,因此符合了上面的條件,BTF被置“1”。在TxE=1、BTF=1的情況下,就產生EV8_2;
⑤檢測到EV8_2後,主機就可以產生停止信號來結束本次傳輸了。
有關寫入一個位元組數據到EEPROM中函數的分析到這裡就結束了,下麵來說一些從EEPROM中讀取一個位元組數據的相關代碼以及分析。
2、從EEPROM中指定地址讀取一個位元組數據 1)讀取時序2)時序分析
①第一步:主機產生起始信號;
②第二步:主機發送從機設備地址,寫選通(即R/W為‘0’),從機接收到地址後應答;
③第三步:主機發送要讀取數據在EEPROM中的地址,從機接收到後應答;
④第四步:主機再次產生起始信號;
⑤第五步:主機發送從機設備地址,讀選通(即R/W為‘1’),從機接收到後應答;
⑥第六步:從機發送數據,主機接收,接收到最後一個數據後非應答;
⑦第七步:主機產生停止信號結束傳輸。
3)根據時序分析調用STM32庫函數編寫具體的程式 /* * 函數名:I2C_EEPROM_ByteRead * 描述:從I2C EEPROM中讀取一個位元組數據 * 輸入:pBuffer—指針變數,*pBuffer用來存放讀取到的數據 * ReadAddr:讀取數據的地址 * 輸出:無 */ void I2C_EEPROM_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr) { /*等待EEPROM準備好*/ I2C_EE_WaitEepromStandbyState(); /*①產生起始信號*/ I2C_GenerateSTART(I2C1, ENABLE); /*檢測EV5並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*②發送從機地址,寫選通*/ I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /*檢測EV6並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /*③發送要讀取數據的地址*/ I2C_SendData(I2C1, ReadAddr); /*檢測EV8並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /*④重新發送起始信號 */ I2C_GenerateSTART(I2C1, ENABLE); /*檢測EV5並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*⑤發送從機設備地址,讀選通*/ I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver); /*檢測EV6並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); /*⑥檢測EV7,然後讀取數據清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); *pBuffer = I2C_ReceiveData(I2C1); /*⑦非應答*/ I2C_AcknowledgeConfig(I2C1, DISABLE); /*⑧產生停止信號*/ I2C_GenerateSTOP(I2C1, ENABLE); } 4)從EEPROM中讀取一個位元組數據的函數分析 與之前的寫一個位元組函數一樣,會發現這裡除了我們根據讀取時序列出的步驟之外,還多了很多事件EVx的檢測,而且開頭還有一個等待EEPROM準備好的函數。下麵同樣根據EEPROM的讀取時序結合《STM32中文參考手冊_V10》中有關I2C相關章節的說明來解釋一下,首先我們來看看STM32的I2C主接收器傳送序列圖(這裡看7位主接收):問題說明:
問題1:為什麼要加一句等待EEPROM準備好的函數:
EEPROM的寫入是需要時間的,只有當前面的寫入操作完成,它才能繼續響應後面的讀取操作,否則在它還沒有準備好的情況下,進行讀取肯定是不成功的。關於等待EEPROM函數準備好的函數,它的實現原理就是通過向EEPROM發送從機地址,看從機是否成功應答,如果應答了就表示EEPROM已經準備好,如果應答失敗就說明EEPROM還沒準備好,然後主機繼續發送從機地址來呼叫EEPROM等待應答,直到從機應答成功就退出該函數執行下一步,其代碼如下(這段代碼是我在野火的工程代碼上修改了一點點,大家可以直接去看野火的官方常式,一定會有所收穫的):
void I2C_EE_WaitEepromStandbyState(void) { vu16 SR1_Tmp = 0; do { /* Send START condition */ I2C_GenerateSTART(I2C1, ENABLE); /* Read I2C1 SR1 register */ SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1); /* Send EEPROM address for write */ I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); }while(!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002)); /* Clear AF flag */ I2C_ClearFlag(I2C1, I2C_FLAG_AF); } 問題2: 可能到這裡有人會有疑問,一個是STM32的I2C的主接收序列,一個是EEPROM的讀時序,那寫代碼時應該依照哪一個啊?我的理解是,我們要兩者結合,按照EEPROM的讀時序來搞清楚讀一個數據需要那幾步,按照STM32的I2C主發送和主接收序列來搞清楚每一步需要檢測什麼事件(本質就是檢測哪些寄存器的哪些位來判斷相應的步驟已經順利完成)。 下麵結合主接收和主發送的序列圖以及EEPROM的讀時序圖來分析代碼為什麼這樣寫: ①等待從機準備好函數,這個上面已經解釋過了,這裡不再贅述; ②根據圖8 EEPROM讀取一個位元組時序圖可知,首先我們要產生一個起始信號S,然後根據圖4主發送序列圖可知,在成功發送起始條件後,會產生事件EV5,因此我們通過檢測事件EV5來判斷起始條件是否發送成功並清除寄存器; ③根據圖8 EEPROM讀取一個位元組時序圖可知,發送起始信號後,我們接下來要發送從機的設備地址(寫選通,即R/W為‘0’),根據圖4主發送序列圖可知,如果成功發送從機設備地址並收到應答信號後,會產生時間EV6,因此我們通過檢測EV6來判斷從機設備地址的發送情況; ④根據圖8 EEPROM讀取一個位元組時序圖可知,發送完從機設備地址後,我們接下來要發送讀取數據的地址,根據圖4主發送序列圖可知(這裡由於依然是主機在向從機發送數據,所以要參考圖4主發送序列圖),如果成功發送地址後,可以通過檢測事件EV8來判斷; ⑤根據圖8 EEPROM讀取一個位元組時序圖可知,接下來要重新發送起始信號(從這一步開始就要參考圖9主接收序列圖),同樣檢測事件EV5; ⑥根據圖8 EEPROM讀取一個位元組時序圖可知,接下來要發送從機設備地址,讀選通(即R/W為‘1’),根據圖9主接收序列圖可知,如果讀取地址發送成功,會產生事件EV6,因此我們通過檢測EV6來判斷和清除標誌; ⑦根據圖9主接收序列圖可知,接下來就是從機發送數據,主機接收數據,在主機接收一個數據後,會產生事件EV7,我們通過判斷EV7來查看數據接收是否完成,如果接收完成,就將數據寄存器中的數據讀取出來; ⑧根據圖9主接收序列圖可知,讀取一個數據後,主機要產生一個非應答信號,然後產生一個停止信號來結束本次傳輸(因為我們只讀取一個位元組數據,所以讀取的第一個數據也是最後一個數據,最後一個數據接收完成後產生非應答信號)。 到這裡,有關STM32的I2C寫入和讀取EEPROM操作就結束了,以上的內容都是我在學習相關知識內容時的一點總結和理解,希望能給需要的人帶來一些幫助,如果有什麼理解不對說明錯誤的地方,還請大家不吝批評指正,我會感激不盡,謝謝!