見出し画像

製品レビュー|電子機器1:温度センサー(ADT7410)


1.概要

 購入した製品の使い方および感想用記事です。今回は温度センサーADT7410(350円/個(税込))をレビューしました。


https://akizukidenshi.com/catalog/g/g106860/

1-1.製品の仕様:ADT7410

 ADT7410はANALOG DEVICES社が提供する温度センサーであり、高分解能0.0078℃(16ビット設定時)でI2Cバスに測定温度データを出力できます。詳細な情報は下記の通りです。

【仕様】
 仕様は下記の通りです。特に注目した点を別途抽出しました。

https://akizukidenshi.com/goodsaffix/AE-ADT7410_aw.pdf
  1. 13ビットのA/Dコンバーター(ADC)を搭載:アナログ-デジタルコンバータ(別名ADC)は、現実の信号(温度、圧力、加速度、速度など)を測定し、それをその信号のデジタル表現に変換する電子回路

    • 動作原理:アナログ入力電圧のサンプル(サンプル/ホールド回路を使用して作成)を既知のリファレンス電圧と比較した後、このアナログ入力のデジタル表現を作成する。ADCの出力はデジタル二進符号である。

    • 前提としてコンピューターが処理できるのはデジタル情報のみである。よって、アナログ信号はデジタル信号に変換しないと温度の値が読み取れない。ADT7410はADCがあるため、コンピューター側に追加のADCが無くても温度を表示できる。

  2. I2C通信(シリアル通信):センサーからの温度情報はI2Cで通信

    • ADCで変換した情報はデジタル信号となる。アナログ信号(電流や電圧)であれば(多分)CVVケーブルCVV-Sケーブルなどで繋ぐだけで検出できる(電流や電圧の違いを見ればよいだけ)が、デジタル情報は難しい。

    • デジタルに変換した情報を渡すためにI2C通信を使用している(と思う)

https://www.rohm.co.jp/electronics-basics/ad-converters/ad_what2

【図面】

https://www.analog.com/jp/products/adt7410.html

1-2.製品原理:IC温度センサ

 温度センサーは下図のように複数種類の製品があります。


Tech Web_Rohm
Tech Web_Rohm

 ADT7410は「温度センサIC」であり、回路基板用などに使用されます。下図の通り温度とデバイスの消費電力が線形の関係にあるため、これを利用して温度変化によって得られる電圧または電流を出力として取ることで温度を計測できます。

https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf
https://www.analog.com/media/jp/technical-documentation/application-notes/AN-892_jp.pdf

2.部材の購入

2-1.購入品

 今回は秋月電子よりADT7410が組み込まれている基盤を購入しました。

2-2.必須品

 その他必需品は下記の通りです。

  • Raspberry Pi4(I2C通信が可能なものなら何でも可)

  • ジャンピングワイヤー(メスーメス)

    • メスーメスが無い場合はブレッドボードを追加

  • はんだ付けセット

3.環境構築

 Raspberry Pi環境を準備します。詳細は下記記事の通りです。

 今回はsmbusライブラリを使用するため事前に下記を実行しておきます(smbus2ライブラリがあるけど下記はもう古いのかも)。

[Terminal]
pip install smbus

4.使用前の準備

4-1.ADT7410のはんだ付け

 秋月電子購入品は下図の通りADT7410とピンヘッダは個別です。

 ピンヘッダをADT7410に垂直にはんだ付けしました。希望に応じて水平に付けたり、直接電線にはんだ付けも問題ありません。

https://akizukidenshi.com/goodsaffix/AE-ADT7410_aw.pdf

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配置は下図の通りです。

https://www.raspberrypi.com/documentation/computers/raspberry-pi.html

【参考:ブレッドボードの使い方】
 はんだ付け不要で部品やリード線を差すだけで回路が組み立てられます。特定のラインが電気的に繋がるため、ジャンパー線接続の手間が省けます。

4-3.仕様確認

 各種仕様を確認していきます。まとめは下記の通りです。

  1. I2Cバスアドレス:0x48

  2. (温度データを読み取る)レジスタのアドレス:0x00

  3. 最小有効ビット(LSB : Least Significant Bit):0.0625℃/ADC

    • ADC resolution(解像度)はデフォルトで13 bits (0.0625°C)

  4. 最上位ビット(MSB):つまり一番左のbitは符号ビットであり正負を判断

  5. 最上位ビット(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.」

  6. 温度とデジタル出力の関係は下図の通り

【I2Cバスアドレス】
 シリアル通信であるI2Cのためのアドレスは下記より0x48です。

https://akizukidenshi.com/goodsaffix/AE-ADT7410_aw.pdf

【レジスタアドレス】
 レジスタはCPU内の一時的な記憶装置です。このレジスタにアドレスがあり、デフォルトは0x00です。

https://publg.com/multi_search_jump/pdf/web/viewer.html?file#2111259827&zoom=100

【温度データフォーマット】
 温度による電圧?から得られたアナログデータを13bitの2進数に変換し、それを10進数のデジタル出力(ADC Code(dec))に変換します。この時の計算式および考え方は下記の通りです。

  1. 測定温度は仕様(下記式)に応じてデジタル出力に変換される。

    • 温度の値が直接2進数に変換されているわけではない。

  2. ADC code(bin)は13ビットあるが、MBS(一番左のビット)は正負を判断するためのビットであり、後ろの12bitが実際の温度データ

  3. $${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を作成しました。設計思想は下記の通りです。

  1. 学習用に可視化用コード追加

    • format_binaryでデータの2進数にしてデータ結合とシフトを可視化

    • データの動きが見えるようにprint文を追加

    • argparseを使用してprint文を表示/非表示の切り替えが可能

  2. I2Cアドレスや温度センサのレジスタアドレスは仕様書から参照

  3. smbusを用いてI2C通信で温度センサから情報を取得

  4. 得られたデジタル出力(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目としてカウント

  1. 最初の1byte(8bit)をビットシフトの左シフトで8bitずらします。つまり、8~15ビット目がblock[0]の値、0~7ビットは0で埋まった状態になります。

    • イメージは<1byteのデータ>00000000

  2. 次に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のデータができる

  3. 最後に得られた16bitを3bit右シフトする。

    1. 仕様書の通り、最下位ビット (LSB)の3つは温度データとは関係ない。

    2. 右に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.おまけ:追加機能の実装

 前章では温度センサを学習するためにシンプルなコードにしました(プログラミング的な部分は省略)。

 本章では実際に自分で使う場合に欲しい機能を追加してみました。設計思想は下記の通りです。

  1. 学習・検証用の出力文は削除し、Timestampと温度を表示

    • 本当はloggingモジュールを使った方が良かったのですが今回は削除

  2. 可変値(データ取得周期、グラフのx軸)を変数として分離

  3. 下記機能を選択できるようにargparseでオプション引数を設定

  4. リアルタイムのグラフ機能追加

    • Matplotlibを使用して取得したデータをグラフ化

    • x軸の表示周期を変更できるように変数設定:X軸は動的に調整できる方がよいが、手間なので放置

    • y軸の最小値を0基準(最小値≧0)にするか拡大するか設定可能

    • 各種変数の設定は引数ではなくモジュールを直接編集(コマンドで設定できる方が楽な気はしますが)

  5. 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モジュールの詳細は別途対応

この記事が気に入ったらサポートをしてみませんか?