Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 13:55 08 Jan 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : "Driver" for vl53l5cx 8x8 pixel time-of-flight distance sensor

Author Message
karlelch

Guru

Joined: 30/10/2014
Location: Germany
Posts: 310
Posted: 07:22am 06 Jan 2026
Copy link to clipboard 
Print this post

Hi,

I case someone is interested in a distance sensor - I recently wrote a "driver" for the vl53l5cx sensor, which can provide an 8x8 pixel "image" of distances in a field-of-view of approx. 60 degrees. It's available on breakout boards from several companies and a nice solution for robotic "vision".

The writing the driver was challenging, as it first needs to upload approx. 90k of driver binaries to the chip via I2C. All the files are in this
zip. Up to a point, I used AI to port the MicroPython version, but the code has still two issues I could not solve even with the support of different LLMs (at some point, the complained that Basic does not work as its in the code, despite having the excellent MMBasic manual ...). In any case, the driver works in 8x8 mode but the distances are approx. a (linear) factor 4 off, pointing at some issue with the complex way the sensor uses calibration data.

Thanks to all the fast MEMORY, MATH etc. commands, the driver loads within a few seconds and retrieving (and converting) a new distance image takes only a few ms. This really demonstrates what one can do with MMBasic on the hardware/data processing side.

' VL53L5CX Driver for MMBasic (PicoMite) v0.7.3
' --------------------------------------
' Copyright (c) 2025-26 Thomas Euler
' MIT Licence
'
' Many functions are a direct port of the underlying API functions from the
' `vl53l5cx_api.cpp` library (`SparkFun_VL53L5CX_Arduino_Library`) written by
' SparkFun Electronics, November 2024
'
' v0.7.0 (2026-01-01)
' - LInput command for reading binary files accelerates loading firmware
' v0.7.1
' - Using structures (Types) for internal variables
' - Main program to test subroutine towards library
' v0.7.2
' - Before loading firmware, check if sensor has already initialized to
'   shorten startup time
' - BytesToInt16 improved, now faster
' - ToF functions tested, including calibration
' v0.7.3
' - ToF functions removed; turned into a library
'
' Unsolved issues:
' ---------------
' - 8x8 data is not correctly scaled (approx. 4x larger); this is currently
'   "heuristically" corrected
' - 4x4 mode does not generate data
'
' Requirements:
' ------------
' File structure on SD card:
'   /vl53l5cx/firmware.bin  (86016 bytes)
'   /vl53l5cx/config.bin    (972 bytes)
'   /vl53l5cx/xtalk.bin     (776 bytes)
'   /vl53l5cx/nvm_cmd.bin   (40 bytes)
'
' I/O pins:
' - GP12 (SDA) and GP13 (SCL) for I2C2
'
' List of available target status:
'   0 - Ranging data are not updated
'   1 - Signal rate too low on SPAD array
'   2 - Target phase
'   3 - Sigma estimator too high
'   4 - Target consistency failed
'   5 - Range valid
'   6 - Wrap around not performed (typically the first range)
'   7 - Rate consistency failed
'   8 - Signal rate too low for the current target
'   9 - Range valid with large pulse (may be due to a merged target)
'  10 - Range valid, but no target detected at previous range
'  11 - Measurement consistency failed
'  12 - Target blurred by another one, due to sharpener
'  13 - Target detected but inconsistent data. Frequently happens for
'       secondary targets.
' 255 - No target detected (only if number of targets detected is enabled)
'
' ----------------------------------------------------------------------------
Option Base 0
Option explicit
Option Default Integer
Option Escape

Print "VL53L5CX sensor driver in library"
Print "| Hardware requirements: I2C (GP12, GP13)"

' ----------------------------------------------------------------------------
' Constants
Const VL53_I2C_SDA          = MM.Info(PinNo GP12)
Const VL53_I2C_SCL          = MM.Info(PinNo GP13)
Const VL53_I2C_ADDR         = &H29
Const VL53_BUF_SIZE         = 16384    ' Buffer size for reading
Const VL53_I2C_CHUNK        = 192      ' I2C write reliable chunk size
Const VL53_I2C_FREQ         = 1000
Const VL53_DEV_ID           = &HF0
Const VL53_REV_ID           = &H02
Const VL53_RES_4x4          = 16
Const VL53_RES_8x8          = 64
Const VL53_RMODE_CONTINOUS  = 1
Const VL53_RMODE_AUTONOMOUS = 3
Const VL53_VERBOSE          = 0

' Register map
Const VL53_REG_SOFT_RESET   = &H0000
Const VL53_REG_PAGE_SEL     = &H7FFF
Const VL53_REG_CMD_STAT     = &H2C00
Const VL53_REG_BOOT_STAT    = &H0006
Const VL53_REG_SYSTEM_STAT  = &H00E5
Const VL53_KUI_CMD_START    = &H2C04
Const VL53_KUI_CMD_END      = &H2FFF
Const VL53_KDCI_FREQ_HZ     = &H5458
Const VL53_KDCI_ZONE_CONFIG = &H5450
Const VL53_KDCI_DSS_CONFIG  = &HAD38
Const VL53_KDCI_RANGE_MODE  = &HAD30

' Firmware section and buffer sizes
Const VL53_FW_PART1_SIZE    = &H8000   ' 32KB
Const VL53_FW_PART2_SIZE    = &H8000   ' 32KB
Const VL53_FW_PART3_SIZE    = &H5000   ' 20KB
Const VL53_KNMV_DATA_SIZE   = 492
Const VL53_KOFFS_DATA_SIZE  = 488
Const VL53_KTEMP_BUF_SIZE   = 1440
Const VL53_KXTALK_DATA_SIZE = 776
Const VL53_KCONFIG_SIZE     = 972

' Internal structure
Type TVL53
 stream_count As integer
 dread_size As integer
 kBHTypeShift As integer
 kBHTypeMask As integer
 kBHSizeShift As integer
 kBHSizeMask As integer
 kBHIdxShift As integer
 kBHIdxMask As integer
 frame_count As Integer
End Type

' Ranging data pixel
Type TVL53Pixel
 dist_mm As integer  ' distance in [mm]
 t_state As integer  ' pixel state (see list above)
 n_trgts As integer  ' # of targets detected
End Type

' ----------------------------------------------------------------------------
Sub VL53.test _res%, _freq%
 ' Testing the sensor
 Dim Integer res = 0, iFr
 If _res% = 0 Then _res% = VL53_RES_8x8
 If _freq% = 0 Then _freq% = 5

 VL53.init
 Pause 500

 tof_params.dxy = Int(Sqr(_res%))
 tof_params.freq_Hz = _freq%

 res = VL53.set_freq_Hz(_freq%)
 Print "Ranging frequency [Hz]=";VL53.get_freq_Hz()
 res = VL53.set_resolution(_res%)
 Print "Resolution=";VL53.get_resolution()
 res = VL53.set_ranging_mode(VL53_RMODE_CONTINOUS)
 res = VL53.get_ranging_mode()
 Print "Ranging mode=";Choice(res = 1, "continuous", "autonomous")

 res = VL53.start_ranging()
 Print "Start ranging ->";res
 Pause 200
 For iFr=0 To 199
   VL53.update_data
   Struct Print vl53_data()
   Pause 500
 Next
 res = VL53.stop_ranging()
 Print "Stop ranging ->";res
 VL53.close
 Print "Done."
