■ 作るもの
アナログ温度センサーとA/Dコンバータを利用した室温測定器を作ってみる。
ソースコードはこちら
※ この記事の続きとして、測った温度を7セグ表示器に表示してみました。
■ 回路図
こんな感じの回路を作ってみた。
温度センサーのアナログ出力をA/Dコンバーターで0~1023のデジタル値に変換して、その値を使ってラズパイで温度を計算するしくみ。
■ 利用する部品
MCP3002
10Bit A/Dコンバーター
基本情報
- 分解能: 10bit
- 電源電圧: 2.7V ~ 5V
- SPI: モード0とモード3が利用可能
- 最大サンプリングレート 200ksps(5V), 75ksps(2.7V)
ピン
VDD/VREF CLK DOUT DIN
--------------------
| ) MCP3002 |
--------------------
CS/SHDN CH0 CH1 VSS
- CS/SHDN :ラズパイのCE0(CE1)端子に接続
- 通信時にMCP3002からラズパイのCE0(CE1)端子をLOWに設定する
- CH0: アナログデバイスの出力端子に接続
- アナログデバイスの出力電圧を受け取る
- CH1: アナログデバイスの出力端子に接続
- アナログデバイスの出力電圧を受け取る
- VSS: ラズパイのGND端子に接続
- DIN: ラズパイのMISO(Master In Slave Out)端子に接続
- ラズパイからMCP3002へのデータ入力
- DOUT: ラズパイのMOSI(Master Out Slave In)端子に接続
- MCP3002からラズパイへのデータ入力
- CLK: ラズパイのSCKL(シリアルクロック)端子に接続
- 通信するデバイス同士のタイミング合わせで利用
- 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単位)
送信データ
送信データは2バイトで、上位5bitにオプションを設定する。
- 固定で0
- スタートビット: 1 (固定)
- SGL/DIFF: 動作モードを選択
- 0: 擬似差動モード (よくわからん)
- 1: シングルエンドモード (とりあえず今回はこっち)
- ODD/SIGN: シングルエンドモードで使用されるチャンネルを選択。
- 0: CH0 (CH0端子にアナログデバイスを接続している場合はこちら)
- 1: CH1 (CH1端子にアナログデバイスを接続している場合はこちら)
- MSBF: 受信データのフォーマットを選択。
- 0: MCP3002はMSBファーストフォーマット(最上位ビットから)で送信
- 1: MCP3002はMSBファーストフォーマットで送信した後、LSBファーストフォーマット(最下位ビットから)に変換された値を送信
- 以降は何でも良い
受信データ
受信データは2バイトで、下位10bitが値(アナログデバイスの出力電圧 / VREF * 1023)となる。
LM61CIZ
温度センサー
基本情報
- 動作温度範囲: -30℃ ~ 100℃
- 電源電圧: 2.7V ~ 10V
- 消費電力: 125μA (最大値)
- 出力電圧: 600mVを0℃として、1℃あたり10mVの増減
- -30℃: 300mV
- 0℃: 600mV
- 100℃: 1600mV
ピン
*** 下から見た図 ***
-----------------
| |
| +VS VOUT GND |
| --- --- --- |
\ /
\ /
\ /
\_________/
- +VS: ラズパイの3.3V端子に接続
- 電源入力 (2.7V~10V)
- VOUT: A/Dコンバータのアナログインプット端子に接続
- 温度センサーのアナログアウトプット(300mV(-30℃)~1600mV(100℃))
- GND: ラズパイのGND端子に接続
■ SPIモードがよく分からなかったので調べてみた
記事としてはこのあたりがわかりやすい
SPIメイン(ラズパイ)はクロックの極性と位相を選択できるとのことだが、、、
早い話がアイドル時のクロックがHIGHかLOWかを選択できて、クロックがHIGHになるときにデータを読み込むか、LOWになるときにデータを読み込むかを選択できるということ。
この、アイドル時のクロック(HIGH, LOW)、データをいつ読み込むか(HIGH, LOW) の組み合わせで4つのモード(SPIモード)を選択できる。
ちなみに、アイドル時のクロックを設定するオプションを CPOLビット
、データをいつ読み込むかを設定するオプションを CPHAビット
と呼び、まとめると↓のような感じ。
SPIモード | CPOLビット | CPHAビット | 説明 |
---|---|---|---|
0 | 0 | 0 | アイドル時のクロックは0。 クロックの立上がりでデータをサンプリング、立下がりでシフト |
1 | 0 | 1 | アイドル時のクロックは0。 クロックの立下がりでデータをサンプリング、立上がりでシフト |
2 | 1 | 0 | アイドル時のクロックは1。 クロックの立下がりでデータをサンプリング、立上がりでシフト |
3 | 1 | 1 | アイドル時のクロックは1。 クロックの立上がりでデータをサンプリング、立下がりでシフト |
MCP3002のデータシートのシリアルタイミングを確認すると
- アイドル状態のクロックの極性は0(
CPOL=0
)。 - ラズパイ(マスター)側はビットの立ち上がりでデータのサンプリングを行い、立ち下がりでシフトを行えば良い (
CPHA=0
)
なので、SPIモードは 0
■ 実装
少し調べたら、WiringPi は開発中止?になっていたので、pigpioを使ってみる。
pigpioは pigpiod
というデーモンを立ち上げて、このデーモン経由でGPIOを操作するらしい。
インストール
# 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通信有効化
実装
公式ドキュメントを参考に実装してみた
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