Raspberry Pi PicoでPicoMite(MMBasic)を使う〜SPIデバイスのADT7310で温度測定

# はじめに

シリアルバス規格の一つであるSPI（Serial Peripheral Interface）を利用したデバイスの接続をPicoMite（MMBasic）で試してみた。

# PicoMiteのSPI

PicoMite（MMBasic）でSPIを利用する手順は次の通り。

1. SPIデバイスとPicoのピンの対応付け
2. SPIチャンネルを開く
3. SPIデバイスの設定
4. SPIデバイスからのデータ受信
5. SPIチャンネルを閉じる

## ピン割り当て

SETPINコマンドでSPIデバイスのピンとRaspberry Pi Picoのピンを対応付ける。構文は次の通り。

	SETPIN RX, TX, CLK, SPI

- SPIはチャンネル1でチャンネル2に割り当てる場合はSPI2にする
- TXはRaspberry Pi PicoからSPIデバイスへのデータ送信
- RXはRaspberry Pi PicoがSPIデバイスからデータ受信
- RX、TX、CLKに割り当てできるPicoのピンは下表の通り

| パラメータ | チャンネル1のピン         | チャンネル2のピン      |
| :---: | ----------------- | -------------- |
|  RX   | GP0、GP4、GP16、GP20 | GP8、GP12、GP28  |
|  TX   | GP3、GP7、GP19      | GP11、GP15、GP27 |
|  CLK  | GP2、GP6、GP18      | GP10、GP14、GP26 |

## SPIチェンネルを開く

SPIを利用するには`SPI OPEN`コマンドでSPIチャンネルを開く必要がある。
構文は次の通り。

```
SPI OPEN speed, mode, bit
```

それぞれの引数は次の通り

- speed：Hz単位のクロック周波数
- mode：下表に示す送信モード0〜3までの数値で指定する
- bit：送受信するデータ幅の指定、4〜16ビットを指定する

| モード | 説明                                           | CPOL | CPHA |
| :-: | -------------------------------------------- | :--: | :--: |
|  0  | クロックはアクティブハイで、データは立ち上がりエッジでキャプチャ、立ち下がりエッジで出力 |  0   |  0   |
|  1  | クロックはアクティブハイで、データは立ち下がりエッジでキャプチャ、立ち上がりエッジで出力 |  0   |  1   |
|  2  | クロックはアクティブローで、データは立ち下がりエッジでキャプチャ、立ち上がりエッジで出力 |  1   |  0   |
|  3  | クロックはアクティブローで、データは立ち上がりエッジでキャプチャ、立ち下がりエッジで出力 |  1   |  1   |

## データの送受信

SPIデバイスとのデータの送受信には`SPI()`関数を使います。構文は次の通り。

```
receive_data = SPI(data_to_send)
```

data_to_sendは送信データで同時に受信も行なう。受信したデータはreceive_dataに格納される。
受信のみを実行する場合は送信データは任意の値、たとえば0でよい。

## バルク送受信

データの一括送受信が可能です。
データの一括送信は送信するデータの列挙、文字列、配列を引数に指定できます。構文を以下に示します。

```
SPI WRITE nbr, data1, data2, data3,..
SPI WRITE nbr, string$
SPI WRITE nbr, array()
```

- nbrは送信データ数
- 文字列や、配列はnbr以上の文字数、要素数が必要です

一括受信では受信したデータを配列に格納します。構文を以下に示します。

```
SPI READ nbr, array()
```

# 使用部品