End Sub

' =============================================================================
' Initialization/finalization of sensor
' -----------------------------------------------------------------------------
Sub VL53.init
 ' Initialize sensor and load firmware
 Local Integer res, n, m, tmp, status = 0, i, p_tmp, p2
 Local string path$
 Local Float t_ms = Timer

 ' Some global data buffers and variables
 Dim Integer vl53_offs_data(VL53_KOFFS_DATA_SIZE -1)
 Dim Integer vl53_xtalk_data(VL53_KXTALK_DATA_SIZE -1)
 Dim Integer vl53_tmp_buf(VL53_KTEMP_BUF_SIZE -1)
 p_tmp = Peek(VarAddr vl53_tmp_buf())
 Dim _vl53 As TVL53
 _vl53.kBHTypeShift = 0
 _vl53.kBHTypeMask  = &H000F << _vl53.kBHTypeShift
 _vl53.kBHSizeShift = 4
 _vl53.kBHSizeMask  = &H0FFF << _vl53.kBHSizeShift
 _vl53.kBHIdxShift  = 16
 _vl53.kBHIdxMask   = &HFFFF << _vl53.kBHIdxShift
 Dim vl53_data(VL53_RES_8x8 -1) As TVL53Pixel

 ' Setup I2C
 Print "Opening VL53L5CX ..."
 SetPin VL53_I2C_SDA, VL53_I2C_SCL, I2C
 I2C OPEN VL53_I2C_FREQ, 1000

 ' Check sensor presence
 I2C Write VL53_I2C_ADDR, 0, 1, 0
 If MM.I2C = 0 And VL53.isAlife(1) Then
   Print "| Sensor detected."
 Else
   Print "| Error: Sensor not found at &H"; Hex$(VL53_I2C_ADDR)
   I2C CLOSE
   Exit Sub
 EndIf
 Pause 500

 ' Check if sensor already initialized, if so, exit
 res = VL53.get_resolution()
 If res = 16 Or res = 64 Then
   Print "| Sensor already initialized"
   Exit Sub
 EndIf

 ' Initializing sensor
 Print "| Initializing sensor ..."

 ' Sw reboot sequence
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 _WriteI2CReg16 &H0009, &H04
 _WriteI2CReg16 &H000F, &H40
 _WriteI2CReg16 &H000A, &H03
 tmp = _ReadI2CReg16(&H7FFF)
 _WriteI2CReg16 &H000C, &H01
 _WriteI2CReg16 &H0101, &H00
 _WriteI2CReg16 &H0102, &H00
 _WriteI2CReg16 &H010A, &H01
 _WriteI2CReg16 &H4002, &H01
 _WriteI2CReg16 &H4002, &H00
 _WriteI2CReg16 &H010A, &H03
 _WriteI2CReg16 &H0103, &H01
 _WriteI2CReg16 &H000C, &H00
 _WriteI2CReg16 &H000F, &H43
 Pause 1
 _WriteI2CReg16 &H000F, &H40
 _WriteI2CReg16 &H000A, &H01
 Pause 100

 ' Wait for sensor booted (several ms required to get sensor ready)
 ' (self.poll_for_answer(1, 0,  &H06, 0xff, 1))
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 status = status Or VL53._waitForStatus(1, 0, &H06, &HFF, 1)
 _WriteI2CReg16 &H000E, &H01
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H02

 ' Enable Fw access
 ' (self.poll_for_answer(1, 0, &H21, &H10, &H10))
 _WriteI2CReg16 &H03, &H0D
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H01
 status = status Or VL53._waitForStatus(1, 0, &H21, &H10, &H10)
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00

 ' Enable host access to GO1
 _WriteI2CReg16 &H000C, &H01

 ' Power ON status
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 _WriteI2CReg16 &H0101, &H00
 _WriteI2CReg16 &H0102, &H00
 _WriteI2CReg16 &H010A, &H01
 _WriteI2CReg16 &H4002, &H01
 _WriteI2CReg16 &H4002, &H00
 _WriteI2CReg16 &H010A, &H03
 _WriteI2CReg16 &H0103, &H01
 _WriteI2CReg16 &H400F, &H00
 _WriteI2CReg16 &H021A, &H43
 _WriteI2CReg16 &H021A, &H03
 _WriteI2CReg16 &H021A, &H01
 _WriteI2CReg16 &H021A, &H00
 _WriteI2CReg16 &H0219, &H00
 _WriteI2CReg16 &H021B, &H00

 ' Wake up MCU
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 _WriteI2CReg16 &H000C, &H00
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H01
 _WriteI2CReg16 &H0020, &H07
 _WriteI2CReg16 &H0020, &H06

 ' Load firmware in three sections
 Print "| Loading firmware ..."
 path$ = "/vl53l5cx/firmware.bin"
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H09
 n = 0
 m = VL53_FW_PART1_SIZE
 res = VL53._loadFirmwareSect(Path$, 0, n, m, 1)
 If Not(res) Then GoTo VL53.LoadError

 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H0A
 n = VL53_FW_PART1_SIZE
 m = VL53_FW_PART2_SIZE
 res = VL53._LoadFirmwareSect(Path$, 0, n, m)
 If Not(res) Then GoTo VL53.LoadError

 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H0B
 n = VL53_FW_PART1_SIZE +VL53_FW_PART2_SIZE
 m = VL53_FW_PART3_SIZE
 res = VL53._LoadFirmwareSect(Path$, 0, n, m, 2)
 If Not(res) Then GoTo VL53.LoadError

 ' Check if FW correctly downloaded
 ' (self.poll_for_answer(1, 0, &H21, &H10, &H10))
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H02
 _WriteI2CReg16 &H0003, &H0D
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H01
 status = status Or VL53._waitForStatus(1, 0, &H21, &H10, &H10)
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 _WriteI2CReg16 &H000C, &H01

 ' Reset MCU And wait boot
 ' (self.poll_for_answer(1, 0, &H06, &Hff, &H00))
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 _WriteI2CReg16 &H0114, &H00
 _WriteI2CReg16 &H0115, &H00
 _WriteI2CReg16 &H0116, &H42
 _WriteI2CReg16 &H0117, &H00
 _WriteI2CReg16 &H000B, &H00
 _WriteI2CReg16 &H000C, &H00
 _WriteI2CReg16 &H000B, &H01
 status = status Or VL53._waitForStatus(1, 0, &H06, &HFF, 0)
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H02

 ' Get offset NVM data and store them into the offset buffer
 ' (self.poll_for_answer(4, 0, self.kUiCmdStatus, 0xff, 0x02))
 path$ = "/vl53l5cx/nvm_cmd.bin"
 res = VL53._loadSmallFile(Path$, &H2FD8, 40)
 If Not(res) Then GoTo VL53.LoadError
 res = VL53._waitForStatus(4, 0, VL53_REG_CMD_STAT, &HFF, &H02)
 If res Then
   Print "| Error: NVM command timeout"
   GoTo VL53.LoadError
 EndIf
 _ReadI2CReg16Mult VL53_KUI_CMD_START, vl53_tmp_buf(), VL53_KNMV_DATA_SIZE
 p2 = Peek(VarAddr vl53_offs_data())
 Memory Copy Integer p_tmp, p2, VL53_KOFFS_DATA_SIZE
 res = _sendOffsetData(VL53_RES_4X4)

 ' Set default Xtalk shape, send Xtalk to sensor
 path$ = "/vl53l5cx/xtalk.bin"
 res = VL53._loadSmallFile(Path$, 0, VL53_KXTALK_DATA_SIZE, 1)
 If Not(res) Then
   Print "| Error: Timeout when loading calibration data"
   GoTo VL53.LoadError
 EndIf
 p2 = Peek(VarAddr vl53_xtalk_data())
 Memory Copy Integer p_tmp, p2, VL53_KXTALK_DATA_SIZE
 res = _sendXTalkData(VL53_RES_4x4)
 If res Then GoTo VL53.LoadError

 ' Send default configuration to VL53L5CX firmware
 ' (self.poll_for_answer(4, 1, self.kUiCmdStatus, 0xff, 0x03))
 path$ = "/vl53l5cx/conf.bin"
 res = VL53._loadSmallFile(Path$, &H2C34, VL53_KCONFIG_SIZE)
 If Not(res) Then GoTo VL53.LoadError
 res = VL53._waitForStatus(4, 1, VL53_REG_CMD_STAT, &HFF, &H03)
 If res Then
   Print "| Error: Timeout when loading configuration"
   GoTo VL53.LoadError
 EndIf

 ' Set to kNbTargetPerZone=1 and single_range=0x01
 Local Integer buf(3) = (1, 0, &H01, 0), srange = &H01
 res = _DCIWriteData(buf(), &HCF78, 4)
 Memory Unpack Peek(VarAddr srange), buf(), 4, 8
 res = _DCIWriteData(buf(), &HCD5C, 4)

 Print "| Done in ";Str$((Timer -t_ms)/1000,0,1);" sec."
 Exit Sub

