您當前的位置:首頁 > 攝影

關於I2C的點滴總結

作者:由 小麥大叔 發表于 攝影時間:2020-11-22

背景

硬體層

資料傳輸協議

實際上如何工作?

單個主裝置連線多個從機

多個主裝置連線多個從機

如何程式設計?

總結

背景

I²C

Inter-Integrated Circuit

),中文應該叫

積體電路匯流排

,它是一種序列通訊匯流排,使用多主從架構,是由飛利浦公司在1980年代初設計的,方便了主機板、嵌入式系統或手機與周邊裝置元件之間的通訊。由於其簡單性,它被廣泛用於微控制器與感測器陣列,顯示器,IoT裝置,EEPROM等之間的通訊。

I²C

最重要的功能包括:

只需要兩條匯流排;

沒有嚴格的波特率要求,例如使用RS232,主裝置生成匯流排時鐘;

所有元件之間都存在簡單的主/從關係,連線到匯流排的每個裝置均可透過唯一地址進行軟體定址;

I²C是真正的多主裝置匯流排,可提供仲裁和衝突檢測;

傳輸速度;

標準模式:Standard Mode = 100

Kbps

快速模式:Fast Mode = 400

Kbps

高速模式: High speed mode = 3。4

Mbps

超快速模式: Ultra fast mode = 5

Mbps

最大主裝置數:無限制;

最大從機數:理論上是127;

以上是I²C的一些重要特點,下面會進一步對I²C進行介紹。

硬體層

I²C

協議僅需要一個

SDA和SCL

引腳。SDA是

序列資料線的

縮寫,而SCL是

序列時鐘線的

縮寫。這兩條資料線需要接上拉電阻。

裝置間的連線如下所示:

關於I2C的點滴總結

使用I²C,可以將多個從機(

Slave

)連線到單個主裝置(

Master

),並且還可以有多個主裝置(

Master

)控制一個或多個從機(

Slave

)。

假如希望有多個微控制器(

MCU

)將資料記錄到單個儲存卡或將文字顯示到單個LCD時,這個功能就非常有用。

I²C匯流排(

SDA

SCL

)內部都使用漏極開路驅動器(開漏驅動),因此

SDA

SCL

可以被拉低為低電平,但是不能被驅動為高電平

,所以每條線上都要使用一個上拉電阻,預設情況下將其保持在高電平;

關於I2C的點滴總結

拉電阻的

值取決於許多因素。

德州儀器TI 建議

使用以下公式來計算正確的上拉電阻值:

R_p(min)=\cfrac{V_{DD}-V_{OL}(max)}{I_{OL}}\\

R_p(min)=\cfrac{t_r}{0.8473xC_b}\\

其中

V_{ OL}

是邏輯低電壓;

I_{OL}

是邏輯低電流;

t_r

是訊號的最大上升時間;

C_b

是匯流排(電線)電容;

具體如下所示:

關於I2C的點滴總結

從上表可知,使用I2C裝置必須在

3mA

的灌電流下工作,

這裡不難發現需要在做選型需要滿足幾個條件;

灌電流 最大值為

3mA

另外I2C匯流排規範和使用者手冊還為低電平輸出電壓

V_{OL}

設定了最大值為0。4V

R_p(min) = \cfrac{VCC - 0.4V}{3mA}\\

所以根據上述公式可以計算,對於5V的電源,每個上拉電阻必須至少具有1。53kΩ,而對於3。3V的電源,每個電阻必須至少具有967Ω。

如果覺得計算電阻值比較麻煩,也可以使用

典型值 4.7kΩ

上述推導過程可以參考 TI的文件《I2C Bus Pullup Resistor Calculation》

https://www。

ti。com/lit/an/slva689/s

lva689。pdf

最終在除錯的時候,當我們測量SDA或SCL訊號並且邏輯LOW上的電壓高於0。4V時,我們就知道可以知道灌電流太高了;

關於I2C的點滴總結

當然,這並不意味著每當灌電流超過3mA時,裝置就會立即停止工作。但是,在操作超出其規格的裝置時,應始終小心,因為它可能導致通訊故障,縮短其使用壽命甚至甚至永久損壞裝置。

資料傳輸協議

主裝置和從裝置進行資料傳輸時遵循以下協議格式。資料透過一條SDA資料線在主裝置和從裝置之間傳輸

0

1

的序列資料。序列資料序列的結構可以分為,開始條件,地址位,讀寫位,應答位,資料位,停止條件,具體如下所示;

關於I2C的點滴總結

