實驗: I2C 的操作

透過 I2C 改變 EEPROM 的數值

I2C 的連線與測試環境

I2C (Inter-Integrated Circuit) 是一個廣泛被實現的通訊協定,通常用於不同晶片之間的通訊。 I2C有兩條通訊線路: SDA (Serial Data Line) 和 SCL (Serial Clock Line),此兩條線路皆為Open Drain。

一般來說,IC 的輸入有兩種: push-pull (0, 1) 或是 Open drain,Open drain沒有預設的輸出電壓數值 (浮接狀態),因此需要一個上拉電阻 (Rp) 來確保其初始態為高電位。

I2C 的裝置有兩種模式: master (主設備) 和多個 slave (從設備),資料存取模式為 master 對 slave 進行資料的寫入與讀取。在一個 I2C 匯流排上可以有多個 master 和 slave,其中,為了避免 master 之間相互干擾, master 在傳送資料前會先聽其他 master 的動作,避免互相影響,其中原理可以參考以下連結:

實作中所使用的 IC (EEPROM) 是 24LC02。其SPEC文件可以參考:

其中,A0-A2 用以定義其位置,表示法為: [1 0 1 0 A2 A1 A0],考慮到我們把 A0-A2 都接到GND,因此其裝置的位址為 0101000 也就是16 進位的 0x50。

若是要考慮正確的 I2C 的匯流排上,則要按照下圖來完成電路:

此上拉電阻(Rp)的數值約略在 1-10K,若不確定可用2K的電阻。對於I2C而言,上拉電阻的選擇主要在於 falling 或是 raising 的區間,考慮到 falling 或是 raising 有一定的時間要求,若此上拉電阻的選擇錯誤,可能導致falling或是raising的判讀錯誤。

I2C 的軟體安裝與環境設定

GL-AR750S 已經宣告了 I2C 使用 GPIO 的腳位 (gpio-5 sda; gpio-21 scl),卻沒有安裝好相對應的I2C套件,因此,我們仍必須先下載 GPIO 所需的操作軟體。

root@GL-AR750S:~# opkg update
Downloading http://www.gl-inet.com/lede/3.0/ar71xx/nand/Packages.gz
Updated list of available packages in /var/opkg-lists/packages
Downloading http://www.gl-inet.com/lede/3.0/ar71xx/nand/Packages.sig
Signature check passed.
root@GL-AR750S:~# opkg install kmod-i2c-gpio-custom
Installing kmod-i2c-gpio-custom (4.9.109-2) to root...
Downloading http://www.gl-inet.com/lede/3.0/ar71xx/nand/kmod-i2c-gpio-custom_4.9.109-2_mips_24kc.ipk
Installing kmod-i2c-algo-bit (4.9.109-1) to root...
Downloading http://www.gl-inet.com/lede/3.0/ar71xx/nand/kmod-i2c-algo-bit_4.9.109-1_mips_24kc.ipk
Installing kmod-i2c-gpio (4.9.109-1) to root...
Downloading http://www.gl-inet.com/lede/3.0/ar71xx/nand/kmod-i2c-gpio_4.9.109-1_mips_24kc.ipk
Configuring kmod-i2c-algo-bit.
Configuring kmod-i2c-gpio.
Configuring kmod-i2c-gpio-custom.
root@GL-AR750S:~# opkg install i2c-tools
Installing i2c-tools (3.1.2-1) to root...
Downloading http://www.gl-inet.com/lede/3.0/ar71xx/nand/i2c-tools_3.1.2-1_mips_24kc.ipk
Configuring i2c-tools.

假若沒有將 GPIO 對應至 I2C 設定,其對應的指令如下:

# insmod /lib/modules/3.18.29/i2c-gpio-custom.ko bus0=0,1,17

Note that the three parameters 0,1,17 are I2C-BUS number, SDA and SCL respectively. We use GPIO0 for SDA, and GPIO17 for SCL. Certainly you can use any idle and available GPIO for SDA and SCL.

I2C 的操作與實驗結果

  • 列出 I2C 的裝置 i2cdetect -l

在此 OpenWRT 上只有一個 I2C 模組,編號為0, 一個模組對應到一個 I2C 的匯流排。若是有多個模組,就可以同時操控多個匯流排。

root@GL-AR750S:~# i2cdetect -l
i2c-0   i2c             i2c-gpio0                               I2C adapter
  • 列出匯流排上的 I2C 裝置 i2cdetect -y 0

讀取在此 I2C 模組上的 slave 裝置, 我們可以看到, 0x50 的位置上有我們掛上去的 EEPROM。此位置的設定和 A0-A2 的設定相關。

  • 讀取 I2C EEPROM i2cdump -y -f 0 0x50

