TOPに戻る

センサ・シリーズ 温度①確度±0.1℃ TMP117 その1 温度の読み出し

 使用環境
  • Raspberry Pi Raspberry Pi 4 Model B 2MB
  • OS Raspberry Pi OS(32ビット)5.4.51
  • Python3 3.7.3
  • smbusライブラリ 4.1-1
  • エディタ Mu

温度センサTMP117のおもなスペック

  • 動作電圧 1.8~5.5V
  • 分解能 16ビット、LSBは0.007812℃
  • 動作温度範囲  -55~150℃
  • 確度
    • -20~+50℃ の範囲で±0.1℃ (最大値)
    • -40~+100℃ の範囲で±0.2℃ (最大値)
    • -55~+150℃ の範囲で±0.3℃ (最大値)
  • 消費電流 1Hzの変換サイクルで3.5uA
  • インターフェース I2C(1~400kHz)
  • データ長 2の補数形式の16ビット
  • 平均化出力が可能
  • 温度設定のアラートあり

 とても小さなパッケージに入っているので、ICを購入しても、利用しにくいです。ここでは、評価ボードTMP117EVMを入手しました。使用例は、こちらを参照してください。

 センサの載っている部分を切り離します。切り離したブレークアウト・ボードにワイヤをはんだ付けします。約10cmで、先端にはピンヘッダを圧着しました。

センサのインターフェース

 自然界の情報を得るために、様々なセンサが考案され、市販されています。そのセンサとマイコン間でデータをやり取りするインターフェースには、次のような種類があります。

  • ディジタル端子(1本もしくは複数)
  • アナログ端子
  • I2C(アイスクエアシー)バス
  • SPIバス
  • UART(調歩同期式、RS-232C)

 この中でI2CとSPIが一番利用が多いです。I2Cはオリジナルがフィリップス、現行NXPの発明ですが、インテルがほとんど同じ規格でSMBUSを作ったため、ラズパイのI2CはSMBUSライブラリでアクセスします。

 PCやマイコンの世界では、古くはパラレル通信がありましたが、現在は、すべてシリアル通信になっています。シリアル通信では、データとクロックが必要です。データとクロックを混在して送受信する方法がありますが、I2Cは、

  • クロック SCL
  • データ SDA

の2本立てです。これ以外に電源が必要です。ラズパイのGPIO端子の信号レベルは3.3Vなので、

  • 3.3V
  • GND(グラウンド)

の2本です。したがって、4本の線でつなぐことになります。ラズパイでは、物理ピンの2番と3番を使います。

  • 3.3V 1番
  • データ SDA 3番
  • クロック SCL 5番
  • GND 9番もしくは6番

 この組み合わせで使うのが一般的です。ここはi2cの1と呼ばれるバスです。

 4ピンだけならコネクタを用意したほうが便利だと考えた会社があります。古くはSeeedはGroveを、最近ではAdafruitはSTEMMA QTを推進しています。

I2Cの有効化

 OSをインストールした直後では、I2Cは無効です。同様にSPIも無効です。

 変更は、メイン・メニューの設定-Raspberry Piの設定から行います。DisableからEnableへチェックを変更します。

 再起動は不要です(2019年春ごろから)。

 また、ほかのLinuxではGPIO、I2C、SPIをアクセスするプログラムはroot権限が必要です。ラズパイでpiユーザだけは、I2Cのグループにpiが入っているとか複数の理由で、piユーザの権限でアクセスできます。

ラズパイとの接続

 上記のようにつなぎました。

TMP117 ラズパイ
SDA SDA 3番
SCL SCL 5番
ALRT  -
GND GND 9番
V+ 3.3V 1番

プログラムで使うライブラリ

 pythonのプログラムでは、smbusライブラリを使うのが一般的です。現在のOSには、最初から入っています。

 smbusライブラリの書式です。アドレスは7ビットのスレーブ・アドレス、コマンドはレジスタの値などを入れます。

READでよく使う関数

 1バイトだけ;read_byte_data(アドレス, コマンド)

 2バイト以上;read_i2c_block_data(アドレス, コマンド, バイト数)

