/ 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]