ストロベリー・リナックスから低価格版の照度センサとして TSL2561 のモジュールが発売されています.
これを Raspberry Pi 上の Python から使う方法を紹介します.
混乱するライブラリ
TSL2561 を使ったモジュールは秋月電子からも発売されているので,すでにいろんなライブラリがあるかと思いきや,なかなか定番がありません.
その辺の混乱ぶりは,下記の記事によく表れています.
Raspberry Pi 2でAdafruit TSL2561を正しく使うメモ
http://shimobayashi.hatenablog.com/entry/2015/07/27/001708
照度を取得するだけなのに作業内容が多く,そこで紹介されている下記のライブラリは何故かノイズを拾いやすい設定(測定期間13ms, 16倍ゲイン)を使っているという状況.
下記のライブラリはバグ持っており,通常ゲインにすると値がおかしくなります.
janheise/TSL2561
https://github.com/janheise/TSL2561
Python 用の TSL2561 ライブラリ
そこで,『Raspberry Pi で温湿度センサ HDC1050 を使う』で作成した i2cbus モジュールを使って TSL2561 を制御するライブラリを作成してみました.
こんな感じ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
import time import struct import i2cbus class TSL2561: NAME = 'TSL2561' DEV_ADDR = 0x39 # 7bit REG_CTRL = 0xC0 REG_TIMING = 0x81 REG_DATA = 0x9B REG_ID = 0x8A INTEG_13MS = 0x00 INTEG_101MS = 0x01 INTEG_402MS = 0x02 GAIN_1X = 0x00 GAIN_16X = 0x10 POWER_ON = 0x03 POWER_OFF = 0x00 gain = GAIN_1X integ = INTEG_402MS def __init__(self, bus, dev_addr=DEV_ADDR): self.bus = bus self.dev_addr = dev_addr self.i2cbus = i2cbus.I2CBus(bus) self.is_init = False def init(self): data = self.i2cbus.read(self.DEV_ADDR, 1, 0x90) data = int.from_bytes(data, byteorder='big') if (data & 0x0F) != 0x03: self.set_timing() self.enable() self.wait() self.is_init = True def enable(self): self.i2cbus.write(self.dev_addr, [self.REG_CTRL, self.POWER_ON]) def set_timing(self): value = self.gain | self.integ self.i2cbus.write(self.dev_addr, [self.REG_TIMING, value]) def set_gain(self, gain): self.gain = gain def set_integ(self, integ): self.integ = integ def wait(self): if self.integ == self.INTEG_13MS: time.sleep(0.13 + 0.2) if self.integ == self.INTEG_101MS: time.sleep(0.101 + 0.2) if self.integ == self.INTEG_402MS: time.sleep(0.402 + 0.2) def ping(self): dev_id = 0 try: value = self.i2cbus.read(self.DEV_ADDR, 1, self.REG_ID) dev_id = int.from_bytes(value, byteorder='little') except: pass return (dev_id >> 4) == 0x1 def get_value(self): if not self.is_init: self.init() value = self.i2cbus.read(self.dev_addr, 5, self.REG_DATA) ch0 = int.from_bytes(value[1:3], byteorder='little') ch1 = int.from_bytes(value[3:5], byteorder='little') if (self.gain == self.GAIN_1X): ch0 *=16 ch1 *=16 if (self.integ == self.INTEG_13MS): ch0 *= 322.0/11 ch1 *= 322.0/11 elif (self.integ == self.INTEG_101MS): ch0 *= 322.0/81 ch1 *= 322.0/81 if (ch0 == 0): return [ 0.0 ] if (ch1/ch0) <= 0.52: return [ round(0.0304*ch0 - 0.062*ch0*((ch1/ch0)**1.4), 1) ] elif (ch1/ch0) <= 0.65: return [ round(0.0224*ch0 - 0.031*ch1, 1) ] elif (ch1/ch0) <= 0.80: return [ round(0.0128*ch0 - 0.0153*ch1, 1) ] elif (ch1/ch0) <= 1.30: return [ round(0.00146*ch0 - 0.00112*ch1, 1) ] else: return [ 0.0 ]; def get_value_map(self): value = self.get_value() return { 'lux': value[0] } |
やっていることは次の通りです.
- Block Read を使って,光全体用のセンサ値と,赤外用のセンサ値の 2 つを取得.
- 積算時間の設定に基づき,データシートの下記の記述に従ってスケーリング変換.
- データシートの下記の記述に従って LUX に変換.
データシートに載っているサンプルコードは,浮動小数点を使わずに計算するために,パッと見複雑な感じになっています.実際の内容としては上記のようにシンプルです.
Python による TSL2561 の制御
TSL2561 を制御するスクリプト全体は次のようになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
#!/usr/bin/env python # -*- coding: utf-8 -*- import io import fcntl import time import struct class I2CBus: # ioctl 用 (Linux の i2c-dev.h の定義から引用) I2C_SLAVE = 0x0703 def __init__(self, bus): self.wh = io.open('/dev/i2c-' + str(bus), mode='wb', buffering=0) self.rh = io.open('/dev/i2c-' + str(bus), mode='rb', buffering=0) def write(self, dev_addr, reg_addr, param=None): fcntl.ioctl(self.wh, self.I2C_SLAVE, dev_addr) data=bytearray() data.append(reg_addr) if type(param) is list: data.extend(param) elif not param is None: data.append(param) self.wh.write(data) def read(self, dev_addr, count, reg_addr=None): fcntl.ioctl(self.wh, self.I2C_SLAVE, dev_addr) fcntl.ioctl(self.rh, self.I2C_SLAVE, dev_addr) if not reg_addr is None: self.wh.write(bytearray([reg_addr])) return self.rh.read(count) class TSL2561: NAME = 'TSL2561' DEV_ADDR = 0x39 # 7bit REG_CTRL = 0xC0 REG_TIMING = 0x81 REG_DATA = 0x9B REG_ID = 0x8A INTEG_13MS = 0x00 INTEG_101MS = 0x01 INTEG_402MS = 0x02 GAIN_1X = 0x00 GAIN_16X = 0x10 POWER_ON = 0x03 POWER_OFF = 0x00 gain = GAIN_1X integ = INTEG_402MS def __init__(self, bus, dev_addr=DEV_ADDR): self.bus = bus self.dev_addr = dev_addr self.i2cbus = i2cbus.I2CBus(bus) self.is_init = False def init(self): data = self.i2cbus.read(self.DEV_ADDR, 1, 0x90) data = int.from_bytes(data, byteorder='big') if (data & 0x0F) != 0x03: self.set_timing() self.enable() self.wait() self.is_init = True def enable(self): self.i2cbus.write(self.dev_addr, [self.REG_CTRL, self.POWER_ON]) def set_timing(self): value = self.gain | self.integ self.i2cbus.write(self.dev_addr, [self.REG_TIMING, value]) def set_gain(self, gain): self.gain = gain def set_integ(self, integ): self.integ = integ def wait(self): if self.integ == self.INTEG_13MS: time.sleep(0.13 + 0.2) if self.integ == self.INTEG_101MS: time.sleep(0.101 + 0.2) if self.integ == self.INTEG_402MS: time.sleep(0.402 + 0.2) def ping(self): dev_id = 0 try: value = self.i2cbus.read(self.DEV_ADDR, 1, self.REG_ID) dev_id = int.from_bytes(value, byteorder='little') except: pass return (dev_id >> 4) == 0x1 def get_value(self): if not self.is_init: self.init() value = self.i2cbus.read(self.dev_addr, 5, self.REG_DATA) ch0 = int.from_bytes(value[1:3], byteorder='little') ch1 = int.from_bytes(value[3:5], byteorder='little') if (self.gain == self.GAIN_1X): ch0 *=16 ch1 *=16 if (self.integ == self.INTEG_13MS): ch0 *= 322.0/11 ch1 *= 322.0/11 elif (self.integ == self.INTEG_101MS): ch0 *= 322.0/81 ch1 *= 322.0/81 if (ch0 == 0): return [ 0.0 ] if (ch1/ch0) <= 0.52: return [ round(0.0304*ch0 - 0.062*ch0*((ch1/ch0)**1.4), 1) ] elif (ch1/ch0) <= 0.65: return [ round(0.0224*ch0 - 0.031*ch1, 1) ] elif (ch1/ch0) <= 0.80: return [ round(0.0128*ch0 - 0.0153*ch1, 1) ] elif (ch1/ch0) <= 1.30: return [ round(0.00146*ch0 - 0.00112*ch1, 1) ] else: return [ 0.0 ]; def get_value_map(self): value = self.get_value() return { 'lux': value[0] } I2C_BUS = 0x1 # Raspberry Pi tsl2561 = TSL2561(I2C_BUS) print(tsl2561.get_value_map()) |
これを tsl2561.py
という名前で保存して実行すると次のように,LUX が出力されます.
1 2 |
$ python tsl2561.py LUX: 69 |
ライブラリ化
以上のコードをライブラリ化したものを下記に置いてます.
https://github.com/kimata/rasp-python
コメント
貴重な情報を提供いただきありがとうございます。raspberryPi3にjessieをセットアップしpython3で計測関係を学び始めましたが、tsl2561によるLUXの計測で困っています。次のようなエラー表示が出ます。何かヒントになるようなことがあればご教示くださいますようお願い申し上げます。
Traceback (most recent call last):
File “/home/pi/tsl2561.py”, line 127, in
print(‘LUX: %d’ % (tsl2561.get_lux()))
File “/home/pi/tsl2561.py”, line 111, in get_lux
if (ch1/ch0) <= 0.52:
ZeroDivisionError: float division by zero
TSL2561 との通信がうまくいっていないのが原因と思われます.
簡易オシロスコープ等で通信ラインの波形を確認されると解析は進むと思います.
TSL2561はもう廃番のようで今更ですが、手元に残っていたので使いたく、いろいろなページをさまよってこちらに来ました。スクリプトが大変参考になりました。ありがとうございました。
最後のスクリプトの62行目、その上のスクリプトは31行目の
self.i2cbus = i2cbus.I2CBus(bus) は
self.i2cbus = I2CBus(bus) でないとエラーが出ますね。
新しい環境だとそうなるのかな?一応、報告しておきます。
コメントありがとうございます.
I2CBus クラスを別ファイルにしてimport,の形を取らず同一ファイルに記載するとそのようになると思います.