diff --git a/adafruit_vl6180x.py b/adafruit_vl6180x.py index 93482dc..ea14a44 100644 --- a/adafruit_vl6180x.py +++ b/adafruit_vl6180x.py @@ -9,7 +9,7 @@ CircuitPython module for the VL6180X distance sensor. See examples/simpletest.py for a demo of the usage. -* Author(s): Tony DiCola +* Author(s): Tony DiCola, Jonas Schatz Implementation Notes -------------------- @@ -26,12 +26,14 @@ * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice """ import struct +import time + from micropython import const from adafruit_bus_device import i2c_device try: - import typing # pylint: disable=unused-import + from typing import Optional, List from busio import I2C except ImportError: pass @@ -40,23 +42,31 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VL6180X.git" - -# Internal constants: -_VL6180X_DEFAULT_I2C_ADDR = const(0x29) +# Registers _VL6180X_REG_IDENTIFICATION_MODEL_ID = const(0x000) + +_VL6180X_REG_SYSTEM_HISTORY_CTRL = const(0x012) _VL6180X_REG_SYSTEM_INTERRUPT_CONFIG = const(0x014) _VL6180X_REG_SYSTEM_INTERRUPT_CLEAR = const(0x015) _VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET = const(0x016) + _VL6180X_REG_SYSRANGE_START = const(0x018) +_VL6180X_REG_SYSRANGE_INTERMEASUREMENT_PERIOD = const(0x01B) +_VL6180X_REG_SYSRANGE_PART_TO_PART_RANGE_OFFSET = const(0x024) + _VL6180X_REG_SYSALS_START = const(0x038) _VL6180X_REG_SYSALS_ANALOGUE_GAIN = const(0x03F) _VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI = const(0x040) _VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO = const(0x041) -_VL6180X_REG_RESULT_ALS_VAL = const(0x050) -_VL6180X_REG_RESULT_RANGE_VAL = const(0x062) + _VL6180X_REG_RESULT_RANGE_STATUS = const(0x04D) _VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO = const(0x04F) -_VL6180X_REG_SYSRANGE_PART_TO_PART_RANGE_OFFSET = const(0x024) +_VL6180X_REG_RESULT_ALS_VAL = const(0x050) +_VL6180X_REG_RESULT_HISTORY_BUFFER_0 = const(0x052) +_VL6180X_REG_RESULT_RANGE_VAL = const(0x062) + +# Internal constants: +_VL6180X_DEFAULT_I2C_ADDR = const(0x29) # User-facing constants: ALS_GAIN_1 = const(0x06) @@ -82,7 +92,7 @@ class VL6180X: - """Create an instance of the VL6180X distance sensor. You must pass in + """Create an instance of the VL6180X distance sensor. You must pass in the following parameters: :param i2c: An instance of the I2C bus connected to the sensor. @@ -103,22 +113,85 @@ def __init__( self._write_8(_VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET, 0x00) self.offset = offset + # Reset a sensor that crashed while in continuous mode + if self.continuous_mode_enabled: + self.stop_range_continuous() + time.sleep(0.1) + + # Activate history buffer for range measurement + self._write_8(_VL6180X_REG_SYSTEM_HISTORY_CTRL, 0x01) + @property def range(self) -> int: """Read the range of an object in front of sensor and return it in mm.""" - # wait for device to be ready for range measurement - while not self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) & 0x01: - pass - # Start a range measurement - self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01) - # Poll until bit 2 is set - while not self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) & 0x04: - pass - # read range in mm - range_ = self._read_8(_VL6180X_REG_RESULT_RANGE_VAL) - # clear interrupt - self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07) - return range_ + if self.continuous_mode_enabled: + return self._read_range_continuous() + return self._read_range_single() + + @property + def range_from_history(self) -> Optional[int]: + """Read the latest range data from history + To do so, you don't have to wait for a complete measurement.""" + + if not self.range_history_enabled: + return None + + return self._read_8(_VL6180X_REG_RESULT_HISTORY_BUFFER_0) + + @property + def ranges_from_history(self) -> Optional[List[int]]: + """ Read the last 16 range measurements from history """ + + if not self.range_history_enabled: + return None + + return [ + self._read_8(_VL6180X_REG_RESULT_HISTORY_BUFFER_0 + age) + for age in range(16) + ] + + @property + def range_history_enabled(self) -> bool: + """ Checks if history buffer stores range data """ + + history_ctrl: int = self._read_8(_VL6180X_REG_SYSTEM_HISTORY_CTRL) + + if history_ctrl & 0x0: + print("History buffering not enabled") + return False + + if (history_ctrl > 1) & 0x1: + print("History buffer stores ALS data, not range") + return False + + return True + + def start_range_continuous(self, period: int = 100) -> None: + """Start continuous range mode + :param period: Time delay between measurements in ms + """ + # Set range between measurements + period_reg: int = 0 + if period > 10: + if period < 2250: + period_reg = (period // 10) - 1 + else: + period_reg = 254 + self._write_8(_VL6180X_REG_SYSRANGE_INTERMEASUREMENT_PERIOD, period_reg) + + # Start continuous range measurement + self._write_8(_VL6180X_REG_SYSRANGE_START, 0x03) + + def stop_range_continuous(self) -> None: + """Stop continuous range mode. It is advised to wait for about 0.3s + afterwards to avoid issues with the interrupt flags""" + if self.continuous_mode_enabled: + self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01) + + @property + def continuous_mode_enabled(self) -> bool: + """ Checks if continuous mode is enabled """ + return self._read_8(_VL6180X_REG_SYSRANGE_START) > 1 & 0x1 @property def offset(self) -> int: @@ -132,6 +205,28 @@ def offset(self, offset: int) -> None: ) self._offset = offset + def _read_range_single(self) -> int: + """ Read the range when in single-shot mode""" + while not self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) & 0x01: + pass + self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01) + return self._read_range_continuous() + + def _read_range_continuous(self) -> int: + """ Read the range when in continuous mode""" + + # Poll until bit 2 is set + while not self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) & 0x04: + pass + + # read range in mm + range_ = self._read_8(_VL6180X_REG_RESULT_RANGE_VAL) + + # clear interrupt + self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07) + + return range_ + def read_lux(self, gain: int) -> float: """Read the lux (light value) from the sensor and return it. Must specify the gain value to use for the lux reading: diff --git a/docs/examples.rst b/docs/examples.rst index 5856981..c348e59 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,4 +1,4 @@ -Simple test +Simple Test ------------ Ensure your device works with this simple test. @@ -6,3 +6,43 @@ Ensure your device works with this simple test. .. literalinclude:: ../examples/vl6180x_simpletest.py :caption: examples/vl6180x_simpletest.py :linenos: + + +Calibration Test +----------------- + +Demo of calibrating the part to part range offset per Application Note 4545 for the VL6180X sensor. + +.. literalinclude:: ../examples/vl6180x_calibrationtest.py + :caption: examples/vl6180x_calibrationtest.py + :linenos: + + +Continuous Test +---------------- + +Demo of reading the range from the VL6180x distance sensor in continuous mode. + +.. literalinclude:: ../examples/vl6180x_continuoustest.py + :caption: examples/vl6180x_continuoustest.py + :linenos: + + +History Test +------------- + +Demo of reading the range from the history buffer of the VL6180x distance sensor. + +.. literalinclude:: ../examples/vl6180x_historytest.py + :caption: examples/vl6180x_historytest.py + :linenos: + + +Performance Test +----------------- + +Demo of reading the range from the VL6180x distance sensor in different access modes (single shot, continuous, history). + +.. literalinclude:: ../examples/vl6180x_performancetest.py + :caption: examples/vl6180x_performancetest.py + :linenos: diff --git a/examples/vl6180x_calibrationtest.py b/examples/vl6180x_calibrationtest.py index 8289b71..c3e5630 100644 --- a/examples/vl6180x_calibrationtest.py +++ b/examples/vl6180x_calibrationtest.py @@ -19,7 +19,7 @@ sensor = adafruit_vl6180x.VL6180X(i2c, offset=0) # Place a target at 50mm away from VL6180X Collect a number of range measurements -# with the target in place and calculate mean of the range reseults. For a +# with the target in place and calculate mean of the range results. For a # reliable measurement, take at least 10 measurements. measurements = [] for msmt in range(10): diff --git a/examples/vl6180x_continuoustest.py b/examples/vl6180x_continuoustest.py new file mode 100644 index 0000000..d638915 --- /dev/null +++ b/examples/vl6180x_continuoustest.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2018 Jonas Schatz +# SPDX-License-Identifier: MIT + +# Demo of reading the range from the VL6180x distance sensor in +# continuous mode + +import time + +import board +import busio + +import adafruit_vl6180x + + +# Create I2C bus. +i2c = busio.I2C(board.SCL, board.SDA) + +# Create sensor instance. +sensor = adafruit_vl6180x.VL6180X(i2c) + +# Starting continuous mode +print("Starting continuous mode") +sensor.start_range_continuous(20) + +# Main loop prints the range and lux every 0.01 seconds +for _ in range(100): + # Read the range in millimeters and print it. + range_mm = sensor.range + print("Range: {0}mm".format(range_mm)) + + # Delay for 10 ms + time.sleep(0.01) + +# Stop continuous mode. This is advised as the sensor +# wouldn't stop measuring after the program has ended +sensor.stop_range_continuous() diff --git a/examples/vl6180x_historytest.py b/examples/vl6180x_historytest.py new file mode 100644 index 0000000..e2e1a46 --- /dev/null +++ b/examples/vl6180x_historytest.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2022 Jonas Schatz +# SPDX-License-Identifier: MIT + +# Demo of reading the range from the history buffer of the VL6180x +# distance sensor + +import time + +import board +import busio + +import adafruit_vl6180x + + +# Create I2C bus. +i2c = busio.I2C(board.SCL, board.SDA) + +# Create sensor instance. +sensor = adafruit_vl6180x.VL6180X(i2c) + +# Starting continuous mode +print("Starting continuous mode") +sensor.start_range_continuous() + +# Main loop prints the ranges every 0.01 seconds for about 5 seconds +# You should see changes 'ripple through' the history array +for _ in range(500): + # Read the last 16 ranges from the history buffer as a List[int] + ranges_mm = sensor.ranges_from_history + print(ranges_mm) + + # Delay for 10 ms so that the loop is not too fast + time.sleep(0.01) + +# Stop continuous mode. This is advised as the sensor +# wouldn't stop measuring after the program has ended +sensor.stop_range_continuous() diff --git a/examples/vl6180x_performancetest.py b/examples/vl6180x_performancetest.py new file mode 100644 index 0000000..933a256 --- /dev/null +++ b/examples/vl6180x_performancetest.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2022 Jonas Schatz +# SPDX-License-Identifier: MIT + +# Demo of reading the range from the VL6180x distance sensor in +# different access modes (single shot, continuous, history) + +import time + +import board +import busio + +import adafruit_vl6180x + + +# Create I2C bus. +i2c = busio.I2C(board.SCL, board.SDA) + +# Create sensor instance. +sensor = adafruit_vl6180x.VL6180X(i2c) + +# Define the number of measurements +# n_measurements = 1000 will run for about 2 minutes +n_measurements: int = 100 + +# Single shot +print("Starting single-shot measurement...") +start = time.time() +for i in range(n_measurements): + range_mm = sensor.range +print( + "Performed {} measurements in single-shot mode in {}s\n".format( + n_measurements, time.time() - start + ) +) + +# Sleep is required, otherwise the sensor might freeze when switching to +# continuous mode too quickly after the last single shot +time.sleep(2) + +# Continuous with no delay between measurements +print("Starting continuous measurement...") +sensor.start_range_continuous(0) +start = time.time() +for i in range(n_measurements): + range_mm = sensor.range +print( + "Performed {} measurements in continuous mode in {}s\n".format( + n_measurements, time.time() - start + ) +) + +# Continuous, reading data from history. +# Note: This is fast, since you don't have to wait for the measurement to be +# finished. On the downside, you will read the same value multiple times +print("Starting continuous measurement with history enabled...") +start = time.time() +for i in range(n_measurements): + range_mm = sensor.range_from_history +print( + "Performed {} measurements in continuous mode, reading form history, in {}s\n".format( + n_measurements, time.time() - start + ) +) + +sensor.stop_range_continuous() +print("Finished")