【電子工作】A/Dコンバーター(MCP3002)でアナログ温度センサーを扱う

# ■ 作るもの

アナログ温度センサーとA/Dコンバータを利用した室温測定器を作ってみる。


ソースコードはこちら

https://github.com/ng3rdstmadgke/iot-work


※ この記事の続きとして、測った温度を7セグ表示器に表示してみました。

https://qiita.com/ktamido/items/da9c87b54a241ee16a3a

# ■ 回路図

こんな感じの回路を作ってみた。
温度センサーのアナログ出力をA/Dコンバーターで0~1023のデジタル値に変換して、その値を使ってラズパイで温度を計算するしくみ。

![fig_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/b6925774-0c98-ae1e-0dd6-0399756aca2d.png)



# ■ 利用する部品

## MCP3002

10Bit A/Dコンバーター

[データシート](https://akizukidenshi.com/download/ds/microchip/mcp3002.pdf)

### 基本情報

- 分解能: 10bit
- 電源電圧: 2.7V ~ 5V
- SPI: モード0とモード3が利用可能
- 最大サンプリングレート 200ksps(5V), 75ksps(2.7V)

### ピン

```
VDD/VREF CLK DOUT DIN
 --------------------
| )  MCP3002        |
 --------------------
CS/SHDN  CH0 CH1  VSS
```

1. CS/SHDN :ラズパイのCE0(CE1)端子に接続  
    - 通信時にMCP3002からラズパイのCE0(CE1)端子をLOWに設定する
1. CH0: アナログデバイスの出力端子に接続  
    - アナログデバイスの出力電圧を受け取る
1. CH1: アナログデバイスの出力端子に接続  
    - アナログデバイスの出力電圧を受け取る  
1. VSS: ラズパイのGND端子に接続  
1. DIN: ラズパイのMISO(Master In Slave Out)端子に接続  
    - ラズパイからMCP3002へのデータ入力  
1. DOUT: ラズパイのMOSI(Master Out Slave In)端子に接続  
    - MCP3002からラズパイへのデータ入力  
1. CLK: ラズパイのSCKL(シリアルクロック)端子に接続  
    - 通信するデバイス同士のタイミング合わせで利用
1. VDD/VREF: ラズパイの3.3V端子に接続  
    - 電源/基準電圧入力 (2.7V~5.5V)

### データの送受信

送信するデータは2バイトで、受信するデータも2バイト。受信データはMSBファーストフォーマットで受け取ることができる。(オプションでLSBファーストフォーマットでの受け取りも可能らしい。)
今回はMSBファーストフォーマットでのみデータを受信する方式を利用する。

データシートを確認すると、データの送受信の図があったので見方をざっくり調べてみた。

- CS: ラズパイのCE0(CE1)端子(MCP3002のCS/SHDN端子に接続されている)の電位。LOWに設定するのが通信開始の合図。
- CLK: クロック。この図だとアイドルクロックはLOWになっている。(後述のSPIモードを調べるときに重要になってくる)
- DIN: ラズパイ -> A/Dコンバータに送信するデータ (bit単位)
- DOUT: A/Dコンバータ -> ラズパイに送信するデータ (bit単位)

![fig_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/0c16c88d-bc73-c472-3624-d37d219e7cfd.png)


### 送信データ

送信データは2バイトで、上位5bitにオプションを設定する。

![fig_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/5f80c6a2-67c7-0cef-4ed6-adfe341bb648.png)


1. 固定で0
1. スタートビット: 1 (固定)
1. SGL/DIFF: 動作モードを選択
    - 0: 擬似差動モード (よくわからん)
    - 1: シングルエンドモード (とりあえず今回はこっち)
1. ODD/SIGN: シングルエンドモードで使用されるチャンネルを選択。
    - 0: CH0 (CH0端子にアナログデバイスを接続している場合はこちら)
    - 1: CH1 (CH1端子にアナログデバイスを接続している場合はこちら)
1. MSBF: 受信データのフォーマットを選択。
    - 0: MCP3002はMSBファーストフォーマット(最上位ビットから)で送信
    - 1: MCP3002はMSBファーストフォーマットで送信した後、LSBファーストフォーマット(最下位ビットから)に変換された値を送信
1. 以降は何でも良い

#### 受信データ

受信データは2バイトで、下位10bitが値(アナログデバイスの出力電圧 / VREF * 1023)となる。

![fig_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/6d26b5d4-180c-79f0-579e-df955a155537.png)



## LM61CIZ

温度センサー

[データシート](https://www.ti.com/jp/lit/ds/symlink/lm61.pdf)

### 基本情報

- 動作温度範囲: -30℃ ~ 100℃
- 電源電圧: 2.7V ~ 10V
- 消費電力: 125μA (最大値)
- 出力電圧: 600mVを0℃として、1℃あたり10mVの増減
  - -30℃: 300mV
  - 0℃: 600mV
  - 100℃: 1600mV

### ピン


```
*** 下から見た図 ***

 -----------------
|                 |
|  +VS  VOUT GND  |
|  ---  ---  ---  |
 \               /
  \             /
   \           /
    \_________/
```           


1. +VS: ラズパイの3.3V端子に接続
    - 電源入力 (2.7V~10V)
1. VOUT: A/Dコンバータのアナログインプット端子に接続
    - 温度センサーのアナログアウトプット(300mV(-30℃)~1600mV(100℃))
1. GND: ラズパイのGND端子に接続



# ■ SPIモードがよく分からなかったので調べてみた

記事としてはこのあたりがわかりやすい
- [SPIの基本を学ぶ](https://www.analog.com/jp/analog-dialogue/articles/introduction-to-spi-interface.html)
- [シリアル・ペリフェラル・インターフェース | wiki](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%BB%E3%83%9A%E3%83%AA%E3%83%95%E3%82%A7%E3%83%A9%E3%83%AB%E3%83%BB%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9)

SPIメイン(ラズパイ)はクロックの極性と位相を選択できるとのことだが、、、
早い話がアイドル時のクロックがHIGHかLOWかを選択できて、クロックがHIGHになるときにデータを読み込むか、LOWになるときにデータを読み込むかを選択できるということ。

この、アイドル時のクロック(HIGH, LOW)、データをいつ読み込むか(HIGH, LOW) の組み合わせで4つのモード(SPIモード)を選択できる。

ちなみに、アイドル時のクロックを設定するオプションを `CPOLビット` 、データをいつ読み込むかを設定するオプションを `CPHAビット` と呼び、まとめると↓のような感じ。


| SPIモード | CPOLビット | CPHAビット | 説明 |
| --- | --- | --- | --- |
| 0 | 0 | 0 | アイドル時のクロックは0。<br>クロックの立上がりでデータをサンプリング、立下がりでシフト |
| 1 | 0 | 1 | アイドル時のクロックは0。<br>クロックの立下がりでデータをサンプリング、立上がりでシフト |
| 2 | 1 | 0 | アイドル時のクロックは1。<br>クロックの立下がりでデータをサンプリング、立上がりでシフト |
| 3 | 1 | 1 | アイドル時のクロックは1。<br>クロックの立上がりでデータをサンプリング、立下がりでシフト |


MCP3002のデータシートのシリアルタイミングを確認すると

![fig_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/51991518-6fc6-9c3d-e8ce-99b0e77d0066.png)

- アイドル状態のクロックの極性は0( `CPOL=0` )。
- ラズパイ(マスター)側はビットの立ち上がりでデータのサンプリングを行い、立ち下がりでシフトを行えば良い ( `CPHA=0` )

なので、SPIモードは `0`


# ■ 実装

少し調べたら、WiringPi は開発中止？になっていたので、pigpioを使ってみる。
pigpioは `pigpiod` というデーモンを立ち上げて、このデーモン経由でGPIOを操作するらしい。

## インストール

```bash
# pigpioのインストール
sudo apt update
sudo apt install pigpio

# pigpioデーモン起動
sudo systemctl enable pigpiod
sudo systemctl start pigpiod

# pythonでpigpioを扱うライブラリをインストール
pip install pigpio

# SPIを利用するためのラズパイのオプションも有効化しておく
sudo raspi-config nonint do_spi 0  # SPI通信有効化
```

## 実装

公式ドキュメントを参考に実装してみた

http://abyz.me.uk/rpi/pigpio/python.html#spi_open

```python:temp_pigpio.py
import pigpio
import time
from typing import Union

def int_to_binary(n: int, bits: int = 8):
    return ''.join([str(n >> i & 1 ) for i in reversed(range(0, bits))])

def bytes_to_binary(data: Union[bytearray,bytes]):
    return ','.join([int_to_binary(byte) for byte in data])

def main(debug: bool, chip_select: int, channel: int):
    pi = pigpio.pi()
    if not pi.connected:
        raise Exception("pigpio connection faild...")

    VREF = 3.3  # A/Dコンバータの基準電圧

    # オプション (http://abyz.me.uk/rpi/pigpio/python.html#spi_open)
    # オプションは下記のように22ビットで構成されている
    #
    # 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
    # b  b  b  b  b  b  R  T  n  n  n  n  W  A u2 u1 u0 p2 p1 p0  m  m
    #
    # m m: SPIモード (0 ~ 3)
    # A  : メインSPI(0), AuxSPI(1) どちらを利用するか選択
    # W  : 3線のSPIを利用するなら(1)、4線なら(0) (メインSPIでしか利用できない)
    # あとは使いどころがよくわからん、、、
    SPI_MODE = 0b00  # SPIモード0を設定。アイドル時のクロックはLOW(CPOL=0)、クロックがHIGHになるときにデータをサンプリング(CPHA=0)
    OPTION = 0b0  
    OPTION = OPTION | SPI_MODE
    CLOCK_SPEED = 50000  # 50KHz
    h = pi.spi_open(chip_select, CLOCK_SPEED, OPTION)
    try:
        while True:
            # 1bit: 0固定
            # 2bit: スタートビット (1固定)
            # 3bit: SGL/DIFF: 動作モード。疑似差動モード(0)、シングルエンドモード(1)
            # 4bit: ODD/SIGN: MCP3002で利用するチャンネル。 CH0(0), CH1(1)
            # 5bit: MSBF: 受信データの形式。MSBF + LSBF(0), MSBFのみ(1)、
            write_data = 0b0110100000000000
            write_data = write_data | (0b1 * channel) << 12  # ODD/SIGN: 入力されたチャンネルで設定
            write_data = write_data.to_bytes(2, "big")
            cnt, read_data = pi.spi_xfer(h, write_data)
            if cnt != 2:
                print("[error] skip.")
                continue
            value = int.from_bytes(read_data, "big") & 0b1111111111  # 10ビットを値として取り出す
            volt = (value / 1023.0) * VREF  # 温度センサーから入力された電圧
            temp = (volt - 0.6) / 0.01  # 電圧を温度に変換。(0℃で600mV , 1℃につき10mV増減)

            if (debug):
                print(f"w: {bytes_to_binary(write_data)}")
                print(f"r: {bytes_to_binary(read_data)}")
                print(f"value: {value}, volt: {volt}, temp: {temp}")
            else:
                print(f"Temp: {temp}")
            time.sleep(1)
    finally:
        pi.spi_close(h)
        pi.stop()
        print("[info] spi closed.")

if __name__ == "__main__":
    CHIP_SELECT = 0  # ラズパイの CE0端子, CE1端子どちらに接続するか
    CHANNEL = 0  # MCP3002のCH0端子,CH1端子どちらを利用するか
    main(True, CHIP_SELECT, CHANNEL)
```

とりあえず動いた！

```
(.venv) pi@raspberrypi:~/iot-work $ python src/temp_sensor/temp_pigpio.py 
w: 01101000,00000000
r: 00000001,00000110
value: 262, volt: 0.8451612903225806, temp: 24.51612903225806
w: 01101000,00000000
r: 00000001,00000110
value: 262, volt: 0.8451612903225806, temp: 24.51612903225806
w: 01101000,00000000
r: 00000001,00000110
value: 262, volt: 0.8451612903225806, temp: 24.51612903225806
```

# ■ 参考

- ラズパイのピン配置
    - [Raspberry Pi Pinout](https://pinout.xyz/pinout/spi)
- SPI関連
    - [SPIの基本を学ぶ](https://www.analog.com/jp/analog-dialogue/articles/introduction-to-spi-interface.html)
    - [シリアル・ペリフェラル・インターフェース | wiki](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%BB%E3%83%9A%E3%83%AA%E3%83%95%E3%82%A7%E3%83%A9%E3%83%AB%E3%83%BB%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9)
- pigpio関連
    - [pigpio (公式)](http://abyz.me.uk/rpi/pigpio/python.html)
    - [A-Dコンバータ　その1　10ビットSPI MCP3002 | TOPに戻る作りながら学ぶArduino+=電子工作入門](https://www.denshi.club/cookbook/adda/adc/a-d110spi-mcp3002.html)
    - [pigpioによるI2CとSPIインタフェースの実装 | TomoSoft](https://tomosoft.jp/design/?p=10477)
    - [SPI - Raspberry Pi用pigpio Librar | 腰も砕けよ 膝も折れよ](https://decafish.blog.ss-blog.jp/2016-12-11)