Skip to content

Commit d86a1cd

Browse files
committed
TCS34725 and TCS3400 support
Detects the used sensor. so the same code supports both. TCS3400 is used for the Sense Hat V2 while the (discontinued) TCS34725 is used on the AstroPi sense hat for the ISS. ALso a bug fix for the computation of the red_raw, green_raw, blue_raw and clear_raw values. The original code ony fetched the LSB.
1 parent 7638133 commit d86a1cd

3 files changed

Lines changed: 70 additions & 40 deletions

File tree

sense_hat/colour.py

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""
2-
Python library for the TCS34725 Color Sensor
2+
Python library for the TCS3472x and TCS340x Color Sensors
33
Documentation (including datasheet): https://ams.com/tcs34725#tab/documents
4+
https://ams.com/tcs3400#tab/documents
5+
The sense hat for AstroPi on the ISS uses the TCS34725.
6+
The sense hat v2 uses the TCS3400 the successor of the TCS34725.
7+
The TCS34725 is not available any more. It was discontinued by ams in 2021.
48
"""
59

610
from time import sleep
@@ -11,25 +15,22 @@
1115
class HardwareInterface:
1216
"""
1317
`HardwareInterface` is the abstract class that sits between the
14-
`ColourSensor` class (providing the TCS34725 sensor API) and the
15-
actual hardware. Using this intermediate layer of abstraction, a
16-
`ColourSensor` object interacts with the hardware without being
18+
`ColourSensor` class (providing the TCS34725/TCS3400 sensor API)
19+
and the actual hardware. Using this intermediate layer of abstraction,
20+
a `ColourSensor` object interacts with the hardware without being
1721
aware of how this interaction is implemented.
1822
Different subclasses of the `HardwareInterface` class can provide
1923
access to the hardware through e.g. I2C, `libiio` and its system
2024
files or even a hardware emulator.
2125
"""
2226

23-
GAIN_VALUES = (1, 4, 16, 60)
24-
CLOCK_STEP = 0.0024 # the clock step is 2.4ms
25-
2627
@staticmethod
2728
def max_value(integration_cycles):
2829
"""
2930
The maximum raw value for the RBGC channels depends on the number
3031
of integration cycles.
3132
"""
32-
return 2**16 if integration_cycles >= 64 else 1024*integration_cycles
33+
return 65535 if integration_cycles >= 64 else 1024*integration_cycles
3334

3435
def get_enabled(self):
3536
"""
@@ -123,36 +124,34 @@ def _raw_wrapper(register):
123124
fashion. This is a factory function that implements this retrieval method.
124125
"""
125126
def get_raw_register(self):
126-
return self._read(register)
127+
block = self.bus.read_i2c_block_data(self.ADDR, register, 2)
128+
return (block[0] + (block[1] << 8))
127129
return get_raw_register
128130

129131
class I2C(HardwareInterface):
130132
"""
131-
An implementation of the `HardwareInterface` for the TCS34725 sensor
132-
that uses I2C to control the sensor and retrieve measurements.
133-
Use the datasheet as a reference: https://ams.com/tcs34725#tab/documents
133+
An implementation of the `HardwareInterface` for the TCS34725/TCS3400
134+
sensor that uses I2C to control the sensor and retrieve measurements.
135+
Use the datasheets as a reference.
134136
"""
135137

136138
# device-specific constants
137139
BUS = 1
138-
ADDR = 0x29
139-
140-
COMMAND_BIT = 0x80
141140

142141
# control registers
143-
ENABLE = 0x00 | COMMAND_BIT
144-
ATIME = 0x01 | COMMAND_BIT
145-
CONTROL = 0x0F | COMMAND_BIT
146-
ID = 0x12 | COMMAND_BIT
147-
STATUS = 0x13 | COMMAND_BIT
142+
ENABLE = 0x80
143+
ATIME = 0x81
144+
CONTROL = 0x8F
145+
ID = 0x92
146+
STATUS = 0x93
148147
# (if a register is described in the datasheet but missing here
149148
# it means the corresponding functionality is not provided)
150149

151150
# data registers
152-
CDATA = 0x14 | COMMAND_BIT
153-
RDATA = 0x16 | COMMAND_BIT
154-
GDATA = 0x18 | COMMAND_BIT
155-
BDATA = 0x1A | COMMAND_BIT
151+
CDATA = 0x94
152+
RDATA = 0x96
153+
GDATA = 0x98
154+
BDATA = 0x9A
156155

157156
# bit positions
158157
OFF = 0x00
@@ -162,9 +161,13 @@ class I2C(HardwareInterface):
162161
AVALID = 0x01
163162

164163
GAIN_REG_VALUES = (0x00, 0x01, 0x02, 0x03)
165-
# map gain values to register values and vice-versa
166-
GAIN_TO_REG = dict(zip(HardwareInterface.GAIN_VALUES, GAIN_REG_VALUES))
167-
REG_TO_GAIN = dict(zip(GAIN_REG_VALUES, HardwareInterface.GAIN_VALUES))
164+
# Assume TCS34725 as on the ISS AstroPi
165+
# Adjust for TCS3400 after the detection of the sensor type.
166+
ADDR = 0x29
167+
GAIN_VALUES = (1, 4, 16, 60)
168+
CLOCK_STEP = 0.0024 # 2.4ms
169+
GAIN_TO_REG = dict(zip(GAIN_VALUES, GAIN_REG_VALUES))
170+
REG_TO_GAIN = dict(zip(GAIN_REG_VALUES, GAIN_VALUES))
168171

169172
def __init__(self):
170173

@@ -176,14 +179,43 @@ def __init__(self):
176179
except Exception as e:
177180
explanation = "(I2C is not enabled)" if not self.i2c_enabled() else ""
178181
raise ColourSensorInitialisationError(explanation=explanation) from e
182+
183+
# Test for sensor at I2C addresses 0x29 or 0x39
184+
# Both sensors have variants at 0x29 and 0x39 (See data sheets)
185+
addr1 = addr2 = False
179186
try:
187+
self.bus.write_quick(0x29)
188+
addr1 = True
189+
except:
190+
pass
191+
try:
192+
self.bus.write_quick(0x39)
193+
addr2 = True
194+
except:
195+
pass
196+
197+
if addr2:
198+
self.ADDR = 0x39
199+
if addr1 or addr2:
200+
# get sensor id
180201
id = self._read(self.ID)
181-
except Exception as e:
182-
explanation = "(sensor not present)"
183-
raise ColourSensorInitialisationError(explanation=explanation) from e
184-
if id != 0x44:
185-
explanation = f" (different device id detected: {id})"
186-
raise ColourSensorInitialisationError(explanation=explanation) from e
202+
if (id & 0xf8) == 0x90:
203+
sensor = 'TCS340x'
204+
elif (id & 0xf4) == 0x44:
205+
sensor = 'TCS3472x'
206+
else:
207+
explanation = "(Sensor not present)"
208+
raise ColourSensorInitialisationError(explanation=explanation)
209+
210+
# Set type specific constants
211+
# Assume TCS3472x as in AstroPi
212+
sensor == 'TCS3472x'
213+
if sensor == 'TCS340x':
214+
self.GAIN_VALUES = (1, 4, 16, 64)
215+
self.CLOCK_STEP = 0.00275 # 2.75ms
216+
self.GAIN_TO_REG = dict(zip(self.GAIN_VALUES, self.GAIN_REG_VALUES))
217+
self.REG_TO_GAIN = dict(zip(self.GAIN_REG_VALUES, self.GAIN_VALUES))
218+
187219
@staticmethod
188220
def i2c_enabled():
189221
"""Returns True if I2C is enabled or False otherwise."""
@@ -192,14 +224,14 @@ def i2c_enabled():
192224
def _read(self, attribute):
193225
"""
194226
Read and return the value of a specific register (`attribute`) of the
195-
TCS34725 colour sensor.
227+
TCS34725/TCS3400 colour sensor.
196228
"""
197229
return self.bus.read_byte_data(self.ADDR, attribute)
198230

199231
def _write(self, attribute, value):
200232
"""
201233
Write a value in a specific register (`attribute`) of the
202-
TCS34725 colour sensor.
234+
TCS34725/TCS3400 colour sensor.
203235
"""
204236
self.bus.write_byte_data(self.ADDR, attribute, value)
205237

sense_hat/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def __init__(self, **kwargs):
1111

1212

1313
class ColourSensorInitialisationError(SenseHatException):
14-
fmt = "Failed to initialise TCS34725 colour sensor. {explanation}"
14+
fmt = "Failed to initialise colour sensor. {explanation}"
1515

1616

1717
class InvalidGainError(SenseHatException):

sense_hat/sense_hat.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,8 @@ def stick(self):
208208
def colour(self):
209209
try:
210210
return self._colour
211-
except AttributeError as e:
212-
raise ColourSensorInitialisationError(
213-
explanation="This Sense HAT" +
214-
" does not have a color sensor") from e
211+
except AttributeError:
212+
print('This Sense Hat does not have a colour sensor')
215213

216214
color = colour
217215

0 commit comments

Comments
 (0)