Writeでよく使う関数

 1バイトだけ;write_byte_data(アドレス,  コマンド、1バイトのデータ)

 2バイト以上;write_i2c_block_data(アドレス, コマンド, 複数バイトのデータのlist) 

(※)writeで、「コマンド、1バイトのデータ」の違いはありません。どちらも8ビットのデータです。「コマンド, 複数バイトのデータのlist」でも、すべて8ビットのデータです。

スレーブ・アドレスの確認

 センサのデータシートには、このTMP117ではADD0ピンをGNDにつなぐと'1001000x'、V+につなぐと'1001001x 'のように説明があります。ラズパイとつないだとき、配線が確実につながっていることを確認します。OSにインストールされているi2c-toolsのなかのi2cdetectコマンドを使います。

 ターミナルで、 i2cdetect -y 1と打ち込んで、バス1の接続状況を表示します。0x48にいました。

データ転送速度

 Standard-mode では最大100kbit/s、Fast-mode では最大400kbit/s、Fast-mode Plus では最大 1Mbit/s、High-speedでは最大 3.4Mbit/s のデータ転送が可能です。

 smbusライブラリのデフォルトは100kbit/sです。デバイスの多くは400kbit/sをサポートしています。

 I2Cバスはプルアップして使います。バス1は1.8kΩでプルアップされています。したがって、ブレークアウト・ボード側にプルアップ抵抗は不要です。もしすでにはんだ付けされているとき、10kΩぐらいなら、並列接続になりますが、ラズパイで駆動できた経験があります。

 100kbit/sは100kHzとも表記されます。
 smbusライブラリでスピードを変更するときは、/boot/config.txtに、

dtparam=i2c_baudrate=400000

を追加し、rebootします(※1)。

(※1)/boot/config.txtの変更は、root権限が必要です。piでログインしているときは、sudo nano /boot/config.txtでファイルをオープンし、修正を行い、CTRL-Oで上書き書き込み、CTRL-Xで終了します。

プログラム

 ICに電源が入ったら、Configurationレジスタの設定内容に従って動作します。特にスリープ・モードに入る動作はないようです。レジスタの初期値では連続変換モードになっているので、特に変更する必要はないようです。

import smbus

bus = smbus.SMBus(1)
tmp117_addr = 0x48
TemperatureRegister   = 0x00
data = bus.read_i2c_block_data(tmp117_addr , TemperatureRegister, 2) # tempの読み出し temp = data[0] <<8 | data[1] temperature = temp * 0.0078125 print("Temp is %.5f `C" % temperature)

 2バイトのデータをTemperatureレジスタから読み出した中身がdataに入っています。

data = bus.read_i2c_block_data(tmp117_addr , TemperatureRegister, 2)

 read_i2c_block_data()は、最初に読んだのがdata[0]、次がdata[1]のように順番に収録されます。データシートによると、data[0]は上位8ビットが、data[1]には下位8ビットが入ります。ほしいのは16ビット・データです。

 最初に読み込んだdata[0]に0x80が入っているとすると、

- - - - - - - - 1 0 0 0 0 0 0 0

 8回左にシフトすれば、

1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

になります。次に、data[1]と論理or( | )を取ります。temp = data[0] <<8 | data[1]で、16ビットのデータが用意できたことになります。

 データシートに、1LSB=最小単位は0.0078125℃と書かれているので、

temperature = temp * 0.0078125

と変換して摂氏を求めます。

 このプログラムは、0℃以上の値は正常ですが、零下の温度に対応していません。それは、読み出した16ビットのデータが2の補数形式だからです。

 確かめてみましょう。修正したプログラムです。temp=0xf380は-25.0℃です。

import smbus
bus = smbus.SMBus(1)
tmp117_addr = 0x48
TemperatureRegister   = 0x00

data = bus.read_i2c_block_data(tmp117_addr , TemperatureRegister, 2)  #  tempの読み出し
temp = data[0] <<8 | data[1]

temp=0xf380

temperature = temp * 0.0078125
print("Temp is %.5f `C" % temperature)