SPIデバイスとして秋月電子通商で購入した[ADT7310使用 高精度・高分解能 SPI・16Bit 温度センサーモジュール](https://akizukidenshi.com/catalog/g/g106708/)を使用し、取得した温度の表示にI<sup>2</sup>C接続のOLED、[YUWENW OLEDディスプレイ 0.96インチ 2個入り OLEDモジュール I2C 128X64 4ピン ホワイト Arduinoに対応](https://amzn.to/3EXD2XQ)に使用した。

## ADT7310

ADT7310はADT7410と同様に13/16 ビットADC を内蔵しており、13ビット設定の場合で0.0625℃の、16ビット設定で0.0078℃の分解能がある。
バイト単位で受信した2バイト分のデータをシフト演算などで13ビットあるいは16ビットの値に変換して温度を算出する必要がある。

### 13ビットデータからの温度計算

13ビットデータの場合の計算式を以下に示す。
- $正の温度=読み取ったデータ/16$
- $負の温度=(読み取ったデータ - 8192)/16$

温度の正負判定はADT7410からのデータは2の補数なのでデータの最上位ビット（MSB）が1か否かで判断できる。13ビット出力の場合、4096以上の値が負数である。

### 16ビットデータからの温度計算

16ビットデータの場合の計算式を以下に示す。
- $正の温度=読み取ったデータ/128$
- $負の温度=(読み取ったデータ - 65536)/128$

16ビット出力の場合、読み取ったデータが32768以上の値が負数である。

# 配線

ADT7310とRaspberry Pi Picoの接続は下記のとおり。

| ADT7310のピン | Picoのピン（物理ピン） |
| :--------: | :-----------: |
|    VDD     |    3V3(36)    |
|    SCL     |   GP18(24)    |
|    SDO     |   GP20(26)    |
|    SDI     |   GP19(25)    |
|     CS     |   GP21(27)    |
|    GND     |      GND      |

OLEDとRaspberry Pi Picoの接続は下記のとおり。

| OLEDのピン | Picoのピン（物理ピン） |
| :-----: | :-----------: |
|   GND   |    GND(3)     |
|   VCC   |    3V3(36)    |
|   SCL   |   GP15(20)    |
|   SDA   |   GP14(19)    |

![OLED-ADT7310_ブレッドボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/2d8aefa8-62ff-4b99-a8b1-0909cf4b01c8.png)


# プログラム

ADT7310の出力をデフォルトの13ビット受信するプログラムを以下に示します。

```BASIC:ADT7310.bas
Dim data%(2), raw%, temp!, stemp$               ' Define variables
SetPin GP20, GP19, GP18, SPI                    ' Set the pins to be used as SPI
SetPin GP21, DOUT                               ' Set the pin to SPI chipselect
SPI OPEN 100000, 3, 8                           ' Open SPI,100kHz, mode 3, 8bit
Pin(GP21) = 0                                   ' Select ADT7310
SPI WRITE 4, &HFF, &HFF, &HFF, &HFF             ' Rest SPI
Pin(GP21) = 1                                   ' Deselection ADT7310
Pause 300                                       ' Wait 300msec
Pin(GP21) = 0                                   ' Select ADT7310
Dummy = SPI(&H54)                               ' Set continuous read mode
CLS                                             ' Clear OLED screen
Do
  SPI READ 2, data%()                           ' Read temperature data, store to array 
  raw% = data%(0) << 8 Or data%(1)              ' convert read data
  raw% = raw% >> 3
  If raw% >= 4096 Then raw% = raw% - 8192       ' Conversion when negative
  temp! = raw% / 16                             ' Calculate temperature
  stemp$ = Str$(temp!, 2, 1) + Chr$(&H60) + "C" ' Create temperature string
  Text 0, 0, stemp$,,3                          ' Draw temperature on OLED
  Pause 1000                                    ' Wait 1 sec
Loop

Pin(GP21) = 1                                   ' Deselection

SPI CLOSE                                       ' Close SPI

End

```

このプログラムは次の処理を実行している。

ADT7310とRaspberry Pi Picoの接続に合わせてピンをSPIチャンネル1に設定

```
SetPin GP20, GP19, GP18, SPI
SetPin GP21, DOUT
```

SPIチャンネル1を100kHz、モード3、8ビットデータ送受信で開く

```
SPI OPEN 100000, 3, 8
``` 

SPIインターフェイスのリセット。
SPIデバイスの入力に32シリアルクロック周期以上の1を加えるとインターフェースがリセットされる。リセットしないとうまく動作してくれなかった。
CSをローレベル（`PIN(GP21) = 0`）にしてSPIデバイスにデータを送信する。送信後CSをハイレベル（`PIN(GP21) = 1`）にしている。

```
Pin(GP21) = 0
SPI WRITE 4, &HFF, &HFF, &HFF, &HFF
Pin(GP21) = 1
```

連続読み出しモードに設定する。
ADT7310のデータシートではSPIデバイスへのコマンド`01010100(&H54)`を送信するとADT7310は温度データを連続して読み出せるようになります。

```
Pin(GP21) = 0
Dummy = SPI(&H54)
```

送信するコマンド`01010100(&H54)`の各ビットは次の意味がある。
- C7、C1、C0は常に0
- C6のR/Wは読み書きの指定で、1にすると読み出しに、0にすると書き込みになる
- C5〜C3はレジスタの指定で010(0x02)は温度データのレジスタを示す。ちなみに0x01はコンフィギュレーションレジスタ
- C2は連続読み出しの指定で、1にすると連続読み出しになる

<table >
<tr><th style="text-align:center">C7</th><th style="text-align:center">C6</th><th style="text-align:center">C5</th><th style="text-align:center">C4</th><th style="text-align:center">C3</th><th style="text-align:center">C2</th><th style="text-align:center">C1</th><th style="text-align:center">C0</th></tr>
<tr><td style="text-align:center">0</td><td style="text-align:center">R/W</td><td colspan="3" style="text-align:center">レジスタアドレス</td><td style="text-align:center">連続読み出し</td><td style="text-align:center">0</td><td style="text-align:center">0</td><tr>
<tr><td style="text-align:center">0</td><td style="text-align:center">1</td><td style="text-align:center">0</td><td style="text-align:center">1</td><td style="text-align:center">0</td><td style="text-align:center">1</td><td style="text-align:center">0</td><td style="text-align:center">0</td></tr>
</table>

戻り値は利用しない

ADT7310からのデータを2バイト分読み取り、配列data%に保存する。
先頭8ビット分がdata%(0)に、後続の8ビットがdata%(1)に格納される。

```
SPI READ 2, data%()
```

受信した温度データを13ビットに変換
温度データは8ビットずつ受信し、2つの配列要素に格納されているため13ビットデータへの変換を行なう。先頭8ビット分を左へ8ビットシフトした値（data%(0) << 8）と後半の8ビット（data%(1)）との論理和を求め16ビットの値にすし、変数raw%に格納する。
次に不要な下位3ビット分を取り除くために右へ3ビットシフトする。

```
raw% = data%(0) << 8 Or data%(1)
raw% = raw% >> 3
```

温度データから温度の算出
ADT7310から取得した温度データは2の補数なので符号ビットのみが1の4096（1 0000 0000 0000）以上は負数となり、値の変換が必要になる。負数のときは2の補数の温度データから8192(=$2^{13}$)を引くと負数が得られる。
13ビットの分解能が0.0625℃なので温度データを16（=1/0.0625）で割れば温度が得られる。

```
If raw% >= 4096 Then raw% = raw% - 8192
temp! = raw% / 16    
```

表示用文字列の作成
算出された温度の値を整数部2桁、小数部1桁の文字列に変換し、OLEDへ出力する。

```
stemp$ = Str$(temp!, 2, 1) + Chr$(&H60) + "C"
Text 0, 0, stemp$,,3
```

# 参考サイトなど

- [PicoMite User Manual](https://geoffg.net/Downloads/picomite/PicoMite_User_Manual.pdf)