關於I2C的點滴總結
背景
硬體層
資料傳輸協議
實際上如何工作?
單個主裝置連線多個從機
多個主裝置連線多個從機
如何程式設計?
總結
背景
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是
序列時鐘線的
縮寫。這兩條資料線需要接上拉電阻。
裝置間的連線如下所示:
使用I²C,可以將多個從機(
Slave
)連線到單個主裝置(
Master
),並且還可以有多個主裝置(
Master
)控制一個或多個從機(
Slave
)。
假如希望有多個微控制器(
MCU
)將資料記錄到單個儲存卡或將文字顯示到單個LCD時,這個功能就非常有用。
I²C匯流排(
SDA
,
SCL
)內部都使用漏極開路驅動器(開漏驅動),因此
SDA
和
SCL
可以被拉低為低電平,但是不能被驅動為高電平
,所以每條線上都要使用一個上拉電阻,預設情況下將其保持在高電平;
上
拉電阻的
值取決於許多因素。
德州儀器TI 建議
使用以下公式來計算正確的上拉電阻值:
其中
是邏輯低電壓;
是邏輯低電流;
是訊號的最大上升時間;
是匯流排(電線)電容;
具體如下所示:
從上表可知,使用I2C裝置必須在
的灌電流下工作,
這裡不難發現需要在做選型需要滿足幾個條件;
灌電流 最大值為
;
另外I2C匯流排規範和使用者手冊還為低電平輸出電壓
設定了最大值為0。4V
所以根據上述公式可以計算,對於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時,我們就知道可以知道灌電流太高了;
當然,這並不意味著每當灌電流超過3mA時,裝置就會立即停止工作。但是,在操作超出其規格的裝置時,應始終小心,因為它可能導致通訊故障,縮短其使用壽命甚至甚至永久損壞裝置。
資料傳輸協議
主裝置和從裝置進行資料傳輸時遵循以下協議格式。資料透過一條SDA資料線在主裝置和從裝置之間傳輸
0
和
1
的序列資料。序列資料序列的結構可以分為,開始條件,地址位,讀寫位,應答位,資料位,停止條件,具體如下所示;
開始條件
當主裝置決定開始通訊時,需要傳送開始訊號,需要執行以下動作;
先將SDA線從高壓電平切換到低壓電平;
然後將
SCL
從高電平切換到低電平;
在主裝置傳送開始條件訊號之後,所有從機即使處於睡眠模式也將
變為活動狀態
,並
等待接收地址位
。
具體如下圖所示;
地址位
通常地址位佔7位資料,主裝置如果需要向從機發送/接收資料,首先要傳送對應從機的地址,然後會匹配總線上掛載的從機的地址;
I2C還支援10位定址;
讀寫位
該位指定資料傳輸的方向;
如果主裝置需要將資料傳送到從裝置,則該位設定為
0
;
如果主裝置需要往從裝置接收資料,則將其設定為
1
。
ACK / NACK
主機每次傳送完資料之後會等待從裝置的應答訊號
ACK
;
在第9個時鐘訊號,如果從裝置傳送應答訊號
ACK
,則
SDA
會被拉低;
若沒有應答訊號
NACK
,則
SDA
會輸出為高電平,這過程會引起主裝置發生重啟或者停止;
資料塊
傳輸的資料總共有8位,由傳送方設定,它需要將資料位傳輸到接收方。
傳送之後會緊跟一個
ACK
/
NACK
位,如果接收器成功接收到資料,則設定為0。否則,它保持邏輯“ 1”。
重複傳送,直到資料完全傳輸為止。
停止條件
當主裝置決定結束通訊時,需要傳送開始訊號,需要執行以下動作;
先將SDA線從低電壓電平切換到高電壓電平;
再將SCL線從高電平拉到低電平;
具體如下圖所示;
實際上如何工作?
第一步:起始條件
主裝置透過將SDA線從高電平切換到低電平,再將SCL線從高電平切換到低電平,來向每個連線的從機發送啟動條件 :
第二步:傳送從裝置地址
主裝置向每個從機發送要與之通訊的從機的7位或10位地址,以及相應的
讀/寫位
;
第三步:接收應答
每個從裝置將主裝置傳送的地址與其自己的地址進行比較。如果地址匹配,則從裝置透過
將SDA線拉低一位以表示返回一個ACK位
;
如果來自主裝置的地址與從機自身的地址不匹配,則
從裝置將SDA線拉高,表示返回一個NACK位
;
第四步:收發資料
主裝置傳送或接收資料到從裝置;
第五步:接收應答
在傳輸完每個資料幀後,接收裝置將另一個ACK位返回給傳送方,以確認已成功接收到該幀:
第六步:停止通訊
為了停止資料傳輸,主裝置將SCL切換為高電平,然後再將SDA切換為高電平,從而向從機發送停止條件;
單個主裝置連線多個從機
I2C總線上的主裝置使用7位地址對從裝置進行定址,可以使用128(
)個從機地址。
請使用4。7K上拉電阻將SDA和SCL線連線到Vcc;
多個主裝置連線多個從機
多個主裝置可以連線到一個或多個從機;
當兩個主裝置試圖透過SDA線路同時傳送或接收資料時,同一系統中的多個主裝置就會出現問題。
為了解決這個問題,
每個主裝置都需要在傳送訊息之前檢測SDA線是低電平還是高電平
;
如果SDA線為低電平,則意味著另一個主裝置可以控制匯流排,並且主裝置應等待發送訊息。
如果SDA線為高電平,則可以安全地傳送訊息。
要將多個主裝置連線到多個從機,請使用下圖,其中4。7K上拉電阻將SDA和SCL線連線到Vcc:
如何程式設計?
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協議的硬體層,協議層進行了簡單介紹;作者能力有限,難免存在錯誤和紕漏,請大佬不吝賜教。
相互吹捧,共同進步,歡迎關注;
上一篇:拍的路燈照片怎麼解讀?