開始條件

當主裝置決定開始通訊時,需要傳送開始訊號,需要執行以下動作;

先將SDA線從高壓電平切換到低壓電平;

然後將

SCL

從高電平切換到低電平;

在主裝置傳送開始條件訊號之後,所有從機即使處於睡眠模式也將

變為活動狀態

,並

等待接收地址位

具體如下圖所示;

關於I2C的點滴總結

地址位

通常地址位佔7位資料,主裝置如果需要向從機發送/接收資料,首先要傳送對應從機的地址,然後會匹配總線上掛載的從機的地址;

I2C還支援10位定址;

讀寫位

該位指定資料傳輸的方向;

如果主裝置需要將資料傳送到從裝置,則該位設定為

0

如果主裝置需要往從裝置接收資料,則將其設定為

1

ACK / NACK

主機每次傳送完資料之後會等待從裝置的應答訊號

ACK

在第9個時鐘訊號,如果從裝置傳送應答訊號

ACK

,則

SDA

會被拉低;

若沒有應答訊號

NACK

,則

SDA

會輸出為高電平,這過程會引起主裝置發生重啟或者停止;

關於I2C的點滴總結

資料塊

傳輸的資料總共有8位,由傳送方設定,它需要將資料位傳輸到接收方。

傳送之後會緊跟一個

ACK

/

NACK

位,如果接收器成功接收到資料,則設定為0。否則,它保持邏輯“ 1”。

重複傳送,直到資料完全傳輸為止。

停止條件

當主裝置決定結束通訊時,需要傳送開始訊號,需要執行以下動作;

先將SDA線從低電壓電平切換到高電壓電平;

再將SCL線從高電平拉到低電平;

具體如下圖所示;

關於I2C的點滴總結

實際上如何工作?

第一步:起始條件

主裝置透過將SDA線從高電平切換到低電平,再將SCL線從高電平切換到低電平,來向每個連線的從機發送啟動條件 :

關於I2C的點滴總結

第二步:傳送從裝置地址

主裝置向每個從機發送要與之通訊的從機的7位或10位地址,以及相應的

讀/寫位

關於I2C的點滴總結

第三步:接收應答

每個從裝置將主裝置傳送的地址與其自己的地址進行比較。如果地址匹配,則從裝置透過

將SDA線拉低一位以表示返回一個ACK位

如果來自主裝置的地址與從機自身的地址不匹配,則

從裝置將SDA線拉高,表示返回一個NACK位

關於I2C的點滴總結

第四步:收發資料

主裝置傳送或接收資料到從裝置;

關於I2C的點滴總結

第五步:接收應答

在傳輸完每個資料幀後,接收裝置將另一個ACK位返回給傳送方,以確認已成功接收到該幀:

關於I2C的點滴總結

第六步:停止通訊

為了停止資料傳輸,主裝置將SCL切換為高電平,然後再將SDA切換為高電平,從而向從機發送停止條件;

關於I2C的點滴總結

單個主裝置連線多個從機

I2C總線上的主裝置使用7位地址對從裝置進行定址,可以使用128(

2^7

)個從機地址。

請使用4。7K上拉電阻將SDA和SCL線連線到Vcc;

關於I2C的點滴總結

多個主裝置連線多個從機

多個主裝置可以連線到一個或多個從機;

當兩個主裝置試圖透過SDA線路同時傳送或接收資料時,同一系統中的多個主裝置就會出現問題。

為了解決這個問題,

每個主裝置都需要在傳送訊息之前檢測SDA線是低電平還是高電平

如果SDA線為低電平,則意味著另一個主裝置可以控制匯流排,並且主裝置應等待發送訊息。

如果SDA線為高電平,則可以安全地傳送訊息。

要將多個主裝置連線到多個從機,請使用下圖,其中4。7K上拉電阻將SDA和SCL線連線到Vcc:

關於I2C的點滴總結

如何程式設計?

Talk is cheap。 Show me the code。

參考了STM32的HAL庫中I2C驅動,主裝置傳送函式

HAL_I2C_Master_Transmit()

具體如下:

/**

* @brief Transmits in master mode an amount of data in blocking mode。

* @param hi2c Pointer to a I2C_HandleTypeDef structure that contains

* the configuration information for the specified I2C。

* @param DevAddress Target device address: The device 7 bits address value

* in datasheet must be shifted to the left before calling the interface

* @param pData Pointer to data buffer

* @param Size Amount of data to be sent

* @param Timeout Timeout duration

* @retval HAL status

*/

