-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathadafruit_vcnl4010.py
285 lines (231 loc) · 10 KB
/
adafruit_vcnl4010.py
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_vcnl4010`
====================================================
CircuitPython module for the VCNL4010 proximity and light sensor. See
examples/vcnl4010_simpletest.py for an example of the usage.
* Author(s): Tony DiCola
Implementation Notes
--------------------
**Hardware:**
* Adafruit `VCNL4010 Proximity/Light sensor breakout
<https://www.adafruit.com/product/466>`_ (Product ID: 466)
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://circuitpython.org/downloads
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
from micropython import const
from adafruit_bus_device import i2c_device
try:
import typing # pylint: disable=unused-import
from busio import I2C
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VCNL4010.git"
# Internal constants:
_VCNL4010_I2CADDR_DEFAULT = const(0x13)
_VCNL4010_COMMAND = const(0x80)
_VCNL4010_PRODUCTID = const(0x81)
_VCNL4010_PROXRATE = const(0x82)
_VCNL4010_IRLED = const(0x83)
_VCNL4010_AMBIENTPARAMETER = const(0x84)
_VCNL4010_AMBIENTDATA = const(0x85)
_VCNL4010_PROXIMITYDATA = const(0x87)
_VCNL4010_INTCONTROL = const(0x89)
_VCNL4010_PROXIMITYADJUST = const(0x8A)
_VCNL4010_INTSTAT = const(0x8E)
_VCNL4010_MODTIMING = const(0x8F)
_VCNL4010_MEASUREAMBIENT = const(0x10)
_VCNL4010_MEASUREPROXIMITY = const(0x08)
_VCNL4010_AMBIENTREADY = const(0x40)
_VCNL4010_PROXIMITYREADY = const(0x20)
_VCNL4010_AMBIENT_LUX_SCALE = 0.25 # Lux value per 16-bit result value.
# User-facing constants:
# Number of proximity measuremenrs per second
SAMPLERATE_1_95 = 0
SAMPLERATE_3_90625 = 1
SAMPLERATE_7_8125 = 2
SAMPLERATE_16_625 = 3
SAMPLERATE_31_25 = 4
SAMPLERATE_62_5 = 5
SAMPLERATE_125 = 6
SAMPLERATE_250 = 7
# Proximity modulator timing
FREQUENCY_3M125 = 3
FREQUENCY_1M5625 = 2
FREQUENCY_781K25 = 1
FREQUENCY_390K625 = 0
# Disable pylint's name warning as it causes too much noise. Suffixes like
# BE (big-endian) or mA (milli-amps) don't confirm to its conventions--by
# design (clarity of code and explicit units). Disable this globally to prevent
# littering the code with pylint disable and enable and making it less readable.
# pylint: disable=invalid-name
class VCNL4010:
"""Vishay VCNL4010 proximity and ambient light sensor.
:param ~busio.I2C i2c: The I2C bus the VCNL4010 is connected to
:param int address: (optional) The I2C address of the device. Defaults to :const:`0x13`
**Quickstart: Importing and using the VCNL4010**
Here is an example of using the :class:`VCNL4010` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
import board
import adafruit_vcnl4010
Once this is done you can define your `board.I2C` object and define your sensor object
.. code-block:: python
i2c = board.I2C() # uses board.SCL and board.SDA
sensor = adafruit_vcnl4010.VCNL4010(i2c)
Now you have access to the :attr:`sensor.proximity` and
:attr:`ambient_lux` attributes
.. code-block:: python
proximity = sensor.proximity
ambient_lux = sensor.ambient_lux
"""
# Class-level buffer for reading and writing data with the sensor.
# This reduces memory allocations but means the code is not re-entrant or
# thread safe!
_BUFFER = bytearray(3)
def __init__(self, i2c: I2C, address: int = _VCNL4010_I2CADDR_DEFAULT) -> None:
self._device = i2c_device.I2CDevice(i2c, address)
# Verify chip ID.
revision = self._read_u8(_VCNL4010_PRODUCTID)
if (revision & 0xF0) != 0x20:
raise RuntimeError("Failed to find VCNL4010, check wiring!")
self.led_current = 20
self.samplerate = SAMPLERATE_1_95
self.frequency = FREQUENCY_390K625
self._write_u8(_VCNL4010_INTCONTROL, 0x08)
def _read_u8(self, address: int) -> int:
# Read an 8-bit unsigned value from the specified 8-bit address.
with self._device as i2c:
self._BUFFER[0] = address & 0xFF
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_start=1)
return self._BUFFER[1]
def _read_u16BE(self, address: int) -> int:
# Read a 16-bit big-endian unsigned value from the specified 8-bit address.
with self._device as i2c:
self._BUFFER[0] = address & 0xFF
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_start=1)
return (self._BUFFER[1] << 8) | self._BUFFER[2]
def _write_u8(self, address: int, val: int) -> None:
# Write an 8-bit unsigned value to the specified 8-bit address.
with self._device as i2c:
self._BUFFER[0] = address & 0xFF
self._BUFFER[1] = val & 0xFF
i2c.write(self._BUFFER, end=2)
@property
def led_current(self) -> int:
"""The current of the LED. The value is in units of 10mA
and can only be set to 0 (0mA/off) to 20 (200mA). See the datasheet
for how LED current impacts proximity measurements. The default is
200mA.
"""
return self._read_u8(_VCNL4010_IRLED) & 0x3F
@led_current.setter
def led_current(self, val: int) -> None:
assert 0 <= val <= 20
self._write_u8(_VCNL4010_IRLED, val)
@property
def led_current_mA(self) -> int:
"""The current of the LED in milliamps. The value here is
specified in milliamps from 0-200. Note that this value will be
quantized down to a smaller less-accurate value as the chip only
supports current changes in 10mA increments, i.e. a value of 123 mA will
actually use 120 mA. See the datasheet for how the LED current impacts
proximity measurements, and the led_current property to explicitly set
values without quantization or unit conversion.
"""
return self.led_current * 10
@led_current_mA.setter
def led_current_mA(self, val: int) -> None:
self.led_current = val // 10
@property
def samplerate(self) -> int:
"""
The frequency of proximity measurements per second. Must be a value of:
- SAMPLERATE_1_95: 1.95 measurements/sec (default)
- SAMPLERATE_3_90625: 3.90625 measurements/sec
- SAMPLERATE_7_8125: 7.8125 measurements/sec
- SAMPLERATE_16_625: 16.625 measurements/sec
- SAMPLERATE_31_25: 31.25 measurements/sec
- SAMPLERATE_62_5: 62.5 measurements/sec
- SAMPLERATE_125: 125 measurements/sec
- SAMPLERATE_250: 250 measurements/sec
See the datasheet for how frequency changes the power consumption and
proximity detection accuracy.
"""
return self._read_u8(_VCNL4010_PROXRATE)
@samplerate.setter
def samplerate(self, val: int) -> None:
assert 0 <= val <= 7
self._write_u8(_VCNL4010_PROXRATE, val)
@property
def frequency(self) -> int:
"""
Proximity modulator timimg. This is the frequency of the IR square
wave used for the proximity measurement.
Must be a value of:
- FREQUENCY_3M125: 3.125 Mhz
- FREQUENCY_1M5625: 1.5625 Mhz
- FREQUENCY_781K25: 781.25 Khz
- FREQUENCY_390K625: 390.625 Khz (default)
The datasheet recommended leaving this at the default.
"""
return (self._read_u8(_VCNL4010_MODTIMING) >> 3) & 0x03
@frequency.setter
def frequency(self, val: int) -> None:
assert 0 <= val <= 3
timing = self._read_u8(_VCNL4010_MODTIMING)
timing &= ~0b00011000
timing |= (val << 3) & 0xFF
self._write_u8(_VCNL4010_MODTIMING, timing)
# Pylint gets confused with loops and return values. Disable the spurious
# warning for the next few functions (it hates when a loop returns a value).
# pylint: disable=inconsistent-return-statements
@property
def proximity(self) -> int:
"""The detected proximity of an object in front of the sensor. This
is a unit-less unsigned 16-bit value (0-65535) INVERSELY proportional
to the distance of an object in front of the sensor (up to a max of
~200mm). For example a value of 10 is an object farther away than a
value of 1000. Note there is no conversion from this value to absolute
distance possible, you can only make relative comparisons.
"""
# Clear interrupt.
status = self._read_u8(_VCNL4010_INTSTAT)
status &= ~0x80
self._write_u8(_VCNL4010_INTSTAT, status)
# Grab a proximity measurement.
self._write_u8(_VCNL4010_COMMAND, _VCNL4010_MEASUREPROXIMITY)
# Wait for result, then read and return the 16-bit value.
while True:
result = self._read_u8(_VCNL4010_COMMAND)
if result & _VCNL4010_PROXIMITYREADY:
return self._read_u16BE(_VCNL4010_PROXIMITYDATA)
@property
def ambient(self) -> int:
"""The detected ambient light in front of the sensor. This is
a unit-less unsigned 16-bit value (0-65535) with higher values for
more detected light. See the :attr:`ambient_lux property` for a value in lux.
"""
# Clear interrupt.
status = self._read_u8(_VCNL4010_INTSTAT)
status &= ~0x80
self._write_u8(_VCNL4010_INTSTAT, status)
# Grab an ambient light measurement.
self._write_u8(_VCNL4010_COMMAND, _VCNL4010_MEASUREAMBIENT)
# Wait for result, then read and return the 16-bit value.
while True:
result = self._read_u8(_VCNL4010_COMMAND)
if result & _VCNL4010_AMBIENTREADY:
return self._read_u16BE(_VCNL4010_AMBIENTDATA)
# pylint: enable=inconsistent-return-statements
@property
def ambient_lux(self) -> float:
"""The detected ambient light in front of the sensor as a value in
lux.
"""
return self.ambient * _VCNL4010_AMBIENT_LUX_SCALE