/ adafruit_vl6180x.py
adafruit_vl6180x.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Tony DiCola 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 13 # all 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 21 # THE SOFTWARE. 22 """ 23 `adafruit_vl6180x` 24 ==================================================== 25 26 CircuitPython module for the VL6180X distance sensor. See 27 examples/simpletest.py for a demo of the usage. 28 29 * Author(s): Tony DiCola 30 31 Implementation Notes 32 -------------------- 33 34 **Hardware:** 35 36 * Adafruit `VL6180X Time of Flight Distance Ranging Sensor (VL6180) 37 <https://www.adafruit.com/product/3316>`_ (Product ID: 3316) 38 39 **Software and Dependencies:** 40 41 * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: 42 https://github.com/adafruit/circuitpython/releases 43 * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice 44 """ 45 from micropython import const 46 47 import adafruit_bus_device.i2c_device as i2c_device 48 49 50 __version__ = "0.0.0-auto.0" 51 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VL6180X.git" 52 53 54 # pylint: disable=bad-whitespace 55 # Internal constants: 56 _VL6180X_DEFAULT_I2C_ADDR = const(0x29) 57 _VL6180X_REG_IDENTIFICATION_MODEL_ID = const(0x000) 58 _VL6180X_REG_SYSTEM_INTERRUPT_CONFIG = const(0x014) 59 _VL6180X_REG_SYSTEM_INTERRUPT_CLEAR = const(0x015) 60 _VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET = const(0x016) 61 _VL6180X_REG_SYSRANGE_START = const(0x018) 62 _VL6180X_REG_SYSALS_START = const(0x038) 63 _VL6180X_REG_SYSALS_ANALOGUE_GAIN = const(0x03F) 64 _VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI = const(0x040) 65 _VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO = const(0x041) 66 _VL6180X_REG_RESULT_ALS_VAL = const(0x050) 67 _VL6180X_REG_RESULT_RANGE_VAL = const(0x062) 68 _VL6180X_REG_RESULT_RANGE_STATUS = const(0x04D) 69 _VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO = const(0x04F) 70 71 # User-facing constants: 72 ALS_GAIN_1 = const(0x06) 73 ALS_GAIN_1_25 = const(0x05) 74 ALS_GAIN_1_67 = const(0x04) 75 ALS_GAIN_2_5 = const(0x03) 76 ALS_GAIN_5 = const(0x02) 77 ALS_GAIN_10 = const(0x01) 78 ALS_GAIN_20 = const(0x00) 79 ALS_GAIN_40 = const(0x07) 80 81 ERROR_NONE = const(0) 82 ERROR_SYSERR_1 = const(1) 83 ERROR_SYSERR_5 = const(5) 84 ERROR_ECEFAIL = const(6) 85 ERROR_NOCONVERGE = const(7) 86 ERROR_RANGEIGNORE = const(8) 87 ERROR_SNR = const(11) 88 ERROR_RAWUFLOW = const(12) 89 ERROR_RAWOFLOW = const(13) 90 ERROR_RANGEUFLOW = const(14) 91 ERROR_RANGEOFLOW = const(15) 92 # pylint: enable=bad-whitespace 93 94 95 class VL6180X: 96 """Create an instance of the VL6180X distance sensor. You must pass in 97 the following parameters: 98 99 :param i2c: An instance of the I2C bus connected to the sensor. 100 101 Optionally you can specify: 102 103 :param address: The I2C address of the sensor. If not specified the sensor's 104 default value will be assumed. 105 """ 106 107 def __init__(self, i2c, address=_VL6180X_DEFAULT_I2C_ADDR): 108 self._device = i2c_device.I2CDevice(i2c, address) 109 if self._read_8(_VL6180X_REG_IDENTIFICATION_MODEL_ID) != 0xB4: 110 raise RuntimeError("Could not find VL6180X, is it connected and powered?") 111 self._load_settings() 112 self._write_8(_VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET, 0x00) 113 114 @property 115 def range(self): 116 """Read the range of an object in front of sensor and return it in mm.""" 117 # wait for device to be ready for range measurement 118 while not self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) & 0x01: 119 pass 120 # Start a range measurement 121 self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01) 122 # Poll until bit 2 is set 123 while not self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) & 0x04: 124 pass 125 # read range in mm 126 range_ = self._read_8(_VL6180X_REG_RESULT_RANGE_VAL) 127 # clear interrupt 128 self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07) 129 return range_ 130 131 def read_lux(self, gain): 132 """Read the lux (light value) from the sensor and return it. Must 133 specify the gain value to use for the lux reading: 134 - ALS_GAIN_1 = 1x 135 - ALS_GAIN_1_25 = 1.25x 136 - ALS_GAIN_1_67 = 1.67x 137 - ALS_GAIN_2_5 = 2.5x 138 - ALS_GAIN_5 = 5x 139 - ALS_GAIN_10 = 10x 140 - ALS_GAIN_20 = 20x 141 - ALS_GAIN_40 = 40x 142 """ 143 reg = self._read_8(_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG) 144 reg &= ~0x38 145 reg |= 0x4 << 3 # IRQ on ALS ready 146 self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG, reg) 147 # 100 ms integration period 148 self._write_8(_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI, 0) 149 self._write_8(_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO, 100) 150 # analog gain 151 if gain > ALS_GAIN_40: 152 gain = ALS_GAIN_40 153 self._write_8(_VL6180X_REG_SYSALS_ANALOGUE_GAIN, 0x40 | gain) 154 # start ALS 155 self._write_8(_VL6180X_REG_SYSALS_START, 0x1) 156 # Poll until "New Sample Ready threshold event" is set 157 while ( 158 (self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) >> 3) & 0x7 159 ) != 4: 160 pass 161 # read lux! 162 lux = self._read_16(_VL6180X_REG_RESULT_ALS_VAL) 163 # clear interrupt 164 self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07) 165 lux *= 0.32 # calibrated count/lux 166 if gain == ALS_GAIN_1: 167 pass 168 elif gain == ALS_GAIN_1_25: 169 lux /= 1.25 170 elif gain == ALS_GAIN_1_67: 171 lux /= 1.76 172 elif gain == ALS_GAIN_2_5: 173 lux /= 2.5 174 elif gain == ALS_GAIN_5: 175 lux /= 5 176 elif gain == ALS_GAIN_10: 177 lux /= 10 178 elif gain == ALS_GAIN_20: 179 lux /= 20 180 elif gain == ALS_GAIN_40: 181 lux /= 20 182 lux *= 100 183 lux /= 100 # integration time in ms 184 return lux 185 186 @property 187 def range_status(self): 188 """Retrieve the status/error from a previous range read. This will 189 return a constant value such as: 190 191 - ERROR_NONE - No error 192 - ERROR_SYSERR_1 - System error 1 (see datasheet) 193 - ERROR_SYSERR_5 - System error 5 (see datasheet) 194 - ERROR_ECEFAIL - ECE failure 195 - ERROR_NOCONVERGE - No convergence 196 - ERROR_RANGEIGNORE - Outside range ignored 197 - ERROR_SNR - Too much noise 198 - ERROR_RAWUFLOW - Raw value underflow 199 - ERROR_RAWOFLOW - Raw value overflow 200 - ERROR_RANGEUFLOW - Range underflow 201 - ERROR_RANGEOFLOW - Range overflow 202 """ 203 return self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) >> 4 204 205 def _load_settings(self): 206 # private settings from page 24 of app note 207 self._write_8(0x0207, 0x01) 208 self._write_8(0x0208, 0x01) 209 self._write_8(0x0096, 0x00) 210 self._write_8(0x0097, 0xFD) 211 self._write_8(0x00E3, 0x00) 212 self._write_8(0x00E4, 0x04) 213 self._write_8(0x00E5, 0x02) 214 self._write_8(0x00E6, 0x01) 215 self._write_8(0x00E7, 0x03) 216 self._write_8(0x00F5, 0x02) 217 self._write_8(0x00D9, 0x05) 218 self._write_8(0x00DB, 0xCE) 219 self._write_8(0x00DC, 0x03) 220 self._write_8(0x00DD, 0xF8) 221 self._write_8(0x009F, 0x00) 222 self._write_8(0x00A3, 0x3C) 223 self._write_8(0x00B7, 0x00) 224 self._write_8(0x00BB, 0x3C) 225 self._write_8(0x00B2, 0x09) 226 self._write_8(0x00CA, 0x09) 227 self._write_8(0x0198, 0x01) 228 self._write_8(0x01B0, 0x17) 229 self._write_8(0x01AD, 0x00) 230 self._write_8(0x00FF, 0x05) 231 self._write_8(0x0100, 0x05) 232 self._write_8(0x0199, 0x05) 233 self._write_8(0x01A6, 0x1B) 234 self._write_8(0x01AC, 0x3E) 235 self._write_8(0x01A7, 0x1F) 236 self._write_8(0x0030, 0x00) 237 # Recommended : Public registers - See data sheet for more detail 238 self._write_8(0x0011, 0x10) # Enables polling for 'New Sample ready' 239 # when measurement completes 240 self._write_8(0x010A, 0x30) # Set the averaging sample period 241 # (compromise between lower noise and 242 # increased execution time) 243 self._write_8(0x003F, 0x46) # Sets the light and dark gain (upper 244 # nibble). Dark gain should not be 245 # changed. 246 self._write_8(0x0031, 0xFF) # sets the # of range measurements after 247 # which auto calibration of system is 248 # performed 249 self._write_8(0x0040, 0x63) # Set ALS integration time to 100ms 250 self._write_8(0x002E, 0x01) # perform a single temperature calibration 251 # of the ranging sensor 252 253 # Optional: Public registers - See data sheet for more detail 254 self._write_8(0x001B, 0x09) # Set default ranging inter-measurement 255 # period to 100ms 256 self._write_8(0x003E, 0x31) # Set default ALS inter-measurement period 257 # to 500ms 258 self._write_8(0x0014, 0x24) # Configures interrupt on 'New Sample 259 # Ready threshold event' 260 261 def _write_8(self, address, data): 262 # Write 1 byte of data from the specified 16-bit register address. 263 with self._device: 264 self._device.write(bytes([(address >> 8) & 0xFF, address & 0xFF, data])) 265 266 def _write_16(self, address, data): 267 # Write a 16-bit big endian value to the specified 16-bit register 268 # address. 269 with self._device as i2c: 270 i2c.write( 271 bytes( 272 [ 273 (address >> 8) & 0xFF, 274 address & 0xFF, 275 (data >> 8) & 0xFF, 276 data & 0xFF, 277 ] 278 ) 279 ) 280 281 def _read_8(self, address): 282 # Read and return a byte from the specified 16-bit register address. 283 with self._device as i2c: 284 result = bytearray(1) 285 i2c.write(bytes([(address >> 8) & 0xFF, address & 0xFF])) 286 i2c.readinto(result) 287 return result[0] 288 289 def _read_16(self, address): 290 # Read and return a 16-bit unsigned big endian value read from the 291 # specified 16-bit register address. 292 with self._device as i2c: 293 result = bytearray(2) 294 i2c.write(bytes([(address >> 8) & 0xFF, address & 0xFF])) 295 i2c.readinto(result) 296 return (result[0] << 8) | result[1]