Skip to content
139 changes: 117 additions & 22 deletions adafruit_vl6180x.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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:
Expand All @@ -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:
Expand Down
42 changes: 41 additions & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
Simple test
Simple Test
------------

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:
2 changes: 1 addition & 1 deletion examples/vl6180x_calibrationtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
36 changes: 36 additions & 0 deletions examples/vl6180x_continuoustest.py
Original file line number Diff line number Diff line change
@@ -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()
37 changes: 37 additions & 0 deletions examples/vl6180x_historytest.py
Original file line number Diff line number Diff line change
@@ -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()
Loading