VL53.LoadError:
 Print "| Error: Firmware load failed"
 I2C CLOSE
End Sub


Sub VL53.close
 I2C Close
 Print "VL53L5CX closed."
End Sub

' -----------------------------------------------------------------------------
' Method to check if sensor is alife
' -----------------------------------------------------------------------------
Function VL53.isAlife(log%)
 ' Returns True if device is alife
 Local integer devID, revID
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H00
 devID = _ReadI2CReg16(0)
 revID = _ReadI2CReg16(1)
 _WriteI2CReg16 VL53_REG_PAGE_SEL, &H02
 If log% Then
   Print "| Device ID=";Hex$(devID,4);" revision ID=";Hex$(revID,4)
 EndIf
 VL53.isAlife = (devID = VL53_DEV_ID) And (revID = VL53_REV_ID)
End Function

' -----------------------------------------------------------------------------
' Methods for setting/getting sensor parameters (frequency, resolution, mode)
' -----------------------------------------------------------------------------
Function VL53.get_freq_Hz()
 ' Returns the current ranging frequency in Hz
 Local integer stat
 stat = _DCIReadData(vl53_tmp_buf(), VL53_KDCI_FREQ_HZ, 4)
 VL53.get_freq_Hz = vl53_tmp_buf(1)
End Function


Function VL53.set_freq_Hz(freq%)
 ' Sets current ranging frequency in Hz, returns True if successful
 Local integer stat, tmp(1) = (freq%, 0)
 stat = _DCIReplaceData(vl53_tmp_buf(), VL53_KDCI_FREQ_HZ, 4, tmp(), 1, 1)
 VL53.set_freq_Hz = (stat = 0)
End Function

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function VL53.get_resolution()
 ' Returns the current resolution with 16 for 4x4 and 64 for 8x8
 Local integer stat
 stat = _DCIReadData(vl53_tmp_buf(), VL53_KDCI_ZONE_CONFIG, 8)
 VL53.get_resolution = vl53_tmp_buf(0) *vl53_tmp_buf(1)
End Function


Function VL53.set_resolution(resol%)
 ' Sets resolution, returns 0 if successful
 Local integer stat = 0
 If resol% = VL53_RES_4x4 Then
   stat = _DCIReadData(vl53_tmp_buf(), VL53_KDCI_DSS_CONFIG, 16)
   vl53_tmp_buf(&H04) = 64
   vl53_tmp_buf(&H06) = 64
   vl53_tmp_buf(&H09) = 4
   stat = stat Or _DCIWriteData(vl53_tmp_buf(), VL53_KDCI_DSS_CONFIG, 16)
   stat = stat Or _DCIReadData(vl53_tmp_buf(), VL53_KDCI_ZONE_CONFIG, 8)
   vl53_tmp_buf(&H00) = 4
   vl53_tmp_buf(&H01) = 4
   vl53_tmp_buf(&H04) = 8
   vl53_tmp_buf(&H05) = 8
   stat = stat Or _DCIWriteData(vl53_tmp_buf(), VL53_KDCI_ZONE_CONFIG, 8)
 Else If resol% = VL53_RES_8x8 Then
   stat = _DCIReadData(vl53_tmp_buf(), VL53_KDCI_DSS_CONFIG, 16)
   vl53_tmp_buf(&H04) = 16
   vl53_tmp_buf(&H06) = 16
   vl53_tmp_buf(&H09) = 1
   stat = stat Or _DCIWriteData(vl53_tmp_buf(), VL53_KDCI_DSS_CONFIG, 16)
   stat = stat Or _DCIReadData(vl53_tmp_buf(), VL53_KDCI_ZONE_CONFIG, 8)
   vl53_tmp_buf(&H00) = 8
   vl53_tmp_buf(&H01) = 8
   vl53_tmp_buf(&H04) = 4
   vl53_tmp_buf(&H05) = 4
   stat = stat Or _DCIWriteData(vl53_tmp_buf(), VL53_KDCI_ZONE_CONFIG, 8)
 Else
   Print "Error: Invalid resolution"
 EndIf
 stat = stat Or _sendOffsetData(resol%)
 stat = stat Or _sendXTalkData(resol%)
 VL53.set_resolution = stat
End Function

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function VL53.get_ranging_mode()
 ' Returns the current ranging mode
 Local Integer stat
 stat = _DCIReadData(vl53_tmp_buf(), VL53_KDCI_RANGE_MODE, 8)
 If vl53_tmp_buf(1) = &H01 Then
   VL53.get_ranging_mode = VL53_RMODE_CONTINOUS
 Else
   VL53.get_ranging_mode = VL53_RMODE_AUTONOMOUS
 EndIf
