FPGAとRaspberryPi picoWでSPI通信（その①ラズピコのSPIマスター）

# 概要
FPGAとラズピコをSPI通信で連携して、ラズピコからFPGAのレジスタ設定を書き換えます。
今回は**ピコのSPIマスターの実装方法**について紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/4efc0962-b724-203d-48c6-107c69b626f0.png)

# 通信フォーマット

SPI通信はタイミングについては規定があるものの、データの内訳についてはデバイス依存になります。
つまり、FPGAの場合はデータの内訳は自由ということになりますので、以下の通りの通信フォーマットを採用するものとします。

* CSはLow有意
* クロックエッジは立ち上がりでサンプリング
* クロック周波数は1MHz
* データは32bit(アドレス8bit + コマンド4bit + ダミー54bit + データ16bit)
* データはMSBファースト

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/a1fb01d8-f5b2-c39b-e384-405c34c8cc4e.png)


# コード

```python:main.py
from machine import Pin
from machine import SPI
import sys
import select
#================================================================
# SPI親分クラス
#================================================================
class SPI_master():
    def __init__(self):
        # SPI設定 (SPI0: CLK=GP2, MOSI=GP3, MISO=GP4)
        self.spi = SPI(0, baudrate=1000000, polarity=0, phase=0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
        # SPI Chip Selectピン設定
        self.cs = Pin(5, Pin.OUT)
        self.cs.value(1)  # 初期状態はHigh（非アクティブ）にしておく
    #-----------------------------------------------------------
    # SPI書込み
    #-----------------------------------------------------------
    def Write(self,adr: int, data: int):
        cmd=0x60    # 書込みコマンド
        __txData = bytearray([adr & 0xFF, cmd, (data >> 8) & 0xFF, data & 0xFF])  # 4バイトにパッキング
        self.cs.value(0)        # CSをLowにアサート
        self.spi.write(__txData)# パッキングしたデータを送信
        self.cs.value(1)        # CSをHighにネゲート

        print("Write   data:", [hex(b) for b in __txData])  # バイト単位で表示
    #-----------------------------------------------------------
    # SPI読み出
    #-----------------------------------------------------------
    def Read(self,adr: int):
        cmd=0x90    # 読み出しコマンド
        __txData = bytearray([adr & 0xFF, cmd, 0, 0])
        __rxData = bytearray(len(__txData))
        self.cs.value(0)
        #self.spi.write(__packet)
        #__packet = self.spi.read(4)
        self.spi.write_readinto(__txData, __rxData)
        self.cs.value(1)
        print("Send     data:", [hex(b) for b in __txData])  # バイト単位で表示
        print("Received data:", [hex(b) for b in __rxData])  # バイト単位で表示
        return __rxData
#===========================================================
# 非ブロッキングでキー入力をチェックする関数
def check_key_input():
    if select.select([sys.stdin], [], [], 0)[0]:
        return sys.stdin.read(1)
    return None
#=====================================================================
#
#=====================================================================
spi_master=SPI_master()
while True:
    #キー入力を受け取る
    user_input = check_key_input()
    #キー入力に応じてSPIライト
    if user_input == 'a':
        spi_master.Write(0x55, 0xFFFF)
    if user_input == 'b':
        spi_master.Write(0x55, 0x1234)
    if user_input == 'c':
        spi_master.Write(0x55, 0xEDCB)
    if user_input == 'z':
        spi_master.Write(0x55, 0x0000)
    if user_input == 'r':
        r=spi_master.Read(0x55)
```


# 余談：SPIのインターバル
データ送信する際にバイトデータをパッキングしないで送ることも可能ですが、インターバル時間が長いのでパックした方が良さげです。

```python:
    def SpiWrite(self,adr: int, data: int):
        cmd=0x60    # 書込みコマンド
        self.cs.value(0)  # CSをLowにアサート
        self.spi.write(bytearray([adr & 0xFF]))          # adrを1バイトに変換して送信
        self.spi.write(bytearray([cmd]))                 # cmdを1バイトに変換して送信
        self.spi.write(bytearray([(data >> 8) & 0xFF]))  # dataの上位Byte送信
        self.spi.write(bytearray([data & 0xFF]))         # dataの下位Byte送信
        self.cs.value(1)  # CSをHighにネゲート
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/efca19a9-86db-1488-7769-b8f218eeb7ec.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/3fa64b61-3fd8-dcd9-0940-4cb7e82a92c6.png)

# おわりに
FPGA側のSPIスレーブについては別記事にして投稿したいと思います。