I2C(也常寫作IIC,I2C),全稱為Inter-Integrated Circuit(“互連集成電路”),用于在集成電路之間進(jìn)行短距離數(shù)據(jù)傳輸。它由Philips(現(xiàn)在的NXP半導(dǎo)體)公司于1980年代初開(kāi)發(fā),并成為一種廣泛應(yīng)用于電子設(shè)備之間通信的標(biāo)準(zhǔn)。I2C協(xié)議簡(jiǎn)單、靈活且廣泛支持,常用于連接傳感器、存儲(chǔ)器、顯示屏和其他外設(shè)到微控制器、微處理器或其他集成電路上。這是一種簡(jiǎn)單的雙向雙線總線,非常適合用于微控制器與外設(shè)之間,或者多個(gè)微控制器之間的高效互連控制。
其名稱反映了最初的設(shè)計(jì)目的:它專為“集成電路”間的通信設(shè)計(jì)。I2C的兩條線包括SDA(串行數(shù)據(jù)線),用于傳輸信息;和SCL(串行時(shí)鐘線),負(fù)責(zé)同步。I2C的一個(gè)優(yōu)點(diǎn)是它只需要這兩根線進(jìn)行通信,這可以在復(fù)雜系統(tǒng)中有效地利用資源。
它也是一種在許多產(chǎn)品中廣泛使用的標(biāo)準(zhǔn),擴(kuò)展和集成相對(duì)簡(jiǎn)單。
I2C的主要特點(diǎn)包括:
- 通信方式:I2C采用主從式通信方式。一個(gè)主設(shè)備(通常是微控制器或處理器)控制總線并發(fā)起通信,而一個(gè)或多個(gè)從設(shè)備被動(dòng)地響應(yīng)主設(shè)備的命令或請(qǐng)求。
- 總線結(jié)構(gòu):I2C使用兩根線路進(jìn)行通信:* SCL(Serial Clock):時(shí)鐘線由主設(shè)備提供,用于同步通信速度。* SDA(Serial Data):數(shù)據(jù)線用于傳輸數(shù)據(jù)和控制信號(hào)。
- 信號(hào)電平:I2C使用雙向開(kāi)漏(open-drain)輸出,這意味著信號(hào)線可以被拉低(邏輯0)但不能被主動(dòng)拉高,只能通過(guò)外部上拉電阻回到高電平(邏輯1)。
- 地址和幀格式:I2C使用7位或10位的設(shè)備地址來(lái)唯一標(biāo)識(shí)每個(gè)從設(shè)備。通信幀包括設(shè)備地址、讀/寫位、數(shù)據(jù)字節(jié)和應(yīng)答位(ACK)。
- 速率和模式:I2C支持不同的通信速率,通常有標(biāo)準(zhǔn)模式(100 kbit/s)和快速模式(400 kbit/s)。還有更高速的模式如高速模式(3.4 Mbit/s)和超高速模式(5 Mbit/s)。
- 啟動(dòng)和停止條件:I2C通信的開(kāi)始由主設(shè)備發(fā)送啟動(dòng)信號(hào)(SDA從高電平切換到低電平,同時(shí)SCL保持高電平)表示。通信結(jié)束時(shí),主設(shè)備發(fā)送停止信號(hào)(SDA從低電平切換到高電平,同時(shí)SCL保持高電平)表示。
- 多主設(shè)備支持:I2C協(xié)議允許多個(gè)主設(shè)備共享同一條總線,通過(guò)仲裁機(jī)制來(lái)解決總線競(jìng)爭(zhēng)問(wèn)題。
- 應(yīng)用領(lǐng)域:I2C廣泛應(yīng)用于各種設(shè)備和應(yīng)用領(lǐng)域,例如傳感器、存儲(chǔ)器(如EEPROM)、顯示屏、溫度傳感器、實(shí)時(shí)時(shí)鐘(RTC)、擴(kuò)展IO芯片等。
I2C是一種簡(jiǎn)單、靈活且廣泛支持的串行通信協(xié)議,適用于在電子設(shè)備之間進(jìn)行短距離數(shù)據(jù)傳輸。它具有較低的硬件復(fù)雜性和通信開(kāi)銷,因此在許多嵌入式系統(tǒng)和電子設(shè)備中被廣泛采用。
I2C協(xié)議還有一些其他的特性和擴(kuò)展,如高速模式、高容量模式、地址擴(kuò)展等,以滿足更復(fù)雜的應(yīng)用需求。雖然I2C并不適合所有情況(例如,它并不適用于長(zhǎng)距離或高速應(yīng)用),但它是一種常見(jiàn)的、實(shí)用的選擇,適用于簡(jiǎn)單的內(nèi)部通信。當(dāng)希望許多從設(shè)備共享通信線并由一個(gè)(或多個(gè))主設(shè)備管理時(shí),它特別有用。
I2C總線通用連接框圖:
I2C總線互聯(lián)系統(tǒng)示意圖
在單主機(jī)系統(tǒng)中,可以抽象為:
單主機(jī)I2C總線
典型的多主機(jī)系統(tǒng)則為:
多主機(jī)I2C總線
電氣特性
I2C使用開(kāi)漏/開(kāi)集電極結(jié)構(gòu),同時(shí)在同一數(shù)據(jù)線上使用輸入緩沖器,這允許單個(gè)數(shù)據(jù)線用于雙向數(shù)據(jù)流。
雙向通信的開(kāi)漏結(jié)構(gòu)
開(kāi)漏指的是一種輸出類型,可以將總線拉低到某個(gè)電壓(通常是地線),或者"釋放"總線,讓其被上拉電阻拉高。當(dāng)總線被主設(shè)備或從設(shè)備釋放時(shí),上拉電阻(RPU)負(fù)責(zé)將總線電壓拉高到電源電平。由于沒(méi)有設(shè)備可以強(qiáng)制將線路拉高,這意味著總線永遠(yuǎn)不會(huì)遇到通信問(wèn)題,其中一個(gè)設(shè)備嘗試傳輸高電平,而另一個(gè)設(shè)備傳輸?shù)碗娖剑瑢?dǎo)致短路(電源電平到地)。I2C要求在多主設(shè)備環(huán)境中,如果主設(shè)備發(fā)送高電平,但檢測(cè)到線路低電平(另一個(gè)設(shè)備將其拉低),則停止通信,因?yàn)榱硪粋€(gè)設(shè)備正在使用總線。推挽接口不允許此類自由,這是I2C的一個(gè)優(yōu)點(diǎn)。
SDA/SCL內(nèi)部基本結(jié)構(gòu)
上圖顯示了在SDA/SCL線上的從設(shè)備或主設(shè)備的內(nèi)部結(jié)構(gòu)的簡(jiǎn)化視圖,包括用于讀取輸入數(shù)據(jù)的緩沖器和用于傳輸數(shù)據(jù)的下拉場(chǎng)效應(yīng)晶體管(FET)。設(shè)備只能將總線線路拉低(與地短接)或釋放總線線路(對(duì)地高阻抗),并允許上拉電阻將電壓拉高。這是在處理I2C設(shè)備時(shí)需要了解的重要概念,因?yàn)闆](méi)有設(shè)備可以將總線保持高電平。這個(gè)特性是實(shí)現(xiàn)雙向通信的關(guān)鍵。
拉低總線
如前所述,開(kāi)漏結(jié)構(gòu)只能將總線拉低,或者"釋放"總線并由電阻將其拉高。下圖展示了將總線拉低時(shí)的電流流動(dòng)。希望傳輸?shù)碗娖降倪壿嫊?huì)激活下拉場(chǎng)效應(yīng)晶體管,提供對(duì)地的短路,將線路拉低。
拉低總線
釋放總線
當(dāng)從設(shè)備或主設(shè)備希望傳輸邏輯高時(shí),它只能通過(guò)關(guān)閉下拉場(chǎng)效應(yīng)晶體管來(lái)釋放總線。這將使總線懸空,而上拉電阻將把電壓拉高到電源電平,被解釋為高電平。下圖展示了通過(guò)上拉電阻的電流流動(dòng),將總線拉高。
釋放總線
通信速率模式
速率模式
常規(guī)模式
高速模式
超快模式
接口規(guī)范
一般操作
I2C總線是一種標(biāo)準(zhǔn)的雙向接口,使用控制器(即主設(shè)備)與從設(shè)備進(jìn)行通信。除非被主設(shè)備尋址,否則從設(shè)備不能傳輸數(shù)據(jù)。I2C總線上的每個(gè)設(shè)備都有一個(gè)特定的設(shè)備地址,以區(qū)分其他在同一I2C總線上的設(shè)備。許多從設(shè)備在啟動(dòng)時(shí)需要進(jìn)行配置,以設(shè)置設(shè)備的行為。這通常在主設(shè)備訪問(wèn)從設(shè)備的內(nèi)部寄存器映射時(shí)完成,這些寄存器具有唯一的寄存器地址。一個(gè)設(shè)備可以有一個(gè)或多個(gè)寄存器,用于存儲(chǔ)、寫入或讀取數(shù)據(jù)。
物理的I2C接口由串行時(shí)鐘(SCL)線和串行數(shù)據(jù)(SDA)線組成。SDA和SCL線都必須通過(guò)上拉電阻連接到VCC電源。上拉電阻的大小取決于I2C線路上的電容量。只有當(dāng)總線處于空閑狀態(tài)時(shí),才能啟動(dòng)數(shù)據(jù)傳輸。如果在停止條件之后,SDA和SCL線都保持高電平,總線將被認(rèn)為是空閑的。
主設(shè)備訪問(wèn)從設(shè)備的一般過(guò)程如下:
- 如果主設(shè)備想要向從設(shè)備發(fā)送數(shù)據(jù):
- 主設(shè)備-發(fā)送器發(fā)送起始條件并尋址從設(shè)備-接收器* 主設(shè)備-發(fā)送器向從設(shè)備-接收器發(fā)送數(shù)據(jù)* 主設(shè)備-發(fā)送器通過(guò)停止條件終止傳輸
- 如果主設(shè)備想要接收/讀取從設(shè)備的數(shù)據(jù):
- 主設(shè)備-接收器發(fā)送起始條件并尋址從設(shè)備-發(fā)送器
- 主設(shè)備-接收器向從設(shè)備-發(fā)送器發(fā)送要讀取的寄存器
- 主設(shè)備-接收器從從設(shè)備-發(fā)送器接收數(shù)據(jù)
- 主設(shè)備-接收器通過(guò)停止條件終止傳輸
起始和停止條件
I2C通信通過(guò)主設(shè)備發(fā)送起始條件(START condition)來(lái)初始化,并通過(guò)主設(shè)備發(fā)送停止條件(STOP condition)來(lái)終止。在SCL線為高電平時(shí),SDA線上的高至低的跳變定義了起始條件。在SCL線為高電平時(shí),SDA線上的低至高的跳變定義了停止條件。
起始和停止條件示例
重復(fù)起始條件
重復(fù)的起始條件(Repeated START condition)類似于起始條件(START condition),用作連續(xù)的停止條件和起始條件的替代。它在外觀上與起始條件相同,但與起始條件不同,因?yàn)樗l(fā)生在停止條件之前(總線不處于空閑狀態(tài))。這在主設(shè)備希望啟動(dòng)新的通信,但不希望通過(guò)停止條件使總線空閑,從而可能失去對(duì)總線的控制權(quán)(在多主設(shè)備環(huán)境中)時(shí)非常有用。
數(shù)據(jù)有效性和字節(jié)格式
在每個(gè)SCL時(shí)鐘脈沖期間傳輸一個(gè)數(shù)據(jù)位。一個(gè)字節(jié)由SDA線上的八個(gè)位組成。一個(gè)字節(jié)可以是設(shè)備地址、寄存器地址或從從設(shè)備寫入或讀取的數(shù)據(jù)。數(shù)據(jù)以最高有效位(MSB)優(yōu)先的順序傳輸。在START和STOP條件之間,可以從主設(shè)備向從設(shè)備傳輸任意數(shù)量的數(shù)據(jù)字節(jié)。在時(shí)鐘周期的高電平期間,SDA線上的數(shù)據(jù)必須保持穩(wěn)定,因?yàn)楫?dāng)SCL為高電平時(shí),數(shù)據(jù)線的變化被解釋為控制命令(START或STOP)。
單字節(jié)傳輸示例
應(yīng)答(ACK)和非應(yīng)答(NACK)
接收器在每個(gè)數(shù)據(jù)字節(jié)(包括地址字節(jié))后發(fā)送一個(gè)ACK位。ACK位允許接收器向發(fā)送器傳達(dá)字節(jié)已成功接收,并且可以發(fā)送另一個(gè)字節(jié)。
在接收器發(fā)送ACK之前,發(fā)送器必須釋放SDA線。為了發(fā)送ACK位,接收器在ACK/NACK相關(guān)的時(shí)鐘周期(周期9)的低電平期間拉低SDA線,以便在ACK/NACK相關(guān)的時(shí)鐘周期的高電平期間,SDA線保持穩(wěn)定低電平。必須考慮到設(shè)置時(shí)間和保持時(shí)間。
當(dāng)SDA線在ACK/NACK相關(guān)的時(shí)鐘周期中保持高電平時(shí),這被解釋為非應(yīng)答(NACK)。有幾種情況會(huì)導(dǎo)致產(chǎn)生NACK:
- 接收器由于執(zhí)行某些實(shí)時(shí)功能并且尚未準(zhǔn)備好與主設(shè)備開(kāi)始通信,因此無(wú)法接收或發(fā)送。
- 在傳輸過(guò)程中,接收器接收到無(wú)法理解的數(shù)據(jù)或命令。
- 在傳輸過(guò)程中,接收器無(wú)法接收更多的數(shù)據(jù)字節(jié)。
- 主設(shè)備接收器完成數(shù)據(jù)讀取,并通過(guò)NACK向從設(shè)備表示這一點(diǎn)。
NACK波形示例
I2C數(shù)據(jù)
必須通過(guò)讀取或?qū)懭霃脑O(shè)備的寄存器來(lái)發(fā)送和接收數(shù)據(jù)。寄存器是從設(shè)備內(nèi)存中包含信息的位置,無(wú)論是配置信息還是一些采樣數(shù)據(jù)用于發(fā)送回主設(shè)備。主設(shè)備必須向這些寄存器中寫入信息,以指示從設(shè)備執(zhí)行任務(wù)。
雖然在I2C從設(shè)備中常見(jiàn)的是寄存器,但請(qǐng)注意,并非所有的從設(shè)備都會(huì)有寄存器。一些設(shè)備非常簡(jiǎn)單,只包含一個(gè)寄存器,可以通過(guò)在從設(shè)備地址之后立即發(fā)送寄存器數(shù)據(jù)來(lái)直接寫入該寄存器,而無(wú)需尋址寄存器。單寄存器設(shè)備的一個(gè)例子是8位I2C開(kāi)關(guān),它通過(guò)I2C命令進(jìn)行控制。由于它只有一個(gè)位來(lái)啟用或禁用通道,因此只需要一個(gè)寄存器,主設(shè)備只需在從設(shè)備地址之后寫入寄存器數(shù)據(jù),跳過(guò)寄存器編號(hào)。
向I2C總線上的從設(shè)備寫入數(shù)據(jù)
要在I2C總線上寫入數(shù)據(jù),主設(shè)備將在總線上發(fā)送一個(gè)帶有從設(shè)備地址的起始條件,并將最后一位(R/W位)設(shè)置為0,表示寫入操作。在從設(shè)備發(fā)送應(yīng)答位后,主設(shè)備將發(fā)送要寫入的寄存器地址。從設(shè)備再次發(fā)送應(yīng)答位,通知主設(shè)備它已準(zhǔn)備好。然后,主設(shè)備將開(kāi)始向從設(shè)備發(fā)送寄存器數(shù)據(jù),直到主設(shè)備發(fā)送完所有需要的數(shù)據(jù)(有時(shí)僅為一個(gè)字節(jié)),然后通過(guò)發(fā)送停止條件終止傳輸。
下圖顯示了向從設(shè)備寄存器寫入單個(gè)字節(jié)的示例:
向從設(shè)備寄存器寫數(shù)據(jù)示例
從I2C總線上的從設(shè)備讀取數(shù)據(jù)
從從設(shè)備讀取數(shù)據(jù)與寫入數(shù)據(jù)非常相似,但有一些額外的步驟。為了從從設(shè)備讀取數(shù)據(jù),主設(shè)備首先必須告知從設(shè)備它希望從哪個(gè)寄存器讀取數(shù)據(jù)。主設(shè)備通過(guò)類似寫入操作的方式開(kāi)始傳輸,發(fā)送帶有R/W位為0(表示寫入)的地址,然后是要從中讀取數(shù)據(jù)的寄存器地址。一旦從設(shè)備確認(rèn)了寄存器地址,主設(shè)備將再次發(fā)送起始條件,然后發(fā)送帶有R/W位為1(表示讀取)的從設(shè)備地址。這次,從設(shè)備將確認(rèn)讀取請(qǐng)求,主設(shè)備釋放SDA總線,但仍向從設(shè)備提供時(shí)鐘。在此事務(wù)的這個(gè)階段,主設(shè)備將變?yōu)橹髟O(shè)備接收器,而從設(shè)備將變?yōu)閺脑O(shè)備發(fā)送器。
主設(shè)備將繼續(xù)發(fā)送時(shí)鐘脈沖,但會(huì)釋放SDA線,以便從設(shè)備可以傳輸數(shù)據(jù)。在每個(gè)數(shù)據(jù)字節(jié)結(jié)束時(shí),主設(shè)備將向從設(shè)備發(fā)送一個(gè)ACK,表示主設(shè)備準(zhǔn)備好接收更多數(shù)據(jù)。一旦主設(shè)備接收到了所期望的字節(jié)數(shù),它將發(fā)送一個(gè)NACK,告知從設(shè)備停止通信并釋放總線。主設(shè)備隨后發(fā)送停止條件。
下圖顯示了從從設(shè)備寄存器讀取單個(gè)字節(jié)的示例:
從從設(shè)備寄存器讀數(shù)據(jù)示例
實(shí)例
這兩天調(diào)試這個(gè)模塊時(shí)捕捉了一些波形,可以結(jié)合前文一起理解:
實(shí)例1
對(duì)應(yīng)代碼:
result = ch347_driver.i2c_set(device_index, 1)
if result:
print("Success to set I2C speed.")
else:
print("Failed to set I2C speed.")
result = ch347_driver.i2c_set_delay_ms(device_index, 1)
if result:
print("Success to set I2C delay.")
else:
print("Failed to set I2C delay.")
result = ch347_driver.stream_i2c(device_index, b'x13', 2)
if result:
print("Success!")
else:
print("Failed!")
實(shí)例2
對(duì)應(yīng)代碼:
result = ch347_driver.i2c_set(device_index, 1)
if result:
print("Success to set I2C speed.")
else:
print("Failed to set I2C speed.")
result = ch347_driver.i2c_set_delay_ms(device_index, 1)
if result:
print("Success to set I2C delay.")
else:
print("Failed to set I2C delay.")
result = ch347_driver.stream_i2c(device_index, b'x12x13x14', 8)
if result:
print("Success!")
else:
print("Failed!")
總結(jié)
I2C總結(jié)
評(píng)論