秋月電子通商の温度センサーモジュールが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までは正常に読み書きできているようだ。ただしこれは今現在の話で、将来的にモジュールのバージョンが上がった場合にどうなるかは判らないので要注意。
どうも RaspberryPIの I2Cはリスタートコンディションを正しく設定できないようです.
これはそもそもドライバの作りとしてそうなっている,ということと,
未検証ですが I2Cの IPに HW的なエラッタがあるという情報もあります.
# HiZにするタイミングが (I2Cの) 1クロック時間ズレている,らしい.
それ故にドライバがそのような作りになっているとも考えられます.
その結果アドレッシング後にストップコンディションを送ってしまうので,
デバイスによっては設定したアドレスを失ってしまい
読み出しは必ず 0x00から始まることになります.
i2cdumpの値が全て同じになるのは 1バイトずつ読み込もうとしているからですね.
ブロックで読み出した場合はデバイス内部の自動再アドレッシングに従って
アドレスが変化していくので 0x00以降も読み出すことが出来ます.
しかし,これはデバイスによりますが,
アドレスはリード毎に必ず 1インクリメントされる,というわけではなく,
たとえば今回のケースだと 0x03をリードすると
アドレスは 0x00に設定される,といったデバイスがあります.
# 仕様書に記載があると思います.
これには逐一アドレスを指定せずにだらだらとリードを続けていても
温度をとり続けることが出来る,というメリットがありますが,
こういったデバイスでは (現時点での自分の環境下では)
0x04以降を読み出す手段はありません.
ライトに関してはアドレッシングと同時にデータを送り込めるので,
上記のような制約はありません.
Pythonではどうやってもダメだったんですが、PerlのHiPiモジュールでは全てのレジスタにアクセスできました。モジュールの中身までは読んで無いので、何が違うのか判りませんがGPIOを直叩きしているのかも知れません。
そのへんの話はこちらに→https://raspberrylife.wordpress.com/2013/06/24/temperature-sensor2/
おっと失礼しました.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してユーザーランドから叩いている & レジスタをベタにポーリングで眺めているからかもしれませんね.
> 単発の hipi_i2c_read / hipi_i2c_writeに仕込んでおけば
あぁ,ユーザーランドドライバだからダメか.hipi_i2c_read_register_rs のほうがよいですね..