|
1 |
| -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries |
2 | 1 | # SPDX-FileCopyrightText: Copyright (c) 2023 Liz Clark for Adafruit Industries
|
3 | 2 | #
|
4 | 3 | # SPDX-License-Identifier: MIT
|
| 4 | + |
| 5 | +# Written by Liz Clark (Adafruit Industries) with OpenAI ChatGPT v4 September 25, 2023 build |
| 6 | +# https://help.openai.com/en/articles/6825453-chatgpt-release-notes |
| 7 | + |
| 8 | +# https://chat.openai.com/share/f4f94c37-66a1-42d9-879b-9624c13f3e26 |
5 | 9 | """
|
6 | 10 | `adafruit_vcnl4020`
|
7 | 11 | ================================================================================
|
|
16 | 20 |
|
17 | 21 | **Hardware:**
|
18 | 22 |
|
19 |
| -.. todo:: Add links to any specific hardware product page(s), or category page(s). |
20 |
| - Use unordered list & hyperlink rST inline format: "* `Link Text <url>`_" |
| 23 | +* Adafruit VCNL4020 Proximity and Light Sensor <https://www.adafruit.com/product/5810> |
21 | 24 |
|
22 | 25 | **Software and Dependencies:**
|
23 | 26 |
|
24 | 27 | * Adafruit CircuitPython firmware for the supported boards:
|
25 | 28 | https://circuitpython.org/downloads
|
26 | 29 |
|
27 |
| -.. todo:: Uncomment or remove the Bus Device and/or the Register library dependencies |
28 |
| - based on the library's use of either. |
29 |
| -
|
30 |
| -# * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice |
31 |
| -# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register |
| 30 | +* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice |
| 31 | +* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register |
32 | 32 | """
|
33 | 33 |
|
34 |
| -# imports |
| 34 | +from micropython import const |
| 35 | +from adafruit_bus_device.i2c_device import I2CDevice |
| 36 | +from adafruit_register.i2c_struct import Struct, ROUnaryStruct |
| 37 | +from adafruit_register.i2c_bit import ROBit, RWBit |
| 38 | +from adafruit_register.i2c_bits import RWBits |
| 39 | + |
| 40 | +try: |
| 41 | + import typing # pylint: disable=unused-import |
| 42 | + from busio import I2C |
| 43 | +except ImportError: |
| 44 | + pass |
35 | 45 |
|
36 | 46 | __version__ = "0.0.0+auto.0"
|
37 | 47 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VCNL4020.git"
|
| 48 | + |
| 49 | +_I2C_ADDRESS = const(0x13) |
| 50 | +_REG_COMMAND = const(0x80) |
| 51 | +_REG_PRODUCT_ID = const(0x81) |
| 52 | +_REG_PROX_RATE = const(0x82) |
| 53 | +_REG_IR_LED_CURRENT = const(0x83) |
| 54 | +_REG_AMBIENT_PARAM = const(0x84) |
| 55 | +_REG_AMBIENT_RESULT_HIGH = const(0x85) |
| 56 | +_REG_AMBIENT_RESULT_LOW = const(0x86) |
| 57 | +_REG_PROX_RESULT_HIGH = const(0x87) |
| 58 | +_REG_PROX_RESULT_LOW = const(0x88) |
| 59 | +_REG_INT_CTRL = const(0x89) |
| 60 | +_REG_LOW_THRES_HIGH = const(0x8A) |
| 61 | +_REG_LOW_THRES_LOW = const(0x8B) |
| 62 | +_REG_HIGH_THRES_HIGH = const(0x8C) |
| 63 | +_REG_HIGH_THRES_LOW = const(0x8D) |
| 64 | +_REG_INT_STATUS = const(0x8E) |
| 65 | +_REG_PROX_ADJUST = const(0x8F) |
| 66 | +_INT_TH_HI = const(0x01) |
| 67 | +_INT_TH_LOW = const(0x02) |
| 68 | +_INT_ALS_READY = const(0x04) |
| 69 | +_INT_PROX_READY = const(0x08) |
| 70 | + |
| 71 | + |
| 72 | +# pylint: disable=too-many-instance-attributes |
| 73 | +class Adafruit_VCNL4020: |
| 74 | + """Adafruit VCNL4020 Proximity/Ambient Light sensor driver""" |
| 75 | + |
| 76 | + auto_offset_comp = RWBit(_REG_AMBIENT_PARAM, 3) |
| 77 | + """Auto offset compensation for ambient light measurement.""" |
| 78 | + _command_reg = RWBits(8, _REG_COMMAND, 0) |
| 79 | + continuous_conversion = RWBit(_REG_AMBIENT_PARAM, 7) |
| 80 | + """Continuous conversion mode for ambient light measurement.""" |
| 81 | + _int_ctrl_reg = RWBits(8, _REG_INT_CTRL, 0) |
| 82 | + _int_status_reg = RWBits(3, _REG_INT_STATUS, 0) |
| 83 | + _led_current = RWBits(6, _REG_IR_LED_CURRENT, 0) |
| 84 | + _product_revision = ROUnaryStruct(_REG_PRODUCT_ID, "<B") |
| 85 | + lux = ROUnaryStruct(_REG_AMBIENT_RESULT_HIGH, ">H") |
| 86 | + """Reads the ambient light/lux sensor (ALS) measurement result""" |
| 87 | + _lux_averaging = RWBits(3, _REG_AMBIENT_PARAM, 0) |
| 88 | + lux_enabled = RWBit(_REG_COMMAND, 2) |
| 89 | + """Enable/disable lux sensor""" |
| 90 | + lux_on_demand = RWBit(_REG_COMMAND, 4) |
| 91 | + """On-demand setting for lux measurements""" |
| 92 | + _lux_rate = RWBits(3, _REG_AMBIENT_PARAM, 4) |
| 93 | + lux_ready = ROBit(_REG_COMMAND, 6) |
| 94 | + """Status of ambient light data""" |
| 95 | + proximity = ROUnaryStruct(_REG_PROX_RESULT_HIGH, ">H") |
| 96 | + """Reads the proximity measurement result""" |
| 97 | + proximity_enabled = RWBit(_REG_COMMAND, 1) |
| 98 | + """Enable/disable proximity sensor""" |
| 99 | + _proximity_frequency = RWBits(2, _REG_PROX_ADJUST, 3) |
| 100 | + promixity_on_demand = RWBit(_REG_COMMAND, 3) |
| 101 | + """On-demand setting for proximity measurements""" |
| 102 | + _proximity_rate = RWBits(3, _REG_PROX_RATE, 0) |
| 103 | + proximity_ready = ROBit(_REG_COMMAND, 5) |
| 104 | + """Status of proximity data.""" |
| 105 | + low_threshold = Struct(_REG_LOW_THRES_HIGH, ">H") |
| 106 | + """Sets the low threshold for proximity measurement""" |
| 107 | + high_threshold = Struct(_REG_HIGH_THRES_HIGH, ">H") |
| 108 | + """Sets the high threshold for proximity measurement.""" |
| 109 | + _interrupt_count = RWBits(3, _REG_INT_CTRL, 5) |
| 110 | + proximity_interrupt = RWBit(_REG_INT_CTRL, 3) |
| 111 | + """Enable/disable proximity interrupt""" |
| 112 | + lux_interrupt = RWBit(_REG_INT_CTRL, 2) |
| 113 | + """Enable/disable lux interrupt""" |
| 114 | + high_threshold_interrupt = RWBit(_REG_INT_CTRL, 1) |
| 115 | + """Enable/disable proximity high threshold interrupt""" |
| 116 | + low_threshold_interrupt = RWBit(_REG_INT_CTRL, 0) |
| 117 | + """Enable/disable proximity low threshold interrupt""" |
| 118 | + selftimed_enabled = RWBit(_REG_COMMAND, 0) |
| 119 | + """Enable/disable selftimed reading""" |
| 120 | + |
| 121 | + _proximity_rates = [1.95, 3.9, 7.8, 16.6, 31.2, 62.5, 125, 250] |
| 122 | + _lux_rates = [1, 2, 3, 4, 5, 6, 8, 10] |
| 123 | + _avg_samples = [1, 2, 4, 8, 16, 32, 64, 128] |
| 124 | + _int_counts = [1, 2, 4, 8, 16, 32, 64, 128] |
| 125 | + _proximity_frequencies = [390.625, 781.25, 1.5625, 3.125] |
| 126 | + |
| 127 | + def __init__(self, i2c: I2C, addr: int = _I2C_ADDRESS) -> None: |
| 128 | + """ |
| 129 | + Initializes the VCNL4020 sensor and checks for a valid Product ID Revision. |
| 130 | + :param i2c: The I2C interface to use |
| 131 | + :param addr: The I2C address of the VCNL4020, defaults to _I2C_ADDRESS |
| 132 | + """ |
| 133 | + self.i2c_device = I2CDevice(i2c, addr) |
| 134 | + |
| 135 | + # Check the Product ID Revision |
| 136 | + if self._product_revision != 0x21: |
| 137 | + raise RuntimeError(f"Invalid Product ID Revision {self._product_revision}") |
| 138 | + try: |
| 139 | + # Configuration settings |
| 140 | + self.proximity_rate = 250 |
| 141 | + self.led_current = 200 |
| 142 | + self.lux_rate = 10 |
| 143 | + self.lux_averaging = 1 |
| 144 | + except Exception as error: |
| 145 | + raise RuntimeError(f"Failed to initialize: {error}") from error |
| 146 | + |
| 147 | + @property |
| 148 | + def _enable(self) -> bool: |
| 149 | + return self.lux_enabled and self.proximity_enabled and self.selftimed_enabled |
| 150 | + |
| 151 | + @_enable.setter |
| 152 | + def _enable(self, value: bool) -> None: |
| 153 | + self.lux_enabled = value |
| 154 | + self.proximity_enabled = value |
| 155 | + self.selftimed_enabled = value |
| 156 | + |
| 157 | + @property |
| 158 | + def clear_interrupts(self) -> None: |
| 159 | + """ |
| 160 | + Clears the interrupt flags. |
| 161 | +
|
| 162 | + :param value: True to clear all interrupt flags. |
| 163 | + """ |
| 164 | + clear_bits = 0 |
| 165 | + clear_bits |= _INT_PROX_READY |
| 166 | + clear_bits |= _INT_ALS_READY |
| 167 | + clear_bits |= _INT_TH_LOW |
| 168 | + clear_bits |= _INT_TH_HI |
| 169 | + self._int_status_reg |= clear_bits |
| 170 | + |
| 171 | + @property |
| 172 | + def interrupt_count(self) -> int: |
| 173 | + """ |
| 174 | + Interrupt count setting |
| 175 | +
|
| 176 | + :rtype: int |
| 177 | + """ |
| 178 | + return self._int_counts[self._interrupt_count] |
| 179 | + |
| 180 | + @interrupt_count.setter |
| 181 | + def interrupt_count(self, value: int) -> None: |
| 182 | + self._enable = False |
| 183 | + if value not in self._int_counts: |
| 184 | + raise ValueError( |
| 185 | + f"Invalid interrupt count: {value}. Available counts: {self._int_counts}" |
| 186 | + ) |
| 187 | + count = self._int_counts.index(value) |
| 188 | + self._interrupt_count = count |
| 189 | + self._enable = True |
| 190 | + |
| 191 | + @property |
| 192 | + def led_current(self) -> int: |
| 193 | + """ |
| 194 | + The LED current for proximity mode in mA. |
| 195 | +
|
| 196 | + :return: The LED current in mA. |
| 197 | + """ |
| 198 | + return self._led_current * 10 |
| 199 | + |
| 200 | + @led_current.setter |
| 201 | + def led_current(self, value: int) -> None: |
| 202 | + self._enable = False |
| 203 | + self._led_current = value // 10 |
| 204 | + self._enable = True |
| 205 | + |
| 206 | + @property |
| 207 | + def lux_averaging(self) -> int: |
| 208 | + """ |
| 209 | + Ambient averaging sample rate |
| 210 | +
|
| 211 | + :rtype: int |
| 212 | + """ |
| 213 | + return self._avg_samples[self._lux_averaging] |
| 214 | + |
| 215 | + @lux_averaging.setter |
| 216 | + def lux_averaging(self, value: int) -> None: |
| 217 | + self._enable = False |
| 218 | + if value not in self._avg_samples: |
| 219 | + raise ValueError( |
| 220 | + f"Invalid sample rate: {value}. Available sample rates: {self._avg_samples}" |
| 221 | + ) |
| 222 | + sample_rate = self._avg_samples.index(value) |
| 223 | + self._lux_averaging = sample_rate |
| 224 | + self._enable = True |
| 225 | + |
| 226 | + @property |
| 227 | + def lux_rate(self) -> int: |
| 228 | + """ |
| 229 | + Ambient light measurement rate |
| 230 | +
|
| 231 | + :rtype: int |
| 232 | + """ |
| 233 | + return self._lux_rates[self._lux_rate] |
| 234 | + |
| 235 | + @lux_rate.setter |
| 236 | + def lux_rate(self, value: int) -> None: |
| 237 | + self._enable = False |
| 238 | + if value not in self._lux_rates: |
| 239 | + raise ValueError( |
| 240 | + f"Invalid ambient rate: {value}. Available rates: {self._lux_rates}" |
| 241 | + ) |
| 242 | + rate = self._lux_rates.index(value) |
| 243 | + self._lux_rate = rate |
| 244 | + self._enable = True |
| 245 | + |
| 246 | + @property |
| 247 | + def proximity_frequency(self) -> int: |
| 248 | + """ |
| 249 | + Proximity frequency setting |
| 250 | +
|
| 251 | + :rtype: int |
| 252 | + """ |
| 253 | + return self._proximity_frequencies[self._proximity_frequency] |
| 254 | + |
| 255 | + @proximity_frequency.setter |
| 256 | + def proximity_frequency(self, value: int) -> None: |
| 257 | + self._enable = False |
| 258 | + if value not in self._proximity_frequencies: |
| 259 | + raise ValueError( |
| 260 | + f"Invalid frequency: {value}. Available frequencies: {self._proximity_frequencies}" |
| 261 | + ) |
| 262 | + freq = self._proximity_frequencies.index(value) |
| 263 | + self._proximity_frequency = freq |
| 264 | + self._enable = True |
| 265 | + |
| 266 | + @property |
| 267 | + def proximity_rate(self) -> int: |
| 268 | + """ |
| 269 | + Proximity measurement rate |
| 270 | +
|
| 271 | + :rtype: int |
| 272 | + """ |
| 273 | + return self._proximity_rates[self._proximity_rate] |
| 274 | + |
| 275 | + @proximity_rate.setter |
| 276 | + def proximity_rate(self, value: int) -> None: |
| 277 | + self._enable = False |
| 278 | + if value not in self._proximity_rates: |
| 279 | + raise ValueError( |
| 280 | + f"Invalid proximity rate: {value}. Available rates: {self._proximity_rates}" |
| 281 | + ) |
| 282 | + rate = self._proximity_rates.index(value) |
| 283 | + self._proximity_rate = rate |
| 284 | + self._enable = True |
0 commit comments