1、I2C簡介
1.1、I2C總線
I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接於總線上的器件之間傳送信息。
主器件用於啟動總線傳送數據,並產生時鍾以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發和收的關系不是恆定的,而取決於此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件.然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下.主機負責產生定時時鍾和終止數據傳送。
1.2、工作原理編輯
SDA(串行數據線)和SCL(串行時鍾線)都是雙向I/O線,接口電路為開漏輸出,需通過上拉電阻接電源VCC。當總線空閑時,兩根線都是高電平,連接總線的外同器件都是CMOS器件,輸出級也是開漏電路.在總線上消耗的電流很小,因此,總線上擴展的器件數量主要由電容負載來決定,因為每個器件的總線接口都有一定的等效電容.而線路中電容會影響總線傳輸速度.當電容過大時,有可能造成傳輸錯誤.所以,其負載能力為400pF,因此可以估算出總線允許長度和所接器件數量。
主器件用於啟動總線傳送數據,並產生時鍾以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發和收的關系不是恆定的,而取決於此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件.然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下.主機負責產生定時時鍾和終止數據傳送。
1.3、特點
I2C總線特點可以概括如下:
(1)、在硬件上,I2C總線只需要一根數據線和一根時鍾線兩根線,總線接口已經集成在芯片內部,不需要特殊的接口電路,而且片上接口電路的濾波器可以濾去總線數據上的毛刺.因此I2C總線簡化了硬件電路PCB布線,降低了系統成本,提高了系統可靠性。因為I2C芯片除了這兩根線和少量中斷線,與系統再沒有連接的線,用戶常用IC可以很容易形成標准化和模塊化,便於重復利用。
(2)、I2C總線是一個真正的多主機總線,如果兩個或多個主機同時初始化數據傳輸,可以通過沖突檢測和仲裁防止數據破壞,每個連接到總線上的器件都有唯一的地址,任何設備既可以作為主機也可以作為從機,但同一時刻只允許有一個主機,而且每一個設備都對應着唯一的地址。數據傳輸和地址設定由軟件設定,非常靈活。總線上的器件增加和刪除不影響其他器件正常工作。在通常的應用中,我們把CPU帶I2C總線接口的模塊作為主設備,把掛接在總線上的其他設備都作為從設備。
(3)、I2C總線可以通過外部連線進行在線檢測,便於系統故障診斷和調試,故障可以立即被尋址,軟件也利於標准化和模塊化,縮短開發時間。
(4)、 I2C總線上可掛接的設備數量受總線的最大電容400pF 限制,如果所掛接的是相同型號的器件,則還受器件地址位的限制。串行的8位雙向數據傳輸位速率在標准模式下可達100Kbit/s,快速模式下可達400Kbit/s,高速模式下可達3.4Mbit/s。一般通過I2C總線接口可編程時鍾來實現傳輸速率的調整,同時也跟所接的上拉電阻的阻值有關。I2C總線上的主設備與從設備之間以字節(8位)為單位進行雙向的數據傳輸。
(5)、總線具有極低的電流消耗.抗高噪聲干擾,增加總線驅動器可以使總線電容擴大10倍,傳輸距離達到15m;兼容不同電壓等級的器件,工作溫度范圍寬。
1.4、數據傳輸
(1)、字節格式
發送到SDA 線上的每個字節必須為8 位,每次傳輸可以發送的字節數量不受限制。每個字節后必須跟一個響應位。首先傳輸的是數據的最高位(MSB),如果從機要完成一些其他功能后(例如一個內部中斷服務程序)才能接收或發送下一個完整的數據字節,可以使時鍾線SCL 保持低電平,迫使主機進入等待狀態,當從機准備好接收下一個數據字節並釋放時鍾線SCL 后數據傳輸繼續。
(2)、應答響應
數據傳輸必須帶響應,相關的響應時鍾脈沖由主機產生。在響應的時鍾脈沖期間發送器釋放SDA 線(高)。
在響應的時鍾脈沖期間,接收器必須將SDA 線拉低,使它在這個時鍾脈沖的高電平期間保持穩定的低電平。
通常被尋址的接收器在接收到的每個字節后,除了用CBUS 地址開頭的數據,必須產生一個響應。當從機不能響應從機地址時(例如它正在執行一些實時函數不能接收或發送),從機必須使數據線保持高電平,主機然后產生一個停止條件終止傳輸或者產生重復起始條件開始新的傳輸。
如果從機接收器響應了從機地址,但是在傳輸了一段時間后不能接收更多數據字節,主機必須再一次終止傳輸。這個情況用從機在第一個字節后沒有產生響應來表示。從機使數據線保持高電平,主機產生一個停止或重復起始條件。
如果傳輸中有主機接收器,它必須通過在從機發出的最后一個字節時產生一個響應,向從機發送器通知數據結束。從機發送器必須釋放數據線,允許主機產生一個停止或重復起始條件。
I2C總線數據傳輸和應答如下圖:
(3)、時鍾同步
所有主機在SCL線上產生它們自己的時鍾來傳輸I2C總線上的報文。數據只在時鍾的高電平周期有效,因此需要一個確定的時鍾進行逐位仲裁。
時鍾同步通過線與連接I2C 接口到SCL 線來執行。這就是說SCL 線的高到低切換會使器件開始數它們的低電平周期,而且一旦器件的時鍾變低電平,它會使SCL 線保持這種狀態直到到達時鍾的高電平。但是如果另一個時鍾仍處於低電平周期,這個時鍾的低到高切換不會改變SCL 線的狀態。因此SCL 線被有最長低電平周期的器件保持低電平。此時低電平周期短的器件會進入高電平的等待狀態。
當所有有關的器件數完了它們的低電平周期后,時鍾線被釋放並變成高電平。之后,器件時鍾和SCL線的狀態沒有差別,而且所有器件會開始數它們的高電平周期。首先完成高電平周期的器件會再次將SCL線拉低。
這樣產生的同步SCL 時鍾的低電平周期由低電平時鍾周期最長的器件決定,而高電平周期由高電平時鍾周期最短的器件決定。
1.5、傳輸模式
(1)、快速模式
快速模式器件可以在400kbit/s 下接收和發送。最小要求是:它們可以和400kbit/s 傳輸同步,可以延長SCL 信號的低電平周期來減慢傳輸。快速模式器件都向下兼容,可以和標准模式器件在0~100kbit/s 的I2C 總線系統通訊。但是,由於標准模式器件不向上兼容,所以不能在快速模式I2C 總線系統中工作。快速模式I2C 總線規范與標准模式相比有以下特征:
A、最大位速率增加到400kbit/s;
B、調整了串行數據(SDA) 和串行時鍾(SCL )信號的時序;
C、快速模式器件的輸入有抑制毛刺的功能,SDA 和SCL輸入有施密特觸發器;
D、快速模式器件的輸出緩沖器對SDA 和SCL 信號的下降沿有斜率控制功能;
E、如果快速模式器件的電源電壓被關斷,SDA 和SCL 的I/O 管腳必須懸空,不能阻塞總線;
F、連接到總線的外部上拉器件必須調整以適應快速模式I2C 總線更短的最大允許上升時間。對於負載最大是200pF 的總線,每條總線的上拉器件可以是一個電阻,對於負載在200pF~400pF 之間的總線,上拉器件可以是一個電流源(最大值3mA )或者是一個開關電阻電路。
(2)、高速模式
高速模式(Hs 模式)器件對I2C 總線的傳輸速度有巨大的突破。Hs 模式器件可以在高達3.4Mbit/s 的位速率下傳輸信息,而且保持完全向下兼容快速模式或標准模式(F/S 模式)器件,它們可以在一個速度混合的總線系統中雙向通訊。
Hs 模式傳輸除了不執行仲裁和時鍾同步外,與F/S 模式系統有相同的串行總線協議和數據格式。
高速模式下I2C 總線規范如下:
A、Hs 模式主機器件有一個SDAH 信號的開漏輸出緩沖器和一個在SCLH 輸出的開漏極下拉和電流源上拉電路。這個電流源電路縮短了SCLH 信號的上升時間,任何時候在Hs 模式,只有一個主機的電流源有效;
B、在多主機系統的Hs 模式中,不執行仲裁和時鍾同步,以加速位處理能力。仲裁過程一般在前面用F/S 模式傳輸主機碼后結束;
C、Hs 模式主機器件以高電平和低電平是1:2 的比率產生一個串行時鍾信號。解除了建立和保持時間的時序要求;
D、可以選擇Hs 模式器件有內建的電橋。在Hs 模式傳輸中,Hs 模式器件的高速數據(SDAH)和高速串行時鍾(SCLH )線通過這個電橋與F/S 模式器件的SDA 和SCL 線分隔開來。減輕了SDAH 和SCLH 線的電容負載,使上升和下降時間更快;
E、Hs 模式從機器件與F/S 從機器件的唯一差別是它們工作的速度。Hs 模式從機在SCLH 和SDAH輸出有開漏輸出的緩沖器。SCLH 管腳可選的下拉晶體管可以用於拉長SCLH 信號的低電平,但只允許在Hs 模式傳輸的響應位后進行;
F、Hs 模式器件的輸出可以抑制毛刺,而且SDAH 和SCLH 輸出有一個施密特觸發器;
G、Hs 模式器件的輸出緩沖器對SDAH 和SCLH 信號的下降沿有斜率控制功能。
1.6、I2C線路圖
2、I2C 通訊詳解
I2C 通訊協議(Inter-Integrated Circuit)是由 Phiilps 公司開發的,由於它引腳少,硬件實現簡單,可擴展性強,不需要 USART、 CAN 等通訊協議的外部收發設備,現在被廣泛地使用在系統內多個集成電路(IC)間的通訊。下面我們分別對 I2C 協議的物理層及協議層進行講解。
2.1、I2C 物理層
I2C 通訊設備之間的常用連接方式見下圖:
它的物理層有如下特點:
(1)、它是一個支持多設備的總線。“總線”指多個設備共用的信號線。在一個 I2C 通訊總線中,可連接多個 I2C 通訊設備,支持多個通訊主機及多個通訊從機。
(2)、一個 I2C 總線只使用兩條總線線路,一條雙向串行數據線(SDA) ,一條串行時鍾線(SCL)。數據線即用來表示數據,時鍾線用於數據收發同步。
(3)、每個連接到總線的設備都有一個獨立的地址,主機可以利用這個地址進行不同設備之間的訪問。
(4)、總線通過上拉電阻接到電源。當 I2C 設備空閑時,會輸出高阻態,而當所有設備都空閑,都輸出高阻態時,由上拉電阻把總線拉成高電平。
(5)、 多個主機同時使用總線時,為了防止數據沖突,會利用仲裁方式決定由哪個設備占用總線。
(6)、具有三種傳輸模式:標准模式傳輸速率為 100kbit/s ,快速模式為 400kbit/s ,高速模式下可達 3.4Mbit/s,但目前大多 I2C 設備尚不支持高速模式。
(7)、連接到相同總線的 IC 數量受到總線的最大電容 400pF 限制 。
2.2、 協議層
I2C 的協議定義了通訊的起始和停止信號、數據有效性、響應、仲裁、時鍾同步和地址廣播等環節。
(1)、I2C 基本讀寫過程
先看看 I2C 通訊過程的基本結構,它的通訊過程見下圖:
I2C 通訊復合格式
這些圖表示的是主機和從機通訊時, SDA 線的數據包序列。
A、S 表示由主機的 I2C 接口產生的傳輸起始信號(S),這時連接到 I2C 總線上的所有從機都會接收到這個信號。
B、起始信號產生后,所有從機就開始等待主機緊接下來廣播 的從機地址信號(SLAVE_ADDRESS)。 在 I2C 總線上,每個設備的地址都是唯一的, 當主機廣播的地址與某個設備地址相同時,這個設備就被選中了,沒被選中的設備將會忽略之后的數據信號。根據 I2C 協議,這個從機地址可以是 7 位或 10 位。
C、在地址位之后,是傳輸方向的選擇位,該位為 0(W) 時,表示后面的數據傳輸方向是由主機傳輸至從機,即主機往從機中寫數據。該位為 1(R) 時,則相反,即主機由從機讀數據。
D、從機接收到匹配的地址后,從機會返回一個應答(ACK)或非應答(NACK)信號,只有接收到應答信號后,主機才能繼續發送或接收數據。
E、若配置的方向傳輸位為“寫數據”方向, 廣播完地址,接收到應答信號后, 主機開始正式向從機傳輸數據(DATA),數據包的大小為 8 位,主機每發送完一個字節數據,都要等待從機的應答信號(ACK),重復這個過程,可以向從機傳輸 N 個數據,這個 N 沒有大小限制。當數據傳輸結束時,主機向從機發送一個停止傳輸信號(P),表示不再傳輸數據。
F、若配置的方向傳輸位為“讀數據”方向, 廣播完地址,接收到應答信號后, 從機開始向主機返回數據(DATA),數據包大小也為 8 位,從機每發送完一個數據,都會等待主機的應答信號(ACK),重復這個過程,可以返回 N 個數據,這個 N 也沒有大小限制。當主機希望停止接收數據時,就向從機返回一個非應答信號(NACK),則從機自動停止數據傳輸。
G、除了基本的讀寫, I2C 通訊更常用的是復合格式,該傳輸過程有兩次起始信號(S)。一般在第一次傳輸中,主機通過 SLAVE_ADDRESS 尋找到從設備后,發送一段“數據”,這段數據通常用於表示從設備內部的寄存器或存儲器地址(注意區分它與 SLAVE_ADDRESS 的區別);在第二次的傳輸中,對該地址的內容進行讀或寫。也就是說,第一次通訊是告訴從機讀寫地址,第二次則是讀寫的實際內容。
(2)、通訊的起始和停止信號
前文中提到的起始(S)和停止(P)信號是兩種特殊的狀態,見下圖。當 SCL 線是高電平時 SDA 線從高電平向低電平切換,這個情況表示通訊的起始。當 SCL 是高電平時 SDA線由低電平向高電平切換,表示通訊的停止。起始和停止信號一般由主機產生。
I2C協議規定,總線上數據的傳輸必須以一個起始信號作為開始條件,以一個結束信號作為傳輸的停止條件。起始和結束信號總是由主設備產生(意味着從設備不可以主動通信?所有的通信都是主設備發起的,主設備可以發出詢問的command,然后等待從設備的通信)。
起始和結束信號產生條件:總線在空閑狀態時,SCL和SDA都保持着高電平。當SCL為高電平而SDA由高到低的跳變,表示產生一個起始條件;當SCL為高而SDA由低到高的跳變,表示產生一個停止條件。
在起始條件產生后,總線處於忙狀態,由本次數據傳輸的主從設備獨占,其他I2C器件無法訪問總線;而在停止條件產生后,本次數據傳輸的主從設備將釋放總線,總線再次處於空閑狀態。起始和結束如圖所示:
在了解起始條件和停止條件后,我們再來看看在這個過程中數據的傳輸是如何進行的。前面我們已經提到過,數據傳輸以字節為單位。主設備在SCL線上產生每個時鍾脈沖的過程中將在SDA線上傳輸一個數據位,當一個字節按數據位從高位到低位的順序傳輸完后,緊接着從設備將拉低SDA線,回傳給主設備一個應答位, 此時才認為一個字節真正的被傳輸完成。當然,並不是所有的字節傳輸都必須有一個應答位,比如:當從設備不能再接收主設備發送的數據時,從設備將回傳一個否定應答位。數據傳輸的過程如圖所示:
在前面我們還提到過,I2C總線上的每一個設備都對應一個唯一的地址,主從設備之間的數據傳輸是建立在地址的基礎上,也就是說,主設備在傳輸有效數據之前要先指定從設備的地址,地址指定的過程和上面數據傳輸的過程一樣,只不過大多數從設備的地址是7位的,然后協議規定再給地址添加一個最低位用來表示接下來數據傳輸的方向,0表示主設備向從設備寫數據,1表示主設備向從設備讀數據。向指定設備發送數據的格式如圖所示:(每一最小包數據由9bit組成,8bit內容+1bit ACK, 如果是地址數據,則8bit包含1bit方向)
(3)、數據有效性
I2C 使用 SDA 信號線來傳輸數據,使用 SCL 信號線進行數據同步。見圖 23-6。 SDA數據線在 SCL 的每個時鍾周期傳輸一位數據。傳輸時, SCL 為高電平的時候 SDA 表示的數據有效,即此時的 SDA 為高電平時表示數據“1”,為低電平時表示數據“0”。當 SCL為低電平時, SDA 的數據無效,一般在這個時候 SDA 進行電平切換,為下一次表示數據做好准備。
每次數據傳輸都以字節為單位,每次傳輸的字節數不受限制。
(4)、地址及數據方向
I2C 總線上的每個設備都有自己的獨立地址,主機發起通訊時,通過 SDA 信號線發送設備地址(SLAVE_ADDRESS)來查找從機。 I2C 協議規定設備地址可以是 7 位或 10 位,實際中 7 位的地址應用比較廣泛。緊跟設備地址的一個數據位用來表示數據傳輸方向,它是數據方向位(R/W),第 8 位或第 11 位。數據方向位為“1”時表示主機由從機讀數據,該位為“0”時表示主機向從機寫數據。見下圖:
讀數據方向時,主機會釋放對 SDA 信號線的控制,由從機控制 SDA 信號線,主機接收信號,寫數據方向時, SDA 由主機控制, 從機接收信號。
(5)、響應
I2C 的數據和地址傳輸都帶響應。響應包括“應答(ACK)”和“非應答(NACK)”兩種信號。作為數據接收端時,當設備(無論主從機)接收到 I2C 傳輸的一個字節數據或地址后,若希望對方繼續發送數據,則需要向對方發送“應答(ACK)”信號,發送方會繼續發送下一個數據;若接收端希結束數據傳輸,則向對方發送“非應答(NACK)”信號,發送方接收到該信號后會產生一個停止信號,結束信號傳輸。見下圖:
傳輸時主機產生時鍾,在第 9 個時鍾時,數據發送端會釋放 SDA 的控制權,由數據接收端控制 SDA,若 SDA 為高電平,表示非應答信號(NACK),低電平表示應答信號(ACK)。
讀數據方向時,主機會釋放對 SDA 信號線的控制,由從機控制 SDA 信號線,主機接收信號,寫數據方向時, SDA 由主機控制, 從機接收信號。
3、I2C總線操作
對I2C總線的操作實際就是主從設備之間的讀寫操作。大致可分為以下三種操作情況:
(1)、主設備往從設備中寫數據。數據傳輸格式如下:
(2)、主設備從從設備中讀數據。數據傳輸格式如下:
(3)、主設備往從設備中寫數據,然后重啟起始條件,緊接着從從設備中讀取數據;或者是主設備從從設備中讀數據,然后重啟起始條件,緊接着主設備往從設備中寫數據。數據傳輸格式如下:
第三種操作在單個主設備系統中,重復的開啟起始條件機制要比用STOP終止傳輸后又再次開啟總線更有效率。
4、軟件I2C程序
/** * @brief 配置I2C總線的GPIO * @param 無 * @retval 無 */ static void I2C_1_GPIO_Config(void) { I2C_1_GPIO_ClockCmd(I2C_1_GPIO_CLK, ENABLE); //打開GPIO時鍾 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Pin = I2C_1_SCL_GPIO_PIN ; GPIO_Init(I2C_1_SCL_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStructure); } /** * @brief I2C初始化 * @param 無 * @retval 無 */ void I2C_1_Config_Init(void) { I2C_1_GPIO_Config(); I2C_1_Stop(); //給一個停止信號, 復位I2C總線上的所有設備到待機模式 } /** * @brief 配置I2C的SDA線為輸入 * @param 無 * @retval 無 */ static void I2C_1_SDA_INTPUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //輸入 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空輸入 GPIO_InitStructure.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStructure); } /** * @brief 配置I2C的SDA線為輸出 * @param 無 * @retval 無 */ static void I2C_1_SDA_OUTPUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //輸出 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //開漏輸出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStructure); } /** * @brief I2C總線位延遲,最快400KHz * @param 無 * @retval 無 */ static void I2C_1_Delay(void) { for(uint8_t i = 0; i < 30; i++); //上面的時間是通過邏輯分析儀測試得到的 //工作條件:CPU主頻72MHz ,MDK編譯環境,1級優化 //循環次數為10時,SCL頻率 = 205KHz //循環次數為7時,SCL頻率 = 347KHz, SCL高電平時間1.5us,SCL低電平時間2.87us //循環次數為5時,SCL頻率 = 421KHz, SCL高電平時間1.25us,SCL低電平時間2.375us //工作條件:CPU主頻180MHz ,MDK編譯環境,1級優化 //循環次數為20~250時都能通訊正常 } /** * @brief CPU發起I2C總線啟動信號 * @param 無 * @retval 無 */ void I2C_1_Start(void) { //當SCL高電平時,SDA出現一個下跳沿表示I2C總線啟動信號 I2C_1_SDA_OUTPUT(); I2C_1_SDA_H(); I2C_1_SCL_H(); I2C_1_Delay(); I2C_1_SDA_L(); I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); } /** * @brief CPU發起I2C總線停止信號 * @param 無 * @retval 無 */ void I2C_1_Stop(void) { //當SCL高電平時,SDA出現一個上跳沿表示I2C總線停止信號 I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_L(); I2C_1_Delay(); I2C_1_SCL_H(); I2C_1_Delay(); I2C_1_SDA_H(); I2C_1_Delay(); } /** * @brief CPU產生一個時鍾,並讀取器件的ACK應答信號 * @param 無 * @retval 返回0表示正確應答,1表示無器件響應 */ uint8_t I2C_1_Wait_Ack(void) { uint8_t re; //應答信號 0:應答 1:非應答 I2C_1_SCL_L(); I2C_1_SDA_H(); //CPU釋放SDA總線 I2C_1_Delay(); I2C_1_SCL_H(); //CPU驅動SCL = 1, 此時器件會返回ACK應答 I2C_1_Delay(); I2C_1_SDA_INTPUT(); while(I2C_1_SDA_READ()) { re++; if(re>250) { I2C_1_Stop(); return 1; } } I2C_1_SCL_L(); I2C_1_Delay(); return 0; } /** * @brief CPU產生一個ACK應答信號,SDA低電平 * @param 無 * @retval 無 */ void I2C_1_Ack(void) { I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_L(); //CPU驅動SDA = 0 I2C_1_Delay(); I2C_1_SCL_H(); //CPU產生1個時鍾 I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); I2C_1_SDA_H(); //CPU釋放SDA總線 I2C_1_Delay(); } /** * @brief CPU產生1個NACK非應答信號,SDA高電平 * @param 無 * @retval 無 */ void I2C_1_NAck(void) { I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_H(); //CPU驅動SDA = 1 I2C_1_Delay(); I2C_1_SCL_H(); //CPU產生1個時鍾 I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); I2C_1_SDA_H(); //CPU釋放SDA總線 I2C_1_Delay(); } /** * @brief CPU向I2C總線設備寫8bit數據 * @param Data :要發送的數據 * @retval 無 */ void I2C_1_SendData(uint8_t Data) { uint8_t temp = 0; I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); for(uint8_t i = 0; i < 8; i++) //先發送字節的高位bit7 { temp = (Data & 0x80) >> 7; if(temp == 0x01) //判斷當前位是否為高電平 { I2C_1_SDA_H(); } else { I2C_1_SDA_L(); } I2C_1_Delay(); I2C_1_SCL_H(); //SCL處於高電平時SDA數據有效 I2C_1_Delay(); I2C_1_SCL_L(); //SCL處於低電平時SDA進行高低電平切換 Data <<= 1; if (i == 7) //最后一位數據發送完 { I2C_1_SDA_H(); //CPU釋放SDA總線 } } } /** * @brief CPU從I2C總線設備讀取8bit數據 * @param Ack:0:產生應答信號 1:產生非應答信號 * @retval 讀到的數據 */ uint8_t I2C_1_ReceiveData(void) { uint8_t Data=0; I2C_1_SDA_INTPUT(); I2C_1_SCL_L(); for(uint8_t i = 0; i < 8; i++) //讀到第1個bit為數據的bit7 { Data = Data << 1; //移位放在讀SDA數據前面,放后面的話讀完數據后會向左移一位,數據出錯 I2C_1_SCL_H(); //SCL處於高電平時讀取SDA數據 I2C_1_Delay(); if(I2C_1_SDA_READ()) //讀SDA口的狀態,讀數據 { Data++; } I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); } return Data; } /** * @brief 檢測I2C總線設備,CPU向I2C發送設備地址,然后讀取設備應答來判斷該設備是否存在 * @param Address:設備的I2C總線地址 * @retval 返回值 0 表示有應答, 返回1表示未探測到 */ uint8_t I2C_1_CheckDevice(uint8_t Address) { uint8_t Ack; I2C_1_Start(); //發送啟動信號 I2C_1_SendData(Address | I2C_1_WR); //發送設備地址+讀寫控制bit(0 = w, 1 = r) bit7 先傳 Ack = I2C_1_Wait_Ack(); //檢測設備的ACK應答 ,0表示應答,1表示非應答 I2C_1_Stop(); //發送停止信號 return Ack; }