/ adafruit_vl53l0x.py
adafruit_vl53l0x.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_vl53l0x`
 24  ====================================================
 25  
 26  CircuitPython driver for the VL53L0X distance sensor.  This code is adapted
 27  from the pololu driver here:
 28  https://github.com/pololu/vl53l0x-arduino
 29  
 30  See usage in the examples/vl53l0x_simpletest.py file.
 31  
 32  * Author(s): Tony DiCola
 33  
 34  Implementation Notes
 35  --------------------
 36  
 37  **Hardware:**
 38  
 39  * Adafruit `VL53L0X Time of Flight Distance Sensor - ~30 to 1000mm
 40    <https://www.adafruit.com/product/3317>`_ (Product ID: 3317)
 41  
 42  **Software and Dependencies:**
 43  
 44  * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
 45    https://github.com/adafruit/circuitpython/releases
 46  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 47  """
 48  import math
 49  import time
 50  
 51  import adafruit_bus_device.i2c_device as i2c_device
 52  from micropython import const
 53  
 54  __version__ = "0.0.0-auto.0"
 55  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VL53L0X.git"
 56  
 57  # Configuration constants:
 58  # pylint: disable=bad-whitespace
 59  _SYSRANGE_START                              = const(0x00)
 60  _SYSTEM_THRESH_HIGH                          = const(0x0C)
 61  _SYSTEM_THRESH_LOW                           = const(0x0E)
 62  _SYSTEM_SEQUENCE_CONFIG                      = const(0x01)
 63  _SYSTEM_RANGE_CONFIG                         = const(0x09)
 64  _SYSTEM_INTERMEASUREMENT_PERIOD              = const(0x04)
 65  _SYSTEM_INTERRUPT_CONFIG_GPIO                = const(0x0A)
 66  _GPIO_HV_MUX_ACTIVE_HIGH                     = const(0x84)
 67  _SYSTEM_INTERRUPT_CLEAR                      = const(0x0B)
 68  _RESULT_INTERRUPT_STATUS                     = const(0x13)
 69  _RESULT_RANGE_STATUS                         = const(0x14)
 70  _RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN       = const(0xBC)
 71  _RESULT_CORE_RANGING_TOTAL_EVENTS_RTN        = const(0xC0)
 72  _RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF       = const(0xD0)
 73  _RESULT_CORE_RANGING_TOTAL_EVENTS_REF        = const(0xD4)
 74  _RESULT_PEAK_SIGNAL_RATE_REF                 = const(0xB6)
 75  _ALGO_PART_TO_PART_RANGE_OFFSET_MM           = const(0x28)
 76  _I2C_SLAVE_DEVICE_ADDRESS                    = const(0x8A)
 77  _MSRC_CONFIG_CONTROL                         = const(0x60)
 78  _PRE_RANGE_CONFIG_MIN_SNR                    = const(0x27)
 79  _PRE_RANGE_CONFIG_VALID_PHASE_LOW            = const(0x56)
 80  _PRE_RANGE_CONFIG_VALID_PHASE_HIGH           = const(0x57)
 81  _PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT          = const(0x64)
 82  _FINAL_RANGE_CONFIG_MIN_SNR                  = const(0x67)
 83  _FINAL_RANGE_CONFIG_VALID_PHASE_LOW          = const(0x47)
 84  _FINAL_RANGE_CONFIG_VALID_PHASE_HIGH         = const(0x48)
 85  _FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = const(0x44)
 86  _PRE_RANGE_CONFIG_SIGMA_THRESH_HI            = const(0x61)
 87  _PRE_RANGE_CONFIG_SIGMA_THRESH_LO            = const(0x62)
 88  _PRE_RANGE_CONFIG_VCSEL_PERIOD               = const(0x50)
 89  _PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI          = const(0x51)
 90  _PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO          = const(0x52)
 91  _SYSTEM_HISTOGRAM_BIN                        = const(0x81)
 92  _HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT       = const(0x33)
 93  _HISTOGRAM_CONFIG_READOUT_CTRL               = const(0x55)
 94  _FINAL_RANGE_CONFIG_VCSEL_PERIOD             = const(0x70)
 95  _FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI        = const(0x71)
 96  _FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO        = const(0x72)
 97  _CROSSTALK_COMPENSATION_PEAK_RATE_MCPS       = const(0x20)
 98  _MSRC_CONFIG_TIMEOUT_MACROP                  = const(0x46)
 99  _SOFT_RESET_GO2_SOFT_RESET_N                 = const(0xBF)
100  _IDENTIFICATION_MODEL_ID                     = const(0xC0)
101  _IDENTIFICATION_REVISION_ID                  = const(0xC2)
102  _OSC_CALIBRATE_VAL                           = const(0xF8)
103  _GLOBAL_CONFIG_VCSEL_WIDTH                   = const(0x32)
104  _GLOBAL_CONFIG_SPAD_ENABLES_REF_0            = const(0xB0)
105  _GLOBAL_CONFIG_SPAD_ENABLES_REF_1            = const(0xB1)
106  _GLOBAL_CONFIG_SPAD_ENABLES_REF_2            = const(0xB2)
107  _GLOBAL_CONFIG_SPAD_ENABLES_REF_3            = const(0xB3)
108  _GLOBAL_CONFIG_SPAD_ENABLES_REF_4            = const(0xB4)
109  _GLOBAL_CONFIG_SPAD_ENABLES_REF_5            = const(0xB5)
110  _GLOBAL_CONFIG_REF_EN_START_SELECT           = const(0xB6)
111  _DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD         = const(0x4E)
112  _DYNAMIC_SPAD_REF_EN_START_OFFSET            = const(0x4F)
113  _POWER_MANAGEMENT_GO1_POWER_FORCE            = const(0x80)
114  _VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV           = const(0x89)
115  _ALGO_PHASECAL_LIM                           = const(0x30)
116  _ALGO_PHASECAL_CONFIG_TIMEOUT                = const(0x30)
117  _VCSEL_PERIOD_PRE_RANGE   = const(0)
118  _VCSEL_PERIOD_FINAL_RANGE = const(1)
119  # pylint: enable=bad-whitespace
120  
121  
122  def _decode_timeout(val):
123      # format: "(LSByte * 2^MSByte) + 1"
124      return float(val & 0xFF) * math.pow(2.0, ((val & 0xFF00) >> 8)) + 1
125  
126  def _encode_timeout(timeout_mclks):
127      # format: "(LSByte * 2^MSByte) + 1"
128      timeout_mclks = int(timeout_mclks) & 0xFFFF
129      ls_byte = 0
130      ms_byte = 0
131      if timeout_mclks > 0:
132          ls_byte = timeout_mclks - 1
133          while ls_byte > 255:
134              ls_byte >>= 1
135              ms_byte += 1
136          return ((ms_byte << 8) | (ls_byte & 0xFF)) & 0xFFFF
137      return 0
138  
139  def _timeout_mclks_to_microseconds(timeout_period_mclks, vcsel_period_pclks):
140      macro_period_ns = (((2304 * (vcsel_period_pclks) * 1655) + 500) // 1000)
141      return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns // 2)) // 1000
142  
143  def _timeout_microseconds_to_mclks(timeout_period_us, vcsel_period_pclks):
144      macro_period_ns = (((2304 * (vcsel_period_pclks) * 1655) + 500) // 1000)
145      return ((timeout_period_us * 1000) + (macro_period_ns // 2)) // macro_period_ns
146  
147  class VL53L0X:
148      """Driver for the VL53L0X distance sensor."""
149      # Class-level buffer for reading and writing data with the sensor.
150      # This reduces memory allocations but means the code is not re-entrant or
151      # thread safe!
152      _BUFFER = bytearray(3)
153  
154      def __init__(self, i2c, address=41, io_timeout_s=0):
155          # pylint: disable=too-many-statements
156          self._device = i2c_device.I2CDevice(i2c, address)
157          self.io_timeout_s = io_timeout_s
158          # Check identification registers for expected values.
159          # From section 3.2 of the datasheet.
160          if (self._read_u8(0xC0) != 0xEE or self._read_u8(0xC1) != 0xAA or
161                  self._read_u8(0xC2) != 0x10):
162              raise RuntimeError('Failed to find expected ID register values. Check wiring!')
163          # Initialize access to the sensor.  This is based on the logic from:
164          #   https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp
165          # Set I2C standard mode.
166          for pair in ((0x88, 0x00), (0x80, 0x01), (0xFF, 0x01), (0x00, 0x00)):
167              self._write_u8(pair[0], pair[1])
168          self._stop_variable = self._read_u8(0x91)
169          for pair in ((0x00, 0x01), (0xFF, 0x00), (0x80, 0x00)):
170              self._write_u8(pair[0], pair[1])
171          # disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4)
172          # limit checks
173          config_control = self._read_u8(_MSRC_CONFIG_CONTROL) | 0x12
174          self._write_u8(_MSRC_CONFIG_CONTROL, config_control)
175          # set final range signal rate limit to 0.25 MCPS (million counts per
176          # second)
177          self.signal_rate_limit = 0.25
178          self._write_u8(_SYSTEM_SEQUENCE_CONFIG, 0xFF)
179          spad_count, spad_is_aperture = self._get_spad_info()
180          # The SPAD map (RefGoodSpadMap) is read by
181          # VL53L0X_get_info_from_device() in the API, but the same data seems to
182          # be more easily readable from GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through
183          # _6, so read it from there.
184          ref_spad_map = bytearray(7)
185          ref_spad_map[0] = _GLOBAL_CONFIG_SPAD_ENABLES_REF_0
186          with self._device:
187              self._device.write(ref_spad_map, end=1)
188              self._device.readinto(ref_spad_map, start=1)
189  
190          for pair in ((0xFF, 0x01),
191                       (_DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00),
192                       (_DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C),
193                       (0xFF, 0x00),
194                       (_GLOBAL_CONFIG_REF_EN_START_SELECT, 0xB4)):
195              self._write_u8(pair[0], pair[1])
196  
197          first_spad_to_enable = 12 if spad_is_aperture else 0
198          spads_enabled = 0
199          for i in range(48):
200              if i < first_spad_to_enable or spads_enabled == spad_count:
201                  # This bit is lower than the first one that should be enabled,
202                  # or (reference_spad_count) bits have already been enabled, so
203                  # zero this bit.
204                  ref_spad_map[1 + (i // 8)] &= ~(1 << (i % 8))
205              elif (ref_spad_map[1 + (i // 8)] >> (i % 8)) & 0x1 > 0:
206                  spads_enabled += 1
207          with self._device:
208              self._device.write(ref_spad_map)
209          for pair in ((0xFF, 0x01), (0x00, 0x00), (0xFF, 0x00), (0x09, 0x00),
210                       (0x10, 0x00), (0x11, 0x00), (0x24, 0x01), (0x25, 0xFF),
211                       (0x75, 0x00), (0xFF, 0x01), (0x4E, 0x2C), (0x48, 0x00),
212                       (0x30, 0x20), (0xFF, 0x00), (0x30, 0x09), (0x54, 0x00),
213                       (0x31, 0x04), (0x32, 0x03), (0x40, 0x83), (0x46, 0x25),
214                       (0x60, 0x00), (0x27, 0x00), (0x50, 0x06), (0x51, 0x00),
215                       (0x52, 0x96), (0x56, 0x08), (0x57, 0x30), (0x61, 0x00),
216                       (0x62, 0x00), (0x64, 0x00), (0x65, 0x00), (0x66, 0xA0),
217                       (0xFF, 0x01), (0x22, 0x32), (0x47, 0x14), (0x49, 0xFF),
218                       (0x4A, 0x00), (0xFF, 0x00), (0x7A, 0x0A), (0x7B, 0x00),
219                       (0x78, 0x21), (0xFF, 0x01), (0x23, 0x34), (0x42, 0x00),
220                       (0x44, 0xFF), (0x45, 0x26), (0x46, 0x05), (0x40, 0x40),
221                       (0x0E, 0x06), (0x20, 0x1A), (0x43, 0x40), (0xFF, 0x00),
222                       (0x34, 0x03), (0x35, 0x44), (0xFF, 0x01), (0x31, 0x04),
223                       (0x4B, 0x09), (0x4C, 0x05), (0x4D, 0x04), (0xFF, 0x00),
224                       (0x44, 0x00), (0x45, 0x20), (0x47, 0x08), (0x48, 0x28),
225                       (0x67, 0x00), (0x70, 0x04), (0x71, 0x01), (0x72, 0xFE),
226                       (0x76, 0x00), (0x77, 0x00), (0xFF, 0x01), (0x0D, 0x01),
227                       (0xFF, 0x00), (0x80, 0x01), (0x01, 0xF8), (0xFF, 0x01),
228                       (0x8E, 0x01), (0x00, 0x01), (0xFF, 0x00), (0x80, 0x00)):
229              self._write_u8(pair[0], pair[1])
230  
231          self._write_u8(_SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04)
232          gpio_hv_mux_active_high = self._read_u8(_GPIO_HV_MUX_ACTIVE_HIGH)
233          self._write_u8(_GPIO_HV_MUX_ACTIVE_HIGH,
234                         gpio_hv_mux_active_high & ~0x10) # active low
235          self._write_u8(_SYSTEM_INTERRUPT_CLEAR, 0x01)
236          self._measurement_timing_budget_us = self.measurement_timing_budget
237          self._write_u8(_SYSTEM_SEQUENCE_CONFIG, 0xE8)
238          self.measurement_timing_budget = self._measurement_timing_budget_us
239          self._write_u8(_SYSTEM_SEQUENCE_CONFIG, 0x01)
240          self._perform_single_ref_calibration(0x40)
241          self._write_u8(_SYSTEM_SEQUENCE_CONFIG, 0x02)
242          self._perform_single_ref_calibration(0x00)
243          # "restore the previous Sequence Config"
244          self._write_u8(_SYSTEM_SEQUENCE_CONFIG, 0xE8)
245  
246      def _read_u8(self, address):
247          # Read an 8-bit unsigned value from the specified 8-bit address.
248          with self._device:
249              self._BUFFER[0] = address & 0xFF
250              self._device.write(self._BUFFER, end=1)
251              self._device.readinto(self._BUFFER, end=1)
252          return self._BUFFER[0]
253  
254      def _read_u16(self, address):
255          # Read a 16-bit BE unsigned value from the specified 8-bit address.
256          with self._device:
257              self._BUFFER[0] = address & 0xFF
258              self._device.write(self._BUFFER, end=1)
259              self._device.readinto(self._BUFFER)
260          return (self._BUFFER[0] << 8) | self._BUFFER[1]
261  
262      def _write_u8(self, address, val):
263          # Write an 8-bit unsigned value to the specified 8-bit address.
264          with self._device:
265              self._BUFFER[0] = address & 0xFF
266              self._BUFFER[1] = val & 0xFF
267              self._device.write(self._BUFFER, end=2)
268  
269      def _write_u16(self, address, val):
270          # Write a 16-bit BE unsigned value to the specified 8-bit address.
271          with self._device:
272              self._BUFFER[0] = address & 0xFF
273              self._BUFFER[1] = (val >> 8) & 0xFF
274              self._BUFFER[2] = val & 0xFF
275              self._device.write(self._BUFFER)
276  
277  
278      def _get_spad_info(self):
279          # Get reference SPAD count and type, returned as a 2-tuple of
280          # count and boolean is_aperture.  Based on code from:
281          #   https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp
282          for pair in ((0x80, 0x01), (0xFF, 0x01), (0x00, 0x00), (0xFF, 0x06)):
283              self._write_u8(pair[0], pair[1])
284          self._write_u8(0x83, self._read_u8(0x83) | 0x04)
285          for pair in ((0xFF, 0x07), (0x81, 0x01), (0x80, 0x01),
286                       (0x94, 0x6b), (0x83, 0x00)):
287              self._write_u8(pair[0], pair[1])
288          start = time.monotonic()
289          while self._read_u8(0x83) == 0x00:
290              if self.io_timeout_s > 0 and \
291                 (time.monotonic() - start) >= self.io_timeout_s:
292                  raise RuntimeError('Timeout waiting for VL53L0X!')
293          self._write_u8(0x83, 0x01)
294          tmp = self._read_u8(0x92)
295          count = tmp & 0x7F
296          is_aperture = ((tmp >> 7) & 0x01) == 1
297          for pair in ((0x81, 0x00), (0xFF, 0x06)):
298              self._write_u8(pair[0], pair[1])
299          self._write_u8(0x83, self._read_u8(0x83) & ~0x04)
300          for pair in ((0xFF, 0x01), (0x00, 0x01), (0xFF, 0x00), (0x80, 0x00)):
301              self._write_u8(pair[0], pair[1])
302          return (count, is_aperture)
303  
304      def _perform_single_ref_calibration(self, vhv_init_byte):
305          # based on VL53L0X_perform_single_ref_calibration() from ST API.
306          self._write_u8(_SYSRANGE_START, 0x01 | vhv_init_byte & 0xFF)
307          start = time.monotonic()
308          while (self._read_u8(_RESULT_INTERRUPT_STATUS) & 0x07) == 0:
309              if self.io_timeout_s > 0 and \
310                 (time.monotonic() - start) >= self.io_timeout_s:
311                  raise RuntimeError('Timeout waiting for VL53L0X!')
312          self._write_u8(_SYSTEM_INTERRUPT_CLEAR, 0x01)
313          self._write_u8(_SYSRANGE_START, 0x00)
314  
315      def _get_vcsel_pulse_period(self, vcsel_period_type):
316          # pylint: disable=no-else-return
317          # Disable should be removed when refactor can be tested
318          if vcsel_period_type == _VCSEL_PERIOD_PRE_RANGE:
319              val = self._read_u8(_PRE_RANGE_CONFIG_VCSEL_PERIOD)
320              return (((val) + 1) & 0xFF) << 1
321          elif vcsel_period_type == _VCSEL_PERIOD_FINAL_RANGE:
322              val = self._read_u8(_FINAL_RANGE_CONFIG_VCSEL_PERIOD)
323              return (((val) + 1) & 0xFF) << 1
324          return 255
325  
326      def _get_sequence_step_enables(self):
327          # based on VL53L0X_GetSequenceStepEnables() from ST API
328          sequence_config = self._read_u8(_SYSTEM_SEQUENCE_CONFIG)
329          # pylint: disable=bad-whitespace
330          tcc         = (sequence_config >> 4) & 0x1 > 0
331          dss         = (sequence_config >> 3) & 0x1 > 0
332          msrc        = (sequence_config >> 2) & 0x1 > 0
333          pre_range   = (sequence_config >> 6) & 0x1 > 0
334          final_range = (sequence_config >> 7) & 0x1 > 0
335          return (tcc, dss, msrc, pre_range, final_range)
336  
337      def _get_sequence_step_timeouts(self, pre_range):
338          # based on get_sequence_step_timeout() from ST API but modified by
339          # pololu here:
340          #   https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp
341          pre_range_vcsel_period_pclks = self._get_vcsel_pulse_period(_VCSEL_PERIOD_PRE_RANGE)
342          msrc_dss_tcc_mclks = (self._read_u8(_MSRC_CONFIG_TIMEOUT_MACROP) + 1) & 0xFF
343          msrc_dss_tcc_us = _timeout_mclks_to_microseconds(
344              msrc_dss_tcc_mclks, pre_range_vcsel_period_pclks)
345          pre_range_mclks = _decode_timeout(self._read_u16(_PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI))
346          pre_range_us = _timeout_mclks_to_microseconds(pre_range_mclks, pre_range_vcsel_period_pclks)
347          final_range_vcsel_period_pclks = self._get_vcsel_pulse_period(_VCSEL_PERIOD_FINAL_RANGE)
348          final_range_mclks = _decode_timeout(self._read_u16(_FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI))
349          if pre_range:
350              final_range_mclks -= pre_range_mclks
351          final_range_us = _timeout_mclks_to_microseconds(
352              final_range_mclks, final_range_vcsel_period_pclks)
353          return (msrc_dss_tcc_us,
354                  pre_range_us,
355                  final_range_us,
356                  final_range_vcsel_period_pclks,
357                  pre_range_mclks)
358  
359      @property
360      def signal_rate_limit(self):
361          """The signal rate limit in mega counts per second."""
362          val = self._read_u16(_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT)
363          # Return value converted from 16-bit 9.7 fixed point to float.
364          return val / (1 << 7)
365  
366      @signal_rate_limit.setter
367      def signal_rate_limit(self, val):
368          assert 0.0 <= val <= 511.99
369          # Convert to 16-bit 9.7 fixed point value from a float.
370          val = int(val * (1 << 7))
371          self._write_u16(_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, val)
372  
373      @property
374      def measurement_timing_budget(self):
375          """The measurement timing budget in microseconds."""
376          budget_us = 1910 + 960  # Start overhead + end overhead.
377          tcc, dss, msrc, pre_range, final_range = self._get_sequence_step_enables()
378          step_timeouts = self._get_sequence_step_timeouts(pre_range)
379          msrc_dss_tcc_us, pre_range_us, final_range_us, _, _ = step_timeouts
380          if tcc:
381              budget_us += (msrc_dss_tcc_us + 590)
382          if dss:
383              budget_us += 2*(msrc_dss_tcc_us + 690)
384          elif msrc:
385              budget_us += (msrc_dss_tcc_us + 660)
386          if pre_range:
387              budget_us += (pre_range_us + 660)
388          if final_range:
389              budget_us += (final_range_us + 550)
390          self._measurement_timing_budget_us = budget_us
391          return budget_us
392  
393      @measurement_timing_budget.setter
394      def measurement_timing_budget(self, budget_us):
395          # pylint: disable=too-many-locals
396          assert budget_us >= 20000
397          used_budget_us = 1320 + 960  # Start (diff from get) + end overhead
398          tcc, dss, msrc, pre_range, final_range = self._get_sequence_step_enables()
399          step_timeouts = self._get_sequence_step_timeouts(pre_range)
400          msrc_dss_tcc_us, pre_range_us, _ = step_timeouts[:3]
401          final_range_vcsel_period_pclks, pre_range_mclks = step_timeouts[3:]
402          if tcc:
403              used_budget_us += (msrc_dss_tcc_us + 590)
404          if dss:
405              used_budget_us += 2*(msrc_dss_tcc_us + 690)
406          elif msrc:
407              used_budget_us += (msrc_dss_tcc_us + 660)
408          if pre_range:
409              used_budget_us += (pre_range_us + 660)
410          if final_range:
411              used_budget_us += 550
412              # "Note that the final range timeout is determined by the timing
413              # budget and the sum of all other timeouts within the sequence.
414              # If there is no room for the final range timeout, then an error
415              # will be set. Otherwise the remaining time will be applied to
416              # the final range."
417              if used_budget_us > budget_us:
418                  raise ValueError('Requested timeout too big.')
419              final_range_timeout_us = budget_us - used_budget_us
420              final_range_timeout_mclks = _timeout_microseconds_to_mclks(
421                  final_range_timeout_us,
422                  final_range_vcsel_period_pclks)
423              if pre_range:
424                  final_range_timeout_mclks += pre_range_mclks
425              self._write_u16(_FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI,
426                              _encode_timeout(final_range_timeout_mclks))
427              self._measurement_timing_budget_us = budget_us
428  
429      @property
430      def range(self):
431          """Perform a single reading of the range for an object in front of
432          the sensor and return the distance in millimeters.
433          """
434          # Adapted from readRangeSingleMillimeters &
435          # readRangeContinuousMillimeters in pololu code at:
436          #   https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp
437          for pair in ((0x80, 0x01), (0xFF, 0x01), (0x00, 0x00),
438                       (0x91, self._stop_variable), (0x00, 0x01), (0xFF, 0x00),
439                       (0x80, 0x00), (_SYSRANGE_START, 0x01)):
440              self._write_u8(pair[0], pair[1])
441          start = time.monotonic()
442          while (self._read_u8(_SYSRANGE_START) & 0x01) > 0:
443              if self.io_timeout_s > 0 and \
444                 (time.monotonic() - start) >= self.io_timeout_s:
445                  raise RuntimeError('Timeout waiting for VL53L0X!')
446          start = time.monotonic()
447          while (self._read_u8(_RESULT_INTERRUPT_STATUS) & 0x07) == 0:
448              if self.io_timeout_s > 0 and \
449                 (time.monotonic() - start) >= self.io_timeout_s:
450                  raise RuntimeError('Timeout waiting for VL53L0X!')
451          # assumptions: Linearity Corrective Gain is 1000 (default)
452          # fractional ranging is not enabled
453          range_mm = self._read_u16(_RESULT_RANGE_STATUS + 10)
454          self._write_u8(_SYSTEM_INTERRUPT_CLEAR, 0x01)
455          return range_mm
456  
457      def set_address(self, new_address):
458          """Set a new I2C address to the instantaited object. This is only called when using
459          multiple VL53L0X sensors on the same I2C bus (SDA & SCL pins). See also the
460          `example <examples.html#multiple-vl53l0x-on-same-i2c-bus>`_ for proper usage.
461  
462          :param int new_address: The 7-bit `int` that is to be assigned to the VL53L0X sensor.
463              The address that is assigned should NOT be already in use by another device on the
464              I2C bus.
465  
466          .. important:: To properly set the address to an individual VL53L0X sensor, you must
467              first ensure that all other VL53L0X sensors (using the default address of ``0x29``)
468              on the same I2C bus are in their off state by pulling the "SHDN" pins LOW. When the
469              "SHDN" pin is pulled HIGH again the default I2C address is ``0x29``.
470          """
471          self._write_u8(_I2C_SLAVE_DEVICE_ADDRESS, new_address & 0x7f)
472          self._device.device_address = new_address