End Function


Function VL53.set_ranging_mode(_mode%)
 ' Set the ranging mode. Two modes are `continuous` and `autonomous
 Local Integer stat, sr(3) = (0, 0, 0, 0)
 stat = _DCIReadData(vl53_tmp_buf(), VL53_KDCI_RANGE_MODE, 8)
 If _mode% = VL53_RMODE_CONTINOUS Then
   vl53_tmp_buf(1) = &H01
   vl53_tmp_buf(3) = &H03
 Else If _mode% = VL53_RMODE_AUTONOMOUS Then
   vl53_tmp_buf(1) = &H03
   vl53_tmp_buf(3) = &H02
   sr(0) = &H01
 Else
   stat = 1
 EndIf
 If stat = 0 Then
   stat = stat Or _DCIWriteData(vl53_tmp_buf(), VL53_KDCI_RANGE_MODE, 8)
   stat = stat Or _DCIWriteData(sr(), &HCD5C, 4)
 EndIf
 VL53.set_ranging_mode = stat
End Function

' -----------------------------------------------------------------------------
' Methods for starting/stoping ranging and checking for/retrieving new data
' -----------------------------------------------------------------------------
Function VL53.start_ranging()
 ' Starts ranging ...
 Local Integer stat = 0, op_size = 12, TrgtPerZone = 1
 Local Integer bh_ptr_type, bh_ptr_size, bh_ptr_idx, resol, i, j
 Local Integer hconf(1) = (0, 0), buf(4* op_size -1)
 Local Integer cmd(3) = (0, &H03, 0, 0), output(op_size -1)
 Local Integer output_bh_enable(3) = (&H07, 0, 0, &HC0000000)

 resol = VL53.get_resolution()
 _vl53.stream_count = 255
 _vl53.dread_size = 0
 _vl53.frame_count = 0
 output(0)  = &H0000000D
 output(1)  = &H54B400C0
 output(2)  = &H54C00040
 output(3)  = &H54D00104
 output(4)  = &H55D00404
 output(5)  = &HCF7C0401
 output(6)  = &HCFBC0404
 output(7)  = &HD2BC0402
 output(8)  = &HD33C0402
 output(9)  = &HD43C0401
 output(10) = &HD47C0401
 output(11) = &HCC5008C0

 ' Enable selected outputs
 /*
 Inc output_bh_enable(0), 8    ' VL53L5CX_ENABLE_AMBIENT_PER_SPAD
 Inc output_bh_enable(0), 16   ' VL53L5CX_ENABLE_NB_SPADS_ENABLED
 Inc output_bh_enable(0), 64   ' VL53L5CX_ENABLE_SIGNAL_PER_SPAD
 Inc output_bh_enable(0), 128  ' VL53L5CX_ENABLE_RANGE_SIGMA_MM
 Inc output_bh_enable(0), 512  ' VL53L5CX_ENABLE_REFLECTANCE_PERCENT
 Inc output_bh_enable(0), 2048 ' VL53L5CX_ENABLE_MOTION_INDICATOR
 */
 Inc output_bh_enable(0), 32   ' VL53L5CX_ENABLE_NB_TARGET_DETECTED
 Inc output_bh_enable(0), 256  ' VL53L5CX_ENABLE_DISTANCE_MM
 Inc output_bh_enable(0), 1024 ' VL53L5CX_ENABLE_TARGET_STATUS

 ' Send output addresses
 For i=0 To 11
   j = (output_bh_enable(i \32) And (1 << (i Mod 32))) = 0
   If (output(i) = 0) Or j Then Continue For

   bh_ptr_type = (output(i) And _vl53.kBHTypeMask) >> _vl53.kBHTypeShift
   bh_ptr_size = (output(i) And _vl53.kBHSizeMask) >> _vl53.kBHSizeShift
   bh_ptr_idx  = (output(i) And _vl53.kBHIdxMask)  >> _vl53.kBHIdxShift

   If (bh_ptr_type >= &H01) And (bh_ptr_type < &H0d) Then
     If (bh_ptr_idx >= &H54D0) And (bh_ptr_idx < &H54D0 +960) Then
       bh_ptr_size = resol
       output(i) = output(i) And (INV _vl53.kBHSizeMask)
       output(i) = output(i) Or (resol << _vl53.kBHSizeShift)
     Else
       bh_ptr_size = resol *TrgtPerZone
       output(i) = output(i) And (INV _vl53.kBHSizeMask)
       output(i) = output(i) Or ((resol *TrgtPerZone) << _vl53.kBHSizeShift)
     EndIf
     Inc _vl53.dread_size, bh_ptr_type *bh_ptr_size
   Else
     Inc _vl53.dread_size, bh_ptr_size
   EndIf
   Inc _vl53.dread_size, 4
 Next
 Inc _vl53.dread_size, 20

 _uint32ArrayToBytes output(), buf(), op_size
 stat = stat Or _DCIWriteData(buf(), &HCD78, op_size *4)

 hconf(0) = _vl53.dread_size
 hconf(1) = i +2
 _uint32ArrayToBytes hconf(), buf(), 2
 stat = stat Or _DCIWriteData(buf(), &HCD60, 8)
 _uint32ArrayToBytes output_bh_enable(), buf(), 4
 stat = stat Or _DCIWriteData(buf(), &HCD68, 16)

 ' Start xshut bypass (interrupt mode)
 _WriteI2CReg16 &H7FFF, &H00
 _WriteI2CReg16 &H0009, &H05
 _WriteI2CReg16 &H7FFF, &H02

 ' Start ranging session
 _WriteI2CReg16Mult VL53_KUI_CMD_END -(4 -1), cmd(), 4
 stat = stat Or VL53._waitForStatus(4, 1, VL53_REG_CMD_STAT, &HFF, &H03)

 VL53.start_ranging = stat
End Function

' .............................................................................
Function VL53.check_data_ready()
 ' Checks if new data is ready, returns True if so.
 Local Integer check
 _ReadI2CReg16Mult 0, vl53_tmp_buf(), 4
 check = vl53_tmp_buf(0) <> _vl53.stream_count
 check = check And (vl53_tmp_buf(0) <> 255) And (vl53_tmp_buf(1) = 5)
 check = check And ((vl53_tmp_buf(2) And &H05) = &H05)
 If check And ((vl53_tmp_buf(3) And &H10) = &H10) Then
   _vl53.stream_count = vl53_tmp_buf(0)
   VL53.check_data_ready = 1
   Exit Function
 EndIf
 If VL53_VERBOSE Then
   Print "buf=";Hex$(vl53_tmp_buf(0),2);" ";Hex$(vl53_tmp_buf(1),2);
   Print " ";Hex$(vl53_tmp_buf(2),2);" ";Hex$(vl53_tmp_buf(3),2)
 EndIf
 VL53.check_data_ready = 0
