Temperature Sensor(3)

秋月電子通商温度センサーモジュールがRaspberry Piのsmbusモジュールで正しく読み出せないので、Perl用のHiPiモジュールを使ってみたが、その後色々と試して限定的ではあるがPythonのsmbusモジュールを使用しても温度を読み出すことが可能である事が判った。

レジスタを指定して1byteだけ読み出す方法では、どのレジスタを指定してもレジスタ0x00の値が返ってきてしまう。しかし、レジスタ0x00から複数バイトを読み出す方法なら、4バイト目までは正しく読み出せている。

i2cdumpを使って読み出した場合と、pythonプログラムで0x00~0x0bまでを読み出した場合の結果は、以下のようになる。

$ i2cdump -y 1 0x48
No size specified (using byte-data access)
0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e    ????????????????
10: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e    ????????????????
20: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 00    ???????????????.
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
$ python ADT7410test.py
Register 00  0e
Register 01  a8
Register 02  80
Register 03  00
Register 04  0e
Register 05  a8
Register 06  80
Register 07  00
Register 08  0e
Register 09  a8
Register 0a  80
Register 0b  00
current temperature -> 29.31

i2cdumpでは00x~0x2eまで同じ値が返ってきているが、プログラムで0x00~0x0bまで読み出した場合、0x00~0x03の値がくり返し返ってきている。同じプログラムでもレジスタを個別に指定して読み出した場合は、i2cdumpと同じ結果になってしまう。

$ i2cdump -y 1 0x48
No size specified (using byte-data access)
0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e    ????????????????
10: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e    ????????????????
20: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 00    ???????????????.
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
$ python ADT7410test2.py
Register 00  0e
Register 01  0e
Register 02  0e
Register 03  0e
Register 04  0e
Register 05  0e
Register 06  0e
Register 07  0e
Register 08  0e
Register 09  0e
Register 0a  0e
Register 0b  0e
current temperature -> 28.06

この違いが何故起きるのかは判らないが、ADT7410のデータシートを読むと幸いなことに基本的なコントロールはアドレス0x00~0x03のレジスタだけで行う事ができる。0x04以上のレジスタは設定温度範囲を越えたときに割り込み信号を発生させる設定値レジスタなので、現在の温度を読み出す用途だけならそのレジスタを使う必要が無い。

レジスタ0x0bがデバイスの製造番号とリビジョン番号のレジスタで、それも正しく読み出せないのだが実際に必要になる場面はあまり無いだろう。

テストプログラムは以下の通り。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import smbus
import time
 
class i2c:
    def __init__(self, bus, addr):
        self.b = smbus.SMBus(bus)
        self.addr = addr
    def put(self, cmd, data):
        self.b.write_byte_data(self.addr, cmd, data)
    def get(self, cmd):
        return self.b.read_byte_data(self.addr, cmd)
    def getblock(self, cmd, len):
        return self.b.read_i2c_block_data(self.addr, cmd, len)

if __name__ == '__main__':
    try:
        dev = i2c(1, 0x48)
        while True:
            result = dev.getblock(0x00, 12)
            for i in range(0, 12):
                print "Register %02x" % i, " %02x" % result[i]
            temp = (result[0] << 8 | result[1]) >> 3
            if(temp >= 4096):
                temp -= 8192;
            print("current temperature -> %7.2f" % (temp / 16.0))
            print
            time.sleep(1)
    except KeyboardInterrupt:
        print '\nbreak'

ちなみにレジスタ0x02と0x03に書き込んだ値も正しく読み出せているので、0x00~0x03までは正常に読み書きできているようだ。ただしこれは今現在の話で、将来的にモジュールのバージョンが上がった場合にどうなるかは判らないので要注意。

広告

Temperature Sensor(2)

Raspberry Piで正しく扱えないI2Cデバイスが存在する問題、ドライバのソフトウェア的な問題だと思っていたのだが、その後色々と調べてみるとBroadcomのBCM2835チップのI2Cにバグがあるという話が見つかった。Raspberry Piに使われているのが、まさにそのBCM2835だ。

I2C Broadcom bug workaround.
Fixing the Repeated Start Condition of the I2C Bus on the Raspberry Pi by Hardware

思ったよりも根が深い問題のようだ。

