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 所需的操作軟體。
Copy 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 設定,其對應的指令如下:
Copy # 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 的操作與實驗結果
在此 OpenWRT 上只有一個 I2C 模組,編號為0, 一個模組對應到一個 I2C 的匯流排。若是有多個模組,就可以同時操控多個匯流排。
Copy 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/
Copy 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 的指令表如下:
我們可以先看到其初始值設定和上表一致。
Copy 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 設定為輸出的模式:
Copy root@GL-AR750S:~# i2cset -y 0 0x20 0x00 0x00
root@GL-AR750S:~# i2cset -y 0 0x20 0x01 0x00
接著,我們可以藉由更改 0x12、0x13 的數值,來改變 GPIO 的輸出,以下為指令與結果:
Copy 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,如下所示:
Copy 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 倍。