End Function

' .............................................................................
Sub VL53.update_data()
 ' Gets the ranging data, using the selected output and resolution, and
 ' and saves it in `vl53_data().dist_mm`
'Local Float t = Timer
 Static Integer ptb = Peek(VarAddr vl53_tmp_buf()), bh, pbh = Peek(VarAddr bh)
 Static Integer tmp(VL53_RES_8x8 -1), p_t = Peek(VarAddr tmp())
 Static Integer ntg(VL53_RES_8x8 -1), p_n = Peek(VarAddr ntg())
 Local Integer bh_ptr_type, bh_ptr_size, bh_ptr_idx
 Local Integer stat = 0, i, msize, p

 ' Get the data
 _ReadI2CReg16Mult 0, vl53_tmp_buf(), _vl53.dread_size
 _vl53.stream_count = vl53_tmp_buf(0)
 _swapBuffer vl53_tmp_buf(), _vl53.dread_size
 Inc _vl53.frame_count, 1

 ' Start conversion at position 16 to avoid headers
 For i=16 To _vl53.dread_size -1 Step 4
   Memory Pack ptb +i*8, pbh, 4, 8
   bh_ptr_type = (bh And _vl53.kBHTypeMask) >> _vl53.kBHTypeShift
   bh_ptr_size = (bh And _vl53.kBHSizeMask) >> _vl53.kBHSizeShift
   bh_ptr_idx  = (bh And _vl53.kBHIdxMask)  >> _vl53.kBHIdxShift
   msize = bh_ptr_size
   If (bh_ptr_type > &H01) And (bh_ptr_type < &H0D) Then
     msize = msize *bh_ptr_type
   EndIf
   p = ptb +(i+4)*8

   ' Extract selected data
   If bh_ptr_idx = &HCF7C Then
     ' # of targets detected
     Memory Copy Integer p, p_n, msize
     Struct Insert ntg(), vl53_data().n_trgts

   Else If bh_ptr_idx = &HD33C Then
     ' Distances
     stat = _BytesToI16Arr(vl53_tmp_buf(), tmp(), i+4, msize)
     Struct Insert tmp(), vl53_data().dist_mm

   Else If bh_ptr_idx = &HD47C Then
     ' target status
     Memory Copy Integer p, p_t, msize
     Struct Insert tmp(), vl53_data().t_state
     Exit For
   EndIf
   ' Advance by payload size
   Inc i, msize
 Next

 ' Negate pixel distances for which no target was detected
 Math Scale ntg(), 2, ntg()
 Math Add ntg(), -1, ntg()
 Math C_Mult ntg(), vl53_data().dist_mm, vl53_data().dist_mm
't = Timer -t : Print "update_ranging_data t=";t
End Sub

' .............................................................................
Function VL53.stop_ranging()
 ' Stops a ranging session. It must be used when the sensor streams,
 ' after calling `VL53.start_ranging()`. Returns 0 if successful
 Local integer stat = 0, asf(3), tout = 0, tmp = 0
 _ReadI2CReg16Mult &H2FFC, asf(), 4
 If Not((asf(0)=0) And (asf(1)=0) And (asf(2)=4) And (asf(3)=&HFF)) Then
   _WriteI2CReg16 &H7FFF, &H00

   ' Provoke MCU stop
   _WriteI2CReg16 &H15, &H16
   _WriteI2CReg16 &H14, &H01

   ' Poll for G02 status 0 MCU stop
   Do While (((tmp And &H80) >> 7) = 0) And (tout < 500)
     tmp = _ReadI2CReg16(&H06)
     Pause 10
     Inc tout, 1
     If tout >= 500 Then stat = 255
   Loop
 EndIf

 ' Undo MCU stop
 _WriteI2CReg16 &H7FFF, &H00
 _WriteI2CReg16 &H14, &H00
 _WriteI2CReg16 &H15, &H00

 ' Stop xshut bypass
 _WriteI2CReg16 &H09, &H04
 _WriteI2CReg16 &H7FFF, &H02

 VL53.stop_ranging = stat
End Function