チップのバグならドライバではどうしようも無いが、Perl用のHiPiモジュールではsmbusでダメなI2Cデバイスでも正しく読み書きできているように思える。HiPiのI2Cツールの実行が妙に遅いことを考えると、こちらはGPIO直叩きで読み書きしているのかも知れない。

BCM2835チップのバグについて議論されているスレッドでは、カーネルにGPIO直叩きI2C(Bitbang I2Cと書かれていた)を実装して、そちらを使うという案が出されていたので、対処する方法が無いわけでは無いようだ。チップのバグなら新しいリビジョンで修正されるかも知れないが、既存の基板が交換されるとは思えないので何らかの対処は行われる事になるのだろう。

そうでないと困る。

今のところPerlのHiPiでなら使えることが判っているので、全くダメな状態よりはまだマシ。前回作った温度読み出しのテストプログラムにI2C液晶の表示を付け加えて、温度計にしてみた。R0012031

プログラムは以下の通り。もちろんHiPiがインストールされている必要がある。

#!/usr/bin/perl

# ADT7410 I2C Temperature Sensor & I2C LCD

use strict;
use warnings;
use HiPi::BCM2835::I2C qw( :all );

my $adt = HiPi::BCM2835::I2C->new(
            peripheral => BB_I2C_PERI_1,
            address    => 0x48
        );
my $lcd = HiPi::BCM2835::I2C->new(
            peripheral => BB_I2C_PERI_1,
            address    => 0x3e
        );
# init LCD
$lcd->i2c_write(0x00, 0x38, 0x39, 0x14, 0x7a, 0x56, 0x6c);
$lcd->delay(200);
$lcd->i2c_write(0x00, 0x38, 0x0c, 0x01, 0x06);
$lcd->delay(200);
$lcd->i2c_write(0x00, 0x38);
# main
my @val;
my $tmp;
my @param;
while(1)
{
    @val = $adt->i2c_read_register_rs(0x00, 2);
    #$val[0] = 0xff; # check value
    #$val[1] = 0xff;
    $tmp = ($val[0] << 8 | $val[1]) >> 3;
    if($tmp >= 4096)
    {
        $tmp = ($tmp - 8192);
    }
    my $out = sprintf("Temp.[%7.2f%cC]", $tmp / 16, 0xdf);
    $lcd->i2c_write(0x00, 0x38, 0x80);
    @param = (0x40, unpack("C*", $out));
    $lcd->i2c_write(@param);
    sleep(1);
}

HiPiが/dev/memにアクセスするため、実行にはsudo権限が必要。

温度センサから読み出したデータをそのまま液晶に表示しているだけで、AVRやPICのような8ビットマイコンでも同じ事ができてしまうからわざわざRaspberry Piを使う必要は無いのだが、Raspberry Piならデータをネットワークで送ったり、データベースに蓄積したりといった応用が比較的簡単にできるので、そういった用途であれば意味があるだろう。

Temperature Sensor

秋月電子通商温度センサーモジュールがRaspberry Piのsmbusモジュールでは正しく使えなかったので、PerlモジュールHiPiのI2Cツールで試すとこちらでは正しく値が読み出せる事が確認できた。

HiPiに含まれるI2Cモジュールのソースコードを読むと、デバイスのレジスタを読み出す関数に「i2c_read_register」と「i2c_read_register_rs」の二種類があり、後者がRepeated Start Conditionが必要なデバイスに対応した関数のようだ。そこまで判ればあとは実際に試してみれば良い。

まずは現在の温度を1秒毎に読み出して表示するだけの、単純なPerlプログラムを作って試してみた。

#!/usr/bin/perl

# ADT7410 I2C Temperature Sensor Test

use strict;
use warnings;
use HiPi::BCM2835::I2C qw( :all );

my $dev = HiPi::BCM2835::I2C->new(
            peripheral => BB_I2C_PERI_1,
            address    => 0x48
        );
