王 輝
(銳捷網絡股份有限公司,福建 福州 350002)
I2C(Inter-Integrated Circuit)總線是一種由PHILIPS公司開發的用于連接微控制器及其外圍設備的總線。它是同步通信的一種特殊形式,具有接口線少、控制方式簡化等優點,成為近年來在微電子通信控制領域廣泛采用的一種總線標準。但由于I2C總線是共享性總線,總線上任何一個器件的SDA/SCL信號的異常都會影響整個總線的正常工作,因此I2C協議有提供了復位操作來解決問題。但在實際應用中還是會有一些異常的情況出現,例如操作過程中I2C主機異常復位,導致操作未完成,這樣總線就有可能出現死鎖的情況。該文就I2C死鎖的各種情況做了深入具體的分析說明,并給出了一種完整、可靠的解決方案。
一次完整的數據傳送如圖1所示,I2C是以字節(8位)為單位進行數據傳輸的。在起始位出現后,發送8位數據(首先傳輸的是數據的最高位),每傳輸8位后必須跟一個應答位(ACK)。ACK為0代表有效,反之則說明無人應答。數據傳輸一般由主機產生的停止位終止。
如果當從機需要完成一些其他功能后才能繼續完成數據通信時,從機可以使時鐘SCL保持低電平,迫使主機進入等待狀態。當從機準備好并釋放時鐘線SCL后,數據傳輸繼續[1-2]。
I2C協議里定義了一個總線復位的機制,即主機在非正常操作過程中主動在SCL信號線上發9個時鐘周期,總線上的從機檢測到后,必須主動復位各自的I2C接口[3]。
這個機制可以解決實際應用中從機一直拉低SDA的總線死鎖的情況。但遇到SCL被拉低以及其他復雜的死鎖,I2C協議并沒有給出解決的機制。
I2C死鎖有多種故障類型,可以歸納為如下3種情況。
2.1.1 情況1(應答位死鎖)
I2C總線讀或寫操作指令應答位死鎖導致。在I2C設備進行讀或寫操作指令的過程中,主設備在產生開始信號后在SCL線上產生8個時鐘脈沖,然后拉低SCL信號的電平。此時,從設備輸出應答信號,將SDA信號拉為低電平。如果這個時候主控制器突然復位,導致SCL信號被拉為高電平,而從設備將SDA一直拉為低電平,直到SCL變為低電平才會結束應答信號。主、從設備之間相互等待,進入應答位死鎖狀態。
2.1.2 情況2(讀操作死鎖)
當I2C主控制器進行讀操作時,I2C 從設備應答后輸出數據。如果這時主控制器突然復位,而此時從設備輸出的數據位正好為0(SDA為低電平),主控制器就會將SCL拉為高電平,從設備將SDA拉為低電平,這樣也會導致I2C總線進入死鎖狀態。
2.1.3 情況3(I2C控制器狀態異常)
I2C控制器狀態異常,SCL和SDA電平處于“正?!睜顟B,但主控又無法繼續操作I2C總線,也無法讀取I2C狀態寄存器值。這個情況一般都是主設備沒有發送STOP命令,進而使其I2C控制器進入異常狀態。
針對死鎖,I2C協議里的推薦是發送9×CLK,進行總線復位。這個方法是能解決一些死鎖的情況,但針對上述死鎖情況是沒法解決的。例如對應答位死鎖狀態,由于I2C正常發送9×CLK(8個數據位+應答位),此時發送8CLK會使I2C總線死鎖位移到下一個字節的應答位,SDA仍然為低電平,即陷入死循環而無法解鎖。
2.2.1 應答位死鎖
應答位死鎖進入過程如下:主設備對從設備進行讀寫操作時,進行到應答位,此時主設備異常復位,SCL被主設備拉高,SDA被從設備拉低,如圖2所示。
應答位是在第9個CLK,主設備只要將SCL輸出一個低電平,從設備就會將SDA釋放到高電平,再發送一個STOP命令,就可將I2C總線解鎖,即發送1×CLK+STOP就可以將總線解鎖。另外從I2C 協議可知,I2C總線是以byte+ACK為單位進行傳輸的,即9個CLK為一組,如果發送8×CLK+STOP,則會使I2C進入下一個byte的ACK,無法從死鎖中恢復。由此可知,對應答位死鎖狀解鎖,發送(1+N)×CLK+STOP可以解鎖,N必須是8的整數倍。
2.2.2 讀操作時死鎖
讀操作時進入死鎖的過程如下:主設備對從設備正在進行讀操作,當從設備輸出的數據為0(1到8之間任意bit)時主設備異常復位,SCL就會被主設備拉高,SDA被從設備拉低,進入死鎖狀態(如圖3所示),也即讀操作有可能在1到8 bit之間的任意bit進入死鎖狀態。當第1個bit進入死鎖時,解鎖需要8×CLK+STOP。當第8個bit進入死鎖時,解鎖需要1×CLK+STOP,也即最多需要8×CLK+STOP,最少需要1×CLK+STOP。同時也有驗證CLK數量大于8時,比如9×CLK+STOP也能正常解鎖??偨Y來看,N×CLK+STOP(N≥8)可以對所有讀操作時死鎖狀態進行解鎖。
2.2.3 控制器狀態異常
控制器狀態異常進入死鎖的過程如下:I2C總線的SCL異常被拉低,或者配置為GPIO模式發送命令過程中,主設備主動或者被動停止繼續發現CLK,進而使主設備的I2C控制器進入異常狀態。
出現異常,一般可從主機I2C控制器內部狀態寄存器的信息分析出來。通常I2C控制器的邏輯都是用I2C CORE IP來實現的[4],這樣從State Reg來獲取控制器的運行狀態,當狀態碼為00時就代表總線出現異常狀態(bus error)。此時直接發送一個STOP命令可以使控制器跳出bus error狀態,恢復正常。這個方法我們在Cavium/NXP等系列的CPU平臺測試驗證均有效[5-6]。
通過上述分析,該文針對不同的死鎖情況給出了對應的解決方案,見表1。但在實際應用中,由于并不知道死鎖發生的具體情況,如果每次都要把這3種解決方案都試一遍,軟件實現起來有些復雜,因此需要找到歸一化的解決方案。