' ============================================================================
' Internal methods
' -----------------------------------------------------------------------------
Function VL53._loadFirmwareSect(fname$, tAddr, fOffs, sectSize, flag)
 ' Load a section of firmware from file
 Local Integer bytesRead, totalRead, currAddr
 Local Integer i, j, writeSize, byteVal, res, n
 Local Integer DataBuf(VL53_BUF_SIZE \8 +1), pDB = Peek(VarAddr DataBuf())
 Local Integer writeBuf(VL53_I2C_CHUNK +2 -1)

 VL53._loadFirmwareSect = 0
 currAddr = tAddr
 totalRead = 0

 ' Check if file exists
 If Not(MM.Info(EXISTS FILE fname$)) Then
   Print "| Error: File `";fname$;"` not found"
   Exit Function
 EndIf

 ' Open file for binary reading
 Open fname$ For RANDOM As #1
 If flag = 1 Then Print "| Reading `";fname$;"` ";

 ' Read and write in chunks
 Do While totalRead < sectSize
   ' Calculate chunk size to read
   bytesRead = Min(VL53_BUF_SIZE, sectSize -totalRead)

   ' Read chunk from file
   Seek #1, fOffs +totalRead +1
   n = LInput(DataBuf(), #1, bytesRead)
   If n <> bytesRead Then
     Print "| Error: Size issue with file `";fname$;"`"
     Exit Function
   EndIf
   Memory Copy Integer pDB +8, pDB, bytesRead \8

   ' Write chunk to sensor via I2C
   j = 0
   Do While j < bytesRead
     writeSize = Min(VL53_I2C_CHUNK, bytesRead -j)

     ' Prepare I2C data
     writeBuf(0) = (currAddr >> 8) And &HFF
     writeBuf(1) = currAddr And &HFF
     For i=0 To writeSize -1
       writeBuf(i +2) = Peek(Var DataBuf(), j +i)
     Next

     ' Write to I2C
     I2C WRITE VL53_I2C_ADDR, 0, writeSize +2, writeBuf()
     res = MM.I2C
     If res <> 0 Then
       Print "| I2C write error ";res;" at 0x";Hex$(currAddr)
       Close #1
       VL53._loadFirmwareSect = 0
       Exit Function
     EndIf

     Inc currAddr, writeSize
     Inc j, writeSize
   Loop
   Inc totalRead, bytesRead

   ' Show progress
   If (totalRead Mod VL53_BUF_SIZE) = 0 Then Print ".";
 Loop

 Close #1
 If flag = 2 Then Print " Done"
 VL53._LoadFirmwareSect = 1
End Function

' -----------------------------------------------------------------------------
Function VL53._loadSmallFile(fname$, tAddr, fSize, toTmpBuf)
 ' Load small configuration file and write it to the I2C register or into
 ' `vl53_tmp_buf()`
 Local Integer n1, tmp1(fSize \8 +1)
 VL53._loadSmallFile = 1

 If Not(MM.Info(EXISTS FILE fname$)) Then
   Print "| Error: File `";fname$;"` not found"
   VL53._loadSmallFile = 0
   Exit Function
 EndIf

 ' Open file for binary reading
 Open fname$ For RANDOM As #1
 Print "| Reading `";fname$;"` ...";

 ' Read entire small file to buffer
 Seek #1, 1
 n1 = LInput(tmp1(), #1, fSize)
 If n1 <> fSize Then
   Print "| Error: Size issue with file `";fname$;"`"
   VL53._loadSmallFile = 0
   Exit Function
 EndIf
 Memory Unpack Peek(VarAddr tmp1()) +8, vl53_tmp_buf(), fSize, 8
 Close #1

 ' Write directly to I2C bus, if requested
 If Not(toTmpBuf) Then
   _WriteI2CReg16Mult tAddr, vl53_tmp_buf(), fsize
 EndIf
 Print " Done"
End Function

' -----------------------------------------------------------------------------
Function VL53._waitForStatus(n%, p%, reg%, mask%, vExp%)
 ' Wait for status register to reach expected value
 Local Integer stat = 0, j, buf(Max(n% -1, 1))
 Local Integer tout_ms = 2000
 If VL53_VERBOSE Then
   Print "_waitForStatus ";n%;" ";p%;" ";reg%;" ";mask%;" ";vExp%
 EndIf
 Do
   _ReadI2CReg16Mult reg%, buf(), n%
   If (n% >= 4) Then
     If (buf(2) >= &H7F) Then stat = stat Or 1
   EndIf
   If (buf(p%) And mask%) = vExp% Then
     VL53._waitForStatus = stat
     If VL53_VERBOSE Then Print "->ok"
     Exit Function
   EndIf
   Pause 10
   Inc tout_ms, -10
 Loop Until tout_ms <= 0

 stat = stat Or buf(2)
 VL53._waitForStatus = stat
 If VL53_VERBOSE Then Print "->timeout"
End Function

' -----------------------------------------------------------------------------
' Support functions
' -----------------------------------------------------------------------------
Function _sendXTalkData(res%)
 ' Set the Xtalk data from generic configuration, or user's calibration.
 ' (`vl53_tmp_buf()` already contains the xtalk_data)
 Local Integer stat1 = 0, i, j, v, p11, p21
 Local Integer sg(63)

 ' Copy xtalk data into temp buffer
 p11 = Peek(VarAddr vl53_xtalk_data())
 P21 = Peek(VarAddr vl53_tmp_buf())
 Memory Copy Integer p11, P21, VL53_KXTALK_DATA_SIZE

 If res% = VL53_RES_4x4 Then
   ' Data extrapolation is required for 4x4 Xtalk
   vl53_tmp_buf(&H08)    = &H0F
   vl53_tmp_buf(&H08 +1) = &H04
   vl53_tmp_buf(&H08 +2) = &H04
   vl53_tmp_buf(&H08 +3) = &H17
   vl53_tmp_buf(&H08 +4) = &H08
   vl53_tmp_buf(&H08 +5) = &H10
   vl53_tmp_buf(&H08 +6) = &H10
   vl53_tmp_buf(&H08 +7) = &H07

   vl53_tmp_buf(&H20)    = &H00
   vl53_tmp_buf(&H20 +1) = &H78
   vl53_tmp_buf(&H20 +2) = &H00
   vl53_tmp_buf(&H20 +3) = &H08
   vl53_tmp_buf(&H20 +4) = &H00
   vl53_tmp_buf(&H20 +5) = &H00
   vl53_tmp_buf(&H20 +6) = &H00
   vl53_tmp_buf(&H20 +7) = &H08
   _swapBuffer vl53_tmp_buf(), VL53_KXTALK_DATA_SIZE

   For i=0 To 63
     sg(i) = vl53_tmp_buf(&H34 +i)
   Next
   For j=0 To 3
     For i=0 To 3
       v = 0
       Inc v, sg((2 *i) +(16 *j) +0)
       Inc v, sg((2 *i) +(16 *j) +1)
       Inc v, sg((2 *i) +(16 *j) +8)
       Inc v, sg((2 *i) +(16 *j) +9)
       sg(i +(4 *j)) = v \4
     Next
   Next
   ' TODO: The cpp lib was a bit odd here, it appeared to memset well
   ' past the end of the arrays. Not sure if that was intentional or
   ' necessary
   ' It's possible these were located at known memory locations in the
   ' cpp lib and we wanted to zero out the stuff directly after them
   ' as well
   Memory Set Integer Peek(VarAddr sg()) +&H10 *8, 0, 64 -&H10
   For i=0 To 63
     vl53_tmp_buf(&H34 +i) = sg(i)
   Next
   _swapBuffer vl53_tmp_buf(), VL53_KXTALK_DATA_SIZE
   vl53_tmp_buf(&H134)    = &HA0
   vl53_tmp_buf(&H134 +1) = &HFC
   vl53_tmp_buf(&H134 +2) = &H01
   vl53_tmp_buf(&H134 +3) = &H00
   Memory Set Integer Peek(VarAddr vl53_tmp_buf()) +&H078 *8, 0, 4
 EndIf

 _WriteI2CReg16Mult &H2CF8, vl53_tmp_buf(), VL53_KXTALK_DATA_SIZE
 stat1 = stat1 Or VL53._waitForStatus(4, 1, VL53_REG_CMD_STAT, &HFF, &H03)
 _sendXTalkData = stat1
End Function

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function _sendOffsetData(resol%)
  ' Set the offset data gathered from NVM
 Local Integer stat1 = 0, i1, j1, v, p11, p21
 Local Integer sg(63), rg(63)

 ' Copy offset data into temp buffer
 p11 = Peek(VarAddr vl53_offs_data())
 p21 = Peek(VarAddr vl53_tmp_buf())
 Memory Copy Integer p11, p21, VL53_KOFFS_DATA_SIZE

 If resol% = VL53_RES_4x4 Then
   vl53_tmp_buf(&H10)    = &H0F
   vl53_tmp_buf(&H10 +1) = &H04
   vl53_tmp_buf(&H10 +2) = &H04
   vl53_tmp_buf(&H10 +3) = &H00
   vl53_tmp_buf(&H10 +4) = &H08
   vl53_tmp_buf(&H10 +5) = &H10
   vl53_tmp_buf(&H10 +6) = &H10
   vl53_tmp_buf(&H10 +7) = &H07
   _swapBuffer vl53_tmp_buf(), VL53_KOFFS_DATA_SIZE

   For i1=0 To 63
     sg(i1) = vl53_tmp_buf(&H03C +i1)
     rg(i1) = vl53_tmp_buf(&H140 +i1)
   Next

   For j1=0 To 3
     For i1=0 To 3
       v = 0
       Inc v, sg((2 *i1) +(16 *j1) +0)
       Inc v, sg((2 *i1) +(16 *j1) +1)
       Inc v, sg((2 *i1) +(16 *j1) +8)
       Inc v, sg((2 *i1) +(16 *j1) +9)
       sg(i1 +(4 *j1)) = v \4
       v = 0
       Inc v, rg((2 *i1) +(16 *j1) +0)
       Inc v, rg((2 *i1) +(16 *j1) +1)
       Inc v, rg((2 *i1) +(16 *j1) +8)
       Inc v, rg((2 *i1) +(16 *j1) +9)
       rg(i1 +(4 *j1)) = v \4
     Next
   Next
   ' TODO: The cpp lib was a bit odd here, it appeared to memset well
   ' past the end of the arrays. Not sure if that was intentional or
   ' necessary
   ' It's possible these were located at known memory locations in the
   ' cpp lib and we wanted to zero out the stuff directly after them
   ' as well
   Memory Set Integer Peek(VarAddr rg()) +&H10 *8, 0, 64 -&H10
   Memory Set Integer Peek(VarAddr sg()) +&H10 *8, 0, 64 -&H10
   For i1=0 To 63
     vl53_tmp_buf(&H03C +i1) = sg(i1)
     vl53_tmp_buf(&H140 +i1) = rg(i1)
   Next
   _swapBuffer vl53_tmp_buf(), VL53_KOFFS_DATA_SIZE
 EndIf

 For i1=0 To VL53_KOFFS_DATA_SIZE -4 -1
   vl53_tmp_buf(i1) = vl53_tmp_buf(i1 +8)
 Next
 vl53_tmp_buf(&H1E0)    = 0
 vl53_tmp_buf(&H1E0 +1) = 0
 vl53_tmp_buf(&H1E0 +2) = 0
 vl53_tmp_buf(&H1E0 +3) = &H0F
 vl53_tmp_buf(&H1E0 +4) = &H03
 vl53_tmp_buf(&H1E0 +5) = &H01
 vl53_tmp_buf(&H1E0 +6) = &H01
 vl53_tmp_buf(&H1E0 +7) = &HE4
 _WriteI2CReg16Mult &H2E18, vl53_tmp_buf(), VL53_KOFFS_DATA_SIZE
 stat1 = stat1 Or VL53._waitForStatus(4, 1, VL53_REG_CMD_STAT, &HFF, &H03)
 _sendOffsetData = stat1
End Function

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function _DCIReadData(_data%(), index%, n%)
 ' Read 'extra data' from DCI, returns 0 if o.k.
 Local integer stat2 = 0, rd_size = n% +12, i
 Local integer cmd(11) = (0, 0, 0, 0, 0, 0, 0, &H0F, 0, &H02, 0, &H08)
 cmd(0) = index% >> 8
 cmd(1) = index% And &HFF
 cmd(2) = (n% And &HFF0) >> 4
 cmd(3) = (n% And &HF) << 4

 ' Request data reading from FW
 _WriteI2CReg16Mult VL53_KUI_CMD_END -11, cmd(), 12
 stat2 = stat2 Or VL53._waitForStatus(4, 1, VL53_REG_CMD_STAT, &HFF, &H03)

 ' Read new data sent (4 bytes header + data_size + 8 bytes footer)
 _ReadI2CReg16Mult VL53_KUI_CMD_START, vl53_tmp_buf(), rd_size
 _swapBuffer vl53_tmp_buf(), rd_size
 For i=0 To n% -1 : _data%(i) = vl53_tmp_buf(i +4) : Next
 _DCIReadData = stat2
End Function


Function _DCIWriteData(_data%(), index%, n%)
 ' Write 'extra data' to DCI, returns 0 if o.k.
 Local integer stat2 = 0, addr, i2
 Local integer header(3), footer(7) = (0, 0, 0, &H0F, &H05, &H01, 0, 0)
 header(0) = index% >> 8
 header(1) = index% And &HFF
 header(2) = (n% And &HFF0) >> 4
 header(3) = (n% And &HF) << 4
 footer(6) = (n% +8) >> 8
 footer(7) = (n% +8) And &HFF
 addr = VL53_KUI_CMD_END -(n% +12) +1

 ' Copy data from array to FW format (+4 bytes to add header) and
 ' add headers and footer
 _swapBuffer _data%(), n%
 For i2=n% -1 To 0 Step -1 : vl53_tmp_buf(i2 +4) = _data%(i2) : Next
 For i2=0 To 3 : vl53_tmp_buf(i2) = header(i2) : Next
 For i2=0 To 7 : vl53_tmp_buf(i2 +4 +n%) = footer(i2) : Next

 ' Send data to FW
 _WriteI2CReg16Mult addr, vl53_tmp_buf(), n% +12
 stat2 = stat2 Or VL53._waitForStatus(4, 1, VL53_REG_CMD_STAT, &HFF, &H03)
 _swapBuffer _data%(), n%
 _DCIWriteData = stat2
End Function


Function _DCIReplaceData(_data%(), index%, n%, _data2%(), n2%, p%)
 ' Replace 'extra data' in DCI, returns 0 if successful
 Local integer stat1 = 0, i
 stat1 = stat1 Or _DCIReadData(_data%(), index%, n%)
 For i=p% To p% +n2% -1 : _data%(i) = _data2%(i -p%) : Next
 stat1 = stat1 Or _DCIWriteData(_data%(), index%, n%)
 _DCIReplaceData = stat1
End Function

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sub _WriteI2CReg16 reg%, _val%
 ' Write a single byte to a 16-bit register
 Local Integer _data(2)
 _data(0) = (reg% >> 8) And &HFF
 _data(1) = reg% And &HFF
 _data(2) = _val% And &HFF
 I2C WRITE VL53_I2C_ADDR, 0, 3, _data()
End Sub


Sub _WriteI2CReg16Mult reg%, vals%(), n%
 ' Write multiple bytes to a 16-bit register
 Local Integer chMaxSize = 128, _data(chMaxSize +1)
 Local Integer i3, j3, nCh = n% \chMaxSize +1
 Local Integer pCh, chLen, currReg, m
 If VL53_VERBOSE Then Print "_WriteI2CReg16Mult"
 For i3=0 To nCh -1
   ' Send data in chunks
   currReg = reg% +i3 *chMaxSize
   _data(0) = (currReg >> 8) And &HFF
   _data(1) = currReg And &HFF
   pCh = i3 *chMaxSize
   chLen = Min(n% -chMaxSize *i3, chMaxSize)
   For j3=0 To chLen -1
     ' Copy chunk to output array
     _data(2 +j3) = vals%(pCh +j3) And &HFF
   Next
   m = Choice(j3 < (chLen -1), 1, 0)
   I2C WRITE VL53_I2C_ADDR, m, chLen +2, _data()
 Next
 If VL53_VERBOSE Then _printByteArrayAsHex vals%(), n%
End Sub


Function _ReadI2CReg16(reg%)
 ' Read a single byte from a 16-bit register and returns the byte
 Local Integer _data(1), res3
 _data(0) = (reg% >> 8) And &HFF
 _data(1) = reg% And &HFF
 I2C WRITE VL53_I2C_ADDR, 0, 2, _data()
 I2C READ VL53_I2C_ADDR, 0, 1, res3
 _ReadI2CReg16 = res3
End Function


Sub _ReadI2CReg16Mult reg%, vals%(), n%
 ' Read multiple bytes from a 16-bit register into `vals%()`
 I2C WRITE VL53_I2C_ADDR, 0, 2, (reg% >> 8) And &HFF, reg% And &HFF
 I2C READ VL53_I2C_ADDR, 0, n%, vals%()
End Sub


Sub _swapBuffer buf%(), size%
 ' Reverse the sequence of groups of 4 values
 Local Integer tmp3(size% -1), n3 = size% /4
 Local Integer pb = Peek(VarAddr buf%()), pt = Peek(VarAddr tmp3())
 Memory Copy Integer pb,    pt+24, n3, 4, 4
 Memory Copy Integer pb+8,  pt+16, n3, 4, 4
 Memory Copy Integer pb+16, pt+8,  n3, 4, 4
 Memory Copy Integer pb+24, pt,    n3, 4, 4
 Memory Copy Integer pt, pb, size%
End Sub


Sub _uint32ArrayToBytes _uint32%(), _bytes%(), n_uint32%
 ' Converts a list of uint32 values to a list of bytes.
 Local Integer i3
 Math Set 0, _bytes%()
 For i3=0 To n_uint32% -1
   _bytes%(i3*4)    = _uint32(i3) And &HFF
   _bytes%(i3*4 +1) = (_uint32(i3) >> 8) And &HFF
   _bytes%(i3*4 +2) = (_uint32(i3) >> 16) And &HFF
   _bytes%(i3*4 +3) = _uint32(i3) >> 24
 Next
End Sub

/*
Function _BytesToUI32Arr(_bytes%(), _uint32%(), p%, n%)
 ' Converts a list of `n% bytes into uint32 values, starting from byte `p%
 ' Returns 0 if successful
 Local Integer nb, i3, tmp3(3)
 Local Integer pb3 = Peek(VarAddr _bytes%()) +p%
 Local Integer pt3 = Peek(VarAddr tmp3())
 nb = Choice(n% = 0, Bound(_bytes%()) +1 -p%, n%)
 If (nb Mod 4) <> 0 Then
   _BytesToUI32Arr = -1
 Else If (Bound(_uint32%()) +1) < (nb \4) Then
   _BytesToUI32Arr = -1
 Else
   For i3=0 To nb-1 Step 4
     Memory Copy pb3 +p% +i3*4, pt3, 4
     Memory Pack tmp3(), Peek(VarAddr _uint32%()) +i3\4, 4, 8
   Next
 EndIf
 _BytesToUI32Arr = 0
End Function
*/

Function _BytesToI16Arr(_bytes%(), _int16%(), p%, n%)
 ' Converts a list of `n%` bytes into int16 values, starting from byte `p%`;
 ' returns 0 if successful
'Local Integer i3
 Local Integer _pb = Peek(VarAddr _bytes%()) +p% *8
 Local Integer _pi = Peek(VarAddr _int16%())
 If (n% Mod 2) <> 0 Then
   _BytesToI16Arr = -1
 Else If (Bound(_int16%()) +1) < (n% \2) Then
   _BytesToI16Arr = -1
 Else
   Memory Copy _pb, _pi, n%, 16, 8
   Memory Copy _pb +8, _pi +1, n%, 16, 8
   /*
   _printByteArrayAsHex _bytes%(), n%, p%
   _printByteArrayAsHex _int16%(), n% \2
   For i3=0 To n%-1 Step 2
     _int16%(i3 \2) = (_bytes%(p% +i3 +1) << 8) Or _bytes%(p% +i3)
    'If _int16%(i3 \2) > 32767 Then Inc _int16%(i3 \2), -65536
   Next
   _printByteArrayAsHex _int16%(), n% \2
   */
   _BytesToI16Arr = 0
 EndIf
End Function

' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sub _printByteArrayAsHex _array%(), _size%, _p%
 Local Integer i2
 For i2=_p% To _size% +_p% -1
   If (i2 Mod 16) = 0 Then Print Str$(i2, 3);": ";
   Print Hex$(_array%(i2), 2);" ";
   If ((i2 +1) Mod 16) = 0 Or i2 = (_size% -1) Then Print
 Next
End Sub


Sub _printMat m%(), title$, dy%, scl!
 ' Print `m%() as matrix, convert to matrix, if 1D, and scale it by `scl`
 Local Integer nx = Bound(m%(), 1), ny = Bound(m%(), 2)
 Local Integer ix, iy, nn = 0
 If scl! = 0 Then scl! = 1

 ' Make sure that data is in matrix form and scale
 If ny = 0 Then
   nn = nx +1
   nx = nn \dy% -1
   ny = dy% -1
 Else
   nn = (nx +1) *(ny +1)
 EndIf
 Local Integer tmp2d(nx, ny), pt = Peek(VarAddr tmp2d())
 Memory Copy Integer Peek(VarAddr m%()), pt, nn
 Local Float vals(nx, ny)
 Math Scale tmp2d(), scl!, vals()

 ' Print matrix
 Print "| ";title$
 For iy=0 To ny
   Print "| ";
   For ix=0 To nx : Print Str$(vals(ix, iy), 4, 1) +" "; : Next
   Print
 Next
End Sub

' -----------------------------------------------------------------------------
 
Frank N. Furter
Guru

Joined: 28/05/2012
Location: Germany
Posts: 1037
Posted: 08:17am 06 Jan 2026
Copy link to clipboard 
Print this post

   
Many thanks for sharing!

Frank
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5592
Posted: 02:08pm 06 Jan 2026
Copy link to clipboard 
Print this post

Hi Thomas,

Thanks for sharing. Your output is currently numerical. Is this going to feed a robot for autonomous movement ? The range is up to 2 meters, correct ?

Volhout
PicomiteVGA PETSCII ROBOTS
 
karlelch

Guru

Joined: 30/10/2014
Location: Germany
Posts: 310
Posted: 05:16pm 06 Jan 2026
Copy link to clipboard 
Print this post

Hi Volhout,
  Volhout said  Thanks for sharing. Your output is currently numerical. Is this going to feed a robot for autonomous movement ? The range is up to 2 meters, correct ?
Volhout

Yes, this is just the driver "VL53.update_data" updates the global structure "vl53_data()", which contains 64 (8x8) distance measurements in mm plus for each pixel some information about measurement reliability. The range is up to 2 m, under good conditions. The data is a in linear array for simplicity, it needs to be mapped to a 8x8 array to get the spatial arrangement back.

My robot code (to be published soon) uses this driver as a library to calculate distances in front of the robot. Currently, I am working on filtering the data for reliability and calculating the distances relative to a flat surface to detect obstacles and cliffs. These data will then steer the robot.

Best
Thomas
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026