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

# 本記事について
[pythonでシリアル通信制御してＩ２Ｃ通信する(USBGPIO8デバイスを使用します)](https://qiita.com/ikuo0/items/a43d2e1dedbb6b943c78)の系列記事となりますので先にＩ２Ｃ編をお読みください。

# ＳＰＩ通信について
基本的な仕様はありますがＩＣ毎のデータシートを参照したほうが良いと思います。
一般的なSPI通信については下記が分かりやすく説明されています。
http://www.picfun.com/f1/f05.html
https://lab.fujiele.co.jp/articles/8191/

# 注意
本記事で使用するEEPROM(AT93C46） はＣＳ（チップセレクト）が通常と逆で
**HIGHで通信開始、LOWで通信終了となります。**
一般的にははLOWで通信開始、HIGHで通信終了です。

一般的なICで実験すれば良かったのですがSPIを使うICの中でAT93C46が３０円と一番安かったのでAT93C46にしました、その結果汎用性に欠けた記事となりました。

# なにをするか
pythonでシリアル通信を使いUSBGPIO8を制御、USBGPIO8からIC（EEPROM）を制御、２バイト書き込みその後２バイト読み込み書き込んだ値が読み込める事を確認します。

# 回路図

USBGPIO8 の 0番ポートを CS とします
USBGPIO8 の 1番ポートを SK とします(クロック)
USBGPIO8 の 2番ポートを DI とします
USBGPIO8 の 3番ポートを DO とします

![GPIO8_SPI_回路図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104691/bf6b7756-6334-3561-8b06-75a420c5757b.png)


# EEPROM(AT93C46)の使い方
## ハード設定
6番（ORG）をVcc接続で16bitモード
6番（ORG）をGND接続で8bitモード
となります、今回は16bitモードで動作させます。
16bitモードでは６４アドレスｘ２バイト、合計１２８バイトのデータを保存できます。

## AT93C46命令セット
・EWEN 書き込み許可状態
・WRITE 書き込み
・READ 読み込み
今回はこの３命令を使います。
書き込むためには起動時にEWENモードにする必要があります、一度EWENモードにしたら電源を切るまでモードは維持されます。

送信データは
SB ＋ OP ＋ [データ等...]
という構成を取ります。

SB ＝ [00000001] 固定
OP ＝ [????????] モードにより定められているパターン＋アドレス等を付加する
データ等はあれば追加する。

具体的な例を示す
※ ? 記号は任意の 0 か 1 を設定する

---
EWENモード

```python
SB[00000001]
OP[00110000]
＞0000000100110000
と続けて送信
```

---
WRITEモード

```python
SB[00000001]
OP[010?????] 下位５ビットは書き込みアドレス
上位バイト[????????]
下位バイト[????????]
＞00000001010?????????????????????
と続けて送信
```

---
READモード

```python
SB[00000001]
OP[100?????] 下位５ビットは読み込みアドレス
上位バイト用ダミー[00000000] DOから受信するためのダミーデータ
下位バイト用ダミー[00000000] DOから受信するためのダミーデータ
＞00000001100?????0000000000000000
と続けて送信
```

# プログラム構造
[pythonでシリアル通信制御してＩ２Ｃ通信する(USBGPIO8デバイスを使用します)](https://qiita.com/ikuo0/items/a43d2e1dedbb6b943c78)と同様ですのでそちらを参照

# ソースコード

```python

# usbgpio8_spi_read_write_sample.py

import serial
import sys
import time

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

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 CS_LOW():
    gpioLow(0)

def CS_HIGH():
    gpioHigh(0)

def SCK_LOW():
    gpioLow(1)

def SCK_HIGH():
    gpioHigh(1)

def DI_LOW():
    gpioLow(2)

def DI_HIGH():
    gpioHigh(2)

def READ_DATA():
    return gpioRead(3)

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

def SPI_CommandExec(cmd):
    # start condition
    size = len(cmd)
    data = []
    SCK_LOW()
    for i in range(size):
        d = cmd[i]
        if d == 0:
            DI_LOW()
        elif d == 1:
            DI_HIGH()
        SCK_HIGH()
        if d == 2:
            b = READ_DATA()
            data.append(b)
        SCK_LOW()
    return parseData(data)

def WriteBytes(addr, buffer1, buffer2):
    # EWEN command
    SB = ByteToLH(0b00000001)
    OP = ByteToLH(0b00110000)
    cmdEWEN = SB + OP
    
    # exec
    CS_HIGH()
    resEWEN = SPI_CommandExec(cmdEWEN)
    CS_LOW()
    
    # write command
    SB = ByteToLH(0b00000001)
    OP = ByteToLH(0b01000000 | (addr & 0x3f))
    buffer1 = ByteToLH(buffer1)
    buffer2 = ByteToLH(buffer2)
    cmdWrite = SB + OP + buffer1 + buffer2
    
    # exec
    CS_HIGH()
    resWrite = SPI_CommandExec(cmdWrite)
    CS_LOW()
    # 書き込み待ち、仕様では5msだが適当に長く待つ
    time.sleep(0.01)
    
    response = resEWEN + resWrite
    return response

def ReadBytes(addr):
    # create command
    SB = ByteToLH(0b00000001)
    OP = ByteToLH(0b10000000 | (addr & 0x3f))
    buffer1 = [2] * 8
    buffer2 = [2] * 8
    cmd = SB + OP + buffer1 + buffer2
    CS_HIGH()
    response = SPI_CommandExec(cmd)
    CS_LOW()
    return response


def Test_WriteBytes(comString):
    SerialInit(comString)
    
    response = WriteBytes(7, 0x34, 0x56)
    print(response)
    
    SerialEnd()

def Test_ReadBytes(comString):
    SerialInit(comString)
    
    response = ReadBytes(7)
    print(response)
    
    SerialEnd()

def run(comString):
    Test_WriteBytes(comString)
    Test_ReadBytes(comString)
    
if __name__ == "__main__":
    run(sys.argv[1])
# python usbgpio8_spi_read_write_sample.py COM4

```

## 使い方
Linuxの場合

```pytyhon
python usbgpio8_spi_read_write_sample.py /dev/ttyUSB0
```

Windowsの場合

```python
python usbgpio8_spi_read_write_sample.py COM4
```

## 戻り値の見方

```python
[] # 書き込み時は特に無し
['0', '0', '1', '1', '0', '1', '0', '0', '0', '1', '0', '1', '0', '1', '1', '0'] # 先頭８ビットは読み込んだ上位バイト、続く８ビットは読み込んだ下位バイト
```

# 以上です