pythonでシリアル通信制御してＩ２Ｃ通信する(USBGPIO8デバイスを使用します)

# 本記事について
電子回路経験がありGPIO、I2Cという単語は知っている前提としますのでその辺の用語解説を省きます。

# USBGPIO8とは
シリアル通信でGPIOを制御できるデバイスです
Numato Labs社のUSB GPIO(デジタル入出力）ボードで日本だとエレファイン通販から購入できます。
https://www.elefine.jp/SHOP/USBGPIO8.html
ADコンバータも付いているので各種単体実験に便利です。

# I2C通信について
http://www.picfun.com/f1/f06.html
こちらの解説がとても分かりやすいです。

# なにをするか
python でシリアル通信を制御してUSBGPIO8を制御しEEPROMに1バイト書き込み、その後読み込んで書き込まれている事を確認します。
EEPROM は 24LC64 を使用します。
24LC64はこちらの製品です → http://akizukidenshi.com/catalog/g/gI-00194/

# ハードウェアの準備
USBGPIO8 の 0番ポートをSDA とします。
USBGPIO8 の 1番ポートをSCL とします。
各々10kΩでプルアップします。
上記構成でブレッドボード上で接続してます。

![GPIO8_I2C_回路図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104691/992f29ee-7c1e-827c-c333-d39e7899b272.png)


## USBGPIO8のコマンドについて

エレファインのサイトや公式からマニュアルがダウンロードできますので詳しくはそちらを参照ください。
最初からプログラム制御ではなくTeraterm等のシリアル端末ソフトから制御実験することをおすすめします。

```python
gpio [command] [arg]\r
```

という形式です、arg は有ったり無かったりします、末尾の ￥ｒ を忘れないように注意します。
書き込み系のコマンドを送信すると

```python
[送ったコマンド]\n\r>
# 例えば gpio set 0\n\r>
```
とコマンドにプロンプトを付けて返ってきます。
また読み込み系コマンドを送信すると

```python
[送ったコマンド]\n\r[戻り値]\n\r>
# 例えば gpio read 0\n\r1\n\r>
# \n\r に挟まれている 1 が戻り値
```
とコマンドに戻り値とプロンプトを付けて返ってきます。

## GPIOのLOW-HIGH制御
0番ポートをLOWにするには clear コマンドを使います

```python
SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio clear 0\r".encode())
```

0番ポートをHIGHにするには set コマンドを使います

```python
SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio set 0\r".encode())
```

コマンド

timeout=0.01 としてタイムアウトを設定します、設定しないと一生応答を返さなくなるので設定したほうが良いです

## GPIOの読み取り
0番ポートのLOW-HIGH状態を読み取るには

```python
SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio read 0\r".encode())
```
とします

```python
gpio read 0\n\r0\n\r>
```
と応答が返ってくればGPIOの状態は0（LOW）ということになります。

## プログラム構造
0=LOW
1=HIGH
2=読み取りクロック
と定義します。
定義に従い配列を作成し、その配列の通りにSCL、SDAを制御します。

例
１バイト書き込む場合
EEPROMデバイスアドレス 0x50
書き込みアドレス 0xa0
書き込みデータ 0x33
この条件の時以下の配列を作成する

```python
[
1, 0, 1, 0, 0, 0, 0, 0, 2,
0, 0, 0, 0, 0, 0, 0, 0, 2,
1, 0, 1, 0, 0, 0, 0, 0, 2,
0, 0, 1, 1, 0, 0, 1, 1, 2
]
```
各行を解説する
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭７ビット 0x50 のアドレスを表し、８ビット目は書き込みフラグ、９ビット目はACKの読み出し
[0, 0, 0, 0, 0, 0, 0, 0, 2] 先頭８ビットは書き込みアドレスの上位バイト、９ビット目はACKの読み出し
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭８ビットは書き込みアドレスの下位バイト、９ビット目はACKの読み出し
[0, 0, 1, 1, 0, 0, 1, 1, 2] 先頭８ビットは書き込む１バイトのデータ、９ビット目はACKの読み出し

次に１バイト読み込む場合

```python
[
1, 0, 1, 0, 0, 0, 0, 1, 0,
2, 2, 2, 2, 2, 2, 2, 2, 1
]
```
各行を解説する
[1, 0, 1, 0, 0, 0, 0, 0, 0] 先頭７ビット 0x50 のアドレスを表し、８ビット目は読み込みフラグ、９ビット目はACKの送信
[2, 2, 2, 2, 2, 2, 2, 2, 1] 先頭８ビットは読み出し、９ビット目はNOACKの送信（読み込み完了の通知）

上記のようにＳＤＡのパターンを配列化しそれに従いクロックとデータを送信・受信する構造とする。



# ソースコード

```python
# i2c_byte_write_and_read.py
import serial
import sys
import time
import threading

SerialInstance = None

def SerialInit(comString):
    global SerialInstance
    SerialInstance = serial.Serial(comString, 115200, timeout=0.01)
    #SerialInstance = serial.Serial(comString, 19200, timeout=0.1)

def SerialEnd():
    SerialInstance.close()

def SerialTalk(cmd, response=False):
    readLen = len(cmd) + 1 # gpio read 0\n\r # 最初から \r が付いているので +2 ではなく +1 する
    if response == True:
        readLen += 3 # N\n\r
    readLen += 1 # >
    cnt = SerialInstance.write(cmd.encode())
    res = SerialInstance.read(readLen)
    res = res.decode("utf-8").strip()
    return res
    # 返される文字数を正確に読み取り切ること、1文字でも過不足があると応答待ち状態が続く事になる

def gpioHigh(n):
    SerialTalk("gpio set {}\r".format(n))

def gpioLow(n):
    SerialTalk("gpio clear {}\r".format(n))

def gpioRead(n):
    res = SerialTalk("gpio read {}\r".format(n), response=True)
    return res

def ByteToLH(b):
    lh = []
    for i in range(8):
        if (b << i & 128) == 0:
            lh.append(0)
        else:
            lh.append(1)
    return lh

def SDA_LOW():
    gpioLow(0)

def SDA_HIGH():
    gpioHigh(0)

def SCL_LOW():
    gpioLow(1)

def SCL_HIGH():
    gpioHigh(1)
def READ_DATA():
    return gpioRead(0)

def parseData(all):
    res = []
    for l in all:
        a = l.split("\n\r")
        res.append(a[1])
    return res

# 0 = LOW
# 1 = HIGH
# 2 = READ
# スタートコンディション: SCLがHIGHの間にSDAをLOWにする
# ストップコンディション: SCLをHIGHにしてから、SDAをHIGHにする
def WriteProc():
    ctrlByte = ByteToLH(constDeviceAddress << 1)
    addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
    addrLow = ByteToLH(constDataAdress & 0xff)
    write1Byte = ByteToLH(constData)
    cmdWrite = ctrlByte + [2] + addrHigh + [2] + addrLow + [2] + write1Byte + [2]
    
    # START CONDITION
    SCL_HIGH()
    SDA_LOW()
    
    size = len(cmdWrite)
    data = []
    for i in range(size):
        SCL_LOW()
        d = cmdWrite[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す
    
    # STOP CONDITION
    SDA_LOW()
    SCL_HIGH()
    SDA_HIGH()
    
    # 書き込み待ち、仕様では5msだが適当に長く待つ
    time.sleep(0.1)
    print(parseData(data))
    print("write end")

def ReadProc():
    #
    #
    # set address
    ctrlByte = ByteToLH(constDeviceAddress << 1)
    addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
    addrLow = ByteToLH(constDataAdress & 0xff)
    cmdSetAddress = ctrlByte + [2] + addrHigh + [2] + addrLow + [2]
    
    # START CONDITION
    SCL_HIGH()
    SDA_LOW()
    
    size = len(cmdSetAddress)
    data = []
    for i in range(size):
        SCL_LOW()
        #print(i)
        d = cmdSetAddress[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す
    
    # STOP CONDITION
    SCL_HIGH()
    SDA_HIGH()
    
    print(parseData(data))
    
    #
    #
    # read data
    ctrlByte = ByteToLH((constDeviceAddress << 1) | 0x01)
    readByte = [2] * 8
    cmdReadByte = ctrlByte + [0] + readByte + [1]
    
    # START CONDITION
    SCL_HIGH()
    SDA_LOW()
    
    size = len(cmdReadByte)
    data = []
    for i in range(size):
        SCL_LOW()
        #print(i)
        d = cmdReadByte[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す
    
    # STOP CONDITION
    SCL_HIGH()
    SDA_HIGH()
    
    print(parseData(data))
    print("read end")

# defines
constDeviceAddress = 0x50 # ICの設定に従う
constDataAdress = 0x0a88 # ICのアドレス範囲内で適当に決める、今回は最大２バイト（0-65535）と想定したコード、3バイトアドレスの場合はコード修正が必要
constData = 0x44 # 0-255 の範囲で適当に決める

def run(comString):
    SerialInit(comString)
    
    WriteProc()
    ReadProc()
    
    SerialEnd()

if __name__ == "__main__":
    run(sys.argv[1])
# python i2c_byte_write_and_read.py COM4
```

# ソースコード解説
## 使い方
Linuxの場合

```
python i2c_byte_write_and_read.py /dev/ttyUSB0
```

Windowsの場合

```
python i2c_byte_write_and_read.py COM4
```

## 戻り値の見方

```python
['0', '0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK、書き込みデータのACK
write end
['0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK
['0', '0', '1', '1', '0', '0', '1', '1'] # 読み込んだ８ビットの状態
read end
```



# 以上です