此時裝置內都沒有數值,初始為1。 讀取裝置的指令為 i2cdump -y -f [#mode] [dev addr] 我們可以看到此 EEPROM 的定址空間為 16*16*16 bits。

  • 寫入 I2C EEPROM i2cset -y -f 0 0x50 0x00 0x41

寫入裝置的指令為 i2set -y -f [#mode] [dev addr] [data addr] [data value]

使用 MCP23017 擴充 GPIO

MCP23017 是一顆將 I2C 轉換成 GPIO 的 IC, 一共可以輸出 16 個 GPIO 的輸出 (GPA0-7,GPB0-7),輸入則藉由 I2C 的介面 (VCC-VDD、GND-VSS、SCL、SDA)。

可以參考 SPEC: http://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf

在接線部分,就只要將上述 4 個 I2C 介面對接,並透過 A0、A1、A2 的設定給定 I2C 位址,假設把這三個腳位接到 GND,位址為 0x20。詳細的接法,可以參考這一篇文章中的接線方式,雖然這篇文章用的是 Raspberry Pi 作為開發板,但在接線上兩者是一致的。

來自: https://www.raspberrypi-spy.co.uk/2013/07/how-to-use-a-mcp23017-i2c-port-expander-with-the-raspberry-pi-part-1/

root@GL-AR750S:~# i2cdetect -l
i2c-0   i2c             i2c-gpio0                               I2C adapter
root@GL-AR750S:~# i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

設定好線路之後,接著,要對 MCP23017 進行設定,MCP23017 的指令表如下:

我們可以先看到其初始值設定和上表一致。

root@GL-AR750S:~# i2cdump -y -f 0 0x20
No size specified (using byte-data access)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
10: 00 00 ff 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

雖然,MCP23017 有許多功能,但是這次的測試,只把它當作 GPIO 的擴展器,將原有 I2C 的輸入單向擴展為 16 個 GPIO 腳位,因此,我們需要先將 GPIO 設定為輸出的模式:

root@GL-AR750S:~# i2cset -y 0 0x20 0x00 0x00
root@GL-AR750S:~# i2cset -y 0 0x20 0x01 0x00

接著,我們可以藉由更改 0x12、0x13 的數值,來改變 GPIO 的輸出,以下為指令與結果:

root@GL-AR750S:~# i2cset -y 0 0x20 0x12 0x0F
root@GL-AR750S:~# i2cset -y 0 0x20 0x13 0x0F
root@GL-AR750S:~# i2cdump -y -f 0 0x20
No size specified (using byte-data access)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
10: 00 00 0f 0f 0f 0f 00 00 00 00 00 00 00 00 00 00    ..????..........
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

其中,可以看到 0x14 (OLATA) 以及 0x15 (OLATB) 的數值會隨設定改變,這兩個地方的數值代表兩組 Latch,會將所輸入的數值儲存下來。

由於 LED 為了限流而反接,因此亮燈代表 0,反之則是 1。這樣的結果和實驗的設定一致。

MCP23017 反應時間量測

考慮到 MCP23017 是透過 I2c 的介面更改 GPIO 的數值,其中一定牽涉了資料的寫入、儲存等工作,因此比起直接存取 GPIO 而言,會有較長的延遲。為了測試此延遲時間,我們寫了一個簡單的 script,如下所示:

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo 18 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio18/direction

i2cset -y 0 0x20 0x00 0x00
i2cset -y 0 0x20 0x01 0x00

echo "===== Initial: I2C and gpio 18 set to LOW ====="

i2cset -y 0 0x20 0x12 0x00
i2cset -y 0 0x20 0x13 0x00
echo 0 > /sys/class/gpio/gpio18/value

echo "===== Phase 1: togging gpio 18 10 times ====="

i=1
while [ "$i" -le 10 ];do
    i=$((i+1));
    echo 1 > /sys/class/gpio/gpio18/value
    echo 0 > /sys/class/gpio/gpio18/value
done

echo "===== Phase 2: togging I2CA 10 times ====="

i=1
while [ "$i" -le 10 ];do
    i=$((i+1));
    i2cset -y 0 0x20 0x12 0xFF
    i2cset -y 0 0x20 0x12 0x00
done

echo "===== Phase 3: togging I2CB 10 times ====="

i=1
while [ "$i" -le 10 ];do
    i=$((i+1));
    i2cset -y 0 0x20 0x13 0xFF
    i2cset -y 0 0x20 0x13 0x00
done

一開始,我們先初始化 GPIO 18 和 I2C 的狀態,接著把兩組 I2C (A、B) 和 GPIO 都設為 0,接著 (phase 1) 切換 GPIO 10 次, (phase 2) 切換 I2CA 10 次,(phase 3) 之後再切換 I2CB 10 次。每一次切換之間並沒有延遲。

我們用示波器來量測 I2C A 轉出來的 GPIO 訊號,以及 GPIO 18 訊號,並假設波型反映後,就執行下一行指令。以下為量測的照片:

結果顯示於下圖,綠色是 GPIO 的波型變化,黃色是透過 I2C 轉換後的波型變化。

根據上圖的分析,我們可以得到以下結果:

  • 從 GPIO 下指令之後到反映在波型上的時間約為 100 us (0.1 ms)

  • 從 I2C 下指令之後到反映在波型上的時間約為 3 ms

當然,這只是一次的量測數據,並不代表精確的量測結果,每一次量測的結果應該有些許不同。但是,透過這次實驗大概可以發現透過 I2C 下指令會產生約 3 ms 的延遲,相較之下,若是直接操作 GPIO 延遲約為 0.1 ms,兩者反應速度約差 30 倍。

Last updated