def sign16(x):
    return (-(x & 0b1000000000000000) | (x & 0b0111111111111111))

temperature = sign16(temp) * 0.0078125
print("Temp is %.5f `C" % temperature)

 実行結果です。

def sign16(x):
    return (-(x & 0b1000000000000000) | (x & 0b0111111111111111))

 このsign16()関数は、16ビット・データの最上位ビットが'1'だったらマイナスの符号をつけます。プラス(最上位ビットが'0')の温度データならば、そのまま何もせずに値を戻します。

 細かく見ていきます。まず、左側のブロックです。

x & 0b1000000000000000

 左側のブロックで使っているビット演算子&は、論理積(AND)です。xと0b1000000000000000の両方で1だと1で、どちらかもしくは両方が0だと0です。なので、先頭ビットが'1'であれば結果は-です。ほかは0です。
 次は右のブロックです。

x & 0b0111111111111111

 右側のブロックでは、最上位ビット以外を全部1でAND演算しているので、最上位ビットは0になり、それ以下のビットは、元のデータxと同じになります。つまり、最上位ビットを捨てたデータになります。

 両方のブロックの演算をビット演算子 |(論理和 OR)で演算するので、左辺と右辺の同じ位置にあるビットのどちらかが1であれば1(両方1であれば1を含む)に、両方とも0のときは0です。
 結果、符号ビットが1であれば、-符号が追加された以外は元のデータが、符号ビットが0であれば、元のデータがそのまま戻ります。

 データシートに温度とデータの関係が掲載されています。一部を抜き出します。動作確認に使えます。

データ(16進) データ(バイナリ) 温度(摂氏)
0xf380 1111 0011 1000 0000 -25

0xffff

1111 1111 1111 1111 –0.0078125
0x0000 0000 0000 0000 0000  0
0x0001 0000 0000 0000 0001 0.0078125
0x0c80 0000 1100 1000 0000 25

  

 必要な部分だけ残しました。

import smbus
bus = smbus.SMBus(1)
tmp117_addr = 0x48
TemperatureRegister   = 0x00

data = bus.read_i2c_block_data(tmp117_addr , TemperatureRegister, 2)  #  tempの読み出し
temp = data[0] <<8 | data[1]

def sign16(x):
    return (-(x & 0b1000000000000000) | (x & 0b0111111111111111))

temperature = sign16(temp) * 0.0078125
print("Temp is %.5f `C" % temperature)

平均化などを変更する

 Configurationレジスタの中に、CONV[2:0] Conversion cycle bit.とAVG[1:0] Conversion averaging modes. があります。デフォルトでは、CONV[2:0]='100' 、AVG[1:0]='01' 8回の平均になっています。

 これを、64回の平均に変更します。S/N比が上がって確度も上がると期待できます。変換時間は、125msから1秒に伸びます。

  0000 0010 0110 0000 -> 0x02 0x60

import smbus
bus = smbus.SMBus(1)

tmp117_addr = 0x48
TemperatureRegister   = 0x00
ConfigurationRegister = 0x01

bus.write_i2c_block_data(tmp117_addr, ConfigurationRegister, [0x02, 0x60])
data = bus.read_i2c_block_data(tmp117_addr , TemperatureRegister, 2)  #  tempの読み出し
temp = data[0] <<8 | data[1]

def sign16(x):
    return (-(x & 0b1000000000000000) | (x & 0b0111111111111111))

temperature = sign16(temp) * 0.0078125
print("Temp is %.5f `C" % temperature)

連載 ラズパイ センサ・シリーズ

(1) 温度①確度±0.1℃ TMP117 その1 温度の読み出し

(2) 温度①確度±0.1℃ TMP117 その2 アラートの設定

(3) 温度①確度±0.1℃ TMP117 その3 アラートでLED点灯

(4) 温度①確度±0.1℃ TMP117 その4 アラートでリレー駆動

(5) 温度②温度調節器 その1 Modbusの設定

(6) 温度②温度調節器 その2 Modbusで読み書き

(7) 温湿度①SHT31 その1 測定準備