製品レビュー|電子機器1:温度センサー(ADT7410)
1.概要
購入した製品の使い方および感想用記事です。今回は温度センサーADT7410(350円/個(税込))をレビューしました。
1-1.製品の仕様:ADT7410
ADT7410はANALOG DEVICES社が提供する温度センサーであり、高分解能0.0078℃(16ビット設定時)でI2Cバスに測定温度データを出力できます。詳細な情報は下記の通りです。
【仕様】
仕様は下記の通りです。特に注目した点を別途抽出しました。
13ビットのA/Dコンバーター(ADC)を搭載:アナログ-デジタルコンバータ(別名ADC)は、現実の信号(温度、圧力、加速度、速度など)を測定し、それをその信号のデジタル表現に変換する電子回路
動作原理:アナログ入力電圧のサンプル(サンプル/ホールド回路を使用して作成)を既知のリファレンス電圧と比較した後、このアナログ入力のデジタル表現を作成する。ADCの出力はデジタル二進符号である。
前提としてコンピューターが処理できるのはデジタル情報のみである。よって、アナログ信号はデジタル信号に変換しないと温度の値が読み取れない。ADT7410はADCがあるため、コンピューター側に追加のADCが無くても温度を表示できる。
I2C通信(シリアル通信):センサーからの温度情報はI2Cで通信
【図面】
1-2.製品原理:IC温度センサ
温度センサーは下図のように複数種類の製品があります。
ADT7410は「温度センサIC」であり、回路基板用などに使用されます。下図の通り温度とデバイスの消費電力が線形の関係にあるため、これを利用して温度変化によって得られる電圧または電流を出力として取ることで温度を計測できます。
2.部材の購入
2-1.購入品
今回は秋月電子よりADT7410が組み込まれている基盤を購入しました。
2-2.必須品
その他必需品は下記の通りです。
Raspberry Pi4(I2C通信が可能なものなら何でも可)
ジャンピングワイヤー(メスーメス)
メスーメスが無い場合はブレッドボードを追加
はんだ付けセット
3.環境構築
Raspberry Pi環境を準備します。詳細は下記記事の通りです。
今回はsmbusライブラリを使用するため事前に下記を実行しておきます(smbus2ライブラリがあるけど下記はもう古いのかも)。
[Terminal]
pip install smbus
4.使用前の準備
4-1.ADT7410のはんだ付け
秋月電子購入品は下図の通りADT7410とピンヘッダは個別です。
ピンヘッダをADT7410に垂直にはんだ付けしました。希望に応じて水平に付けたり、直接電線にはんだ付けも問題ありません。
4-2.部品の組付け
部品を下記要領で組付けました(緑は電気の流れ)。
Raspberry PiのGPIO2(SDA)とADT7410のSDAを電気的に接続
Raspberry PiのGPIO3(SCL)とADT7410のSCLを電気的に接続
Raspberry Piの3.3V電源をADT7410のVDDに電気的に接続
Raspberry PiのGNDとADT7410のGNDを電気的に接続
おそらく直接下記のように直接繋いでも問題ないと思います。
【参考:Raspberry Pi4のGPIO配置】
Raspberry Pi4のGPIO配置は下図の通りです。
【参考:ブレッドボードの使い方】
はんだ付け不要で部品やリード線を差すだけで回路が組み立てられます。特定のラインが電気的に繋がるため、ジャンパー線接続の手間が省けます。
4-3.仕様確認
各種仕様を確認していきます。まとめは下記の通りです。
I2Cバスアドレス:0x48
(温度データを読み取る)レジスタのアドレス:0x00
最小有効ビット(LSB : Least Significant Bit):0.0625℃/ADC
ADC resolution(解像度)はデフォルトで13 bits (0.0625°C)
最上位ビット(MSB):つまり一番左のbitは符号ビットであり正負を判断
最上位ビット(MSB):右端からの3bitは温度とは関係なし
P12より「The three LSBs, Bit 0 to Bit 2, on power-up, are not part of the temperature conver-sion result and are flag bits for $${T_{CRIT}}$$, $${T_{HIGH}}$$, and $${T_{LOW}}$$. Table 5 shows the 13-bit temperature data format without Bit 0 to Bit 2.」
温度とデジタル出力の関係は下図の通り
【I2Cバスアドレス】
シリアル通信であるI2Cのためのアドレスは下記より0x48です。
【レジスタアドレス】
レジスタはCPU内の一時的な記憶装置です。このレジスタにアドレスがあり、デフォルトは0x00です。
【温度データフォーマット】
温度による電圧?から得られたアナログデータを13bitの2進数に変換し、それを10進数のデジタル出力(ADC Code(dec))に変換します。この時の計算式および考え方は下記の通りです。
測定温度は仕様(下記式)に応じてデジタル出力に変換される。
温度の値が直接2進数に変換されているわけではない。
ADC code(bin)は13ビットあるが、MBS(一番左のビット)は正負を判断するためのビットであり、後ろの12bitが実際の温度データ
$${2^{12}=4096}$$であり、$${4096*0.0625=256}$$である。つまり、0.0625刻みで4096刻みだと、0~255.9375まで数値を表現できる。※仕様では上限温度は255℃まで
上記と同じ考えなら計算上は負も温度域も同じ範囲を計測できると思うが、おそらくデバイス(IC温度センサ)による制約と思う
$$
温度=\frac{ADC Code(dec)}{16} = 0.0625\times ADC Code (T\geq0)
$$
$$
温度=\frac{ADC Code(dec)-8192}{16} (T<0)
$$
[IN]
# 指定ビット(Default:8bit)の固定幅で2進数をフォーマットする関数
def format_binary(num, bits=8):
return f'{num:0{bits}b}'
def calc_ADC_code_Positive(temp:int):
return 16 * temp
def calc_ADC_code_Negative(temp:int):
return 16 * temp + 8192
tempertures = [-55, -50, -25, -0.0625, 0, 0.0625, 25, 50, 125, 150, 255, 255.9375, 256]
ADC_code_dec = [calc_ADC_code_Negative(temp) if temp < 0 else calc_ADC_code_Positive(temp) for temp in tempertures]
datas_binary = [format_binary(int(temp), bits=12) for temp in ADC_code_dec]
datas_hex = [f'{int(temp):04X}' for temp in ADC_code_dec]
df = pd.DataFrame({'温度(℃)':tempertures,
'ADCコード(10進数)':ADC_code_dec,
'ADCコード(2進数)':datas_binary,
'ADCコード(16進数)':datas_hex})
df
[OUT]
5.Pythonスクリプトの作成
5-1.任意:デバイス接続の確認
ADT7410がI2Cで接続している確認するために下記コマンドを実行します。デフォルトではバスアドレスとして0x48(16進数の48)が表示されます。
[Terminal]
sudo i2cdetect -y 1
5-2.コード作成(実装)
モジュールADT7410.pyを作成しました。設計思想は下記の通りです。
学習用に可視化用コード追加
format_binaryでデータの2進数にしてデータ結合とシフトを可視化
データの動きが見えるようにprint文を追加
argparseを使用してprint文を表示/非表示の切り替えが可能
I2Cアドレスや温度センサのレジスタアドレスは仕様書から参照
smbusを用いてI2C通信で温度センサから情報を取得
得られたデジタル出力(ADC_code)は仕様書記載の計算式で温度に変換
$$
温度=\frac{ADC Code(dec)}{16} = 0.0625\times ADC Code (T\geq0)
$$
$$
温度=\frac{ADC Code(dec)-8192}{16} (T<0)
$$
[ADT7410.py]
import smbus
import time
import argparse
#可視化用にargparseを設定
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', action='store_true') #引数があるときはTrue, ないときはFalse
args = parser.parse_args()
#バイナリの動きを可視化
def format_binary(num, bits=8):
return f'{num:0{bits}b}'
#センサー情報取得用のコード
bus = smbus.SMBus(1) #Raspberry PiのI2Cバスの指定
def adt7410(address, register, num_bytes, verbose=False, verbose2=False):
block = bus.read_i2c_block_data(address, register, num_bytes) #I2Cデータの読み込み
data = (block[0] <<8 | block[1]) >>3 #データの結合とシフト
if (data >= 4096):
data -= 8192
temp = data * 0.0625
if verbose:
print(f'block: {block}, data: {data}, temp: {temp}')
print(f'block0: {format_binary(block[0])} , block1: {format_binary(block[1])}')
print(f'block0 <<8: {format_binary(block[0] <<8)}')
print(f'block0 <<8 | block1: {format_binary(block[0] <<8 | block[1])}')
print(f'(block[0] <<8 | block[1]) >>3: {format_binary((block[0] <<8 | block[1]) >>3)}', end='\n\n')
return temp
#条件設定
address = 0x48 #ADT7410のI2Cアドレス
register = 0x00 #温度データのレジスタ
num_bytes = 2 #読み込むバイト数: 2バイト
try:
while True:
value = adt7410(address, register, num_bytes, args.verbose) #温度データの取得
print(f'Temp.= {value}°C')
time.sleep(1.0)
except KeyboardInterrupt:
pass
【解説:data = (block[0] <<8 | block[1]) >>3 #データの結合とシフト】
ADT7410は通信(bus.read_i2c_block_data(address, register, num_bytes))により2byteのデータが得られますが、データは1byteずつリストで出力されます。
それを下記手順を実行することで最終的に13bitに変換します。
※ビット数は右端を0bit目としてカウント
最初の1byte(8bit)をビットシフトの左シフトで8bitずらします。つまり、8~15ビット目がblock[0]の値、0~7ビットは0で埋まった状態になります。
イメージは<1byteのデータ>00000000
次に2つ目の1byte(8bit)データ:block[1]を先ほどのデータに結合します。結合はOR演算で実行できます。
前提としてblock[1]は8bitのため16桁で見ると00000000<1byteのデータ>である。
OR演算の特徴として、片方の値が0の場合、入力した数値をそのまま出力する特性がある。
8bit左シフトしたデータは0~7ビットは0であり、結合したいblock[1]は(1byteデータのため)8~15ビット目は0である。つまりOR演算をすると、結果的に<block[0]の8bit><block[1]の8bit>=16bitのデータができる
最後に得られた16bitを3bit右シフトする。
仕様書の通り、最下位ビット (LSB)の3つは温度データとは関係ない。
右に3bitシフトする=LSBの3ビットをカットする。16bitで見ると頭に0が入るが、10進数に変換すると桁落ちするため、結果的にLSBを3bit切り落としたのと同義になる。
理解用に動作確認用の関数を作成しましたのでご参考までに。
[IN]
def format_binary(num, bits=8):
return f'{num:0{bits}b}'
def visualize_LogicOpe(byte_1st:int, byte_2nd:int):
byte_1st_bits, byte_2nd_bits = format_binary(byte_1st, bits=16), format_binary(byte_2nd, bits=16)
byte_1st_8shifted = byte_1st << 8
byte_1st_8shifted_bits = format_binary(byte_1st_8shifted, bits=16)
num_joined = byte_1st_8shifted | byte_2nd
num_joined_bits = format_binary(num_joined, bits=16)
num_joined_3shifted = num_joined >> 3
num_joined_3shifted_bits = format_binary(num_joined_3shifted, bits=16)
#ビット演算の可視化
df = pd.DataFrame(index=['X', 'Y', 'X(8bit左シフト)', 'XとYの結合(16bit化)', '3bit右シフト(13bit化)'],
columns=['10進数']+[f'bit{i+1}' for i in range(16)])
#10進数をDataFrameのセルに値を代入
df.iloc[0, 0] = byte_1st
df.iloc[1, 0] = byte_2nd
df.iloc[2, 0] = byte_1st_8shifted
df.iloc[3, 0] = num_joined
df.iloc[4, 0] = num_joined_3shifted
#2進数の各bitをDataFrameのセルに値を代入
for i in range(16):
df.iloc[0, i+1] = byte_1st_bits[i]
df.iloc[1, i+1] = byte_2nd_bits[i]
df.iloc[2, i+1] = byte_1st_8shifted_bits[i]
df.iloc[3, i+1] = num_joined_bits[i]
df.iloc[4, i+1] = num_joined_3shifted_bits[i]
return df
num1, num2 = 10, 168
df = visualize_LogicOpe(num1, num2)
df
[OUT]
5-3.実行
モジュールを実行して温度を計測しました。エアコンの温度設定を20℃にしているため大きくずれた値ではないと思います(本当は校正できる何かがあればいいのですが、熱湯や氷につけてよいのかは不明)。
[IN]
python3 ADT7410.py
[OUT]
emp.= 21.5625°C
Temp.= 21.375°C
Temp.= 21.25°C
Temp.= 21.6875°C
Temp.= 21.6875°C
Temp.= 21.5°C
中身を理解するための可視化としてオプション引数も実行しました。
[IN]
python3 ADT7410.py -v
[OUT]
block: [10, 168], data: 341, temp: 21.3125
block0: 00001010 , block1: 10101000
block0 <<8: 101000000000
block0 <<8 | block1: 101010101000
(block[0] <<8 | block[1]) >>3: 101010101
Temp.= 21.3125°C
block: [10, 168], data: 341, temp: 21.3125
block0: 00001010 , block1: 10101000
block0 <<8: 101000000000
block0 <<8 | block1: 101010101000
(block[0] <<8 | block[1]) >>3: 101010101
Temp.= 21.3125°C
block: [10, 152], data: 339, temp: 21.1875
block0: 00001010 , block1: 10011000
block0 <<8: 101000000000
block0 <<8 | block1: 101010011000
(block[0] <<8 | block[1]) >>3: 101010011
Temp.= 21.1875°C
6.おまけ:追加機能の実装
前章では温度センサを学習するためにシンプルなコードにしました(プログラミング的な部分は省略)。
本章では実際に自分で使う場合に欲しい機能を追加してみました。設計思想は下記の通りです。
学習・検証用の出力文は削除し、Timestampと温度を表示
本当はloggingモジュールを使った方が良かったのですが今回は削除
可変値(データ取得周期、グラフのx軸)を変数として分離
下記機能を選択できるようにargparseでオプション引数を設定
リアルタイムのグラフ機能追加
Matplotlibを使用して取得したデータをグラフ化
x軸の表示周期を変更できるように変数設定:X軸は動的に調整できる方がよいが、手間なので放置
y軸の最小値を0基準(最小値≧0)にするか拡大するか設定可能
各種変数の設定は引数ではなくモジュールを直接編集(コマンドで設定できる方が楽な気はしますが)
CSVファイルへの出力機能
上書きされないように、開始時刻のタイムスタンプを取ってファイル名をユニーク化(重複防止)
※長時間やるとメモリに乗らなくなるはずですが、そこは未検証
[ADT7410_R1.py]
import smbus
import time
import os
import argparse
import matplotlib.pyplot as plt
import pandas as pd
from datetime import datetime
import matplotlib.dates as mdates
# 可視化とCSV出力用の設定
parser = argparse.ArgumentParser()
parser.add_argument('--plot', '-p', action='store_true', help="Plot temperature data in real-time")
parser.add_argument('--expansion', '-ex', action='store_true', help="Expand the y-axis range")
parser.add_argument('--save', '-s', action='store_true', help="Save temperature data to CSV")
args = parser.parse_args()
# データ保存用のリスト
temp_data = []
timestamps = []
#バイナリの動きを可視化
def format_binary(num, bits=8):
return f'{num:0{bits}b}'
#センサー情報取得用のコード
bus = smbus.SMBus(1) #Raspberry PiのI2Cバスの指定
def adt7410(address, register, num_bytes, verbose=False, verbose2=False):
block = bus.read_i2c_block_data(address, register, num_bytes) #I2Cデータの読み込み
data = (block[0] <<8 | block[1]) >>3 #データの結合とシフト
if (data >= 4096):
data -= 8192
temp = data * 0.0625
return temp
#条件設定:固定値
address = 0x48 #ADT7410のI2Cアドレス
register = 0x00 #温度データのレジスタ
num_bytes = 2 #読み込むバイト数: 2バイト
#条件設定:可変値
timeperiod = 1.0 #データ取得間隔
xticks_interval = 2 #グラフのX軸の間隔 [sec]
try:
plt.ion() # インタラクティブモードを有効化
fig, ax = plt.subplots()
timestamp_start = datetime.now().strftime('%Y%m%d_%H%M%S') #CSVファイル保存用:開始時刻
while True:
temp = adt7410(address, register, num_bytes) #温度データの取得
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') #文字列で現在時刻を取得
print(f'{timestamp} Temp.= {temp}°C')
# データをリストに追加
temp_data.append(temp)
timestamps.append(timestamp)
if args.plot:
# リアルタイムで温度をプロット
ax.clear()
df_datetime = pd.to_datetime(timestamps) #文字列を日時型に変換
ax.plot(df_datetime, temp_data, label='Temp1[℃]')
# X軸のフォーマットと間隔を設定
ax.xaxis.set_major_locator(mdates.SecondLocator(interval=xticks_interval)) # 指定秒ごとにX軸のメジャーラベルを表示
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) # 時間:分:秒 の形式で表示
#グラフの体裁調整
##y軸の最小値設定用
if not args.expansion:
y_max = max(temp_data)
y_min = min(temp_data)
if y_min > 0:
y_min = 0
else:
y_min = y_min*1.1 #最小値の10%下
ax.set(xlabel='Time', ylabel='Temperature(℃)', title='Time Series of Temperature',
ylim=(y_min, y_max*1.1)) #y軸の最大値設定用
else:
ax.set(xlabel='Time', ylabel='Temperature(℃)', title='Time Series of Temperature') #y軸の設定無し
plt.xticks(rotation=90) #x軸ラベルを90度回転
plt.title('Temperature Data', y=-0.6)
plt.tight_layout()
plt.pause(0.1)
time.sleep(timeperiod) #データ取得間隔
except KeyboardInterrupt:
if args.save:
# CSVフォルダがなければ作成
if not os.path.exists('CSV'):
os.makedirs('CSV')
# データフレームを作成してCSVに保存(終了時に一度だけ実行)
df = pd.DataFrame({'Timestamp': timestamps, 'Temperature': temp_data})
df.to_csv(f'CSV/{timestamp_start}_temperature_data.csv', index=False)
print(f'{timestamp_start}_temperature_data.csv has been saved in CSV folder.')
plt.ioff()
if args.plot:
plt.show()
6-1.リアルタイムのグラフ出力
グラフによる可視化はMatplotlibを使用しました。plt.ion()がインタラクティブにグラフを更新できるみたいなのでこちらを利用しました。
[IN]
python3 ADT7410.py -p
[OUT]
こちらは拡大(正確にはyticksを未設定のグラフ)図です。分解能が0.0625℃であり拡大されているため微小な温度変化でも大きく変化したように見えます。
参考までに20sec後の温度上昇は温度センサーを指で触ってみたためです。温度検出速度はそこまでよくない(1~2secで人肌温度は検出しない)感じです。
[IN]
python3 ADT7410.py -p -ex
[OUT]
6-2.CSVファイル出力
CSVファイルを出力します。ファイル名は実行時のTimestampを秒単位で取っているため、通常の使用範囲でファイル名の重複はなくファイルが上書きされることはありません。
[IN]
python3 ADT7410.py -s
[OUT]
7.所感
簡単な所感は下記の通り
分解能が0.0625℃はものすごくよい。プラントや化学実験だと0.1℃単位あれば十分なのですごい。
温度範囲は-55℃~255℃なので日常、家庭向けなら全然使える。
動作電源は2.7~5.5Vで動くので使いやすい。USBの規格電圧=5Vなので多分USBでも動くはず。
はんだ付けが必要だけどめっちゃ安い。モジュール単位だけど1個600円は破格。
通信やセンサ原理、デジタル出力の変換など覚えること多すぎる。(仕様書がしっかりしているのはすごい)
参考資料
別添1:Rasberry Pi関係
別添2:Python関係
別添3:通信関係
別添4:電子機器
【A/Dコンバータ (ADC)】
あとがき
センサー1個使うだけでも調べること多すぎる・・・・多分2週間(対応は休日のみ)くらいかかった。
SMBUSモジュールの詳細は別途対応