diff --git a/adafruit_vl53l0x.py b/adafruit_vl53l0x.py index 1dc4bc5..2e38a94 100644 --- a/adafruit_vl53l0x.py +++ b/adafruit_vl53l0x.py @@ -156,6 +156,9 @@ class VL53L0X: # thread safe! _BUFFER = bytearray(3) + # Is VL53L0X is currently continuous mode? (Needed by `range` property) + _continuous_mode = False + def __init__(self, i2c, address=41, io_timeout_s=0): # pylint: disable=too-many-statements self._device = i2c_device.I2CDevice(i2c, address) @@ -527,11 +530,21 @@ def measurement_timing_budget(self, budget_us): @property def range(self): - """Perform a single reading of the range for an object in front of - the sensor and return the distance in millimeters. + """Perform a single (or continuous if `start_continuous` called) + reading of the range for an object in front of the sensor and + return the distance in millimeters. """ - # Adapted from readRangeSingleMillimeters & - # readRangeContinuousMillimeters in pololu code at: + # Adapted from readRangeSingleMillimeters in pololu code at: + # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp + if not self._continuous_mode: + self.do_range_measurement() + return self.read_range() + + def do_range_measurement(self): + """Perform a single reading of the range for an object in front of the + sensor, but without return the distance. + """ + # Adapted from readRangeSingleMillimeters in pololu code at: # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp for pair in ( (0x80, 0x01), @@ -551,6 +564,16 @@ def range(self): and (time.monotonic() - start) >= self.io_timeout_s ): raise RuntimeError("Timeout waiting for VL53L0X!") + + def read_range(self): + """Return a range reading in millimeters. + + Note: Avoid calling this directly. If you do single mode, you need + to call `do_range_measurement` first. Or your program will stuck or + timeout occurred. + """ + # Adapted from readRangeContinuousMillimeters in pololu code at: + # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp start = time.monotonic() while (self._read_u8(_RESULT_INTERRUPT_STATUS) & 0x07) == 0: if ( @@ -564,6 +587,68 @@ def range(self): self._write_u8(_SYSTEM_INTERRUPT_CLEAR, 0x01) return range_mm + @property + def is_continuous_mode(self): + """Is the sensor currently in continuous mode?""" + return self._continuous_mode + + def continuous_mode(self): + """Activate the continuous mode manager""" + return self + + def __enter__(self): + """For continuous mode manager, called when used on `with` keyword""" + self.start_continuous() + return self + + def __exit__(self, exc_type, exc_value, traceback): + """For continuous mode manager, called at the end of `with` scope""" + self.stop_continuous() + + def start_continuous(self): + """Perform a continuous reading of the range for an object in front of + the sensor. + """ + # Adapted from startContinuous in pololu code at: + # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp + for pair in ( + (0x80, 0x01), + (0xFF, 0x01), + (0x00, 0x00), + (0x91, self._stop_variable), + (0x00, 0x01), + (0xFF, 0x00), + (0x80, 0x00), + (_SYSRANGE_START, 0x02), + ): + self._write_u8(pair[0], pair[1]) + start = time.monotonic() + while (self._read_u8(_SYSRANGE_START) & 0x01) > 0: + if ( + self.io_timeout_s > 0 + and (time.monotonic() - start) >= self.io_timeout_s + ): + raise RuntimeError("Timeout waiting for VL53L0X!") + self._continuous_mode = True + + def stop_continuous(self): + """Stop continuous readings.""" + # Adapted from stopContinuous in pololu code at: + # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp + for pair in ( + (_SYSRANGE_START, 0x01), + (0xFF, 0x01), + (0x00, 0x00), + (0x91, 0x00), + (0x00, 0x01), + (0xFF, 0x00), + ): + self._write_u8(pair[0], pair[1]) + self._continuous_mode = False + + # restore the sensor to single ranging mode + self.do_range_measurement() + def set_address(self, new_address): """Set a new I2C address to the instantaited object. This is only called when using multiple VL53L0X sensors on the same I2C bus (SDA & SCL pins). See also the diff --git a/examples/vl53l0x_multiple_sensors_continuous.py b/examples/vl53l0x_multiple_sensors_continuous.py new file mode 100644 index 0000000..622597e --- /dev/null +++ b/examples/vl53l0x_multiple_sensors_continuous.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2021 Smankusors for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +Example of how to use the adafruit_vl53l0x library to change the assigned address of +multiple VL53L0X sensors on the same I2C bus. This example only focuses on 2 VL53L0X +sensors, but can be modified for more. BE AWARE: a multitude of sensors may require +more current than the on-board 3V regulator can output (typical current consumption during +active range readings is about 19 mA per sensor). + +This example like vl53l0x_multiple_sensors, but this with sensors in continuous mode. +So you don't need to wait the sensor to do range measurement and return the distance +for you. + +For example, you have 2 VL53L0X sensors, with timing budget of 200ms, on single mode. +When you want to get distance from sensor #1, sensor #2 will idle because waiting +for sensor #1 completes the range measurement. You could do multithreading so you +can ask both the sensor at the same time, but it's quite expensive. + +When you use continuous mode, the sensor will always do range measurement after it +completes. So when you want to get the distance from both of the device, you don't +need to wait 400ms, just 200ms for both of the sensors. +""" +import time +import board +from digitalio import DigitalInOut +from adafruit_vl53l0x import VL53L0X + +# declare the singleton variable for the default I2C bus +i2c = board.I2C() + +# declare the digital output pins connected to the "SHDN" pin on each VL53L0X sensor +xshut = [ + DigitalInOut(board.D17), + DigitalInOut(board.D18), + # add more VL53L0X sensors by defining their SHDN pins here +] + +for power_pin in xshut: + # make sure these pins are a digital output, not a digital input + power_pin.switch_to_output(value=False) + # These pins are active when Low, meaning: + # if the output signal is LOW, then the VL53L0X sensor is off. + # if the output signal is HIGH, then the VL53L0X sensor is on. +# all VL53L0X sensors are now off + +# initialize a list to be used for the array of VL53L0X sensors +vl53 = [] + +# now change the addresses of the VL53L0X sensors +for i, power_pin in enumerate(xshut): + # turn on the VL53L0X to allow hardware check + power_pin.value = True + # instantiate the VL53L0X sensor on the I2C bus & insert it into the "vl53" list + vl53.insert(i, VL53L0X(i2c)) # also performs VL53L0X hardware check + + # start continous mode + vl53[i].start_continous() + + # you will see the benefit of continous mode if you set the measurement timing + # budget very high. + # vl53[i].measurement_timing_budget = 2000000 + + # no need to change the address of the last VL53L0X sensor + if i < len(xshut) - 1: + # default address is 0x29. Change that to something else + vl53[i].set_address(i + 0x30) # address assigned should NOT be already in use +# there is a helpful list of pre-designated I2C addresses for various I2C devices at +# https://learn.adafruit.com/i2c-addresses/the-list +# According to this list 0x30-0x34 are available, although the list may be incomplete. +# In the python REPR, you can scan for all I2C devices that are attached and detirmine +# their addresses using: +# >>> import board +# >>> i2c = board.I2C() +# >>> if i2c.try_lock(): +# >>> [hex(x) for x in i2c.scan()] +# >>> i2c.unlock() + + +def detect_range(count=5): + """ take count=5 samples """ + while count: + for index, sensor in enumerate(vl53): + print("Sensor {} Range: {}mm".format(index + 1, sensor.range)) + time.sleep(1.0) + count -= 1 + + +def stop_continuous(): + """this is not required, if you use XSHUT to reset the sensor. + unless if you want to save some energy + """ + for sensor in vl53: + sensor.stop_continuous() + + +if __name__ == "__main__": + detect_range() + stop_continuous() +else: + print( + "Multiple VL53L0X sensors' addresses are assigned properly\n" + "execute detect_range() to read each sensors range readings.\n" + "When you are done with readings, execute stop_continuous()\n" + "to stop the continuous mode." + ) diff --git a/examples/vl53l0x_simplecontinuous.py b/examples/vl53l0x_simplecontinuous.py new file mode 100644 index 0000000..ad2daa9 --- /dev/null +++ b/examples/vl53l0x_simplecontinuous.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021 Smankusors for Adafruit Industries +# SPDX-License-Identifier: MIT + +# Simple demo of the VL53L0X distance sensor with continuous mode. +# Will print the sensed range/distance as fast as possible. +import time + +import board +import busio + +import adafruit_vl53l0x + +# Initialize I2C bus and sensor. +i2c = busio.I2C(board.SCL, board.SDA) +vl53 = adafruit_vl53l0x.VL53L0X(i2c) + +# Optionally adjust the measurement timing budget to change speed and accuracy. +# See the example here for more details: +# https://github.com/pololu/vl53l0x-arduino/blob/master/examples/Single/Single.ino +# For example a higher speed but less accurate timing budget of 20ms: +# vl53.measurement_timing_budget = 20000 +# Or a slower but more accurate timing budget of 200ms: +vl53.measurement_timing_budget = 200000 +# The default timing budget is 33ms, a good compromise of speed and accuracy. + +# You will see the benefit of continous mode if you set the measurement timing +# budget very high, while your program doing something else. When your program done +# with something else, and the sensor already calculated the distance, the result +# will return instantly, instead of waiting the sensor measuring first. + +# Main loop will read the range and print it every second. +with vl53.continuous_mode(): + while True: + # try to adjust the sleep time (simulating program doing something else) + # and see how fast the sensor returns the range + time.sleep(0.1) + + curTime = time.time() + print("Range: {0}mm ({1:.2f}ms)".format(vl53.range, time.time() - curTime))