Skip to content

Commit 03003e9

Browse files
committed
Class to interact with the TSL2561.
1 parent 9ff733d commit 03003e9

File tree

2 files changed

+295
-0
lines changed

2 files changed

+295
-0
lines changed

Adafruit_TSL2561/Adafruit_I2C.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../Adafruit_I2C/Adafruit_I2C.py

Adafruit_TSL2561/Adafruit_TSL2561.py

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
#!/usr/bin/python
2+
3+
import time
4+
import warnings
5+
from Adafruit_I2C import Adafruit_I2C
6+
7+
def _sleepms(ms):
8+
"""A wrapper around time.sleep that tries to be as accurate as possible.
9+
May still overshoot the requested sleep time, depending on the system's
10+
clock accuracy."""
11+
# Experience shows that adding an extra millisecond is sometimes necessary,
12+
# even with the sleep loop below.
13+
target = time.time() + (ms + 1) / 1000.0
14+
while time.time() < target:
15+
time.sleep(target - time.time())
16+
17+
class SensorSaturatedException(Exception):
18+
"""Used when the sensor is saturated and cannot provide accurate readings."""
19+
def __init__(self, channel):
20+
if type(channel) == int:
21+
self.msg('Channel {0} is saturated.'.format(channel))
22+
else:
23+
self.msg('Both channels are saturated.')
24+
25+
26+
class _integrationTime:
27+
_DATA = {
28+
# time bits scale max
29+
13: [ 13.7, 0x00, 11.0/322.0, 5047],
30+
101: [101, 0x01, 81.0/322.0, 37177],
31+
402: [402, 0x02, 322.0/322.0, 65535],
32+
}
33+
def __init__(self, time):
34+
if not time in _integrationTime._DATA.keys():
35+
raise IndexError, 'Incorrect integration time: {0}; must be one of {1}'.format(time, sorted(_integrationTime._DATA.keys()))
36+
self.key = time
37+
self.time = _integrationTime._DATA[time][0]
38+
self.bits = _integrationTime._DATA[time][1]
39+
self.scale = _integrationTime._DATA[time][2]
40+
self.max = _integrationTime._DATA[time][3]
41+
42+
class TSL2561(object):
43+
"""Main class for interacting with a TSL2561 light sensor.
44+
45+
The constructor takes several named parameters, all optional:
46+
* address - The I2C address of the TSL2561 chipset. The class defines three
47+
constants for the possible chipset addresses:
48+
* ADDR_LOW - The address used when the ADDR SEL terminal is connected to
49+
ground.
50+
* ADDR_FLOAT - The address used when the ADDR SEL terminal is not
51+
connected to anything. This is the default address.
52+
* ADDR_HIGH - The address used when the ADDR SEL terminal is connected to
53+
V_DD (the bus voltage).
54+
* mode - The mode of operation for the lux readings. The class defines two
55+
constants for the two possible operating modes:
56+
* MODE_CONTINUOUS - The sensor is activated when the TSL2561 object is
57+
initialized. The `lux` property will return immediately with whatever
58+
the sensor's current reading is, but that reading is only updated at
59+
intervals specified by the `integrationTime` parameter. This mode is ideal
60+
for relatively continuous readings. This is the default mode.
61+
* MODE_SINGLE - The sensor is normally off. Every time the `lux` property is
62+
read, the sensor is turned on, the program sleeps for `integrationTime`
63+
milliseconds, the sensor data is read, the sensor is turned off, and the
64+
lux value is returned. This mode is ideal if sensor readings are only
65+
needed occasionally and you don't want the sensor drawing current all the
66+
time.
67+
* gain - The gain applied to sensor readings. See the `gain` class property
68+
for more information and acceptable values. Defaults to 1.
69+
* integrationTime - The integration time in milliseconds for a sensor
70+
reading. See the `integrationTime` class property for more information
71+
and acceptable values. Defaults to 402.
72+
* package - A string indicating which TSL2561 package is in use. Takes one
73+
of two values:
74+
* 'T' - TMB package. This is the default.
75+
* 'CS' - Chipscale package.
76+
* debug - Controls whether additional debugging information is printed as
77+
the class performs its functions. Defaults to False.
78+
79+
To use the class, simply instantiate it and then check the `lux` parameter
80+
for sensor readings. The `gain` and `integrationTime` properties may be
81+
used to alter the sensor's parameters between readings.
82+
83+
Manual integration intervals are not currently supported.
84+
Interrupts are not currently supported.
85+
"""
86+
87+
# Address
88+
ADDR_LOW = 0x29
89+
ADDR_FLOAT = 0x39 # Default address (pin left floating)
90+
ADDR_HIGH = 0x49
91+
92+
# Operating modes
93+
MODE_CONTINUOUS = 1
94+
MODE_SINGLE = 2
95+
_MODES = set([MODE_CONTINUOUS, MODE_SINGLE])
96+
97+
_REG_MOD = {
98+
'COMMAND': 0x80, # Must be 1
99+
'CLEAR': 0x40, # Clears any pending interrupt (write 1 to clear)
100+
'WORD': 0x20, # 1 = read/write word (rather than byte)
101+
'BLOCK': 0x10, # 1 = using block read/write
102+
}
103+
104+
_REGISTER = {
105+
'CONTROL': 0x00,
106+
'TIMING': 0x01,
107+
'THRESHHOLDL': 0x02, # 16-bit
108+
'THRESHHOLDH': 0x04, # 16-bit
109+
'INTERRUPT': 0x06,
110+
'CRC': 0x08, # Not for end use.
111+
'ID': 0x0A,
112+
'CHAN0': 0x0C, # 16-bit
113+
'CHAN1': 0x0E, # 16-bit
114+
}
115+
116+
_CONTROL = {
117+
'ON': 0x03,
118+
'OFF': 0x00,
119+
}
120+
121+
_GAIN = {
122+
1: 0x00,
123+
16: 0x10,
124+
}
125+
126+
# For calculating lux.
127+
# General formula is: B * CH0 - M1 * CH1 * (CH1/CH0)^M2
128+
# "K" is the ratio of CH1/CH0 and determines which set of coefficients are
129+
# used.
130+
# Coefficients are from the "Calculating Lux" section of the datasheet.
131+
# The TMB and Chipscale packages have different coeffecients. At
132+
# instantiation time, the dicts here are replaced by the appropriate
133+
# component lists.
134+
_K = {
135+
'T': [0.500, 0.610, 0.800, 1.300],
136+
'CS': [0.520, 0.650, 0.800, 1.300],
137+
}
138+
_B = {
139+
'T': [0.0304, 0.0224, 0.0128, 0.00146, 0.0],
140+
'CS': [0.0315, 0.0229, 0.0157, 0.00338, 0.0],
141+
}
142+
_M1 = {
143+
'T': [0.0620, 0.0310, 0.0153, 0.00112, 0.0],
144+
'CS': [0.0593, 0.0291, 0.0180, 0.00260, 0.0],
145+
}
146+
_M2 = {
147+
'T': [1.4, 0.0, 0.0, 0.0, 0.0],
148+
'CS': [1.4, 0.0, 0.0, 0.0, 0.0],
149+
}
150+
151+
_gain = 1
152+
_integrationTime = _integrationTime(402)
153+
154+
def __init__(self, address=ADDR_FLOAT, mode=MODE_CONTINUOUS, gain=_gain, integrationTime=_integrationTime.key, package='T', debug=False):
155+
if mode not in self._MODES:
156+
raise ValueError('Incorrect value passed as operating mode.')
157+
if not address in [TSL2561.ADDR_LOW, TSL2561.ADDR_FLOAT, TSL2561.ADDR_HIGH]:
158+
addr_msg = '0x{0:x} is not a standard TSL2561 address; continuing anyway'.format(address)
159+
warnings.warn(addr_msg)
160+
self.debug = debug
161+
self._i2c = Adafruit_I2C(address, debug=debug)
162+
id = self._i2c.readU8(TSL2561._REGISTER['ID'])
163+
if id < 0 or not id & 0x0A:
164+
raise EnvironmentError, 'Device at 0x{0:x} does not appear to be a TSL2561.'.format(address)
165+
166+
self.mode = mode
167+
if self.mode == TSL2561.MODE_CONTINUOUS:
168+
self._poweron()
169+
170+
self.gain = gain
171+
self.integrationTime = integrationTime
172+
self._K = self._K[package]
173+
self._B = self._B[package]
174+
self._M1 = self._M1[package]
175+
self._M2 = self._M2[package]
176+
177+
@property
178+
def gain(self):
179+
"""The gain applied to sensor readings.
180+
181+
Valid values are 1 and 16. In low-light conditions, a 16x gain will
182+
provide more accurate results. In bright-light conditions, 1x gain
183+
should be used, as 16x might oversaturate the sensor.
184+
"""
185+
return self._gain
186+
187+
@gain.setter
188+
def gain(self, value):
189+
if not value in TSL2561._GAIN.keys():
190+
raise IndexError, 'Incorrect gain: {0}; must be one of {1}'.format(value, sorted(TSL2561._GAIN.keys()))
191+
self._gain = value
192+
self._setTiming()
193+
194+
@property
195+
def integrationTime(self):
196+
"""The amount of time, in milliseconds, to spend collecting data for each reading.
197+
198+
Valid values are 13, 101, and 402. Lower values return more quickly,
199+
but are less accurate, especially in low light conditions. 402ms is the
200+
standard setting.
201+
"""
202+
return self._integrationTime.key
203+
204+
@integrationTime.setter
205+
def integrationTime(self, value):
206+
self._integrationTime = _integrationTime(value)
207+
self._setTiming()
208+
209+
@property
210+
def lux(self):
211+
"""The current sensor reading in Lux.
212+
213+
If the operating mode is MODE_SINGLE, this property will take
214+
`integrationTime` milliseconds before it returns. If the operating mode
215+
is MODE_CONTINUOUS, the property will return immediately, regardless of
216+
whether the sensor has completed a new cycle in the time since the last
217+
reading.
218+
"""
219+
(chan0, chan1) = self._getData()
220+
221+
chscale = self._integrationTime.scale * self.gain
222+
schan0 = chan0 / chscale
223+
schan1 = chan1 / chscale
224+
225+
if schan0 != 0:
226+
ratio = schan1 / schan0
227+
else:
228+
ratio = 0
229+
if self.debug:
230+
print 'ratio: {0}'.format(ratio)
231+
idx = self._getRatioIdx(ratio)
232+
if self.debug:
233+
print 'ratio index: {0}'.format(idx)
234+
235+
# Calculate
236+
if self._M2[idx] > 0:
237+
lux = self._B[idx] * schan0 - self._M1[idx] * schan0 * ratio**self._M2[idx]
238+
else:
239+
lux = self._B[idx] * schan0 - self._M1[idx] * schan1
240+
241+
# Minimum value of zero
242+
if lux < 0:
243+
lux = 0
244+
245+
return lux
246+
247+
def _poweron(self):
248+
self._i2c.write8(
249+
TSL2561._REGISTER['CONTROL'] | TSL2561._REG_MOD['COMMAND'],
250+
TSL2561._CONTROL['ON'])
251+
if not self._i2c.readU8(TSL2561._REGISTER['CONTROL'] | TSL2561._REG_MOD['COMMAND']) & 0x03 == 0x03:
252+
raise EnvironmentError, 'TSL2561 did not power on correctly.'.format(address)
253+
254+
def _poweroff(self):
255+
self._i2c.write8(
256+
TSL2561._REGISTER['CONTROL'] | TSL2561._REG_MOD['COMMAND'],
257+
TSL2561._CONTROL['OFF'])
258+
259+
def _setTiming(self):
260+
if self.mode == TSL2561.MODE_SINGLE:
261+
self._poweron()
262+
self._i2c.write8(
263+
TSL2561._REGISTER['TIMING'] | TSL2561._REG_MOD['COMMAND'],
264+
TSL2561._GAIN[self.gain] | self._integrationTime.bits)
265+
if self.mode == TSL2561.MODE_SINGLE:
266+
self._poweroff()
267+
268+
def _getData(self):
269+
if self.mode == TSL2561.MODE_SINGLE:
270+
self._poweron()
271+
_sleepms(self._integrationTime.time)
272+
chan0 = self._i2c.reverseByteOrder(self._i2c.readU16(TSL2561._REGISTER['CHAN0'] | TSL2561._REG_MOD['COMMAND'] | TSL2561._REG_MOD['WORD']))
273+
chan1 = self._i2c.reverseByteOrder(self._i2c.readU16(TSL2561._REGISTER['CHAN1'] | TSL2561._REG_MOD['COMMAND'] | TSL2561._REG_MOD['WORD']))
274+
if self.mode == TSL2561.MODE_SINGLE:
275+
self._poweroff()
276+
277+
if self.debug:
278+
print 'chan0: 0x{0:x}, chan1: 0x{1:x}'.format(chan0, chan1)
279+
280+
if chan0 >= self._integrationTime.max and chan1 >= self._integrationTime.max:
281+
raise SensorSaturatedException('both')
282+
elif chan0 >= self._integrationTime.max:
283+
raise SensorSaturatedException(0)
284+
elif chan1 >= self._integrationTime.max:
285+
raise SensorSaturatedException(1)
286+
287+
return (chan0, chan1)
288+
289+
def _getRatioIdx(self, ratio):
290+
for i in xrange(len(self._K)):
291+
if ratio < self._K[i]:
292+
return i
293+
return len(self._K)
294+

0 commit comments

Comments
 (0)