HAL_StatusTypeDef

HAL_I2C_Master_Transmit

I2C_HandleTypeDef

*

hi2c

uint16_t

DevAddress

uint8_t

*

pData

uint16_t

Size

uint32_t

Timeout

){

uint32_t

tickstart

=

0x00U

/* Init tickstart for timeout management*/

tickstart

=

HAL_GetTick

();

if

hi2c

->

State

==

HAL_I2C_STATE_READY

){

/* Wait until BUSY flag is reset */

if

I2C_WaitOnFlagUntilTimeout

hi2c

I2C_FLAG_BUSY

SET

I2C_TIMEOUT_BUSY_FLAG

tickstart

!=

HAL_OK

){

return

HAL_BUSY

}

/* Process Locked */

__HAL_LOCK

hi2c

);

/* Check if the I2C is already enabled */

if

((

hi2c

->

Instance

->

CR1

&

I2C_CR1_PE

!=

I2C_CR1_PE

){

/* Enable I2C peripheral */

__HAL_I2C_ENABLE

hi2c

);

}

/* Disable Pos */

hi2c

->

Instance

->

CR1

&=

~

I2C_CR1_POS

hi2c

->

State

=

HAL_I2C_STATE_BUSY_TX

hi2c

->

Mode

=

HAL_I2C_MODE_MASTER

hi2c

->

ErrorCode

=

HAL_I2C_ERROR_NONE

/* Prepare transfer parameters */

hi2c

->

pBuffPtr

=

pData

hi2c

->

XferCount

=

Size

hi2c

->

XferOptions

=

I2C_NO_OPTION_FRAME

hi2c

->

XferSize

=

hi2c

->

XferCount

/* Send Slave Address */

if

I2C_MasterRequestWrite

hi2c

DevAddress

Timeout

tickstart

!=

HAL_OK

){

if

hi2c

->

ErrorCode

==

HAL_I2C_ERROR_AF

){

/* Process Unlocked */

__HAL_UNLOCK

hi2c

);

return

HAL_ERROR

}

else

{

/* Process Unlocked */

__HAL_UNLOCK

hi2c

);

return

HAL_TIMEOUT

}

}

/* Clear ADDR flag */

__HAL_I2C_CLEAR_ADDRFLAG

hi2c

);

while

hi2c

->

XferSize

>

0U

){

/* Wait until TXE flag is set */

if

I2C_WaitOnTXEFlagUntilTimeout

hi2c

Timeout

tickstart

!=

HAL_OK

){

if

hi2c

->

ErrorCode

==

HAL_I2C_ERROR_AF

){

/* Generate Stop */

hi2c

->

Instance

->

CR1

|=

I2C_CR1_STOP

return

HAL_ERROR

}

else

{

return

HAL_TIMEOUT

}

}

/* Write data to DR */

hi2c

->

Instance

->

DR

=

*

hi2c

->

pBuffPtr

++

);

hi2c

->

XferCount

——

hi2c

->

XferSize

——

if

((

__HAL_I2C_GET_FLAG

hi2c

I2C_FLAG_BTF

==

SET

&&

hi2c

->

XferSize

!=

0U

)){

/* Write data to DR */

hi2c

->

Instance

->

DR

=

*

hi2c

->

pBuffPtr

++

);

hi2c

->

XferCount

——

hi2c

->

XferSize

——

}

/* Wait until BTF flag is set */

if

I2C_WaitOnBTFFlagUntilTimeout

hi2c

Timeout

tickstart

!=

HAL_OK

){

if

hi2c

->

ErrorCode

==

HAL_I2C_ERROR_AF

){

/* Generate Stop */

hi2c

->

Instance

->

CR1

|=

I2C_CR1_STOP

return

HAL_ERROR

}

else

{

return

HAL_TIMEOUT

}

}

}

/* Generate Stop */

hi2c

->

Instance

->

CR1

|=

I2C_CR1_STOP

hi2c

->

State

=

HAL_I2C_STATE_READY

hi2c

->

Mode

=

HAL_I2C_MODE_NONE

/* Process Unlocked */

__HAL_UNLOCK

hi2c

);

return

HAL_OK

}

else

{

return

HAL_BUSY

}

}

總結

本文主要介紹I2C的入門基礎知識,從I2C協議的硬體層,協議層進行了簡單介紹;作者能力有限,難免存在錯誤和紕漏,請大佬不吝賜教。

相互吹捧,共同進步,歡迎關注;

標簽: hi2c  I2C  裝置  HAL  SDA