http://home.eeworld.com.cn/my/space-uid-716241-blogid-655190.html
1、I2C協議簡介
I2C是兩線式串行總線,用於鏈接微控制器及其外圍設備。兩根信號線分別是:
時鐘信號線SCL和數據信號線SDA。
2、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 傳輸協議數據包構成圖示
3、以I2C讀寫EEPROM(AT24C02)爲例進行說明html
3.1 針對EEPROM的基本操做函數
①寫操做學習
②讀操做ui
針對EEPROM的寫操做和讀操做也能夠細分,如:spa
寫操做:設計
①往EEPROM中寫入一個字節的數據;指針
②往EEPROM中批量寫入數據;htm
讀操做:blog
①從EEPROM中指定地址讀取一個字節數據;事件
②從EEPROM中批量讀取數據;
下面結合上面4種讀寫操做來具體說明:
一、往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?後面的操做中又爲何要檢測EV六、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=一、BTF=1的狀況下,就產生EV8_2;
⑤檢測到EV8_2後,主機就能夠產生中止信號來結束本次傳輸了。
有關寫入一個字節數據到EEPROM中函數的分析到這裏就結束了,下面來講一些從EEPROM中讀取一個字節數據的相關代碼以及分析。
二、從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操做就結束了,以上的內容都是我在學習相關知識內容時的一點總結和理解,但願能給須要的人帶來一些幫助,若是有什麼理解不對說明錯誤的地方,還請你們不吝批評指正,我會感激涕零,謝謝!