01 先說概念
I2C 英文全稱 Inter-Integrated Circuit,字面意思是集成電路之間,也就是我們常說的 I 方 C 總線—I2C bus。它是一種串行通訊總線,使用多主從架構,由飛利浦公司(恩智浦 NXP 的母公司)在 80 年代開發,用於主板、嵌入式系統連接周邊低速設備。
I2C 由兩條雙向開漏線組成,這是一個很大的優勢,接線簡單。兩條線利用上拉電阻將電位上拉。典型電位為 +3.3V 或 +5V。標準傳輸速率為 100Kb/s,低速糢式 10Kb/s。
02 物理層
下圖為 I2C 總線的物理拓撲圖,大家可以看到一共只有兩條總線,一條 SDA(serial data) 數據線用來承載數據、一條 SCL(serial clock line) 時鐘線用來控制數據收發時序。所有 I2C 設備的 SDA 都接到了總線的 SDA 上,SCL 都接到了總線的 SCL 上。每個設備都有自己的唯一地址,以保證設備之間訪問的準確性。

I2C 在物理層的連接可以說是非常簡單,這也是它最大的優勢,原理就是通過控制 SDA 和 SCL 線的高低電平時序,來產生 I2C 總線協議所需要的信號進行數據傳輸。在總線處於空閑狀態時 SCL 和 SDA 被上拉電阻拉高,保持高電平。
需要註意的是 I2C 的通訊方式為半雙工,因為只有一條數據線,某一時刻只可能單向通訊。這也說明了 I2C 不適合大數據量的傳輸應用。
對於主機、從機的區分很簡單,發布主要命令的就是主機,接受命令的就是從機,同一條 I2C 總線允許多個主機的存在。
03 協議層
作為基礎我們先來了解幾個重要的小概念。
1、 初始狀態 (即空閑狀態):SDA 與 SCL 均為上拉電阻所致的高電平時為初始狀態;
2、 開始信號 :當 SCL 為高電平的時候,SDA 被拉低,此為開始信號,表明通訊開始。
3、 終止信號 :當 SCL 為高電平的時候,SDA 被拉高,此為終止信號,表明本次通訊結束。

到這裡大家有沒有發現點什麼?當 SCL 處於高電平的時候,SDA 電平一旦變化就會是一種信號,要麼開始要麼是終止。所以在 數據傳輸過程中 ,SCL 處於高電平時,SDA 必須保持狀態穩定,只有 SCL 處於低電平時 SDA 才可以變化。
4、 應答信號 :當發送器向接收器發送完一個位元組 / 8 位數據後,第 9 個時鐘周期內,接收器必須給發送器一個應答信號,這樣數據才算傳輸成功。高電平表非應答,低電平表應答。


我們了解這幾個信號狀態後,來一步一步看看數據是如何傳輸的。
1、向從機設備的某一個寄存器寫一個位元組數據:開始信號 + 設備地址 (7 位)+ 讀 / 寫 (1 位)+ 等待從機應答 + 寄存器地址 (8 位)+ 等待從機應答 + 要寫的數據 (8 位)+ 等待從機應答 + 終止信號。下圖為 24C02 EEPROM 存儲器寫數據的時序圖。
2、寫我們見識了,那讀一個試試:下圖為讀取 24C02 當前地址一個位元組數據的時序圖,是不是一目了然了。值得註意的是當讀的時候地址 7 位後的讀寫狀態位為 1。這裡說一下為什麼最後是 NO ACK,在「讀」這個操作下,主機為接收器,主機的 NO ACK 表示停止接收 24C02 的數據,不然 24C02 會繼續發。

3、我們再讀一個長一點的:下圖為讀取 24C02 任意地址一個位元組數據的時序圖。開始信號 + 設備地址 (7 位)+ 寫 (1 位)+ 等待從機應答 + 數據地址 (8 位)+ 等待從機應答。前面這一步為假寫,目的是告訴 24C02 要讀哪個地址的數據。繼續,開始信號 + 設備地址 (7 位)+ 讀 (1 位)+ 等待從機應答 + 讀到的數據 (8 位)+ 等待主機 (接收機) 應答 + 終止信號。

04 補點幹貨
1、 設備的地址 。I2C 設備的地址為 8 位,但是時序操作時最後一位不屬於地址,而是讀 or 寫狀態位。這就是為什麼 arduino 的 SH1106 庫裡操作的地址不是 0x7- 而是 0x3-,因為有用的是前 7 位,地址整體右移一位處理了。再一個設備地址的前四位是固定死的,是廠家用來表示設備類型的,比如接口為 I2C 的溫度傳感器類設備地址前四位一般為 1001 即 9X、EEPROM 存儲器地址前四位一般為 1010 即 AX、oled 屏地址前四位一般為 0111 即 7X 等等。
2、I2C 接口的致命缺點就是傳輸距離近同時速度慢 。 大家在使用 I2C 總線接口的時候切記不要長線傳輸,盡量只在 PCB 板內傳輸,不然偶爾丟數據甚至讀不到數據會讓人崩潰,不要問我是怎麼知道的,問只有眼淚。
3、 關於兩線為什麼設計成開漏 ,這個問題我記得我之前在寫《STM32 單片機 I / O 的 8 種工作糢式》時給大家埋下過伏筆。今天就來說一下具體原因。主要有兩點①防止短路:大家想想如果不設為開漏,而設為推挽,幾個設備連在同一條總線上,這時某一設備的某個 IO 輸出高電平,另有一臺設備的某一個 IO 輸出低電平,這時你會發現這兩個 IO 的 VCC 和 GND 短路了;但是開漏就不會有這個問題,如下圖示:

第二個原因是「線與」,我們想個場景:如果總線上的一個 A 設備將 SDA 拉高,這時總線上另一個 B 設備已將 SDA 拉低,這時由於 1 &0=0,所以 A 設備檢查 SDA 的時候會發現不是高電平而是低電平,這就表明總線上已經有其他設備占用總線了,A 只好放棄,如果檢測是高電平那就可以使用。如下圖示為 24C02 芯片內部圖,可以看到狀態檢測腳。

05 總結
I2C 總線作為一個常見的總線協議,是非常值得我們來仔細研究琢磨的,通透以後我們再使用任意 I2C 接口的設備時就可以信手拈來了。我一直覺得在學習的過程中,「會使用」不一定就是我們追求的終點,會用的同時把一些更深的東西搞懂搞透會收獲意想不到的喜悅。