上一篇文章【Maker電子學】I2C 界面解密 — PART 3,我們聊了 I2C(以下寫作 I2C) 界面的底層邏輯協定,但是我們發現 I2C 的標準只定義了對裝置的單一/連續的讀取與寫入,並沒有定義我們常使用的暫存器編號使用方法,那麼這些用法是怎麼來的呢?這一次我們就來探討這個部分。
作者 | Bird,本文經MakerPRO同意轉載,原文連結 |
一切都從 EEPROM 開始
我們上次說過,I2C 誕生在一個淳樸的年代,那時候的週邊晶片並沒有爲數衆多的暫存器可供讀寫,因此 I2C 也就沒有定義什麼暫存器位址之類的協定。當時大部分的 I2C 週邊晶片都只有一兩個 8-bit 的暫存器,因此不管是讀或是寫,頂多連續讀寫幾個 byte 就可以了。
但自從 EEPROM 出現之後,這種只用連續讀寫來存取多個暫存器的方法就顯得很笨拙了。假設今天我們有一顆 256 bytes 的 EEPROM,我們總不能每次讀取時都下一個連續讀取 256 bytes 的指令,把它的內容全部讀出來吧?如果寫入的時候這樣做,EEPROM 的壽命很快就會用完了。
因此,當 I2C 的 EEPROM 出現時,就帶入了索引(index)這個協定上的概念。
索引暫存器
這個概念是,EEPROM 的內部有一個稱為 index 的暫存器,它就像一個指標,指向我們要讀取或是寫入的位置。每一次我們對 EEPROM 下寫入指令時,第一個寫入的 byte 就是寫入這個 index 暫存器,接下來寫入的資料才會寫到這個 index 暫存器所指向的位置。
以 24C02 這樣的 EEPROM 爲例,它的容量是 2 Kbits,或是 2048 / 8 = 256 bytes,因此一個 byte 的 index 暫存器剛好可以用來指向 0-255 的位置,也就可以用來存取整顆 EEPROM 的內容。
如果我要對 EEPROM 的第 0 個 byte 寫入一個 0x77 這樣的值,在 I2C 上就要用這樣的指令:
這是一個 2 bytes 的連續寫入指令,它連續寫了 0x00 和 0x07 這兩個值到 EEPROM 的 I2C slave address。根據上面說明的,寫入 I2C EEPROM 的第一個 byte 一定會寫入它的 index 暫存器,因此當第一個 byte 寫完時,index 暫存器的內容就會變成 0x00,而接下來對 EEPROM 所操作的任何動作,不管是讀取還是寫入,都是對著 index 所指向的這個位址進行,因此上面這個指令就會把 0x77 寫到 EEPROM 的 0x00 這個位置裡。
那要讀取 EEPROM 的內容時該怎麼做呢?讀取的時候比較麻煩。假設我們要讀取 EEPROM 裡面位於 0x02 這個位址的內容,我們得先寫一個 0x02 到 EEPROM 的 I2C slave address,然後再對同一個 slave address 下讀取的指令,讀回一個 byte。整個指令會變成這樣:
看起來比第一個寫入資料的指令複雜很多,但其實只要知道它的結構就不會那麼令人疑惑。
第一個寫入的指令同樣會寫入 index 暫存器,以上面的例子來說就是將 index 暫存器設爲 0x02,因此接下來的動作都會對著 EEPROM 裡面 0x02 這個位址操作。
寫入指令結束後,緊接著是一個 START 狀態,當 I2C 裝置看到 START 狀態,就知道接下來的這個 byte 是 slave address。這種沒有經過 STOP 狀態而直接來的 START 狀態,叫「RESTART」,事實上跟一般的 START 沒有什麼不同,一樣是一個 SCL 爲 high、SDA 下降的訊號,它的目的是用來告訴裝置接下來的動作變成讀取(還記得嗎?I2C slave address 的最後一個 bit 代表這個指令是寫入還是讀取,寫入是 0,讀取是 1)。
當我們下了 RESTART 狀態以及讀取的 I2C slave address 後,目標裝置就會知道接下來要吐資料出來,此時 master 繼續切換 SCL 訊號,slave 裝置會把資料放到 SDA 上,讓 master 讀回來,而根據我們前面說明的協定,由於 index 暫存器已經在前一個寫入指令時被設定爲 0x02,此時讀出來的資料就是 0x02 這個位址的內容。
自動加一
Index 這個索引暫存器除了用來指向要讀寫的資料外,還有一個特點:每次讀寫完之後就會自動加一。因著這個特性,如果我們寫完一個 byte 後,沒有立即送出 STOP 狀態,而是繼續送出資料,那麼接下來的資料就會依序被寫入後面的位址;讀取時也一樣,如果我們讀完一個 byte 之後不送 non-acknowledge 也不接 STOP,而是繼續切換 SCL,EEPROM 就會繼續依序吐出接下來位址裡面的資料。
這種連續讀取的功能叫「sequential read」,它可以從一個特定的位址開始一直讀一直讀,沒有長度的限制,就算你已經讀到 2 Kbit EEPROM 的最後一個位址 0xff,index 暫存器會自動歸零,你可以繼續從第一個位址 0x00 讀起。
不過寫入時狀況就有一點不一樣。由於 EEPROM 寫入需要一點時間,而且這個時間相對 I2C bus 的速度來說並不快,通常在數個 ms 左右,因此 EEPROM 的連續寫入指令長度有限制。
EEPROM 在收到來自 I2C bus 的指令後,會先把要寫入的資料暫存在一個寫入用的 buffer 中,再慢慢寫入,因此這個寫入用 buffer 的大小就限制了連續寫入指令的長度,常見的限制是 16 或 32 個 bytes,詳情要看每一顆 EEPROM 的 datasheet。
在 EEPROM 裡面,記憶單元是以一個 page 一個 page 這樣的單位排列的,page 是 EEPROM 更新資料的最小單位,因此這個寫入 buffer 的長度通常會跟 EEPROM 的 page size 一樣,除此之外,大部分的 EEPROM 在單一的連續寫入指令中,不能跨越 page 的邊界。
頁的邊界
我們用個實際的例子來說明 page 的概念。下圖是 24C02 這個 I2C EEPROM 的內部結構圖(事實上大部分的 I2C EEPROM 結構都是這個樣子):
左邊框起來的那個 address register and counter 就是我們說的 index 索引暫存器。根據 24C02 的 datasheet,它的 page size 是 16 bytes,因爲總容量是 256 bytes,所以總共有 256 / 16 = 16 個 page。
Page 的位址是連續排列的,因此 0x00 – 0x0f 的位址是一個 page,0x10 – 0x1f 的位址是下一個 page,以此類推。如果我們從 0x00 開始下連續寫入的指令,最多就可以下 16 個 bytes,寫到 0x0f,但如果我們從 0x12 開始下連續寫入的指令,最多就只能下 14 個 bytes,寫到 0x1f,再多就會發生錯誤。
大部分的 I2C EEPROM 設計會讓寫入 buffer 繞到同一個 page 的前面,也就是說如果你從 0x12 開始連續寫 16 個 bytes,第 15 和第 16 個 byte 就會分別被寫入 0x10 和 0x11 這兩個位址(雖然這是一種可以預期的結果,但還是儘量不要這樣操作比較好)。
由於 EEPROM 的寫入是以 page 爲單位,因此寫入一個 byte 和寫入一個 page 所需要的時間是一樣的。這個時間大概在數個 ms 左右,對大部分的 MCU 來說都是個不能忽略的時間,因為如果 EEPROM 還在忙著寫入,而 I2C 上又捎來新的指令, EEPROM 就會用 non-acknowledge 狀態回應 slave address 的點名指令,因此對 MCU 來說剛下完寫入指令的 EEPROM 是不能馬上接著操作的。
不是 EEPROM 的裝置
除了 EEPROM 以外,還有很多週邊裝置 IC 使用 I2C 界面作爲和 MCU 或是系統晶片溝通的橋樑。大部分的週邊裝置 IC 內部有許多用來設定功能或是讀取狀態的暫存器,如果暫存器的總數小於 256 個,它們大概就是遵循我們今天所說明的讀寫方式來操作。
舉個例子,我們之前介紹過 TI 的 LM73 這顆數位界面的溫度感測器,這顆晶片就是使用跟 I2C 相容的界面來存取內部的暫存器。LM73 裡面總共有 6 個暫存器,因此它在讀寫時允許的 index 範圍就是 0 到 5。
我們來看一下 LM73 的 datasheet 中,讀取一個暫存器內容的時序圖:
有沒有覺得這個時序圖跟我們這次介紹的時序圖很像?圖中的 Frame 1 就是寫入的 I2C slave address 指令,Frame 2 就是要讀取的暫存器編號,也就是要寫入 index 的值(在這裡 TI 稱之爲 pointer)、Frame 3 是 RESTART 狀態之後讀取 I2C slave address 的指令,接下來讀出來的那個 byte 就是 LM73 對應的暫存器內容。
這些使用 I2C 界面操作的週邊 IC,使用起來其實就像是一顆 I2C EEPROM,而裡面的暫存器就像是 EEPROM 裡面的每一個記憶單位,因此只要會使用 I2C EEPROM,就會使用 I2C 和這些週邊 IC 溝通了。
這些週邊 IC 內部暫存器通常是 SRAM,寫入的速度非常快,因此沒有 EEPROM 那種寫入要等待的麻煩手續,也沒有什麼 page 邊界的問題,使用起來其實更簡單;不過由於各家廠商在實作 IC 時多少還是會有一些細節上的差異,實務上還是要詳細閱讀 IC 的 datasheet 以確定使用 I2C 通訊的正確方法。
小結
這次我們用 EEPROM 爲例子,完整說明了用 I2C 界面操作週邊 IC 的方法。下一回我們要進入到 I2C 界面,比較少人接觸到的一些協定細節,如時脈延展(clock stretch)和廣播(broadcast)等動作。