my @id = $dev->i2c_read_register_rs(0x0b, 1);
print sprintf("device id -> %d.%d\n", ($id[0] >> 3), $id[0] & 0x03);
my @val;
my $tmp;
while(1)
{
    @val = $dev->i2c_read_register_rs(0x00, 2);
    #$val[0] = 0xff; # check value
    #$val[1] = 0xff;
    $tmp = ($val[0] << 8 | $val[1]) >> 3;
    if($tmp >= 4096)
    {
        $tmp = ($tmp - 8192);
    }
    print "current temperature -> ",$tmp / 16,"\n";
    sleep(1);
}

モジュールがデバイスに直接アクセスするため、実行するときはsudoを付ける必要がある。
実行結果は以下のようになった。保冷剤をセンサに近づけた状態で実行したので、温度が急速に下がっているのが判る。

adt7410

今までのI2Cデバイスと同じように、Pythonで簡単に計測できるものと思っていたのだが、意外な所で引っかかった。Perlモジュールが公開されていなかったら、もっと面倒な事になっていただろう。

Raspberry Piで色々と試すのはPythonが最も適していると思っているので、Python用のライブラリでも問題無く使えるようになって欲しい。

Repeated Start

秋月電子通商でI2C接続の温度センサーモジュールが発売されたので、入手してRaspberry Piに接続してみた。小型の基板にアナログ・デバイセズ社のADT7410とプルアップ抵抗が乗っていて、ピンヘッダで端子が出ているからブレッドボードで試すには最適。
R0012030

電源と信号線をつないでi2cdetectツールでチェックすると、デバイスアドレスは正しく読み出せる。

>$ i2cdetect -y 1
0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

ところが、i2cdumpでレジスタを読み出すとこんな値になってしまう。

>$ i2cdump -y 1 0x48
No size specified (using byte-data access)
0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e    ????????????????
10: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e    ????????????????
20: 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 00    ???????????????.
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

pythonでプログラムを書いて読み出してもやはり同じ。
色々と試してみると、どうやらどのアドレスを読んでも、レジスタ$00の値が帰ってきているように思える。

どうも良く分からないのでネットで調べてみると、Raspberry Piのフォーラムに該当すると思われるスレッドがあった。I2Cで通信を行うときの手順として、レジスタにアクセスする時にRepeated Start Conditionが必要なデバイスに、smbusモジュールが対応していないのが根本的な原因らしい。

ArduinoでI2Cデバイスにアクセスする場合は、読み書きするレジスタアドレスを最初に書き込んでから次に読み書きするという、低レベルなライブラリなので問題は生じなかったが、Raspberry Piの場合はsmbusモジュールの内部でその処理が行われているので簡単には対処できない。

Raspberry Pi用のPerlモジュールHiPiはその問題に対応済みという情報があったので、インストールして試してみる事にした。インストールはここの手順に従った。

モジュールのインストールと同時にインストールされるI2Cのコマンドラインツールで再度試すと、明らかに結果が違う。ADT7410のアドレス$00と$01は計測した温度の値が返ってくるレジスタで、i2cgetで読み出すと以下のようになる。

$ i2cget -y 1 0x48 0
0x0e
$ i2cget -y 1 0x48 1
0x0e

$00と$01が同じ値になっているが、hipi-i2cで読み出すと

$ hipi-i2c r 1 0x48 0 1
14
$ hipi-i2c r 1 0x48 1 1
224

となっていて明らかに違う値だ。16進に直すと$00の値は0x0e、$01の値は0xe0になる。
温度は$00のビット6~0と、$01のビット7~3をつないだ12ビットの値を16で割った数値になるので、0x1dc(476)を16で割って29.75度という事になる。これは部屋の温度計とほぼ一致している。

以上の事からADT7410を使ってRaspberry Piで温度を計測する場合、現時点ではsmbusを使うと上手く行かない事が判った。これ以外のI2Cデバイスでも、使用できないものがいくつかあるようなので注意が必要だ。

ハードウェア的な問題では無いので、近いうちに対応されるのではないかと思う。WiringPiにもI2Cの機能があるが、そちらはどうなっているのかは未確認。WiringPi2がリリースされているのでそちらと一緒に試してみる予定。

※6/22追記
smbusモジュールがRepeated Start Conditionに非対応なのではなく、Raspberry PiのコントローラBCM2835がハードウェア的に非対応という情報があった。HiPiモジュールはコントローラの機能を使用せずに、ソフトウェア的に行っているのかも知れない。
この件については引き続き調べてみる。