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(3)」への4件のフィードバック

  1. どうも RaspberryPIの I2Cはリスタートコンディションを正しく設定できないようです.
    これはそもそもドライバの作りとしてそうなっている,ということと,
    未検証ですが I2Cの IPに HW的なエラッタがあるという情報もあります.
    # HiZにするタイミングが (I2Cの) 1クロック時間ズレている,らしい.
    それ故にドライバがそのような作りになっているとも考えられます.

    その結果アドレッシング後にストップコンディションを送ってしまうので,
    デバイスによっては設定したアドレスを失ってしまい
    読み出しは必ず 0x00から始まることになります.

    i2cdumpの値が全て同じになるのは 1バイトずつ読み込もうとしているからですね.
    ブロックで読み出した場合はデバイス内部の自動再アドレッシングに従って
    アドレスが変化していくので 0x00以降も読み出すことが出来ます.

    しかし,これはデバイスによりますが,
    アドレスはリード毎に必ず 1インクリメントされる,というわけではなく,
    たとえば今回のケースだと 0x03をリードすると
    アドレスは 0x00に設定される,といったデバイスがあります.
    # 仕様書に記載があると思います.

    これには逐一アドレスを指定せずにだらだらとリードを続けていても
    温度をとり続けることが出来る,というメリットがありますが,
    こういったデバイスでは (現時点での自分の環境下では)
    0x04以降を読み出す手段はありません.

    ライトに関してはアドレッシングと同時にデータを送り込めるので,
    上記のような制約はありません.

  2. おっと失礼しました.RaspberryPIは新参で,「pythonでの smbus」を調べていたので他のページを確認もせず早とちりしました.折角なのでご教示いただいた HiPiをざっと見てみましたが,GPIOなどは使わず BSCだけでなんとかしていました.
    https://code.google.com/p/hipi-perl-raspberrypi/source/browse/trunk/BCM2835.xs

    そもそものエラッタが
    > Suggested software workaround to reduce the impact of this hardware bug: When a transfer is aborted due to a “NAK” for a byte, issue a single extra clock cycle before the STOP to synchronize the slave. This probably has to be done in software.
    ということで
    // Wait for write to complete and first byte back.
    bcm2835_delayMicroseconds(i2c_byte_wait_us * 3);
    を呼ぶことによって回避しているようです.ただわざわざhipi_i2c_read_register_rs なんてモノを作っていますが,単発の hipi_i2c_read / hipi_i2c_writeに仕込んでおけば済む話な気もしますね.

    ネイティブのドライバとの比較をしたわけではありませんが,これならカーネルドライバもそのように作ればちゃんと動くハズです.遅いのは mmapしてユーザーランドから叩いている & レジスタをベタにポーリングで眺めているからかもしれませんね.

  3. > 単発の hipi_i2c_read / hipi_i2c_writeに仕込んでおけば
    あぁ,ユーザーランドドライバだからダメか.hipi_i2c_read_register_rs のほうがよいですね..

tokoya への返信 コメントをキャンセル