adafruit-circuitpython-tcs3448 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adafruit_circuitpython_tcs3448-1.0.0.dist-info/METADATA +166 -0
- adafruit_circuitpython_tcs3448-1.0.0.dist-info/RECORD +6 -0
- adafruit_circuitpython_tcs3448-1.0.0.dist-info/WHEEL +5 -0
- adafruit_circuitpython_tcs3448-1.0.0.dist-info/licenses/LICENSE +21 -0
- adafruit_circuitpython_tcs3448-1.0.0.dist-info/top_level.txt +1 -0
- adafruit_tcs3448.py +720 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adafruit-circuitpython-tcs3448
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CircuitPython driver for the Adafruit TCS3448 14-Channel Light / Color Sensor Breakout - STEMMA QT / Qwiic
|
|
5
|
+
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adafruit/Adafruit_CircuitPython_TCS3448
|
|
8
|
+
Keywords: adafruit,blinka,circuitpython,micropython,tcs3448,light,,lux,,color
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
12
|
+
Classifier: Topic :: System :: Hardware
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Description-Content-Type: text/x-rst
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: Adafruit-Blinka
|
|
17
|
+
Requires-Dist: adafruit-circuitpython-busdevice
|
|
18
|
+
Requires-Dist: adafruit-circuitpython-register
|
|
19
|
+
Provides-Extra: optional
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
Introduction
|
|
23
|
+
============
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
.. image:: https://readthedocs.org/projects/adafruit-circuitpython-tcs3448/badge/?version=latest
|
|
27
|
+
:target: https://docs.circuitpython.org/projects/tcs3448/en/latest/
|
|
28
|
+
:alt: Documentation Status
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg
|
|
32
|
+
:target: https://adafru.it/discord
|
|
33
|
+
:alt: Discord
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
.. image:: https://github.com/adafruit/Adafruit_CircuitPython_TCS3448/workflows/Build%20CI/badge.svg
|
|
37
|
+
:target: https://github.com/adafruit/Adafruit_CircuitPython_TCS3448/actions
|
|
38
|
+
:alt: Build Status
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
42
|
+
:target: https://github.com/astral-sh/ruff
|
|
43
|
+
:alt: Code Style: Ruff
|
|
44
|
+
|
|
45
|
+
CircuitPython driver for the Adafruit TCS3448 14-Channel Light / Color Sensor Breakout - STEMMA QT / Qwiic
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Dependencies
|
|
49
|
+
=============
|
|
50
|
+
This driver depends on:
|
|
51
|
+
|
|
52
|
+
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
|
|
53
|
+
* `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_
|
|
54
|
+
* `Register <https://github.com/adafruit/Adafruit_CircuitPython_Register>`_
|
|
55
|
+
|
|
56
|
+
Please ensure all dependencies are available on the CircuitPython filesystem.
|
|
57
|
+
This is easily achieved by downloading
|
|
58
|
+
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
|
|
59
|
+
or individual libraries can be installed using
|
|
60
|
+
`circup <https://github.com/adafruit/circup>`_.
|
|
61
|
+
|
|
62
|
+
`Purchase one from the Adafruit shop <http://www.adafruit.com/products/6525>`_
|
|
63
|
+
|
|
64
|
+
Installing from PyPI
|
|
65
|
+
=====================
|
|
66
|
+
|
|
67
|
+
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
|
|
68
|
+
PyPI <https://pypi.org/project/adafruit-circuitpython-tcs3448/>`_.
|
|
69
|
+
To install for current user:
|
|
70
|
+
|
|
71
|
+
.. code-block:: shell
|
|
72
|
+
|
|
73
|
+
pip3 install adafruit-circuitpython-tcs3448
|
|
74
|
+
|
|
75
|
+
To install system-wide (this may be required in some cases):
|
|
76
|
+
|
|
77
|
+
.. code-block:: shell
|
|
78
|
+
|
|
79
|
+
sudo pip3 install adafruit-circuitpython-tcs3448
|
|
80
|
+
|
|
81
|
+
To install in a virtual environment in your current project:
|
|
82
|
+
|
|
83
|
+
.. code-block:: shell
|
|
84
|
+
|
|
85
|
+
mkdir project-name && cd project-name
|
|
86
|
+
python3 -m venv .venv
|
|
87
|
+
source .env/bin/activate
|
|
88
|
+
pip3 install adafruit-circuitpython-tcs3448
|
|
89
|
+
|
|
90
|
+
Installing to a Connected CircuitPython Device with Circup
|
|
91
|
+
==========================================================
|
|
92
|
+
|
|
93
|
+
Make sure that you have ``circup`` installed in your Python environment.
|
|
94
|
+
Install it with the following command if necessary:
|
|
95
|
+
|
|
96
|
+
.. code-block:: shell
|
|
97
|
+
|
|
98
|
+
pip3 install circup
|
|
99
|
+
|
|
100
|
+
With ``circup`` installed and your CircuitPython device connected use the
|
|
101
|
+
following command to install:
|
|
102
|
+
|
|
103
|
+
.. code-block:: shell
|
|
104
|
+
|
|
105
|
+
circup install adafruit_tcs3448
|
|
106
|
+
|
|
107
|
+
Or the following command to update an existing version:
|
|
108
|
+
|
|
109
|
+
.. code-block:: shell
|
|
110
|
+
|
|
111
|
+
circup update
|
|
112
|
+
|
|
113
|
+
Usage Example
|
|
114
|
+
=============
|
|
115
|
+
|
|
116
|
+
.. code-block:: python
|
|
117
|
+
|
|
118
|
+
import time
|
|
119
|
+
import board
|
|
120
|
+
from adafruit_tcs3448 import TCS3448
|
|
121
|
+
|
|
122
|
+
i2c = board.I2C()
|
|
123
|
+
sensor = TCS3448(i2c)
|
|
124
|
+
|
|
125
|
+
CHANNEL_LABELS = [
|
|
126
|
+
"FZ (450nm blue)",
|
|
127
|
+
"FY (560nm yellow-green)",
|
|
128
|
+
"FXL (596nm orange)",
|
|
129
|
+
"NIR (855nm near-IR)",
|
|
130
|
+
"VIS_TL_0 (clear top-left, cycle 1)",
|
|
131
|
+
"VIS_BR_0 (clear btm-right, cycle 1)",
|
|
132
|
+
"F2 (424nm violet-blue)",
|
|
133
|
+
"F3 (473nm blue-cyan)",
|
|
134
|
+
"F4 (516nm green)",
|
|
135
|
+
"F6 (636nm red)",
|
|
136
|
+
"VIS_TL_1 (clear top-left, cycle 2)",
|
|
137
|
+
"VIS_BR_1 (clear btm-right, cycle 2)",
|
|
138
|
+
"F1 (407nm violet)",
|
|
139
|
+
"F7 (687nm deep red)",
|
|
140
|
+
"F8 (748nm near-IR edge)",
|
|
141
|
+
"F5 (546nm green-yellow)",
|
|
142
|
+
"VIS_TL_2 (clear top-left, cycle 3)",
|
|
143
|
+
"VIS_BR_2 (clear btm-right, cycle 3)",
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
while True:
|
|
147
|
+
readings = sensor.all_channels
|
|
148
|
+
print("--- TCS3448 Channel Readings ---")
|
|
149
|
+
for label, value in zip(CHANNEL_LABELS, readings):
|
|
150
|
+
print(f" {label}: {value}")
|
|
151
|
+
print()
|
|
152
|
+
time.sleep(1)
|
|
153
|
+
|
|
154
|
+
Documentation
|
|
155
|
+
=============
|
|
156
|
+
API documentation for this library can be found on `Read the Docs <https://docs.circuitpython.org/projects/tcs3448/en/latest/>`_.
|
|
157
|
+
|
|
158
|
+
For information on building library documentation, please check out
|
|
159
|
+
`this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
|
|
160
|
+
|
|
161
|
+
Contributing
|
|
162
|
+
============
|
|
163
|
+
|
|
164
|
+
Contributions are welcome! Please read our `Code of Conduct
|
|
165
|
+
<https://github.com/adafruit/Adafruit_CircuitPython_TCS3448/blob/HEAD/CODE_OF_CONDUCT.md>`_
|
|
166
|
+
before contributing to help this project stay welcoming.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
adafruit_tcs3448.py,sha256=JCClG_CbKt3SqQn3d1hs2o1Y7IcLP_fSsOfa_45ir-g,24891
|
|
2
|
+
adafruit_circuitpython_tcs3448-1.0.0.dist-info/licenses/LICENSE,sha256=0JsO0yQOspYeKuhk2Y_s1wan9NO0DYeWSRJBrYJdeko,1100
|
|
3
|
+
adafruit_circuitpython_tcs3448-1.0.0.dist-info/METADATA,sha256=2kWp7dwT9aZz9HQT1mBy3Xijkeah2KW9MB5KUl-jYBQ,5424
|
|
4
|
+
adafruit_circuitpython_tcs3448-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
5
|
+
adafruit_circuitpython_tcs3448-1.0.0.dist-info/top_level.txt,sha256=v4wVUk1z4zAVwDNnj8Fyva3wsrKiHvtwcOeNHEws-nQ,17
|
|
6
|
+
adafruit_circuitpython_tcs3448-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Liz Clark for Adafruit Industries
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
adafruit_tcs3448
|
adafruit_tcs3448.py
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Liz Clark for Adafruit Industries
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
`adafruit_tcs3448`
|
|
6
|
+
================================================================================
|
|
7
|
+
|
|
8
|
+
CircuitPython driver for the Adafruit TCS3448 14-Channel Light / Color Sensor Breakout
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
* Author(s): Liz Clark
|
|
12
|
+
|
|
13
|
+
Implementation Notes
|
|
14
|
+
--------------------
|
|
15
|
+
|
|
16
|
+
**Hardware:**
|
|
17
|
+
|
|
18
|
+
* `Adafruit TCS3448 14-Channel Light / Color Sensor Breakout <https://www.adafruit.com/product/6525>`_
|
|
19
|
+
|
|
20
|
+
**Software and Dependencies:**
|
|
21
|
+
|
|
22
|
+
* Adafruit CircuitPython firmware for the supported boards:
|
|
23
|
+
https://circuitpython.org/downloads
|
|
24
|
+
|
|
25
|
+
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
|
26
|
+
* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
from adafruit_bus_device import i2c_device
|
|
32
|
+
from adafruit_register.i2c_bit import ROBit, RWBit
|
|
33
|
+
from adafruit_register.i2c_bits import RWBits
|
|
34
|
+
from adafruit_register.i2c_struct import Struct, UnaryStruct
|
|
35
|
+
from adafruit_register.i2c_struct_array import StructArray
|
|
36
|
+
from micropython import const
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from typing import List, Tuple
|
|
40
|
+
|
|
41
|
+
import busio
|
|
42
|
+
except ImportError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
__version__ = "1.0.0"
|
|
46
|
+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TCS3448.git"
|
|
47
|
+
|
|
48
|
+
_I2C_ADDR = const(0x59)
|
|
49
|
+
_CHIP_ID = const(0x81)
|
|
50
|
+
_AUXID = const(0x58)
|
|
51
|
+
_REVID = const(0x59)
|
|
52
|
+
_ID = const(0x5A)
|
|
53
|
+
_CFG12 = const(0x66)
|
|
54
|
+
_GPIO = const(0x6B)
|
|
55
|
+
_ENABLE = const(0x80)
|
|
56
|
+
_ATIME = const(0x81)
|
|
57
|
+
_WTIME = const(0x83)
|
|
58
|
+
_TH_L = const(0x84)
|
|
59
|
+
_TH_H = const(0x86)
|
|
60
|
+
_STATUS2 = const(0x90)
|
|
61
|
+
_STATUS = const(0x93)
|
|
62
|
+
_ASTATUS = const(0x94)
|
|
63
|
+
_DATA = const(0x95)
|
|
64
|
+
_CFG0 = const(0xBF)
|
|
65
|
+
_CFG1 = const(0xC6)
|
|
66
|
+
_LED = const(0xCD)
|
|
67
|
+
_PERS = const(0xCF)
|
|
68
|
+
_ASTEP = const(0xD4)
|
|
69
|
+
_CFG20 = const(0xD6)
|
|
70
|
+
_AZ_CFG = const(0xDE)
|
|
71
|
+
_FD_STATUS = const(0xE3)
|
|
72
|
+
_INTENAB = const(0xF9)
|
|
73
|
+
_CONTROL = const(0xFA)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CV:
|
|
77
|
+
"""Constant-value helper used as an enum base class.
|
|
78
|
+
|
|
79
|
+
Subclasses define integer class attributes; :meth:`valid` and
|
|
80
|
+
:meth:`get_name` provide validation and reverse-lookup.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def valid(cls, value: int) -> bool:
|
|
85
|
+
"""Return ``True`` if *value* is a defined member of this class."""
|
|
86
|
+
for name, member in cls.__dict__.items():
|
|
87
|
+
if name.startswith("_") or callable(member):
|
|
88
|
+
continue
|
|
89
|
+
if member == value:
|
|
90
|
+
return True
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def get_name(cls, value: int) -> str:
|
|
95
|
+
"""Return the attribute name whose value equals *value*."""
|
|
96
|
+
for name, member in cls.__dict__.items():
|
|
97
|
+
if name.startswith("_") or callable(member):
|
|
98
|
+
continue
|
|
99
|
+
if member == value:
|
|
100
|
+
return name
|
|
101
|
+
raise KeyError(value)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Gain(CV):
|
|
105
|
+
"""Analog gain settings for the ALS engines
|
|
106
|
+
|
|
107
|
+
Higher gain increases sensitivity in low light but saturates sooner in
|
|
108
|
+
bright light. The multiplier for a gain code is ``0.5 * 2 ** code``.
|
|
109
|
+
|
|
110
|
+
+---------------------------+----------+
|
|
111
|
+
| Setting | Gain |
|
|
112
|
+
+===========================+==========+
|
|
113
|
+
| :py:const:`Gain.X0_5` | 0.5x |
|
|
114
|
+
+---------------------------+----------+
|
|
115
|
+
| :py:const:`Gain.X1` | 1x |
|
|
116
|
+
+---------------------------+----------+
|
|
117
|
+
| :py:const:`Gain.X2` | 2x |
|
|
118
|
+
+---------------------------+----------+
|
|
119
|
+
| :py:const:`Gain.X4` | 4x |
|
|
120
|
+
+---------------------------+----------+
|
|
121
|
+
| :py:const:`Gain.X8` | 8x |
|
|
122
|
+
+---------------------------+----------+
|
|
123
|
+
| :py:const:`Gain.X16` | 16x |
|
|
124
|
+
+---------------------------+----------+
|
|
125
|
+
| :py:const:`Gain.X32` | 32x |
|
|
126
|
+
+---------------------------+----------+
|
|
127
|
+
| :py:const:`Gain.X64` | 64x |
|
|
128
|
+
+---------------------------+----------+
|
|
129
|
+
| :py:const:`Gain.X128` | 128x |
|
|
130
|
+
+---------------------------+----------+
|
|
131
|
+
| :py:const:`Gain.X256` | 256x |
|
|
132
|
+
+---------------------------+----------+
|
|
133
|
+
| :py:const:`Gain.X512` | 512x |
|
|
134
|
+
+---------------------------+----------+
|
|
135
|
+
| :py:const:`Gain.X1024` | 1024x |
|
|
136
|
+
+---------------------------+----------+
|
|
137
|
+
| :py:const:`Gain.X2048` | 2048x |
|
|
138
|
+
+---------------------------+----------+
|
|
139
|
+
|
|
140
|
+
Default applied by the driver.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
X0_5 = 0
|
|
144
|
+
X1 = 1
|
|
145
|
+
X2 = 2
|
|
146
|
+
X4 = 3
|
|
147
|
+
X8 = 4
|
|
148
|
+
X16 = 5
|
|
149
|
+
X32 = 6
|
|
150
|
+
X64 = 7
|
|
151
|
+
X128 = 8
|
|
152
|
+
X256 = 9 # driver default
|
|
153
|
+
X512 = 10
|
|
154
|
+
X1024 = 11
|
|
155
|
+
X2048 = 12
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class SmuxMode(CV):
|
|
159
|
+
"""Automatic SMUX channel-cycling mode (CFG20 bits 6:5).
|
|
160
|
+
|
|
161
|
+
The auto-SMUX hardware cycles through several SMUX configurations within a
|
|
162
|
+
single measurement, filling six data registers per cycle.
|
|
163
|
+
|
|
164
|
+
+-----------------------------+--------------------------------------------+
|
|
165
|
+
| Setting | Results produced |
|
|
166
|
+
+=============================+============================================+
|
|
167
|
+
| :py:const:`SmuxMode.CH6` | 6 results (1 cycle) |
|
|
168
|
+
+-----------------------------+--------------------------------------------+
|
|
169
|
+
| :py:const:`SmuxMode.CH12` | 12 results (2 cycles) |
|
|
170
|
+
+-----------------------------+--------------------------------------------+
|
|
171
|
+
| :py:const:`SmuxMode.CH18` | 18 results (3 cycles) * |
|
|
172
|
+
+-----------------------------+--------------------------------------------+
|
|
173
|
+
|
|
174
|
+
Default applied by the driver.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
CH6 = 0 # FZ, FY, FXL, NIR, 2x VIS
|
|
178
|
+
CH12 = 2 # adds F2, F3, F4, F6, 2x VIS
|
|
179
|
+
CH18 = 3 # adds F1, F7, F8, F5, 2x VIS — driver default
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Channel(CV):
|
|
183
|
+
"""Result index within the 18-result automatic SMUX data array.
|
|
184
|
+
|
|
185
|
+
The index is the position in the data registers, which follows the order
|
|
186
|
+
the auto-SMUX engine fills them rather than wavelength order. The index
|
|
187
|
+
order matches the Arduino driver's channel constants.
|
|
188
|
+
|
|
189
|
+
+----------------------------------+--------+---------------+-------------+
|
|
190
|
+
| Setting | Index | Peak | FWHM |
|
|
191
|
+
+==================================+========+===============+=============+
|
|
192
|
+
| :py:const:`Channel.FZ` | 0 | 450 nm | 67 nm |
|
|
193
|
+
+----------------------------------+--------+---------------+-------------+
|
|
194
|
+
| :py:const:`Channel.FY` | 1 | 560 nm | 123 nm |
|
|
195
|
+
+----------------------------------+--------+---------------+-------------+
|
|
196
|
+
| :py:const:`Channel.FXL` | 2 | 596 nm | 93 nm |
|
|
197
|
+
+----------------------------------+--------+---------------+-------------+
|
|
198
|
+
| :py:const:`Channel.NIR` | 3 | 855 nm | 61 nm |
|
|
199
|
+
+----------------------------------+--------+---------------+-------------+
|
|
200
|
+
| :py:const:`Channel.VIS_TL_0` | 4 | clear, top-left, cycle 1 |
|
|
201
|
+
+----------------------------------+--------+-----------------------------+
|
|
202
|
+
| :py:const:`Channel.VIS_BR_0` | 5 | clear, both-right, cycle 1 |
|
|
203
|
+
+----------------------------------+--------+---------------+-------------+
|
|
204
|
+
| :py:const:`Channel.F2` | 6 | 424 nm | 29 nm |
|
|
205
|
+
+----------------------------------+--------+---------------+-------------+
|
|
206
|
+
| :py:const:`Channel.F3` | 7 | 473 nm | 38 nm |
|
|
207
|
+
+----------------------------------+--------+---------------+-------------+
|
|
208
|
+
| :py:const:`Channel.F4` | 8 | 516 nm | 48 nm |
|
|
209
|
+
+----------------------------------+--------+---------------+-------------+
|
|
210
|
+
| :py:const:`Channel.F6` | 9 | 636 nm | 58 nm |
|
|
211
|
+
+----------------------------------+--------+---------------+-------------+
|
|
212
|
+
| :py:const:`Channel.VIS_TL_1` | 10 | clear, top-left, cycle 2 |
|
|
213
|
+
+----------------------------------+--------+-----------------------------+
|
|
214
|
+
| :py:const:`Channel.VIS_BR_1` | 11 | clear, both-right, cycle 2 |
|
|
215
|
+
+----------------------------------+--------+---------------+-------------+
|
|
216
|
+
| :py:const:`Channel.F1` | 12 | 407 nm | 28 nm |
|
|
217
|
+
+----------------------------------+--------+---------------+-------------+
|
|
218
|
+
| :py:const:`Channel.F7` | 13 | 687 nm | 63 nm |
|
|
219
|
+
+----------------------------------+--------+---------------+-------------+
|
|
220
|
+
| :py:const:`Channel.F8` | 14 | 748 nm | 77 nm |
|
|
221
|
+
+----------------------------------+--------+---------------+-------------+
|
|
222
|
+
| :py:const:`Channel.F5` | 15 | 546 nm | 44 nm |
|
|
223
|
+
+----------------------------------+--------+---------------+-------------+
|
|
224
|
+
| :py:const:`Channel.VIS_TL_2` | 16 | clear, top-left, cycle 3 |
|
|
225
|
+
+----------------------------------+--------+-----------------------------+
|
|
226
|
+
| :py:const:`Channel.VIS_BR_2` | 17 | clear, both-right, cycle 3 |
|
|
227
|
+
+----------------------------------+--------+-----------------------------+
|
|
228
|
+
|
|
229
|
+
In :py:const:`SmuxMode.CH12` only indices 0-11 are produced, and in
|
|
230
|
+
:py:const:`SmuxMode.CH6` only indices 0-5.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
FZ = 0
|
|
234
|
+
FY = 1
|
|
235
|
+
FXL = 2
|
|
236
|
+
NIR = 3
|
|
237
|
+
VIS_TL_0 = 4
|
|
238
|
+
VIS_BR_0 = 5
|
|
239
|
+
F2 = 6
|
|
240
|
+
F3 = 7
|
|
241
|
+
F4 = 8
|
|
242
|
+
F6 = 9
|
|
243
|
+
VIS_TL_1 = 10
|
|
244
|
+
VIS_BR_1 = 11
|
|
245
|
+
F1 = 12
|
|
246
|
+
F7 = 13
|
|
247
|
+
F8 = 14
|
|
248
|
+
F5 = 15
|
|
249
|
+
VIS_TL_2 = 16
|
|
250
|
+
VIS_BR_2 = 17
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class FlickerFreq(CV):
|
|
254
|
+
"""Flicker detection result returned by :attr:`TCS3448.flicker_frequency`.
|
|
255
|
+
|
|
256
|
+
+-------------------------------+----------------------------+
|
|
257
|
+
| Setting | Meaning |
|
|
258
|
+
+===============================+============================+
|
|
259
|
+
| :py:const:`FlickerFreq.NONE` | No flicker detected |
|
|
260
|
+
+-------------------------------+----------------------------+
|
|
261
|
+
| :py:const:`FlickerFreq.HZ100` | 100 Hz mains flicker |
|
|
262
|
+
+-------------------------------+----------------------------+
|
|
263
|
+
| :py:const:`FlickerFreq.HZ120` | 120 Hz mains flicker |
|
|
264
|
+
+-------------------------------+----------------------------+
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
NONE = 0
|
|
268
|
+
HZ100 = 100
|
|
269
|
+
HZ120 = 120
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class Measurement:
|
|
273
|
+
"""One set of spectral results.
|
|
274
|
+
|
|
275
|
+
Supports indexing and ``len()``, so a :class:`Channel` constant can be
|
|
276
|
+
used directly::
|
|
277
|
+
|
|
278
|
+
blue = sensor.measurement[Channel.FZ]
|
|
279
|
+
|
|
280
|
+
:param tuple channels: Raw ADC counts in auto-SMUX storage order.
|
|
281
|
+
:param int gain: Gain code that was applied to these counts.
|
|
282
|
+
:param bool saturated: ``True`` when the frame is affected by saturation.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(self, channels: Tuple[int, ...], gain: int, saturated: bool) -> None:
|
|
286
|
+
self.channels = channels
|
|
287
|
+
"""Raw 16-bit ADC counts indexed by :class:`Channel`."""
|
|
288
|
+
|
|
289
|
+
self.gain = gain
|
|
290
|
+
""":class:`Gain` code reported for this exact frame."""
|
|
291
|
+
|
|
292
|
+
self.saturated = saturated
|
|
293
|
+
"""``True`` when analog or digital saturation affected this frame."""
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def gain_multiplier(self) -> float:
|
|
297
|
+
"""Gain applied to this frame as a multiplier, e.g. ``256.0``."""
|
|
298
|
+
return 0.5 * (2**self.gain)
|
|
299
|
+
|
|
300
|
+
def __getitem__(self, index: int) -> int:
|
|
301
|
+
"""Return the count for *index*, a :class:`Channel` constant."""
|
|
302
|
+
return self.channels[index]
|
|
303
|
+
|
|
304
|
+
def __len__(self) -> int:
|
|
305
|
+
"""Return the number of results in this frame."""
|
|
306
|
+
return len(self.channels)
|
|
307
|
+
|
|
308
|
+
def __repr__(self) -> str:
|
|
309
|
+
"""Return a readable one-line summary of the frame."""
|
|
310
|
+
gain = self.gain_multiplier
|
|
311
|
+
return f"<Measurement gain={gain}x saturated={self.saturated} {self.channels}>"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class _Bank1:
|
|
315
|
+
def __init__(self, sensor: "TCS3448") -> None:
|
|
316
|
+
self._sensor = sensor
|
|
317
|
+
|
|
318
|
+
def __enter__(self) -> None:
|
|
319
|
+
self._sensor._reg_bank = True
|
|
320
|
+
|
|
321
|
+
def __exit__(self, *args) -> None:
|
|
322
|
+
self._sensor._reg_bank = False
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class TCS3448: # noqa: PLR0904
|
|
326
|
+
"""CircuitPython driver for the ams OSRAM TCS3448 multi-spectral sensor.
|
|
327
|
+
|
|
328
|
+
:param ~busio.I2C i2c_bus: The I2C bus the device is connected to.
|
|
329
|
+
:param int address: I2C address. Defaults to :const:`0x59`.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
_reg_bank = RWBit(_CFG0, 4) # 1 = bank 1, 0 = bank 0
|
|
333
|
+
|
|
334
|
+
low_power_enabled = RWBit(_CFG0, 5)
|
|
335
|
+
"""``True`` when the sensor idles in low-power mode between measurements."""
|
|
336
|
+
|
|
337
|
+
power_enabled = RWBit(_ENABLE, 0)
|
|
338
|
+
"""``True`` when the internal oscillator is running (PON)."""
|
|
339
|
+
|
|
340
|
+
spectral_measurement_enabled = RWBit(_ENABLE, 1)
|
|
341
|
+
"""``True`` when the spectral measurement engine is running (ALS_EN)."""
|
|
342
|
+
|
|
343
|
+
wait_enabled = RWBit(_ENABLE, 3)
|
|
344
|
+
"""``True`` when the :attr:`wtime` delay between measurements is applied."""
|
|
345
|
+
|
|
346
|
+
flicker_detection_enabled = RWBit(_ENABLE, 6)
|
|
347
|
+
"""``True`` when flicker detection is enabled."""
|
|
348
|
+
|
|
349
|
+
atime = UnaryStruct(_ATIME, "B")
|
|
350
|
+
"""Number of integration steps, 0-255."""
|
|
351
|
+
|
|
352
|
+
wtime = UnaryStruct(_WTIME, "B")
|
|
353
|
+
"""Delay between consecutive measurements, 0-255."""
|
|
354
|
+
|
|
355
|
+
spectral_threshold_low = UnaryStruct(_TH_L, "<H")
|
|
356
|
+
"""16-bit low threshold for the spectral interrupt."""
|
|
357
|
+
|
|
358
|
+
spectral_threshold_high = UnaryStruct(_TH_H, "<H")
|
|
359
|
+
"""16-bit high threshold for the spectral interrupt."""
|
|
360
|
+
|
|
361
|
+
data_ready = ROBit(_STATUS2, 6)
|
|
362
|
+
"""``True`` when a complete measurement is available (AVALID)."""
|
|
363
|
+
|
|
364
|
+
digital_saturated = ROBit(_STATUS2, 4)
|
|
365
|
+
"""``True`` when an ADC counter hit its maximum during the last integration."""
|
|
366
|
+
|
|
367
|
+
analog_saturated = ROBit(_STATUS2, 3)
|
|
368
|
+
"""``True`` when the analogue front end saturated during the last integration."""
|
|
369
|
+
|
|
370
|
+
status = UnaryStruct(_STATUS, "B")
|
|
371
|
+
"""Raw value of the main STATUS register."""
|
|
372
|
+
|
|
373
|
+
_astatus = UnaryStruct(_ASTATUS, "B")
|
|
374
|
+
_frame_6ch = Struct(_ASTATUS, "<B6H")
|
|
375
|
+
_frame_12ch = Struct(_ASTATUS, "<B12H")
|
|
376
|
+
_frame_18ch = Struct(_ASTATUS, "<B18H")
|
|
377
|
+
_channel_data = StructArray(_DATA, "<H", 18)
|
|
378
|
+
|
|
379
|
+
_again = RWBits(5, _CFG1, 0)
|
|
380
|
+
_auto_smux = RWBits(2, _CFG20, 5)
|
|
381
|
+
|
|
382
|
+
led_enabled = RWBit(_LED, 7)
|
|
383
|
+
"""``True`` when the LED driver sinks current through the LDR pin."""
|
|
384
|
+
|
|
385
|
+
_led_drive = RWBits(7, _LED, 0)
|
|
386
|
+
_persistence = RWBits(4, _PERS, 0)
|
|
387
|
+
_astep = UnaryStruct(_ASTEP, "<H")
|
|
388
|
+
_az_config = UnaryStruct(_AZ_CFG, "B")
|
|
389
|
+
|
|
390
|
+
flicker_status = UnaryStruct(_FD_STATUS, "B")
|
|
391
|
+
"""Raw value of the flicker detection status register.
|
|
392
|
+
|
|
393
|
+
:attr:`flicker_frequency` decodes the detection bits.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
system_interrupt_enabled = RWBit(_INTENAB, 0)
|
|
397
|
+
"""``True`` when flicker-status and SMUX-completion interrupts are enabled."""
|
|
398
|
+
|
|
399
|
+
fifo_interrupt_enabled = RWBit(_INTENAB, 2)
|
|
400
|
+
"""``True`` when the FIFO threshold interrupt is enabled."""
|
|
401
|
+
|
|
402
|
+
spectral_interrupt_enabled = RWBit(_INTENAB, 3)
|
|
403
|
+
"""``True`` when the spectral threshold interrupt is enabled."""
|
|
404
|
+
|
|
405
|
+
_sw_reset = RWBit(_CONTROL, 3)
|
|
406
|
+
_part_id = UnaryStruct(_ID, "B")
|
|
407
|
+
_revid = UnaryStruct(_REVID, "B")
|
|
408
|
+
_auxid = UnaryStruct(_AUXID, "B")
|
|
409
|
+
_gpio_in = ROBit(_GPIO, 0)
|
|
410
|
+
_gpio_out = RWBit(_GPIO, 1)
|
|
411
|
+
_gpio_in_en = RWBit(_GPIO, 2)
|
|
412
|
+
_gpio_inv = RWBit(_GPIO, 3)
|
|
413
|
+
_th_ch = RWBits(3, _CFG12, 0)
|
|
414
|
+
|
|
415
|
+
def __init__(self, i2c_bus: "busio.I2C", address: int = _I2C_ADDR) -> None:
|
|
416
|
+
self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
|
|
417
|
+
self._bank1 = _Bank1(self)
|
|
418
|
+
|
|
419
|
+
time.sleep(0.001)
|
|
420
|
+
|
|
421
|
+
with self._bank1:
|
|
422
|
+
chip_id = self._part_id
|
|
423
|
+
|
|
424
|
+
if chip_id != _CHIP_ID:
|
|
425
|
+
raise RuntimeError(
|
|
426
|
+
f"Failed to find TCS3448 at 0x{address:02X} - check wiring! Got ID 0x{chip_id:02X}"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
self.read_timeout: int = 1000
|
|
430
|
+
"""Milliseconds :attr:`measurement` waits for :attr:`data_ready`."""
|
|
431
|
+
|
|
432
|
+
self.reset()
|
|
433
|
+
|
|
434
|
+
def reset(self) -> None:
|
|
435
|
+
"""Force a power-on reset and re-apply the driver defaults.
|
|
436
|
+
|
|
437
|
+
:raises RuntimeError: If the sensor does not answer after the reset.
|
|
438
|
+
"""
|
|
439
|
+
self._sw_reset = True
|
|
440
|
+
|
|
441
|
+
time.sleep(0.001)
|
|
442
|
+
for _ in range(250):
|
|
443
|
+
try:
|
|
444
|
+
with self.i2c_device as i2c:
|
|
445
|
+
i2c.write(b"")
|
|
446
|
+
break
|
|
447
|
+
except OSError:
|
|
448
|
+
time.sleep(0.001)
|
|
449
|
+
else:
|
|
450
|
+
raise RuntimeError("TCS3448 did not respond after a software reset")
|
|
451
|
+
|
|
452
|
+
self.power_enabled = True
|
|
453
|
+
self.gain = Gain.X256
|
|
454
|
+
self.atime = 29
|
|
455
|
+
self.astep = 599
|
|
456
|
+
|
|
457
|
+
self.smux_mode = SmuxMode.CH18
|
|
458
|
+
|
|
459
|
+
with self._bank1:
|
|
460
|
+
self._gpio_in_en = False # output mode
|
|
461
|
+
|
|
462
|
+
self.led_enabled = False
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def gain(self) -> int:
|
|
466
|
+
"""Analog gain applied to the spectral measurement.
|
|
467
|
+
|
|
468
|
+
Must be a :class:`Gain` constant, e.g. ``Gain.X256``. Defaults to
|
|
469
|
+
:attr:`Gain.X256`. The gain in effect for a particular frame is
|
|
470
|
+
reported by :attr:`Measurement.gain`.
|
|
471
|
+
"""
|
|
472
|
+
return self._again
|
|
473
|
+
|
|
474
|
+
@gain.setter
|
|
475
|
+
def gain(self, value: int) -> None:
|
|
476
|
+
if not Gain.valid(value):
|
|
477
|
+
raise ValueError("gain must be a Gain constant")
|
|
478
|
+
self._again = value
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def astep(self) -> int:
|
|
482
|
+
"""Integration step size, 0-65534.
|
|
483
|
+
|
|
484
|
+
Each step is ``(astep + 1) * 2.78 us``. 65535 is reserved by the
|
|
485
|
+
hardware. See :attr:`integration_time_ms` for the resulting time.
|
|
486
|
+
"""
|
|
487
|
+
return self._astep
|
|
488
|
+
|
|
489
|
+
@astep.setter
|
|
490
|
+
def astep(self, value: int) -> None:
|
|
491
|
+
if not 0 <= value <= 65534:
|
|
492
|
+
raise ValueError("astep must be 0-65534 (65535 is reserved)")
|
|
493
|
+
self._astep = value
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def integration_time_ms(self) -> float:
|
|
497
|
+
"""Integration time in milliseconds (read-only).
|
|
498
|
+
|
|
499
|
+
``(atime + 1) * (astep + 1) * 2.78 us``. Change it by setting
|
|
500
|
+
:attr:`atime` or :attr:`astep`.
|
|
501
|
+
"""
|
|
502
|
+
return (self.atime + 1) * (self._astep + 1) * 0.00278
|
|
503
|
+
|
|
504
|
+
@property
|
|
505
|
+
def smux_mode(self) -> int:
|
|
506
|
+
"""Automatic SMUX cycling mode.
|
|
507
|
+
|
|
508
|
+
Must be a :class:`SmuxMode` constant. Defaults to
|
|
509
|
+
:attr:`SmuxMode.CH18`. Only change this while no measurement is
|
|
510
|
+
running.
|
|
511
|
+
"""
|
|
512
|
+
return self._auto_smux
|
|
513
|
+
|
|
514
|
+
@smux_mode.setter
|
|
515
|
+
def smux_mode(self, value: int) -> None:
|
|
516
|
+
if not SmuxMode.valid(value):
|
|
517
|
+
raise ValueError("smux_mode must be a SmuxMode constant")
|
|
518
|
+
self._auto_smux = value
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def channel_count(self) -> int:
|
|
522
|
+
"""Number of results the current :attr:`smux_mode` produces (read-only)."""
|
|
523
|
+
mode = self._auto_smux
|
|
524
|
+
if mode == SmuxMode.CH18:
|
|
525
|
+
return 18
|
|
526
|
+
if mode == SmuxMode.CH12:
|
|
527
|
+
return 12
|
|
528
|
+
return 6
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def measurement(self) -> Measurement:
|
|
532
|
+
"""Run one measurement and return it as a :class:`Measurement`.
|
|
533
|
+
|
|
534
|
+
Stops any measurement in progress, clears stale status, triggers a
|
|
535
|
+
single measurement and waits for :attr:`data_ready` before reading.
|
|
536
|
+
The number of results follows :attr:`channel_count`.
|
|
537
|
+
|
|
538
|
+
:raises TimeoutError: If no result arrives within :attr:`read_timeout`
|
|
539
|
+
milliseconds.
|
|
540
|
+
"""
|
|
541
|
+
self.spectral_measurement_enabled = False
|
|
542
|
+
self.clear_status()
|
|
543
|
+
_ = self._astatus
|
|
544
|
+
|
|
545
|
+
self.spectral_measurement_enabled = True
|
|
546
|
+
|
|
547
|
+
deadline = time.monotonic() + self.read_timeout / 1000
|
|
548
|
+
while not self.data_ready:
|
|
549
|
+
if time.monotonic() > deadline:
|
|
550
|
+
self.spectral_measurement_enabled = False
|
|
551
|
+
raise TimeoutError("Timed out waiting for TCS3448 data ready")
|
|
552
|
+
time.sleep(0.001)
|
|
553
|
+
|
|
554
|
+
frame = self.last_measurement
|
|
555
|
+
|
|
556
|
+
self.spectral_measurement_enabled = False
|
|
557
|
+
return frame
|
|
558
|
+
|
|
559
|
+
@property
|
|
560
|
+
def last_measurement(self) -> Measurement:
|
|
561
|
+
"""The frame currently held in the data registers, read without waiting."""
|
|
562
|
+
count = self.channel_count
|
|
563
|
+
if count == 18:
|
|
564
|
+
frame = self._frame_18ch
|
|
565
|
+
elif count == 12:
|
|
566
|
+
frame = self._frame_12ch
|
|
567
|
+
else:
|
|
568
|
+
frame = self._frame_6ch
|
|
569
|
+
|
|
570
|
+
astatus = frame[0]
|
|
571
|
+
return Measurement(frame[1:], astatus & 0x0F, bool(astatus & 0x80))
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def all_channels(self) -> List[int]:
|
|
575
|
+
"""Run one measurement and return just the raw counts.
|
|
576
|
+
|
|
577
|
+
:raises TimeoutError: If no result arrives within :attr:`read_timeout`
|
|
578
|
+
milliseconds.
|
|
579
|
+
"""
|
|
580
|
+
return list(self.measurement.channels)
|
|
581
|
+
|
|
582
|
+
def channel(self, channel: int) -> int:
|
|
583
|
+
"""A single result from the data registers without waiting.
|
|
584
|
+
|
|
585
|
+
:param int channel: Result index, a :class:`Channel` constant (0-17).
|
|
586
|
+
:returns: Raw 16-bit ADC count.
|
|
587
|
+
"""
|
|
588
|
+
if not 0 <= channel <= 17:
|
|
589
|
+
raise ValueError("channel must be 0-17 (use Channel constants)")
|
|
590
|
+
|
|
591
|
+
_ = self._astatus # latch the data registers
|
|
592
|
+
return self._channel_data[channel][0]
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def led_current_ma(self) -> int:
|
|
596
|
+
"""LED drive current in milliamps, 4-258.
|
|
597
|
+
|
|
598
|
+
The hardware steps in 2 mA increments as ``4 + register * 2``, so odd
|
|
599
|
+
values round down. Enable the driver with :attr:`led_enabled`.
|
|
600
|
+
"""
|
|
601
|
+
return 4 + (self._led_drive * 2)
|
|
602
|
+
|
|
603
|
+
@led_current_ma.setter
|
|
604
|
+
def led_current_ma(self, current_ma: int) -> None:
|
|
605
|
+
current_ma = max(4, min(258, current_ma))
|
|
606
|
+
self._led_drive = (current_ma - 4) // 2
|
|
607
|
+
|
|
608
|
+
@property
|
|
609
|
+
def flicker_frequency(self) -> int:
|
|
610
|
+
"""Detected flicker frequency as a :class:`FlickerFreq` value.
|
|
611
|
+
|
|
612
|
+
Returns :attr:`FlickerFreq.HZ100`, :attr:`FlickerFreq.HZ120` or
|
|
613
|
+
:attr:`FlickerFreq.NONE`, reporting a frequency only when the
|
|
614
|
+
matching valid bit is also set. Requires
|
|
615
|
+
:attr:`flicker_detection_enabled`.
|
|
616
|
+
"""
|
|
617
|
+
status = self.flicker_status
|
|
618
|
+
if (status & 0x08) and (status & 0x02):
|
|
619
|
+
return FlickerFreq.HZ120
|
|
620
|
+
if (status & 0x04) and (status & 0x01):
|
|
621
|
+
return FlickerFreq.HZ100
|
|
622
|
+
return FlickerFreq.NONE
|
|
623
|
+
|
|
624
|
+
def clear_status(self) -> None:
|
|
625
|
+
"""Clear all flags in the main STATUS register."""
|
|
626
|
+
self.status = self.status
|
|
627
|
+
|
|
628
|
+
@property
|
|
629
|
+
def persistence(self) -> int:
|
|
630
|
+
"""Consecutive out-of-threshold measurements needed to raise an interrupt.
|
|
631
|
+
|
|
632
|
+
Valid range 0-15. 0 raises an interrupt on every cycle.
|
|
633
|
+
"""
|
|
634
|
+
return self._persistence
|
|
635
|
+
|
|
636
|
+
@persistence.setter
|
|
637
|
+
def persistence(self, value: int) -> None:
|
|
638
|
+
if not 0 <= value <= 15:
|
|
639
|
+
raise ValueError("persistence must be 0-15")
|
|
640
|
+
self._persistence = value
|
|
641
|
+
|
|
642
|
+
@property
|
|
643
|
+
def auto_zero_frequency(self) -> int:
|
|
644
|
+
"""Measurement cycles between automatic zero-offset calibrations, 0-255.
|
|
645
|
+
|
|
646
|
+
* ``0`` — never, which is not recommended
|
|
647
|
+
* ``1`` — every cycle
|
|
648
|
+
* ``255`` — only before the first measurement, the hardware default
|
|
649
|
+
"""
|
|
650
|
+
return self._az_config
|
|
651
|
+
|
|
652
|
+
@auto_zero_frequency.setter
|
|
653
|
+
def auto_zero_frequency(self, value: int) -> None:
|
|
654
|
+
if not 0 <= value <= 255:
|
|
655
|
+
raise ValueError("auto_zero_frequency must be 0-255")
|
|
656
|
+
self._az_config = value
|
|
657
|
+
|
|
658
|
+
@property
|
|
659
|
+
def threshold_channel(self) -> int:
|
|
660
|
+
"""ADC channel 0-5 compared against the spectral interrupt thresholds."""
|
|
661
|
+
with self._bank1:
|
|
662
|
+
return self._th_ch
|
|
663
|
+
|
|
664
|
+
@threshold_channel.setter
|
|
665
|
+
def threshold_channel(self, channel: int) -> None:
|
|
666
|
+
if not 0 <= channel <= 5:
|
|
667
|
+
raise ValueError("threshold_channel must be 0-5")
|
|
668
|
+
with self._bank1:
|
|
669
|
+
self._th_ch = channel
|
|
670
|
+
|
|
671
|
+
@property
|
|
672
|
+
def gpio_output_mode(self) -> bool:
|
|
673
|
+
"""``True`` when the GPIO pin is an output, ``False`` when an input."""
|
|
674
|
+
with self._bank1:
|
|
675
|
+
return not self._gpio_in_en
|
|
676
|
+
|
|
677
|
+
@gpio_output_mode.setter
|
|
678
|
+
def gpio_output_mode(self, output: bool) -> None:
|
|
679
|
+
with self._bank1:
|
|
680
|
+
self._gpio_in_en = not output
|
|
681
|
+
|
|
682
|
+
@property
|
|
683
|
+
def gpio_value(self) -> bool:
|
|
684
|
+
"""State of the GPIO pin."""
|
|
685
|
+
with self._bank1:
|
|
686
|
+
return self._gpio_in
|
|
687
|
+
|
|
688
|
+
@gpio_value.setter
|
|
689
|
+
def gpio_value(self, high: bool) -> None:
|
|
690
|
+
with self._bank1:
|
|
691
|
+
self._gpio_out = high
|
|
692
|
+
|
|
693
|
+
@property
|
|
694
|
+
def gpio_inverted(self) -> bool:
|
|
695
|
+
"""``True`` when the GPIO output polarity is inverted."""
|
|
696
|
+
with self._bank1:
|
|
697
|
+
return self._gpio_inv
|
|
698
|
+
|
|
699
|
+
@gpio_inverted.setter
|
|
700
|
+
def gpio_inverted(self, invert: bool) -> None:
|
|
701
|
+
with self._bank1:
|
|
702
|
+
self._gpio_inv = invert
|
|
703
|
+
|
|
704
|
+
@property
|
|
705
|
+
def part_id(self) -> int:
|
|
706
|
+
"""Part ID register value, ``0x81`` on the TCS3448."""
|
|
707
|
+
with self._bank1:
|
|
708
|
+
return self._part_id
|
|
709
|
+
|
|
710
|
+
@property
|
|
711
|
+
def revision_id(self) -> int:
|
|
712
|
+
"""Silicon revision."""
|
|
713
|
+
with self._bank1:
|
|
714
|
+
return self._revid & 0x07
|
|
715
|
+
|
|
716
|
+
@property
|
|
717
|
+
def aux_id(self) -> int:
|
|
718
|
+
"""Auxiliary ID."""
|
|
719
|
+
with self._bank1:
|
|
720
|
+
return self._auxid & 0x0F
|