Skip to content

Implements continuous mode #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 89 additions & 4 deletions adafruit_vl53l0x.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand All @@ -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 (
Expand All @@ -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
Expand Down
106 changes: 106 additions & 0 deletions examples/vl53l0x_multiple_sensors_continuous.py
Original file line number Diff line number Diff line change
@@ -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."
)
39 changes: 39 additions & 0 deletions examples/vl53l0x_simplecontinuous.py
Original file line number Diff line number Diff line change
@@ -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))