表1 死鎖解決方案匯總
第2節的3種解鎖方案可以歸結為9×CLK+STOP這一種。對于這個方案是否真的能解決問題,該文做了驗證,發現該方案對上述死鎖均可有效解鎖,只是在寫操作應答位死鎖時,雖然也能解鎖,但可能會存在向從設備誤寫數據的風險。
對讀操作應答位死鎖狀態,發送CLK對其進行解鎖時,主設備知道此時讀取的數據是無效的,可以選擇忽略這些數據。
基于CAVIUM cpu的板卡,我們對從設備EEPROM即24C02(地址為0x57),偏移為0,寫數據0x5a,在ACK之后加上9×CLK+STOP,此時9×CLK發送時SDA的數據不會被寫入EEPROM內部[7],如圖4所示。
因此說明第2節歸納總結的方案存在缺陷。接下來分析如何改進。
針對對寫操作應答位死鎖解鎖無法寫入的情況,從EEPROM的內部流程可知,EEPROM會把接收到的數據先緩存到內部的Buffer中,當接收到STOP條件后,才會往內部寫。所以解鎖過程中發送9個CLK后又直接發STOP命令,就會被EEPROM認為是無效的結束操作,沒法啟動內部寫的動作。因此發完9個CLK后,要先發送START命令形成新開始,再發送STOP命令,就不會有誤操作。
而針對控制器狀態異常,同樣可利用START命令使從設備重新開始這一特性,在解鎖命令之前加入命令START,從設備就不會對START命令之前主設備發送的異常操作進行應答。
綜合來看,解決各種死鎖的歸一化解鎖命令應該如下:START+9×CLK+START+STOP。使用這個解鎖命令分別針對所有故障現象模擬測試的驗證情況見表2。

表2 改進方案驗證記錄
因此,START+9×CLK+START+STOP對讀或寫應答位死鎖狀態、讀操作死鎖狀態和控制器狀態異常情況均可有效解鎖,也即對所有情況均可有效。因此,該文總結了實現的邏輯流程圖[8],如圖5所示。
該文在介紹了I2C基本協議后,全面地分析了各種I2C死鎖的各種原因及其對應解決方案,并在此基礎上提煉了一個完整,可靠的解決方案。通過試驗驗證,說明此方案可以有效得提高I2C總線的魯棒性。