├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── REFERENCE.md ├── examples ├── README.md ├── change_i2c_address.py ├── distance_240x240_lcd.py ├── motion_240x240_lcd.py ├── object_tracking.py ├── reflectance_240x240_lcd.py └── test.py ├── install.sh └── library ├── CHANGELOG.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── platform.c ├── platform.h ├── setup.cfg ├── setup.py ├── vl53l5cx_ctypes └── __init__.py └── vl53l5cx_module.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | __pycache__/ 4 | *.egg-info/ 5 | *.so 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "library/src"] 2 | path = library/src 3 | url = https://github.com/ST-mirror/VL53L5CX_ULD_driver 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pimoroni Ltd. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBRARY_VERSION=$(shell grep version library/setup.cfg | awk -F" = " '{print $$2}') 2 | LIBRARY_NAME=$(shell grep name library/setup.cfg | awk -F" = " '{print $$2}') 3 | 4 | .PHONY: usage install uninstall 5 | usage: 6 | @echo "Library: ${LIBRARY_NAME}" 7 | @echo "Version: ${LIBRARY_VERSION}\n" 8 | @echo "Usage: make , where target is one of:\n" 9 | @echo "install: install the library locally from source" 10 | @echo "uninstall: uninstall the local library" 11 | @echo "check: peform basic integrity checks on the codebase" 12 | @echo "python-readme: generate library/README.md from README.md + library/CHANGELOG.txt" 13 | @echo "python-wheels: build python .whl files for distribution" 14 | @echo "python-sdist: build python source distribution" 15 | @echo "python-clean: clean python build and dist directories" 16 | @echo "python-dist: build all python distribution files" 17 | @echo "python-testdeploy: build all and deploy to test PyPi" 18 | @echo "tag: tag the repository with the current version" 19 | 20 | install: 21 | ./install.sh 22 | 23 | uninstall: 24 | ./uninstall.sh 25 | 26 | check: 27 | @echo "Checking for trailing whitespace" 28 | @! grep -IUrn --color "[[:blank:]]$$" --exclude-dir=src --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO 29 | @echo "Checking for DOS line-endings" 30 | @! grep -lIUrn --color " " --exclude-dir=src --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile 31 | @echo "Checking library/CHANGELOG.txt" 32 | @cat library/CHANGELOG.txt | grep ^${LIBRARY_VERSION} 33 | @echo "Checking library/${LIBRARY_NAME}/__init__.py" 34 | @cat library/${LIBRARY_NAME}/__init__.py | grep "^__version__ = '${LIBRARY_VERSION}'" 35 | 36 | tag: 37 | git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" 38 | 39 | python-readme: library/README.md 40 | 41 | python-license: library/LICENSE.txt 42 | 43 | library/README.md: README.md library/CHANGELOG.txt 44 | cp README.md library/README.md 45 | printf "\n# Changelog\n" >> library/README.md 46 | cat library/CHANGELOG.txt >> library/README.md 47 | 48 | library/LICENSE.txt: LICENSE 49 | cp LICENSE library/LICENSE.txt 50 | 51 | python-wheels: python-readme python-license 52 | cd library; python3 setup.py bdist_wheel 53 | cd library; python setup.py bdist_wheel 54 | 55 | python-sdist: python-readme python-license 56 | cd library; python setup.py sdist 57 | 58 | python-clean: 59 | -rm -r library/dist 60 | -rm -r library/build 61 | -rm -r library/*.egg-info 62 | 63 | python-dist: python-clean python-wheels python-sdist 64 | ls library/dist 65 | 66 | python-testdeploy: python-dist 67 | twine upload --repository-url https://test.pypi.org/legacy/ library/dist/* 68 | 69 | python-deploy: check python-dist 70 | twine upload library/dist/* 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VL53L5CX CTypes Python Wrapper 2 | 3 | [![PyPi Package](https://img.shields.io/pypi/v/vl53l5cx-ctypes.svg)](https://pypi.python.org/pypi/vl53l5cx-ctypes) 4 | [![Python Versions](https://img.shields.io/pypi/pyversions/vl53l5cx-ctypes.svg)](https://pypi.python.org/pypi/vl53l5cx-ctypes) 5 | 6 | CTypes wrapper for the Sitronix VL53L5CX Ultra-light Driver- C source mirror can be found at https://github.com/ST-mirror/VL53L5CX_ULD_driver/tree/lite/en 7 | 8 | # Prerequisites 9 | 10 | You must enable: 11 | 12 | * i2c `sudo raspi-config nonint do_i2c 0` 13 | 14 | If you're not using any i2c devices for which 400KHz is out of range (trackball), you might also want to increase your i2c baud rate. 15 | 16 | VL53L5CX requires a firmware upload on startup, and it's *slow*. Add a baudrate to the i2c line in `/boot/config.txt` to speed it up: 17 | 18 | ``` 19 | dtparam=i2c_arm=on,i2c_arm_baudrate=400000 20 | ``` 21 | 22 | Note: The default baudrate is 200000 (200KHz) and a typical maximum for most devices is 400000 (400KHz), but you can also use 1000000 (1MHz) if you're just driving VL53L5CX sensors. 23 | 24 | # Installing 25 | 26 | * Just run `pip3 install vl53l5cx-ctypes` 27 | 28 | In some cases you may need to use `sudo` or install pip with: `sudo apt install python3-pip` 29 | 30 | Latest/development library from GitHub: 31 | 32 | * `git clone https://github.com/pimoroni/vl53l5cx-python` 33 | * `git submodule update --init` 34 | * `cd vl53l5cx-python/library` 35 | * `python3 setup.py install --user` 36 | -------------------------------------------------------------------------------- /REFERENCE.md: -------------------------------------------------------------------------------- 1 | # VL53L5CX Function Reference 2 | 3 | - [VL53L5CX Function Reference](#vl53l5cx-function-reference) 4 | - [Basic Setup](#basic-setup) 5 | - [Functions](#functions) 6 | - [Enable Ranging & Get Data](#enable-ranging--get-data) 7 | - [Start Ranging](#start-ranging) 8 | - [Stop Ranging](#stop-ranging) 9 | - [Power Mode](#power-mode) 10 | - [Check Data Available](#check-data-available) 11 | - [Get Data](#get-data) 12 | - [Structure of Data](#structure-of-data) 13 | - [Reflectance](#reflectance) 14 | - [Target Status](#target-status) 15 | - [Distance](#distance) 16 | - [Ranging Frequency](#ranging-frequency) 17 | - [Resolution](#resolution) 18 | - [Integration Time](#integration-time) 19 | - [Sharpener](#sharpener) 20 | - [Target Order](#target-order) 21 | - [Motion](#motion) 22 | - [Enable Motion](#enable-motion) 23 | - [Configure Motion Distance Window](#configure-motion-distance-window) 24 | - [Useful Links](#useful-links) 25 | 26 | ## Basic Setup 27 | 28 | By default the VL53L5CX library uses i2c bus 1 (/dev/i2c-1) and address 0x29. 29 | 30 | A basic setup looks like this: 31 | 32 | ```python 33 | import vl53l5cx 34 | 35 | tof = vl53l5cx.vl53l5cx() 36 | 37 | tof.start_ranging() 38 | 39 | while True: 40 | if tof.data_ready(): 41 | data = tof.get_data() 42 | print(data.distance_mm) 43 | ``` 44 | 45 | The sensor will be intialised automatically when the class is constructed. This involves sending > 80kb (yes, you read that right) of firmware data in order for it to function. 46 | 47 | You can skip this initialisation by supplying the `skip_init=True` argument for a faster init on a sensor that hasn't lost power or been reset since it was last initialised: 48 | 49 | ```python 50 | import vl53l5cx 51 | 52 | tof = vl53l5cx.vl53l5cx(skip_init=True) 53 | ``` 54 | 55 | ## Functions 56 | 57 | ### Enable Ranging & Get Data 58 | 59 | #### Start Ranging 60 | 61 | Start ranging should be called after configuring distance settings. Ensure you have set your desired resolution, frequency and integration time then: 62 | 63 | ```python 64 | tof.start_ranging() 65 | ``` 66 | 67 | #### Stop Ranging 68 | 69 | For low-power applications, or intermittent readings you can also stop ranging: 70 | 71 | ```python 72 | tof.stop_ranging() 73 | ``` 74 | 75 | #### Power Mode 76 | 77 | The VL53L5CX has two power modes: Continuous and Autonomous. 78 | 79 | The operating mode can be selected with: 80 | 81 | ```python 82 | tof.set_power_mode(mode) 83 | ``` 84 | 85 | Where `mode` is one of `vl53l5cx.POWER_MODE_SLEEP` or `vl53l5cx.POWER_MODE_WAKEUP`. 86 | 87 | #### Check Data Available 88 | 89 | The availability of new data is indicated by `data_ready` which returns `True` if new data is available. This should happen roughly at the frequency you've configured: 90 | 91 | ```python 92 | if tof.data_ready(): 93 | data = tof.get_data() 94 | ``` 95 | 96 | #### Get Data 97 | 98 | Data is retrieved using the `get_data` method: 99 | 100 | ```python 101 | data = tof.get_data() 102 | ``` 103 | 104 | This returns a structured element (a CTypes wrapper around the raw C struct) which, in practise, behaves like a named tuple. 105 | 106 | ##### Structure of Data 107 | 108 | The returned data contains: 109 | 110 | * `silicon_temp_degc` - chip temperature in degrees celsius 111 | * `ambient_per_spad` - ambient light detected on a SPAD while no light is being emitted by the sensor. 112 | * `nb_target_detected` - targets detected in current zone 113 | * `nb_spads_enabled` - number of SPADs enabled for the measurement - a far or low reflectance target will activate more SPADs. 114 | * `signal_per_spad` - quantity of photons measured during the VCSEL pulse (always on during Continuous, selective during Autonomous ranging) 115 | * `range_sigma_mm` - estimator for the noise in the reported 116 | target distance. 117 | * `distance_mm` - target distance in mm 118 | * `reflectance` - (estimated) target reflectance in % 119 | * `target_status` - target status 120 | * `motion_indicator` - Motion data (see below) 121 | 122 | Most of these values (except temperature) are lists of 64 entries, one for each of the zones in the sensor. 123 | 124 | A SPAD (single photon avalanche diode) is a single sensor element of the 8x8 VL53L5CX array. 125 | 126 | TODO: the reference manual 127 | 128 | ##### Reflectance 129 | 130 | The reflectance value is a percentage of emitted light returned by the target. White targets will generally have a higher reflectance, and grey/black targets will have a lower reflectance. This is useful for tracking a bright target (ping pong ball on a stick perhaps) across the array, irrespective of distance. 131 | 132 | Note that distance and reflectance values are independent and not dependent upon each other. The *phase* of returned light pulses is used to calculate the distance, while the reflectance is simply the amount of light returned. 133 | 134 | See the `examples/reflectance_240x240_lcd.py` example for a visualisation of reflectance on a 240x240 pixel SPI LCD. 135 | 136 | ##### Target Status 137 | 138 | The status value indicates the validity of ranging data. 139 | 140 | Values of `5` *or* `9` indicate that the data is ok. Although in practise only `5` implies 100% confidence in the range data. 141 | 142 | You can convert this data to a bool like so: 143 | 144 | ```python 145 | status = map(lambda status: status in (5, 9), data.target_status) 146 | ``` 147 | 148 | The full list of status values is as follows: 149 | 150 | * 0 - Ranging data are not updated 151 | * 1 - Signal rate too low on SPAD array 152 | * 2 - Target phase 153 | * 3 - Sigma estimator too high 154 | * 4 - Target consistency failed 155 | * 5 - Range valid 156 | * 6 - Wrap around not performed (Typically the first range) 157 | * 7 - Rate consistency failed 158 | * 8 - Signal rate too low for the current target 159 | * 9 - Range valid with large pulse (may be due to a merged target) 160 | * 10 - Range valid, but no target detected at previous range 161 | * 11 - Measurement consistency failed 162 | * 12 - Target blurred by another one, due to sharpener 163 | * 13 - Target detected but inconsistent data. Frequently happens for secondary targets. 164 | * 255 - No target detected (only if number of target detected is enabled) 165 | 166 | ### Distance 167 | 168 | #### Ranging Frequency 169 | 170 | Ranging frequency is the rate at which new range data is calculated. 171 | 172 | At 4x4 resolution this can be from 1-60Hz. 173 | 174 | At 8x8 resolution this can be from 1-15Hz. 175 | 176 | ```python 177 | tof.set_ranging_frequency_hz(15) 178 | ``` 179 | 180 | #### Resolution 181 | 182 | Ranging resolution controls the effective resolution of output data. 183 | 184 | In 8x8 mode the full array is used and the ranging data list will be 64 entries long, with the bottom left of the array being the first element. 185 | 186 | In 4x4 mode the array is treated as 4x4 elements, offers a faster update rate and only outputs 16 entries. 187 | 188 | ```python 189 | tof.set_resolution(8*8) 190 | ``` 191 | 192 | See any of the 240x240 LCD examples for a demonstration of how to convert the output data list into a 2D array (useful for feature recognition or visual representation) using numpy. In brief it looks something like this: 193 | 194 | ```python 195 | while True: 196 | if vl53.data_ready(): 197 | data = vl53.get_data() 198 | distance = numpy.array(data.distance_mm).reshape((8, 8)).astype('float64') 199 | distance = numpy.flipud(distance) 200 | ``` 201 | 202 | #### Integration Time 203 | 204 | Integration time is the amount of time the sensor takes to perform a single reading. This cannot be greater than the ranging frequency period. 205 | 206 | EG: a 15Hz ranging frequency cannot have more than a 66ms integration time. 207 | 208 | ```python 209 | tof.set_ranging_frequency(15) 210 | tof.set_integration_time_ms(66) 211 | ``` 212 | 213 | #### Sharpener 214 | 215 | Signals returned by a target are not clean pulses with sharp edges due to "[veiling glare](https://en.wikipedia.org/wiki/Veiling_glare)". Distances reported in adjacent zones may be affected, almost like the foreground target is blurred. 216 | 217 | The sharpener removes some of the signal caused by veiling glare, sharpeneing the foreground target and potentially revealing targets behind it. The default value is 5% and values from 0% to 99% are supported. 218 | 219 | ```python 220 | tof.set_sharpener_percent(50) 221 | ``` 222 | 223 | #### Target Order 224 | 225 | TODO: Right now the VL53L5CX driver only seems to support one target, enabling multiple targets results in no data. 226 | 227 | If you want to try it, you'll need to change the `VL53L5CX_NB_TARGET_PER_ZONE` value in `setup.py` to `4` (the maximum number of targets) and also change `NB_TARGET_PER_ZONE` in `vl53l5cx_ctypes/__init__.py` before recompiling the library. (You can use `python3 setup.py develop --user` for this.) 228 | 229 | Data for multiple targets is simply concatenated onto the end of `signal_per_spad`, `range_sigma_mm`, `distance_mm`, `reflectance` and `target_status`. 230 | 231 | Target order controls how detected targets are sorted in the output data. They can be sorted by signal strength (`TARGET_ORDER_STRONGES`) or by distance (`TARGET_ORDER_CLOSEST`), eg: 232 | 233 | ```python 234 | tof.set_target_order(TARGET_ORDER_STRONGEST) 235 | ``` 236 | 237 | ### Motion 238 | 239 | The VL53L5CX supports motion data output. Motion is calculated based on the change between sequential data frames, and is detected at a fixed distance window from the sensor. 240 | 241 | Motion data is available in the `motion_indicator` property, and comprises: 242 | 243 | * `global_indicator_1` 244 | * `global_indicator_2` 245 | * `status` 246 | * `nb_of_detected_aggregates` 247 | * `nb_of_aggregates` 248 | * `spare` 249 | * `motion` 250 | 251 | TODO: I cannot determine what any of the additional fields above are for. 252 | 253 | #### Enable Motion 254 | 255 | Motion indication must be enabled before the data will be output: 256 | 257 | ```python 258 | tof.enable_motion_indicator(4*4) 259 | ``` 260 | 261 | It should use the same resolution that the distance sensor is configured to use, albet in practise there are only 32 entries of motion data and only the first 16 ever seem to be populated. These make sense plotted as a 4x4 map. See `examples/motion_240x240_lcd.py` for an example. 262 | 263 | TODO: Why is there no 8x8 motion data despite the resoution being configurable? 264 | 265 | #### Configure Motion Distance Window 266 | 267 | The effective motion distance can be changed, but can be no less than 400mm (40cm) from the sensor and the window no greater than 1500mm (150cm). 268 | 269 | By default this widow is 270 | 271 | To change it, call: 272 | 273 | ```python 274 | tof.set_motion_distance(distance_min, distance_max) 275 | ``` 276 | 277 | The minimum and maximum distances should be given in millimeters. 278 | 279 | ## Useful Links 280 | 281 | * Datasheet - https://www.st.com/resource/en/datasheet/vl53l5cx.pdf 282 | * ULD driver manual - https://www.st.com/resource/en/user_manual/um2884-a-guide-to-using-the-vl53l5cx-multizone-timeofflight-ranging-sensor-with-wide-field-of-view-ultra-lite-driver-uld-stmicroelectronics.pdf -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Visual Examples 2 | 3 | These aren't practical applications for the VL53L5CX but provide a great way to visualise what the sensor is "seeing." 4 | 5 | They require matplotlib and numpy, install with: 6 | 7 | ``` 8 | sudo apt install python3-matplotlib python3-numpy 9 | ``` 10 | 11 | ## distance_240x240_lcd.py 12 | 13 | Basic visualisation of the 8x8 distance field. 14 | 15 | ## motion_240x240_lcd.py 16 | 17 | Basic visualisation of 4x4 motion detection at 40cm to 1.4m 18 | 19 | ## reflectance_240x240_lcd.py 20 | 21 | Basic visualisation of sensor estimated reflectance. 22 | 23 | # Advanced Examples 24 | 25 | Practical examples of the sensor as you might use it to drive a robot. 26 | 27 | ## object_tracking.py 28 | 29 | A basic object tracking example which uses threshold based rejection to filter a single, bright target. 30 | 31 | Uses scipy to find the target center of mass so that a robot could - potentially - follow a suitable target by turning to center it in the sensor view. 32 | 33 | # Other Examples 34 | 35 | ## change_i2c_address.py 36 | 37 | Simple script to change the i2c address of a sensor. 38 | 39 | The i2c address is *not* persistent, you will need to change it again if the sensor loses power. 40 | 41 | For multiple sensors, wire the "LP" pin on the sensor breakout to a GPIO on your Pi and pull it *LOW* to disable i2c comms on your sensor. 42 | 43 | Bring each sensor up by releasing "LP" and setting its address in turn. 44 | 45 | Alternatively you may want to use an i2c multiplexer - https://shop.pimoroni.com/products/tca9548a-i2c-multiplexer 46 | 47 | Usage: 48 | 49 | ``` 50 | ./change_i2c_address.py --current 0x29 --desired 51 | ``` 52 | -------------------------------------------------------------------------------- /examples/change_i2c_address.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | import argparse 6 | import vl53l5cx_ctypes as vl53l5cx 7 | 8 | parser = argparse.ArgumentParser(description='Change address options.') 9 | parser.add_argument('--current', type=lambda x: int(x, 0), help='The current VL53L5CX i2c address.', default=vl53l5cx.DEFAULT_I2C_ADDRESS) 10 | parser.add_argument('--desired', type=lambda x: int(x, 0), help='The desired VL53L5CX i2c address.', required=True) 11 | args = parser.parse_args() 12 | 13 | addr_current = args.current 14 | addr_desired = args.desired 15 | 16 | 17 | print(addr_current, addr_desired) 18 | 19 | print(f"""change_i2c_address.py 20 | 21 | Current address: {addr_current:02x} 22 | Desired address: {addr_desired:02x} 23 | 24 | """) 25 | 26 | # Skip sensor init, since we're not actually going to *use* it right now 27 | sensor = vl53l5cx.VL53L5CX(addr_current, skip_init=True) 28 | 29 | if sensor.set_i2c_address(addr_desired): 30 | time.sleep(0.1) 31 | if sensor.is_alive(): 32 | print("Success!") 33 | sys.exit(0) 34 | else: 35 | print("Could not detect sensor after change!") 36 | sys.exit(2) 37 | else: 38 | print("Failed to set i2c address!") 39 | sys.exit(1) 40 | -------------------------------------------------------------------------------- /examples/distance_240x240_lcd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import vl53l5cx_ctypes as vl53l5cx 5 | import ST7789 6 | import numpy 7 | from PIL import Image 8 | from matplotlib import cm 9 | 10 | 11 | COLOR_MAP = "plasma" 12 | INVERSE = True 13 | 14 | 15 | def get_palette(name): 16 | cmap = cm.get_cmap(name, 256) 17 | 18 | try: 19 | colors = cmap.colors 20 | except AttributeError: 21 | colors = numpy.array([cmap(i) for i in range(256)], dtype=float) 22 | 23 | arr = numpy.array(colors * 255).astype('uint8') 24 | arr = arr.reshape((16, 16, 4)) 25 | arr = arr[:, :, 0:3] 26 | return arr.tobytes() 27 | 28 | 29 | display = ST7789.ST7789( 30 | width=240, 31 | height=240, 32 | rotation=90, 33 | port=0, 34 | cs=ST7789.BG_SPI_CS_BACK, # Otherwise it will block the sensor! 35 | dc=9, 36 | backlight=18, 37 | spi_speed_hz=80 * 1000 * 1000, 38 | offset_left=0, 39 | offset_top=0 40 | ) 41 | 42 | pal = get_palette(COLOR_MAP) 43 | 44 | print("Uploading firmware, please wait...") 45 | vl53 = vl53l5cx.VL53L5CX() 46 | print("Done!") 47 | vl53.set_resolution(8 * 8) 48 | 49 | # This is a visual demo, so prefer speed over accuracy 50 | vl53.set_ranging_frequency_hz(15) 51 | vl53.set_integration_time_ms(20) 52 | vl53.start_ranging() 53 | 54 | 55 | while True: 56 | if vl53.data_ready(): 57 | data = vl53.get_data() 58 | arr = numpy.flipud(numpy.array(data.distance_mm).reshape((8, 8))).astype('float64') 59 | 60 | # Scale view relative to the furthest distance 61 | # distance = arr.max() 62 | 63 | # Scale view to a fixed distance 64 | distance = 512 65 | 66 | # Scale and clip the result to 0-255 67 | arr *= (255.0 / distance) 68 | arr = numpy.clip(arr, 0, 255) 69 | 70 | # Invert the array : 0 - 255 becomes 255 - 0 71 | if INVERSE: 72 | arr *= -1 73 | arr += 255.0 74 | 75 | # Force to int 76 | arr = arr.astype('uint8') 77 | 78 | # Convert to a palette type image 79 | img = Image.frombytes("P", (8, 8), arr) 80 | img.putpalette(pal) 81 | img = img.convert("RGB") 82 | img = img.resize((240, 240), resample=Image.NEAREST) 83 | 84 | # Display the result 85 | display.display(img) 86 | 87 | time.sleep(0.01) # Avoid polling *too* fast 88 | -------------------------------------------------------------------------------- /examples/motion_240x240_lcd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import vl53l5cx_ctypes as vl53l5cx 5 | import ST7789 6 | import numpy 7 | from PIL import Image 8 | from matplotlib import cm 9 | 10 | 11 | COLOR_MAP = "plasma" 12 | INVERSE = False 13 | 14 | 15 | def get_palette(name): 16 | cmap = cm.get_cmap(name, 256) 17 | 18 | try: 19 | colors = cmap.colors 20 | except AttributeError: 21 | colors = numpy.array([cmap(i) for i in range(256)], dtype=float) 22 | 23 | arr = numpy.array(colors * 255).astype('uint8') 24 | arr = arr.reshape((16, 16, 4)) 25 | arr = arr[:, :, 0:3] 26 | return arr.tobytes() 27 | 28 | 29 | display = ST7789.ST7789( 30 | width=240, 31 | height=240, 32 | rotation=90, 33 | port=0, 34 | cs=ST7789.BG_SPI_CS_BACK, # Otherwise it will block the sensor! 35 | dc=9, 36 | backlight=18, 37 | spi_speed_hz=80 * 1000 * 1000, 38 | offset_left=0, 39 | offset_top=0 40 | ) 41 | 42 | pal = get_palette(COLOR_MAP) 43 | 44 | print("Uploading firmware, please wait...") 45 | vl53 = vl53l5cx.VL53L5CX() 46 | print("Done!") 47 | vl53.set_resolution(8 * 8) 48 | 49 | # Enable motion indication at 8x8 resolution 50 | vl53.enable_motion_indicator(8 * 8) 51 | 52 | # Default motion distance is quite far, set a sensible range 53 | # eg: 40cm to 1.4m 54 | vl53.set_motion_distance(400, 1400) 55 | 56 | # This is a visual demo, so prefer speed over accuracy 57 | vl53.set_ranging_frequency_hz(15) 58 | vl53.set_integration_time_ms(5) 59 | vl53.start_ranging() 60 | 61 | 62 | while True: 63 | if vl53.data_ready(): 64 | data = vl53.get_data() 65 | # Grab the first 16 motion entries and reshape into a 4 * 4 field 66 | arr = numpy.flipud(numpy.array(list(data.motion_indicator.motion)[0:16]).reshape((4, 4))).astype('float64') 67 | 68 | # Scale view to a fixed motion intensity 69 | intensity = 1024 70 | 71 | # Scale and clip the result to 0-255 72 | arr *= (255.0 / intensity) 73 | arr = numpy.clip(arr, 0, 255) 74 | 75 | # Invert the array : 0 - 255 becomes 255 - 0 76 | if INVERSE: 77 | arr *= -1 78 | arr += 255.0 79 | 80 | # Force to int 81 | arr = arr.astype('uint8') 82 | 83 | # Convert to a palette type image 84 | img = Image.frombytes("P", (4, 4), arr) 85 | img.putpalette(pal) 86 | img = img.convert("RGB") 87 | img = img.resize((240, 240), resample=Image.NEAREST) 88 | 89 | # Display the result 90 | display.display(img) 91 | 92 | time.sleep(0.01) # Avoid polling *too* fast 93 | -------------------------------------------------------------------------------- /examples/object_tracking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import ST7789 5 | import vl53l5cx_ctypes as vl53l5cx 6 | from vl53l5cx_ctypes import STATUS_RANGE_VALID, STATUS_RANGE_VALID_LARGE_PULSE 7 | import numpy 8 | from PIL import Image, ImageDraw 9 | 10 | 11 | # Reflectance threshold (in % estimated signal return) for tracked target 12 | R_THRESHOLD = 150 13 | 14 | # Distance threshold (in mm) for tracked target 15 | D_THRESHOLD = 400 16 | 17 | 18 | display = ST7789.ST7789( 19 | width=240, 20 | height=240, 21 | rotation=90, 22 | port=0, 23 | cs=ST7789.BG_SPI_CS_BACK, # Otherwise it will block the sensor! 24 | dc=9, 25 | backlight=18, 26 | spi_speed_hz=80 * 1000 * 1000, 27 | offset_left=0, 28 | offset_top=0 29 | ) 30 | 31 | 32 | print("Uploading firmware, please wait...") 33 | vl53 = vl53l5cx.VL53L5CX() 34 | print("Done!") 35 | vl53.set_resolution(8 * 8) 36 | 37 | 38 | vl53.set_sharpener_percent(60) 39 | 40 | # This is a visual demo, so prefer speed over accuracy 41 | vl53.set_ranging_frequency_hz(15) 42 | vl53.set_integration_time_ms(20) 43 | vl53.start_ranging() 44 | 45 | 46 | while True: 47 | if vl53.data_ready(): 48 | data = vl53.get_data() 49 | 50 | status = numpy.isin(numpy.flipud(numpy.array(data.target_status).reshape((8, 8))), (STATUS_RANGE_VALID, STATUS_RANGE_VALID_LARGE_PULSE)) 51 | reflectance = numpy.flipud(numpy.array(data.reflectance).reshape((8, 8))).astype('float64') 52 | distance = numpy.flipud(numpy.array(data.distance_mm).reshape((8, 8))).astype('float64') 53 | 54 | # Scale reflectance (a percentage) to 0 - 255 55 | reflectance *= (255.0 / 100.0) 56 | reflectance = numpy.clip(reflectance, 0, 255) 57 | 58 | # Clear invalid readings 59 | rfilt = numpy.where(status, reflectance, 0) 60 | 61 | # Clear out of range readings 62 | rfilt = numpy.where(distance < D_THRESHOLD, rfilt, 0) 63 | 64 | # Clear out of threshold readings 65 | rfilt = numpy.where(rfilt > R_THRESHOLD, rfilt, 0) 66 | 67 | # Get all possible distances for our target, replacing out of target values with "not a number" 68 | dfilt = numpy.where(rfilt > 0, distance, numpy.nan) 69 | 70 | # Get the mean distance to the target from our collected distance values 71 | mdist = numpy.nanmean(dfilt) 72 | 73 | # Compute the center of mass along each dimension in turn 74 | 75 | # A 1d range of [0, 1, 2, ...] multiplies along the X axis: 76 | x = (rfilt * range(8)).sum() 77 | # Normalise by the sum of the source data 78 | x /= rfilt.sum() 79 | # And finally by the width to give a value from 0 to 1 80 | x /= 7.0 81 | 82 | # A 2d range of [[0], [1], [2], ...] multiplies along the Y axis: 83 | y = (rfilt * [[i] for i in range(8)]).sum() 84 | y /= rfilt.sum() 85 | y /= 7.0 86 | 87 | # Correct X/Y coordinates to center of view 88 | # This gives a handy range from -1 to 1 89 | vx = (x * 2) - 1.0 90 | vy = (y * 2) - 1.0 91 | 92 | valid = not numpy.isnan(x) and not numpy.isnan(y) 93 | 94 | # Print 'em out. Wooohoo! 95 | if valid: 96 | print(f"{vx:.02f}, {vy:.02f}, {mdist:.02f}") 97 | 98 | # TODO: Angle to target can be calculated using trig? 99 | # the distance to target is the hypotenuse (c) 100 | # the offset in the data is the opposite (a) 101 | # the angle is angle (α) 102 | # The sensor FOV (63°) factors into this... somehow 103 | # 104 | # Better target rejection could use distance + feature size 105 | # to reject targets that are too big/small 106 | 107 | # Basic visualisation to confirm our numbers are sensible! 108 | rfilt = rfilt.astype('uint8') 109 | 110 | # Convert to a palette type image 111 | img = Image.frombytes("P", (8, 8), rfilt) 112 | img = img.convert("RGB") 113 | img = img.resize((240, 240), resample=Image.NEAREST) 114 | draw = ImageDraw.Draw(img) 115 | 116 | if valid: 117 | # TODO: maybe display the distance onscreen? 118 | ix = int(239 * x) 119 | iy = int(239 * y) 120 | ix = max(0, min(ix, 239)) 121 | iy = max(0, min(iy, 239)) 122 | r = 10 123 | draw.ellipse((ix - r, iy - r, ix + r, iy + r), (255, 0, 0)) 124 | 125 | # Display the result 126 | display.display(img) 127 | 128 | 129 | time.sleep(0.01) # Avoid polling *too* fast 130 | -------------------------------------------------------------------------------- /examples/reflectance_240x240_lcd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | import vl53l5cx_ctypes as vl53l5cx 5 | import ST7789 6 | import numpy 7 | from PIL import Image 8 | from matplotlib import cm 9 | 10 | 11 | COLOR_MAP = "twilight" 12 | INVERSE = True 13 | 14 | 15 | def get_palette(name): 16 | cmap = cm.get_cmap(name, 256) 17 | 18 | try: 19 | colors = cmap.colors 20 | except AttributeError: 21 | colors = numpy.array([cmap(i) for i in range(256)], dtype=float) 22 | 23 | arr = numpy.array(colors * 255).astype('uint8') 24 | arr = arr.reshape((16, 16, 4)) 25 | arr = arr[:, :, 0:3] 26 | return arr.tobytes() 27 | 28 | 29 | display = ST7789.ST7789( 30 | width=240, 31 | height=240, 32 | rotation=90, 33 | port=0, 34 | cs=ST7789.BG_SPI_CS_BACK, # Otherwise it will block the sensor! 35 | dc=9, 36 | backlight=18, 37 | spi_speed_hz=80 * 1000 * 1000, 38 | offset_left=0, 39 | offset_top=0 40 | ) 41 | 42 | pal = get_palette(COLOR_MAP) 43 | 44 | print("Uploading firmware, please wait...") 45 | vl53 = vl53l5cx.VL53L5CX() 46 | print("Done!") 47 | vl53.set_resolution(8 * 8) 48 | 49 | # This is a visual demo, so prefer speed over accuracy 50 | vl53.set_ranging_frequency_hz(15) 51 | vl53.set_integration_time_ms(5) 52 | vl53.start_ranging() 53 | 54 | 55 | while True: 56 | if vl53.data_ready(): 57 | data = vl53.get_data() 58 | arr = numpy.flipud(numpy.array(data.reflectance).reshape((8, 8))).astype('float64') 59 | 60 | # Scale reflectance (a percentage) to 0 - 255 61 | arr *= (255.0 / 100.0) 62 | arr = numpy.clip(arr, 0, 255) 63 | 64 | # Invert the array : 0 - 255 becomes 255 - 0 65 | if INVERSE: 66 | arr *= -1 67 | arr += 255.0 68 | 69 | # Force to int 70 | arr = arr.astype('uint8') 71 | 72 | # Convert to a palette type image 73 | img = Image.frombytes("P", (8, 8), arr) 74 | img.putpalette(pal) 75 | img = img.convert("RGB") 76 | img = img.resize((240, 240), resample=Image.NEAREST) 77 | 78 | # Display the result 79 | display.display(img) 80 | 81 | time.sleep(0.01) # Avoid polling *too* fast 82 | -------------------------------------------------------------------------------- /examples/test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy 3 | import vl53l5cx_ctypes as vl53l5cx 4 | from vl53l5cx_ctypes import STATUS_RANGE_VALID, STATUS_RANGE_VALID_LARGE_PULSE 5 | 6 | print("Uploading firmware, please wait...") 7 | vl53 = vl53l5cx.VL53L5CX() 8 | print("Done!") 9 | vl53.set_resolution(8 * 8) 10 | vl53.enable_motion_indicator(8 * 8) 11 | # vl53.set_integration_time_ms(50) 12 | 13 | # Enable motion indication at 8x8 resolution 14 | vl53.enable_motion_indicator(8 * 8) 15 | 16 | # Default motion distance is quite far, set a sensible range 17 | # eg: 40cm to 1.4m 18 | vl53.set_motion_distance(400, 1400) 19 | 20 | vl53.start_ranging() 21 | 22 | while True: 23 | if vl53.data_ready(): 24 | data = vl53.get_data() 25 | # 2d array of motion data (always 4x4?) 26 | motion = numpy.flipud(numpy.array(data.motion_indicator.motion[0:16]).reshape((4, 4))) 27 | # 2d array of distance 28 | distance = numpy.flipud(numpy.array(data.distance_mm).reshape((8, 8))) 29 | # 2d array of reflectance 30 | reflectance = numpy.flipud(numpy.array(data.reflectance).reshape((8, 8))) 31 | # 2d array of good ranging data 32 | status = numpy.isin(numpy.flipud(numpy.array(data.target_status).reshape((8, 8))), (STATUS_RANGE_VALID, STATUS_RANGE_VALID_LARGE_PULSE)) 33 | print(motion, distance, reflectance, status) 34 | time.sleep(0.1) 35 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CONFIG=/boot/config.txt 3 | DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` 4 | CONFIG_BACKUP=false 5 | APT_HAS_UPDATED=false 6 | USER_HOME=/home/$SUDO_USER 7 | RESOURCES_TOP_DIR=$USER_HOME/Pimoroni 8 | WD=`pwd` 9 | USAGE="sudo ./install.sh (--unstable)" 10 | POSITIONAL_ARGS=() 11 | FORCE=false 12 | UNSTABLE=false 13 | PYTHON="/usr/bin/python3" 14 | 15 | 16 | user_check() { 17 | if [ $(id -u) -ne 0 ]; then 18 | printf "Script must be run as root. Try 'sudo ./install.sh'\n" 19 | exit 1 20 | fi 21 | } 22 | 23 | confirm() { 24 | if $FORCE; then 25 | true 26 | else 27 | read -r -p "$1 [y/N] " response < /dev/tty 28 | if [[ $response =~ ^(yes|y|Y)$ ]]; then 29 | true 30 | else 31 | false 32 | fi 33 | fi 34 | } 35 | 36 | prompt() { 37 | read -r -p "$1 [y/N] " response < /dev/tty 38 | if [[ $response =~ ^(yes|y|Y)$ ]]; then 39 | true 40 | else 41 | false 42 | fi 43 | } 44 | 45 | success() { 46 | echo -e "$(tput setaf 2)$1$(tput sgr0)" 47 | } 48 | 49 | inform() { 50 | echo -e "$(tput setaf 6)$1$(tput sgr0)" 51 | } 52 | 53 | warning() { 54 | echo -e "$(tput setaf 1)$1$(tput sgr0)" 55 | } 56 | 57 | function do_config_backup { 58 | if [ ! $CONFIG_BACKUP == true ]; then 59 | CONFIG_BACKUP=true 60 | FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" 61 | inform "Backing up $CONFIG to /boot/$FILENAME\n" 62 | cp $CONFIG /boot/$FILENAME 63 | mkdir -p $RESOURCES_TOP_DIR/config-backups/ 64 | cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME 65 | if [ -f "$UNINSTALLER" ]; then 66 | echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER 67 | fi 68 | fi 69 | } 70 | 71 | function apt_pkg_install { 72 | PACKAGES=() 73 | PACKAGES_IN=("$@") 74 | for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do 75 | PACKAGE="${PACKAGES_IN[$i]}" 76 | if [ "$PACKAGE" == "" ]; then continue; fi 77 | printf "Checking for $PACKAGE\n" 78 | dpkg -L $PACKAGE > /dev/null 2>&1 79 | if [ "$?" == "1" ]; then 80 | PACKAGES+=("$PACKAGE") 81 | fi 82 | done 83 | PACKAGES="${PACKAGES[@]}" 84 | if ! [ "$PACKAGES" == "" ]; then 85 | echo "Installing missing packages: $PACKAGES" 86 | if [ ! $APT_HAS_UPDATED ]; then 87 | apt update 88 | APT_HAS_UPDATED=true 89 | fi 90 | apt install -y $PACKAGES 91 | if [ -f "$UNINSTALLER" ]; then 92 | echo "apt uninstall -y $PACKAGES" 93 | fi 94 | fi 95 | } 96 | 97 | while [[ $# -gt 0 ]]; do 98 | K="$1" 99 | case $K in 100 | -u|--unstable) 101 | UNSTABLE=true 102 | shift 103 | ;; 104 | -f|--force) 105 | FORCE=true 106 | shift 107 | ;; 108 | -p|--python) 109 | PYTHON=$2 110 | shift 111 | shift 112 | ;; 113 | *) 114 | if [[ $1 == -* ]]; then 115 | printf "Unrecognised option: $1\n"; 116 | printf "Usage: $USAGE\n"; 117 | exit 1 118 | fi 119 | POSITIONAL_ARGS+=("$1") 120 | shift 121 | esac 122 | done 123 | 124 | user_check 125 | 126 | if [ ! -f "$PYTHON" ]; then 127 | printf "Python path $PYTHON not found!\n" 128 | exit 1 129 | fi 130 | 131 | PYTHON_VER=`$PYTHON --version` 132 | 133 | inform "Installing. Please wait..." 134 | 135 | $PYTHON -m pip install --upgrade configparser 136 | 137 | CONFIG_VARS=`$PYTHON - < $UNINSTALLER 174 | printf "It's recommended you run these steps manually.\n" 175 | printf "If you want to run the full script, open it in\n" 176 | printf "an editor and remove 'exit 1' from below.\n" 177 | exit 1 178 | EOF 179 | 180 | printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" 181 | 182 | if $UNSTABLE; then 183 | warning "Installing unstable library from source.\n\n" 184 | else 185 | printf "Installing stable library from pypi.\n\n" 186 | fi 187 | 188 | cd library 189 | 190 | printf "Installing for $PYTHON_VER...\n" 191 | apt_pkg_install "${PY3_DEPS[@]}" 192 | if $UNSTABLE; then 193 | $PYTHON setup.py install > /dev/null 194 | else 195 | $PYTHON -m pip install --upgrade $LIBRARY_NAME 196 | fi 197 | if [ $? -eq 0 ]; then 198 | success "Done!\n" 199 | echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER 200 | fi 201 | 202 | cd $WD 203 | 204 | for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do 205 | CMD="${SETUP_CMDS[$i]}" 206 | # Attempt to catch anything that touches /boot/config.txt and trigger a backup 207 | if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then 208 | do_config_backup 209 | fi 210 | eval $CMD 211 | done 212 | 213 | for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do 214 | CONFIG_LINE="${CONFIG_TXT[$i]}" 215 | if ! [ "$CONFIG_LINE" == "" ]; then 216 | do_config_backup 217 | inform "Adding $CONFIG_LINE to $CONFIG\n" 218 | sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG 219 | if ! grep -q "^$CONFIG_LINE" $CONFIG; then 220 | printf "$CONFIG_LINE\n" >> $CONFIG 221 | fi 222 | fi 223 | done 224 | 225 | if [ -d "examples" ]; then 226 | if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then 227 | inform "Copying examples to $RESOURCES_DIR" 228 | cp -r examples/ $RESOURCES_DIR 229 | echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER 230 | success "Done!" 231 | fi 232 | fi 233 | 234 | printf "\n" 235 | 236 | if [ -f "/usr/bin/pydoc" ]; then 237 | printf "Generating documentation.\n" 238 | pydoc -w $LIBRARY_NAME > /dev/null 239 | if [ -f "$LIBRARY_NAME.html" ]; then 240 | cp $LIBRARY_NAME.html $RESOURCES_DIR/docs.html 241 | rm -f $LIBRARY_NAME.html 242 | inform "Documentation saved to $RESOURCES_DIR/docs.html" 243 | success "Done!" 244 | else 245 | warning "Error: Failed to generate documentation." 246 | fi 247 | fi 248 | 249 | success "\nAll done!" 250 | inform "If this is your first time installing you should reboot for hardware changes to take effect.\n" 251 | inform "Find uninstall steps in $UNINSTALLER\n" 252 | -------------------------------------------------------------------------------- /library/CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 0.0.3 2 | ----- 3 | 4 | * Rename to vl53l5cx_ctypes to better reflect the differences between this and the pure Python VL53L5CX driver 5 | * Change package name to avoid conflicts with Python VL53L5CX driver 6 | 7 | 0.0.2 8 | ----- 9 | 10 | * Fix segfault bug in is_alive 11 | 12 | 0.0.1 13 | ----- 14 | 15 | * Initial Release 16 | -------------------------------------------------------------------------------- /library/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pimoroni Ltd. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /library/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src/VL53L5CX_ULD_API *.c *.h 2 | include platform.h 3 | include platform.c 4 | recursive-include vl53l5cx_ctypes *py 5 | include README.md 6 | include CHANGELOG.txt 7 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | # VL53L5CX CTypes Python Wrapper 2 | 3 | [![PyPi Package](https://img.shields.io/pypi/v/vl53l5cx-ctypes.svg)](https://pypi.python.org/pypi/vl53l5cx-ctypes) 4 | [![Python Versions](https://img.shields.io/pypi/pyversions/vl53l5cx-ctypes.svg)](https://pypi.python.org/pypi/vl53l5cx-ctypes) 5 | 6 | CTypes wrapper for the Sitronix VL53L5CX Ultra-light Driver- C source mirror can be found at https://github.com/ST-mirror/VL53L5CX_ULD_driver/tree/lite/en 7 | 8 | # Prerequisites 9 | 10 | You must enable: 11 | 12 | * i2c `sudo raspi-config nonint do_i2c 0` 13 | 14 | If you're not using any i2c devices for which 400KHz is out of range (trackball), you might also want to increase your i2c baud rate. 15 | 16 | VL53L5CX requires a firmware upload on startup, and it's *slow*. Add a baudrate to the i2c line in `/boot/config.txt` to speed it up: 17 | 18 | ``` 19 | dtparam=i2c_arm=on,i2c_arm_baudrate=400000 20 | ``` 21 | 22 | Note: The default baudrate is 200000 (200KHz) and a typical maximum for most devices is 400000 (400KHz), but you can also use 1000000 (1MHz) if you're just driving VL53L5CX sensors. 23 | 24 | # Installing 25 | 26 | * Just run `pip3 install vl53l5cx-ctypes` 27 | 28 | In some cases you may need to use `sudo` or install pip with: `sudo apt install python3-pip` 29 | 30 | Latest/development library from GitHub: 31 | 32 | * `git clone https://github.com/pimoroni/vl53l5cx-python 33 | * `cd vl53l5cx-python/library` 34 | * `python3 setup.py install --user` 35 | 36 | # Changelog 37 | 0.0.3 38 | ----- 39 | 40 | * Rename to vl53l5cx_ctypes to better reflect the differences between this and the pure Python VL53L5CX driver 41 | * Change package name to avoid conflicts with Python VL53L5CX driver 42 | 43 | 0.0.2 44 | ----- 45 | 46 | * Fix segfault bug in is_alive 47 | 48 | 0.0.1 49 | ----- 50 | 51 | * Initial Release 52 | -------------------------------------------------------------------------------- /library/platform.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2020, STMicroelectronics - All Rights Reserved 3 | * 4 | * This file is part of the VL53L5CX Ultra Lite Driver and is dual licensed, 5 | * either 'STMicroelectronics Proprietary license' 6 | * or 'BSD 3-clause "New" or "Revised" License' , at your option. 7 | * 8 | ******************************************************************************** 9 | * 10 | * 'STMicroelectronics Proprietary license' 11 | * 12 | ******************************************************************************** 13 | * 14 | * License terms: STMicroelectronics Proprietary in accordance with licensing 15 | * terms at www.st.com/sla0081 16 | * 17 | * STMicroelectronics confidential 18 | * Reproduction and Communication of this document is strictly prohibited unless 19 | * specifically authorized in writing by STMicroelectronics. 20 | * 21 | * 22 | ******************************************************************************** 23 | * 24 | * Alternatively, the VL53L5CX Ultra Lite Driver may be distributed under the 25 | * terms of 'BSD 3-clause "New" or "Revised" License', in which case the 26 | * following provisions apply instead of the ones mentioned above : 27 | * 28 | ******************************************************************************** 29 | * 30 | * License terms: BSD 3-clause "New" or "Revised" License. 31 | * 32 | * Redistribution and use in source and binary forms, with or without 33 | * modification, are permitted provided that the following conditions are met: 34 | * 35 | * Redistribution and use in source and binary forms, with or without 36 | * modification, are permitted provided that the following conditions are met: 37 | * 38 | * 1. Redistributions of source code must retain the above copyright notice, this 39 | * list of conditions and the following disclaimer. 40 | * 41 | * 2. Redistributions in binary form must reproduce the above copyright notice, 42 | * this list of conditions and the following disclaimer in the documentation 43 | * and/or other materials provided with the distribution. 44 | * 45 | * 3. Neither the name of the copyright holder nor the names of its contributors 46 | * may be used to endorse or promote products derived from this software 47 | * without specific prior written permission. 48 | * 49 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 50 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 51 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 52 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 53 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 54 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 55 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 56 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 57 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | * 60 | * 61 | *******************************************************************************/ 62 | 63 | 64 | #include "platform.h" 65 | 66 | uint8_t RdByte( 67 | VL53L5CX_Platform *p_platform, 68 | uint16_t RegisterAddress, 69 | uint8_t *p_value) 70 | { 71 | if (p_platform->i2c_read && p_platform->i2c_read(p_platform->address >> 1, RegisterAddress, p_value, 1) == 0) { 72 | return 0; 73 | } 74 | 75 | return 255; 76 | } 77 | 78 | uint8_t WrByte( 79 | VL53L5CX_Platform *p_platform, 80 | uint16_t RegisterAddress, 81 | uint8_t value) 82 | { 83 | if (p_platform->i2c_write && p_platform->i2c_write(p_platform->address >> 1, RegisterAddress, &value, 1) == 0) { 84 | return 0; 85 | } 86 | 87 | return 255; 88 | } 89 | 90 | uint8_t WrMulti( 91 | VL53L5CX_Platform *p_platform, 92 | uint16_t RegisterAddress, 93 | uint8_t *p_values, 94 | uint32_t size) 95 | { 96 | if (p_platform->i2c_write && p_platform->i2c_write(p_platform->address >> 1, RegisterAddress, p_values, size) == 0) { 97 | return 0; 98 | } 99 | 100 | return 255; 101 | } 102 | 103 | uint8_t RdMulti( 104 | VL53L5CX_Platform *p_platform, 105 | uint16_t RegisterAddress, 106 | uint8_t *p_values, 107 | uint32_t size) 108 | { 109 | if (p_platform->i2c_read && p_platform->i2c_read(p_platform->address >> 1, RegisterAddress, p_values, size) == 0) { 110 | return 0; 111 | } 112 | 113 | return 255; 114 | } 115 | 116 | uint8_t Reset_Sensor( 117 | VL53L5CX_Platform *p_platform) 118 | { 119 | uint8_t status = 0; 120 | 121 | /* (Optional) Need to be implemented by customer. This function returns 0 if OK */ 122 | 123 | /* Set pin LPN to LOW */ 124 | /* Set pin AVDD to LOW */ 125 | /* Set pin VDDIO to LOW */ 126 | WaitMs(p_platform, 100); 127 | 128 | /* Set pin LPN of to HIGH */ 129 | /* Set pin AVDD of to HIGH */ 130 | /* Set pin VDDIO of to HIGH */ 131 | WaitMs(p_platform, 100); 132 | 133 | return status; 134 | } 135 | 136 | void SwapBuffer( 137 | uint8_t *buffer, 138 | uint16_t size) 139 | { 140 | uint32_t i, tmp; 141 | 142 | /*for(auto i = 0u; i < size / 4u; i++) { 143 | uint32_t *dword = &((uint32_t *)buffer)[i]; 144 | *dword = __builtin_bswap32(*dword); 145 | }*/ 146 | 147 | /* Example of possible implementation using */ 148 | for(i = 0; i < size; i = i + 4) 149 | { 150 | tmp = ( 151 | buffer[i]<<24) 152 | |(buffer[i+1]<<16) 153 | |(buffer[i+2]<<8) 154 | |(buffer[i+3]); 155 | 156 | memcpy(&(buffer[i]), &tmp, 4); 157 | } 158 | } 159 | 160 | uint8_t WaitMs( 161 | VL53L5CX_Platform *p_platform, 162 | uint32_t TimeMs) 163 | { 164 | if(p_platform->sleep) p_platform->sleep(TimeMs); 165 | return 0; 166 | } 167 | -------------------------------------------------------------------------------- /library/platform.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2020, STMicroelectronics - All Rights Reserved 3 | * 4 | * This file is part of the VL53L5CX Ultra Lite Driver and is dual licensed, 5 | * either 'STMicroelectronics Proprietary license' 6 | * or 'BSD 3-clause "New" or "Revised" License' , at your option. 7 | * 8 | ******************************************************************************** 9 | * 10 | * 'STMicroelectronics Proprietary license' 11 | * 12 | ******************************************************************************** 13 | * 14 | * License terms: STMicroelectronics Proprietary in accordance with licensing 15 | * terms at www.st.com/sla0081 16 | * 17 | * STMicroelectronics confidential 18 | * Reproduction and Communication of this document is strictly prohibited unless 19 | * specifically authorized in writing by STMicroelectronics. 20 | * 21 | * 22 | ******************************************************************************** 23 | * 24 | * Alternatively, the VL53L5CX Ultra Lite Driver may be distributed under the 25 | * terms of 'BSD 3-clause "New" or "Revised" License', in which case the 26 | * following provisions apply instead of the ones mentioned above : 27 | * 28 | ******************************************************************************** 29 | * 30 | * License terms: BSD 3-clause "New" or "Revised" License. 31 | * 32 | * Redistribution and use in source and binary forms, with or without 33 | * modification, are permitted provided that the following conditions are met: 34 | * 35 | * Redistribution and use in source and binary forms, with or without 36 | * modification, are permitted provided that the following conditions are met: 37 | * 38 | * 1. Redistributions of source code must retain the above copyright notice, this 39 | * list of conditions and the following disclaimer. 40 | * 41 | * 2. Redistributions in binary form must reproduce the above copyright notice, 42 | * this list of conditions and the following disclaimer in the documentation 43 | * and/or other materials provided with the distribution. 44 | * 45 | * 3. Neither the name of the copyright holder nor the names of its contributors 46 | * may be used to endorse or promote products derived from this software 47 | * without specific prior written permission. 48 | * 49 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 50 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 51 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 52 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 53 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 54 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 55 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 56 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 57 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | * 60 | * 61 | *******************************************************************************/ 62 | 63 | #ifndef _PLATFORM_H_ 64 | #define _PLATFORM_H_ 65 | #pragma once 66 | 67 | #include 68 | #include 69 | 70 | /** 71 | * @brief Structure VL53L5CX_Platform needs to be filled by the customer, 72 | * depending on his platform. At least, it contains the VL53L5CX I2C address. 73 | * Some additional fields can be added, as descriptors, or platform 74 | * dependencies. Anything added into this structure is visible into the platform 75 | * layer. 76 | */ 77 | 78 | // calls read_i2c_block_data(address, reg, length) 79 | typedef int (*i2c_read_func)(uint8_t address, uint16_t reg, uint8_t *list, uint32_t length); 80 | // calls write_i2c_block_data(address, reg, list) 81 | typedef int (*i2c_write_func)(uint8_t address, uint16_t reg, uint8_t *list, uint32_t length); 82 | 83 | typedef int (*sleep_func)(uint32_t time_ms); 84 | 85 | typedef struct 86 | { 87 | uint16_t address; 88 | i2c_read_func i2c_read; 89 | i2c_write_func i2c_write; 90 | sleep_func sleep; 91 | } VL53L5CX_Platform; 92 | 93 | /* 94 | * @brief The macro below is used to define the number of target per zone sent 95 | * through I2C. This value can be changed by user, in order to tune I2C 96 | * transaction, and also the total memory size (a lower number of target per 97 | * zone means a lower RAM). The value must be between 1 and 4. 98 | */ 99 | 100 | //#define VL53L5CX_NB_TARGET_PER_ZONE 1U 101 | 102 | /* 103 | * @brief The macro below can be used to avoid data conversion into the driver. 104 | * By default there is a conversion between firmware and user data. Using this macro 105 | * allows to use the firmware format instead of user format. The firmware format allows 106 | * an increased precision. 107 | */ 108 | 109 | // #define VL53L5CX_USE_RAW_FORMAT 110 | 111 | /* 112 | * @brief All macro below are used to configure the sensor output. User can 113 | * define some macros if he wants to disable selected output, in order to reduce 114 | * I2C access. 115 | */ 116 | 117 | // #define VL53L5CX_DISABLE_AMBIENT_PER_SPAD 118 | // #define VL53L5CX_DISABLE_NB_SPADS_ENABLED 119 | // #define VL53L5CX_DISABLE_NB_TARGET_DETECTED 120 | // #define VL53L5CX_DISABLE_SIGNAL_PER_SPAD 121 | // #define VL53L5CX_DISABLE_RANGE_SIGMA_MM 122 | // #define VL53L5CX_DISABLE_DISTANCE_MM 123 | // #define VL53L5CX_DISABLE_REFLECTANCE_PERCENT 124 | // #define VL53L5CX_DISABLE_TARGET_STATUS 125 | // #define VL53L5CX_DISABLE_MOTION_INDICATOR 126 | 127 | /** 128 | * @param (VL53L5CX_Platform*) p_platform : Pointer of VL53L5CX platform 129 | * structure. 130 | * @param (uint16_t) Address : I2C location of value to read. 131 | * @param (uint8_t) *p_values : Pointer of value to read. 132 | * @return (uint8_t) status : 0 if OK 133 | */ 134 | 135 | uint8_t RdByte( 136 | VL53L5CX_Platform *p_platform, 137 | uint16_t RegisterAddress, 138 | uint8_t *p_value); 139 | 140 | /** 141 | * @brief Mandatory function used to write one single byte. 142 | * @param (VL53L5CX_Platform*) p_platform : Pointer of VL53L5CX platform 143 | * structure. 144 | * @param (uint16_t) Address : I2C location of value to read. 145 | * @param (uint8_t) value : Pointer of value to write. 146 | * @return (uint8_t) status : 0 if OK 147 | */ 148 | 149 | uint8_t WrByte( 150 | VL53L5CX_Platform *p_platform, 151 | uint16_t RegisterAddress, 152 | uint8_t value); 153 | 154 | /** 155 | * @brief Mandatory function used to read multiples bytes. 156 | * @param (VL53L5CX_Platform*) p_platform : Pointer of VL53L5CX platform 157 | * structure. 158 | * @param (uint16_t) Address : I2C location of values to read. 159 | * @param (uint8_t) *p_values : Buffer of bytes to read. 160 | * @param (uint32_t) size : Size of *p_values buffer. 161 | * @return (uint8_t) status : 0 if OK 162 | */ 163 | 164 | uint8_t RdMulti( 165 | VL53L5CX_Platform *p_platform, 166 | uint16_t RegisterAddress, 167 | uint8_t *p_values, 168 | uint32_t size); 169 | 170 | /** 171 | * @brief Mandatory function used to write multiples bytes. 172 | * @param (VL53L5CX_Platform*) p_platform : Pointer of VL53L5CX platform 173 | * structure. 174 | * @param (uint16_t) Address : I2C location of values to write. 175 | * @param (uint8_t) *p_values : Buffer of bytes to write. 176 | * @param (uint32_t) size : Size of *p_values buffer. 177 | * @return (uint8_t) status : 0 if OK 178 | */ 179 | 180 | uint8_t WrMulti( 181 | VL53L5CX_Platform *p_platform, 182 | uint16_t RegisterAddress, 183 | uint8_t *p_values, 184 | uint32_t size); 185 | 186 | /** 187 | * @brief Optional function, only used to perform an hardware reset of the 188 | * sensor. This function is not used in the API, but it can be used by the host. 189 | * This function is not mandatory to fill if user don't want to reset the 190 | * sensor. 191 | * @param (VL53L5CX_Platform*) p_platform : Pointer of VL53L5CX platform 192 | * structure. 193 | * @return (uint8_t) status : 0 if OK 194 | */ 195 | 196 | uint8_t Reset_Sensor( 197 | VL53L5CX_Platform *p_platform); 198 | 199 | /** 200 | * @brief Mandatory function, used to swap a buffer. The buffer size is always a 201 | * multiple of 4 (4, 8, 12, 16, ...). 202 | * @param (uint8_t*) buffer : Buffer to swap, generally uint32_t 203 | * @param (uint16_t) size : Buffer size to swap 204 | */ 205 | 206 | void SwapBuffer( 207 | uint8_t *buffer, 208 | uint16_t size); 209 | /** 210 | * @brief Mandatory function, used to wait during an amount of time. It must be 211 | * filled as it's used into the API. 212 | * @param (VL53L5CX_Platform*) p_platform : Pointer of VL53L5CX platform 213 | * structure. 214 | * @param (uint32_t) TimeMs : Time to wait in ms. 215 | * @return (uint8_t) status : 0 if wait is finished. 216 | */ 217 | 218 | uint8_t WaitMs( 219 | VL53L5CX_Platform *p_platform, 220 | uint32_t TimeMs); 221 | 222 | #endif // _PLATFORM_H_ 223 | -------------------------------------------------------------------------------- /library/setup.cfg: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | [metadata] 3 | name = vl53l5cx-ctypes 4 | version = 0.0.3 5 | author = Philip Howard 6 | author_email = phil@pimoroni.com 7 | description = CTypes wrapper for the Sitronix VL53L5CX 8x8 time of flight distance array "ULD" library 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | keywords = Raspberry Pi 11 | url = https://www.pimoroni.com 12 | project_urls = 13 | GitHub=https://www.github.com/pimoroni/vl53l5cx-python 14 | license = MIT 15 | # This includes the license file(s) in the wheel. 16 | # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file 17 | license_files = LICENSE.txt 18 | classifiers = 19 | Development Status :: 4 - Beta 20 | Operating System :: POSIX :: Linux 21 | License :: OSI Approved :: MIT License 22 | Intended Audience :: Developers 23 | Programming Language :: Python :: 3 24 | Topic :: Software Development 25 | Topic :: Software Development :: Libraries 26 | Topic :: System :: Hardware 27 | 28 | [options] 29 | python_requires = >= 3.6 30 | packages = vl53l5cx_ctypes 31 | install_requires = 32 | smbus2 33 | 34 | [flake8] 35 | exclude = 36 | .tox, 37 | .eggs, 38 | .git, 39 | __pycache__, 40 | build, 41 | dist 42 | src 43 | ignore = 44 | E501 45 | 46 | [pimoroni] 47 | py2deps = 48 | py3deps = 49 | configtxt = 50 | commands = 51 | printf "Setting up i2c..\n" 52 | raspi-config nonint do_i2c 0 53 | -------------------------------------------------------------------------------- /library/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | 3 | 4 | extension = Extension( 5 | 'vl53l5cx_ctypes', 6 | define_macros=[('VL53L5CX_NB_TARGET_PER_ZONE', '1')], 7 | extra_compile_args=[], 8 | include_dirs=['.', 'src/VL53L5CX_ULD_API/inc'], 9 | libraries=[], 10 | library_dirs=[], 11 | sources=['platform.c', 12 | 'src/VL53L5CX_ULD_API/src/vl53l5cx_api.c', 13 | 'src/VL53L5CX_ULD_API/src/vl53l5cx_plugin_motion_indicator.c', 14 | 'src/VL53L5CX_ULD_API/src/vl53l5cx_plugin_xtalk.c', 15 | 'src/VL53L5CX_ULD_API/src/vl53l5cx_plugin_detection_thresholds.c', 16 | 'vl53l5cx_module.cpp']) 17 | 18 | 19 | setup(ext_modules=[extension]) 20 | -------------------------------------------------------------------------------- /library/vl53l5cx_ctypes/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import sysconfig 4 | import pathlib 5 | from smbus2 import SMBus, i2c_msg 6 | from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, byref, c_int, c_int8, c_uint8, c_int16, c_uint16, c_uint32 7 | 8 | 9 | __version__ = '0.0.3' 10 | 11 | DEFAULT_I2C_ADDRESS = 0x29 12 | 13 | NB_TARGET_PER_ZONE = 1 14 | 15 | RESOLUTION_4X4 = 16 # For completeness, feels nicer just to use 4*4 16 | RESOLUTION_8X8 = 64 17 | 18 | TARGET_ORDER_CLOSEST = 1 19 | TARGET_ORDER_STRONGEST = 2 20 | 21 | RANGING_MODE_CONTINUOUS = 1 22 | RANGING_MODE_AUTONOMOUS = 3 23 | 24 | POWER_MODE_SLEEP = 0 25 | POWER_MODE_WAKEUP = 1 26 | 27 | STATUS_OK = 0 28 | STATUS_TIMEOUT = 1 29 | STATUS_MCU_ERROR = 66 30 | STATUS_INVALID_PARAM = 127 31 | STATUS_ERROR = 255 32 | 33 | STATUS_RANGE_NOT_UPDATED = 0 34 | STATUS_RANGE_LOW_SIGNAL = 1 35 | STATUS_RANGE_TARGET_PHASE = 2 36 | STATUS_RANGE_SIGMA_HIGH = 3 37 | STATUS_RANGE_TARGET_FAILED = 4 38 | STATUS_RANGE_VALID = 5 39 | STATUS_RANGE_NOWRAP = 6 40 | STATUS_RANGE_RATE_FAILED = 7 41 | STATUS_RANGE_SIGNAL_RATE_LOW = 8 42 | STATUS_RANGE_VALID_LARGE_PULSE = 9 43 | STATUS_RANGE_VALID_NO_TARGET = 10 44 | STATUS_RANGE_MEASUREMENT_FAILED = 11 45 | STATUS_RANGE_TARGET_BLURRED = 12 46 | STATUS_RANGE_TARGET_INCONSISTENT = 13 47 | STATUS_RANGE_NO_TARGET = 255 48 | 49 | _I2C_CHUNK_SIZE = 2048 50 | 51 | _I2C_RD_FUNC = CFUNCTYPE(c_int, c_uint8, c_uint16, POINTER(c_uint8), c_uint32) 52 | _I2C_WR_FUNC = CFUNCTYPE(c_int, c_uint8, c_uint16, POINTER(c_uint8), c_uint32) 53 | _SLEEP_FUNC = CFUNCTYPE(c_int, c_uint32) 54 | 55 | # Path to the library dir 56 | _PATH = pathlib.Path(__file__).parent.parent.absolute() 57 | 58 | # System OS/Arch dependent module name suffix 59 | _SUFFIX = sysconfig.get_config_var('EXT_SUFFIX') 60 | 61 | # Library name 62 | _NAME = pathlib.Path("vl53l5cx_ctypes").with_suffix(_SUFFIX) 63 | 64 | # Load the DLL 65 | _VL53 = CDLL(_PATH / _NAME) 66 | 67 | 68 | class VL53L5CX_MotionData(Structure): 69 | _fields_ = [ 70 | ("global_indicator_1", c_uint32), 71 | ("global_indicator_2", c_uint32), 72 | ("status", c_uint8), 73 | ("nb_of_detected_aggregates", c_uint8), 74 | ("nb_of_aggregates", c_uint8), 75 | ("spare", c_uint8), 76 | ("motion", c_uint32 * 32) 77 | ] 78 | 79 | 80 | class VL53L5CX_ResultsData(Structure): 81 | _fields_ = [ 82 | ("silicon_temp_degc", c_int8), 83 | ("ambient_per_spad", c_uint32 * 64), 84 | ("nb_target_detected", c_uint8 * 64), 85 | ("nb_spads_enabled", c_uint32 * 64), 86 | ("signal_per_spad", c_uint32 * 64 * NB_TARGET_PER_ZONE), 87 | ("range_sigma_mm", c_uint16 * 64 * NB_TARGET_PER_ZONE), 88 | ("distance_mm", c_int16 * 64 * NB_TARGET_PER_ZONE), 89 | ("reflectance", c_uint8 * 64 * NB_TARGET_PER_ZONE), 90 | ("target_status", c_uint8 * 64 * NB_TARGET_PER_ZONE), 91 | ("motion_indicator", VL53L5CX_MotionData) 92 | ] 93 | 94 | 95 | class VL53L5CX: 96 | def __init__(self, i2c_addr=DEFAULT_I2C_ADDRESS, i2c_dev=None, skip_init=False): 97 | """Initialise VL53L5CX. 98 | 99 | :param i2c_addr: Sensor i2c address. (defualt: 0x29) 100 | :param skip_init: Skip (slow) sensor init (if it has not been power cycled). 101 | 102 | """ 103 | self._configuration = None 104 | self._motion_configuration = None 105 | 106 | def _i2c_read(address, reg, data_p, length): 107 | msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff]) 108 | msg_r = i2c_msg.read(address, length) 109 | self._i2c.i2c_rdwr(msg_w, msg_r) 110 | 111 | for index in range(length): 112 | data_p[index] = ord(msg_r.buf[index]) 113 | 114 | return 0 115 | 116 | def _i2c_write(address, reg, data_p, length): 117 | # Copy the ctypes pointer data into a Python list 118 | data = [] 119 | for i in range(length): 120 | data.append(data_p[i]) 121 | 122 | for offset in range(0, length, _I2C_CHUNK_SIZE): 123 | chunk = data[offset:offset + _I2C_CHUNK_SIZE] 124 | msg_w = i2c_msg.write(address, [(reg + offset) >> 8, (reg + offset) & 0xff] + chunk) 125 | self._i2c.i2c_rdwr(msg_w) 126 | 127 | return 0 128 | 129 | def _sleep(ms): 130 | time.sleep(ms / 1000.0) 131 | return 0 132 | 133 | self._i2c = i2c_dev or SMBus(1) 134 | self._i2c_rd_func = _I2C_RD_FUNC(_i2c_read) 135 | self._i2c_wr_func = _I2C_WR_FUNC(_i2c_write) 136 | self._sleep_func = _SLEEP_FUNC(_sleep) 137 | self._configuration = _VL53.get_configuration(i2c_addr << 1, self._i2c_rd_func, self._i2c_wr_func, self._sleep_func) 138 | 139 | if not self.is_alive(): 140 | raise RuntimeError(f"VL53L5CX not detected on 0x{i2c_addr:02x}") 141 | 142 | if not skip_init: 143 | if not self.init(): 144 | raise RuntimeError("VL53L5CX init failed!") 145 | 146 | def init(self): 147 | """Initialise VL53L5CX.""" 148 | return _VL53.vl53l5cx_init(self._configuration) == STATUS_OK 149 | 150 | def __del__(self): 151 | if self._configuration: 152 | _VL53.cleanup_configuration(self._configuration) 153 | if self._motion_configuration: 154 | _VL53.cleanup_motion_configuration(self._motion_configuration) 155 | 156 | def enable_motion_indicator(self, resolution=64): 157 | """Enable motion indicator. 158 | 159 | Switch on motion data output. 160 | 161 | :param resolution: Either 4*4 or 8*8 (default: 8*8) 162 | 163 | """ 164 | if self._motion_configuration is None: 165 | self._motion_configuration = _VL53.get_motion_configuration() 166 | return _VL53.vl53l5cx_motion_indicator_init(self._configuration, self._motion_configuration, resolution) == 0 167 | 168 | def set_motion_distance(self, distance_min, distance_max): 169 | """Set motion indicator detection distance. 170 | 171 | :param distance_min: Minimum distance (mm), must be >= 400 172 | :param distance_max: Maximum distance (mm), distance_max - distance_min must be < 1500 173 | 174 | """ 175 | if self._motion_configuration is None: 176 | raise RuntimeError("Enable motion first.") 177 | if distance_min < 400: 178 | raise ValueError("distance_min must be >= 400mm") 179 | if distance_max - distance_min > 1500: 180 | raise ValueError("distance between distance_min and distance_max must be < 1500mm") 181 | return _VL53.vl53l5cx_motion_indicator_set_distance_motion(self._configuration, self._motion_configuration, distance_min, distance_max) 182 | 183 | def is_alive(self): 184 | """Check sensor is connected. 185 | 186 | Attempts to read and validate device and revision IDs from the sensor. 187 | 188 | """ 189 | is_alive = c_int(0) 190 | status = _VL53.vl53l5cx_is_alive(self._configuration, byref(is_alive)) 191 | return status == STATUS_OK and is_alive.value == 1 192 | 193 | def start_ranging(self): 194 | """Start ranging.""" 195 | _VL53.vl53l5cx_start_ranging(self._configuration) 196 | 197 | def stop_ranging(self): 198 | """Stop ranging.""" 199 | _VL53.vl53l5cx_stop_ranging(self._configuration) 200 | 201 | def set_i2c_address(self, i2c_address): 202 | """Change the i2c address.""" 203 | return _VL53.vl53l5cx_set_i2c_address(self._configuration, i2c_address << 1) == STATUS_OK 204 | 205 | def set_ranging_mode(self, ranging_mode): 206 | """Set ranging mode. 207 | 208 | :param ranging_mode: Either Continuous (RANGING_MODE_CONTINUOUS) or Autonomous (RANGING_MODE_AUTONOMOUS). 209 | 210 | """ 211 | _VL53.vl53l5cx_set_ranging_mode(self._configuration, ranging_mode) 212 | 213 | def set_ranging_frequency_hz(self, ranging_frequency_hz): 214 | """Set ranging frequency. 215 | 216 | Set the frequency of ranging data output in continuous mode. 217 | 218 | :param ranging_frequency_hz: Frequency in hz from 1-60Hz at 4*4 and 1-15Hz at 8*8. 219 | 220 | """ 221 | _VL53.vl53l5cx_set_ranging_frequency_hz(self._configuration, ranging_frequency_hz) 222 | 223 | def set_resolution(self, resolution): 224 | """Set sensor resolution. 225 | 226 | Set the sensor resolution for ranging. 227 | 228 | :param resolution: Either 4*4 or 8*8. The lower resolution supports a faster output data rate, 229 | 230 | """ 231 | _VL53.vl53l5cx_set_resolution(self._configuration, resolution) 232 | 233 | def set_integration_time_ms(self, integration_time_ms): 234 | """Set sensor integration time. 235 | 236 | :param integration_time_ms: From 2ms to 1000ms. Must be lower than the ranging period. 237 | 238 | """ 239 | _VL53.vl53l5cx_set_integration_time_ms(self._configuration, integration_time_ms) 240 | 241 | def set_sharpener_percent(self, sharpener_percent): 242 | """Set sharpener intensity. 243 | 244 | Sharpen the rolloff on the edges of closer targets to prevent them occluding more distant targets. 245 | 246 | :param sharpener_percent: From 0 (off) to 99 (full) (hardware default: 5%) 247 | 248 | """ 249 | _VL53.vl53l5cx_set_sharpener_percent(self._configuration, sharpener_percent) 250 | 251 | def set_target_order(self, target_order): 252 | """Set target order. 253 | 254 | Strongest prefers targets with a higher return signal (reflectance) versus Closest preferring targets that are closer. 255 | 256 | :param target_order: Either Strongest (default, TARGET_ORDER_STRONGEST) or Closest (TARGET_ORDER_CLOSEST) 257 | 258 | """ 259 | _VL53.vl53l5cx_set_target_order(self._configuration, target_order) 260 | 261 | def set_power_mode(self, power_mode): 262 | """Set power mode. 263 | 264 | :param power_mode: One of Sleep (POWER_MODE_SLEEP) or Wakeup (POWER_MODE_WAKEUP) 265 | 266 | """ 267 | _VL53.vl53l5cx_set_power_mode(self._configuration, power_mode) 268 | 269 | def data_ready(self): 270 | """Check if data is ready.""" 271 | ready = c_int(0) 272 | status = _VL53.vl53l5cx_check_data_ready(self._configuration, byref(ready)) 273 | return ready.value and status == STATUS_OK 274 | 275 | def get_data(self): 276 | """Get data.""" 277 | results = VL53L5CX_ResultsData() 278 | status = _VL53.vl53l5cx_get_ranging_data(self._configuration, byref(results)) 279 | if status != STATUS_OK: 280 | raise RuntimeError("Error reading data.") 281 | return results 282 | -------------------------------------------------------------------------------- /library/vl53l5cx_module.cpp: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include "vl53l5cx_api.h" 3 | #include "vl53l5cx_plugin_motion_indicator.h" 4 | 5 | void *__symbols__[] = { 6 | (void *)&vl53l5cx_is_alive, 7 | (void *)&vl53l5cx_init, 8 | (void *)&vl53l5cx_set_i2c_address, 9 | (void *)&vl53l5cx_get_power_mode, 10 | (void *)&vl53l5cx_set_power_mode, 11 | (void *)&vl53l5cx_start_ranging, 12 | (void *)&vl53l5cx_stop_ranging, 13 | (void *)&vl53l5cx_check_data_ready, 14 | (void *)&vl53l5cx_get_ranging_data, 15 | (void *)&vl53l5cx_get_resolution, 16 | (void *)&vl53l5cx_set_resolution, 17 | (void *)&vl53l5cx_get_ranging_frequency_hz, 18 | (void *)&vl53l5cx_set_ranging_frequency_hz, 19 | (void *)&vl53l5cx_get_integration_time_ms, 20 | (void *)&vl53l5cx_set_integration_time_ms, 21 | (void *)&vl53l5cx_get_sharpener_percent, 22 | (void *)&vl53l5cx_set_sharpener_percent, 23 | (void *)&vl53l5cx_get_target_order, 24 | (void *)&vl53l5cx_set_target_order, 25 | (void *)&vl53l5cx_get_ranging_mode, 26 | (void *)&vl53l5cx_set_ranging_mode, 27 | (void *)&vl53l5cx_dci_read_data, 28 | (void *)&vl53l5cx_dci_write_data, 29 | (void *)&vl53l5cx_dci_replace_data, 30 | // Motion 31 | (void *)&vl53l5cx_motion_indicator_init, 32 | (void *)&vl53l5cx_motion_indicator_set_distance_motion 33 | }; 34 | 35 | VL53L5CX_Configuration* get_configuration(uint8_t i2c_addr, i2c_read_func i2c_read, i2c_write_func i2c_write, sleep_func sleep_ms) { 36 | VL53L5CX_Configuration *configuration = new VL53L5CX_Configuration{ 37 | .platform = { 38 | .address = i2c_addr, 39 | .i2c_read = i2c_read, 40 | .i2c_write = i2c_write, 41 | .sleep = sleep_ms 42 | }, 43 | }; 44 | return configuration; 45 | } 46 | 47 | void cleanup_configuration(VL53L5CX_Configuration *configuration) { 48 | delete configuration; 49 | } 50 | 51 | VL53L5CX_Motion_Configuration* get_motion_configuration() { 52 | VL53L5CX_Motion_Configuration *configuration = new VL53L5CX_Motion_Configuration{ 53 | 54 | }; 55 | return configuration; 56 | } 57 | 58 | void cleanup_motion_configuration(VL53L5CX_Motion_Configuration *motion_configuration) { 59 | delete motion_configuration; 60 | } 61 | } 62 | --------------------------------------------------------------------------------