Skip to content

Commit e67ea40

Browse files
authored
Merge pull request #131 from tvoverbeek/TCS34725-and-TCS3400-support
TCS34725 and TCS3400 support
2 parents 7638133 + d86a1cd commit e67ea40

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)