Files
drivers/vl53l1x/vl53l1x.go
T
deadprogram 1bb1b621c6 all: correct go fmt
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-09-25 13:23:16 +02:00

578 lines
17 KiB
Go

// Package vl53l1x provides a driver for the VL53L1X time-of-flight
// distance sensor
//
// Datasheet:
// https://www.st.com/resource/en/datasheet/vl53l1x.pdf
// This driver was based on the library https://github.com/pololu/vl53l1x-arduino
// and ST's VL53L1X API (STSW-IMG007)
// https://www.st.com/content/st_com/en/products/embedded-software/proximity-sensors-software/stsw-img007.html
package vl53l1x // import "tinygo.org/x/drivers/vl53l1x"
import (
"errors"
"time"
"tinygo.org/x/drivers"
)
type DistanceMode uint8
type RangeStatus uint8
type rangingData struct {
mm uint16
status RangeStatus
signalRateMCPS int32 //MCPS : Mega Count Per Second
ambientRateMCPS int32
}
type resultBuffer struct {
status uint8
streamCount uint8
effectiveSPADCount uint16
ambientRateMCPSSD0 uint16
mmCrosstalkSD0 uint16
signalRateCrosstalkMCPSSD0 uint16
}
// Device wraps an I2C connection to a VL53L1X device.
type Device struct {
bus drivers.I2C
Address uint16
mode DistanceMode
timeout uint32
fastOscillatorFreq uint16
oscillatorOffset uint16
calibrated bool
VHVInit uint8
VHVTimeout uint8
rangingData rangingData
results resultBuffer
}
// New creates a new VL53L1X connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
mode: LONG,
timeout: 500,
}
}
// Connected returns whether a VL53L1X has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
return d.readReg16Bit(WHO_AM_I) == CHIP_ID
}
// Configure sets up the device for communication
func (d *Device) Configure(use2v8Mode bool) bool {
if !d.Connected() {
return false
}
d.writeReg(SOFT_RESET, 0x00)
time.Sleep(100 * time.Microsecond)
d.writeReg(SOFT_RESET, 0x01)
time.Sleep(1 * time.Millisecond)
start := time.Now()
for (d.readReg(FIRMWARE_SYSTEM_STATUS) & 0x01) == 0 {
elapsed := time.Since(start)
if d.timeout > 0 && uint32(elapsed.Seconds()*1000) > d.timeout {
return false
}
}
if use2v8Mode {
d.writeReg(PAD_I2C_HV_EXTSUP_CONFIG, d.readReg(PAD_I2C_HV_EXTSUP_CONFIG)|0x01)
}
d.fastOscillatorFreq = d.readReg16Bit(OSC_MEASURED_FAST_OSC_FREQUENCY)
d.oscillatorOffset = d.readReg16Bit(RESULT_OSC_CALIBRATE_VAL)
// static config
d.writeReg16Bit(DSS_CONFIG_TARGET_TOTAL_RATE_MCPS, TARGETRATE)
d.writeReg(GPIO_TIO_HV_STATUS, 0x02)
d.writeReg(SIGMA_ESTIMATOR_EFFECTIVE_PULSE_WIDTH_NS, 8)
d.writeReg(SIGMA_ESTIMATOR_EFFECTIVE_AMBIENT_WIDTH_NS, 16)
d.writeReg(ALGO_CROSSTALK_COMPENSATION_VALID_HEIGHT_MM, 0xFF)
d.writeReg(ALGO_RANGE_MIN_CLIP, 0)
d.writeReg(ALGO_CONSISTENCY_CHECK_TOLERANCE, 2)
// general config
d.writeReg16Bit(SYSTEM_THRESH_RATE_HIGH, 0x0000)
d.writeReg16Bit(SYSTEM_THRESH_RATE_LOW, 0x0000)
d.writeReg(DSS_CONFIG_APERTURE_ATTENUATION, 0x38)
// timing config
d.writeReg16Bit(RANGE_CONFIG_SIGMA_THRESH, 360)
d.writeReg16Bit(RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT_MCPS, 192)
// dynamic config
d.writeReg(SYSTEM_GROUPED_PARAMETER_HOLD_0, 0x01)
d.writeReg(SYSTEM_GROUPED_PARAMETER_HOLD_1, 0x01)
d.writeReg(SD_CONFIG_QUANTIFIER, 2)
d.writeReg(SYSTEM_GROUPED_PARAMETER_HOLD, 0x00)
d.writeReg(SYSTEM_SEED_CONFIG, 1)
// Low power auto mode
d.writeReg(SYSTEM_SEQUENCE_CONFIG, 0x8B) // VHV, PHASECAL, DSS1, RANGE
d.writeReg16Bit(DSS_CONFIG_MANUAL_EFFECTIVE_SPADS_SELECT, 200<<8)
d.writeReg(DSS_CONFIG_ROI_MODE_CONTROL, 2) // REQUESTED_EFFFECTIVE_SPADS
d.SetDistanceMode(d.mode)
d.SetMeasurementTimingBudget(50000)
d.writeReg16Bit(ALGO_PART_TO_PART_RANGE_OFFSET_MM, d.readReg16Bit(MM_CONFIG_OUTER_OFFSET_MM)*4)
return true
}
// SetAddress sets the I2C address which this device listens to.
func (d *Device) SetAddress(address uint8) {
d.writeReg(I2C_SLAVE_DEVICE_ADDRESS, address)
d.Address = uint16(address)
}
// GetAddress returns the I2C address which this device listens to.
func (d *Device) GetAddress() uint8 {
return uint8(d.Address)
}
// SetTimeout configures the timeout
func (d *Device) SetTimeout(timeout uint32) {
d.timeout = timeout
}
// SetDistanceMode sets the mode for calculating the distance.
// Distance mode vs. max. distance
// SHORT: 136cm (dark) - 135cm (strong ambient light)
// MEDIUM: 290cm (dark) - 76cm (strong ambient light)
// LONG: 360cm (dark) - 73cm (strong ambient light)
// It returns false if an invalid mode is provided
func (d *Device) SetDistanceMode(mode DistanceMode) bool {
budgetMicroseconds := d.GetMeasurementTimingBudget()
switch mode {
case SHORT:
// timing config
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_A, 0x07)
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_B, 0x05)
d.writeReg(RANGE_CONFIG_VALID_PHASE_HIGH, 0x38)
// dynamic config
d.writeReg(SD_CONFIG_WOI_SD0, 0x07)
d.writeReg(SD_CONFIG_WOI_SD1, 0x05)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD0, 6)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD1, 6)
case MEDIUM:
// timing config
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_A, 0x0B)
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_B, 0x09)
d.writeReg(RANGE_CONFIG_VALID_PHASE_HIGH, 0x78)
// dynamic config
d.writeReg(SD_CONFIG_WOI_SD0, 0x0B)
d.writeReg(SD_CONFIG_WOI_SD1, 0x09)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD0, 10)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD1, 10)
case LONG:
// timing config
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_A, 0x0F)
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_B, 0x0D)
d.writeReg(RANGE_CONFIG_VALID_PHASE_HIGH, 0xB8)
// dynamic config
d.writeReg(SD_CONFIG_WOI_SD0, 0x0F)
d.writeReg(SD_CONFIG_WOI_SD1, 0x0D)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD0, 14)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD1, 14)
default:
return false
}
d.SetMeasurementTimingBudget(budgetMicroseconds)
d.mode = mode
return true
}
// GetMeasurementTimingBudget returns the timing budget in microseconds
func (d *Device) GetMeasurementTimingBudget() uint32 {
macroPeriod := d.calculateMacroPeriod(uint32(d.readReg(RANGE_CONFIG_VCSEL_PERIOD_A)))
rangeConfigTimeout := timeoutMclksToMicroseconds(decodeTimeout(d.readReg16Bit(RANGE_CONFIG_TIMEOUT_MACROP_A)), macroPeriod)
return 2 * uint32(rangeConfigTimeout) * TIMING_GUARD
}
// SetMeasurementTimingBudget configures the timing budget in microseconds
// It returns false if an invalid timing budget is provided
func (d *Device) SetMeasurementTimingBudget(budgetMicroseconds uint32) bool {
if budgetMicroseconds <= TIMING_GUARD {
return false
}
budgetMicroseconds -= TIMING_GUARD
if budgetMicroseconds > 1100000 {
return false
}
rangeConfigTimeout := budgetMicroseconds / 2
// Update Macro Period for Range A VCSEL Period
macroPeriod := d.calculateMacroPeriod(uint32(d.readReg(RANGE_CONFIG_VCSEL_PERIOD_A)))
// Update Phase timeout - uses Timing A
phasecalTimeoutMclks := timeoutMicrosecondsToMclks(1000, macroPeriod)
if phasecalTimeoutMclks > 0xFF {
phasecalTimeoutMclks = 0xFF
}
d.writeReg(PHASECAL_CONFIG_TIMEOUT_MACROP, uint8(phasecalTimeoutMclks))
// Update MM Timing A timeout
d.writeReg16Bit(MM_CONFIG_TIMEOUT_MACROP_A, encodeTimeout(timeoutMicrosecondsToMclks(1, macroPeriod)))
// Update Range Timing A timeout
d.writeReg16Bit(RANGE_CONFIG_TIMEOUT_MACROP_A, encodeTimeout(timeoutMicrosecondsToMclks(rangeConfigTimeout, macroPeriod)))
macroPeriod = d.calculateMacroPeriod(uint32(d.readReg(RANGE_CONFIG_VCSEL_PERIOD_B)))
// Update MM Timing B timeout
d.writeReg16Bit(MM_CONFIG_TIMEOUT_MACROP_B, encodeTimeout(timeoutMicrosecondsToMclks(1, macroPeriod)))
// Update Range Timing B timeout
d.writeReg16Bit(RANGE_CONFIG_TIMEOUT_MACROP_B, encodeTimeout(timeoutMicrosecondsToMclks(rangeConfigTimeout, macroPeriod)))
return true
}
// Read stores in the buffer the values of the sensor and returns
// the current distance in mm
func (d *Device) Read(blocking bool) uint16 {
if blocking {
start := time.Now()
for !d.dataReady() {
elapsed := time.Since(start)
if d.timeout > 0 && uint32(elapsed.Seconds()*1000) > d.timeout {
d.rangingData.status = None
d.rangingData.mm = 0
d.rangingData.signalRateMCPS = 0
d.rangingData.ambientRateMCPS = 0
return d.rangingData.mm
}
}
}
d.readResults()
if !d.calibrated {
d.setupManualCalibration()
d.calibrated = true
}
d.updateDSS()
d.getRangingData()
d.writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01) //sys_interrupt_clear_range
return d.rangingData.mm
}
// updateDSS updates the DSS
func (d *Device) updateDSS() {
spadCount := d.results.effectiveSPADCount
if spadCount != 0 {
totalRatePerSpad := uint32(d.results.signalRateCrosstalkMCPSSD0) + uint32(d.results.ambientRateMCPSSD0)
if totalRatePerSpad > 0xFFFF {
totalRatePerSpad = 0xFFFF
}
totalRatePerSpad <<= 16
totalRatePerSpad /= uint32(spadCount)
if totalRatePerSpad != 0 {
requireSpads := (uint32(TARGETRATE) << 16) / totalRatePerSpad
if requireSpads > 0xFFFF {
requireSpads = 0xFFFF
}
d.writeReg16Bit(DSS_CONFIG_MANUAL_EFFECTIVE_SPADS_SELECT, uint16(requireSpads))
return
}
}
d.writeReg16Bit(DSS_CONFIG_MANUAL_EFFECTIVE_SPADS_SELECT, 0x8000)
}
// readResults read the register and stores the data in the results buffer
func (d *Device) readResults() {
data := make([]byte, 17)
msb := byte((RESULT_RANGE_STATUS >> 8) & 0xFF)
lsb := byte(RESULT_RANGE_STATUS & 0xFF)
d.bus.Tx(d.Address, []byte{msb, lsb}, data)
d.results.status = data[0]
// data[1] report_status : not used
d.results.streamCount = data[2]
d.results.effectiveSPADCount = readUint(data[3], data[4])
// data[5] , data[6] peak signal count rate mcps sd0 : not used
d.results.ambientRateMCPSSD0 = readUint(data[7], data[8])
// data[9] , data[10] sigma_sd0 : not used
// data[11] , data[12] phase_sd0 : not used
d.results.mmCrosstalkSD0 = readUint(data[13], data[14])
d.results.signalRateCrosstalkMCPSSD0 = readUint(data[15], data[16])
}
// dataReady returns true when the data is ready to be read
func (d *Device) dataReady() bool {
return (d.readReg(GPIO_TIO_HV_STATUS) & 0x01) == 0
}
// Distance returns the distance in mm
func (d *Device) Distance() int32 {
return int32(d.rangingData.mm)
}
// Status returns the status of the sensor
func (d *Device) Status() RangeStatus {
return d.rangingData.status
}
// SignalRate returns the peak signal rate in count per second (cps)
func (d *Device) SignalRate() int32 {
return d.rangingData.signalRateMCPS
}
// AmbientRate returns the ambient rate in count per second (cps)
func (d *Device) AmbientRate() int32 {
return d.rangingData.ambientRateMCPS
}
// getRangingData stores in the buffer the ranging data
func (d *Device) getRangingData() {
d.rangingData.mm = uint16((uint32(d.results.mmCrosstalkSD0)*2011 + 0x0400) / 0x0800)
switch d.results.status {
case 1, // VCSELCONTINUITYTESTFAILURE
2, // VCSELWATCHDOGTESTFAILURE
3, // NOVHVVALUEFOUND
17: // MULTCLIPFAIL
d.rangingData.status = HardwareFail
case 13: // USERROICLIP
d.rangingData.status = MinRangeFail
case 18: // GPHSTREAMCOUNT0READY
d.rangingData.status = SynchronizationInt
case 5: // RANGEPHASECHECK
d.rangingData.status = OutOfBoundsFail
case 4: // MSRCNOTARGET
d.rangingData.status = SignalFail
case 6: // SIGMATHRESHOLDCHECK
d.rangingData.status = SignalFail
case 7: // PHASECONSISTENCY
d.rangingData.status = WrapTargetFail
case 12: // RANGEIGNORETHRESHOLD
d.rangingData.status = XtalkSignalFail
case 8: // MINCLIP
d.rangingData.status = RangeValidMinRangeClipped
case 9: // RANGECOMPLETE
if d.results.streamCount == 0 {
d.rangingData.status = RangeValidNoWrapCheckFail
} else {
d.rangingData.status = RangeValid
}
default:
d.rangingData.status = None
}
d.rangingData.signalRateMCPS = 1000000 * int32(d.results.signalRateCrosstalkMCPSSD0) / (1 << 7)
d.rangingData.ambientRateMCPS = 1000000 * int32(d.results.ambientRateMCPSSD0) / (1 << 7)
}
// setupManualCalibration configures the manual calibration
func (d *Device) setupManualCalibration() {
// save original VHV configs
d.VHVInit = d.readReg(VHV_CONFIG_INIT)
d.VHVTimeout = d.readReg(VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND)
// disable VHV init
d.writeReg(VHV_CONFIG_INIT, d.VHVInit&0x7F)
// set loop bound to tuning param
d.writeReg(VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND, (d.VHVTimeout&0x03)+(3<<2))
// override phasecal
d.writeReg(PHASECAL_CONFIG_OVERRIDE, 0x01)
d.writeReg(CAL_CONFIG_VCSEL_START, d.readReg(PHASECAL_RESULT_VCSEL_START))
}
// StartContinuous starts the continuous sensing mode
func (d *Device) StartContinuous(periodMs uint32) {
d.writeReg32Bit(SYSTEM_INTERMEASUREMENT_PERIOD, periodMs*uint32(d.oscillatorOffset))
d.writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01) // sys_interrupt_clear_range
d.writeReg(SYSTEM_MODE_START, 0x40) // mode_range_timed
}
// StopContinuous stops the continuous sensing mode
func (d *Device) StopContinuous() {
d.writeReg(SYSTEM_MODE_START, 0x80) // mode_range_abort
d.calibrated = false
// restore vhv configs
if d.VHVInit != 0 {
d.writeReg(VHV_CONFIG_INIT, d.VHVInit)
}
if d.VHVTimeout != 0 {
d.writeReg(VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND, d.VHVTimeout)
}
// remove phasecal override
d.writeReg(PHASECAL_CONFIG_OVERRIDE, 0x00)
}
// SetROI sets the 'region of interest' for x and y coordinates. Valid ranges are from 4/4 to 16/16.
func (d *Device) SetROI(x, y uint8) error {
if !validROIRange(x, y) {
return errors.New("ROI value out of range")
}
if x > 10 || y > 10 {
d.writeReg(ROI_CONFIG_USER_ROI_CENTRE_SPAD, 199)
}
d.writeReg(ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE, (y-1)<<4|(x-1))
return nil
}
// GetROI returns the currently configured 'region of interest' for x and y coordinates.
func (d *Device) GetROI() (x, y uint8, err error) {
reg := d.readReg(ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE)
x = (reg & 0x0f) + 1
y = ((reg & 0xf0) >> 4) + 1
if !validROIRange(x, y) {
err = errors.New("ROI value out of range")
}
return
}
func validROIRange(x, y uint8) bool {
return x >= 4 && x <= 16 && y >= 4 && y <= 16
}
// writeReg sends a single byte to the specified register address
func (d *Device) writeReg(reg uint16, value uint8) {
msb := byte((reg >> 8) & 0xFF)
lsb := byte(reg & 0xFF)
d.bus.Tx(d.Address, []byte{msb, lsb, value}, nil)
}
// writeReg16Bit sends two bytes to the specified register address
func (d *Device) writeReg16Bit(reg uint16, value uint16) {
data := make([]byte, 4)
data[0] = byte((reg >> 8) & 0xFF)
data[1] = byte(reg & 0xFF)
data[2] = byte((value >> 8) & 0xFF)
data[3] = byte(value & 0xFF)
d.bus.Tx(d.Address, data, nil)
}
// writeReg32Bit sends four bytes to the specified register address
func (d *Device) writeReg32Bit(reg uint16, value uint32) {
data := make([]byte, 6)
data[0] = byte((reg >> 8) & 0xFF)
data[1] = byte(reg & 0xFF)
data[2] = byte((value >> 24) & 0xFF)
data[3] = byte((value >> 16) & 0xFF)
data[4] = byte((value >> 8) & 0xFF)
data[5] = byte(value & 0xFF)
d.bus.Tx(d.Address, data, nil)
}
// readReg reads a single byte from the specified address
func (d *Device) readReg(reg uint16) uint8 {
data := []byte{0}
msb := byte((reg >> 8) & 0xFF)
lsb := byte(reg & 0xFF)
d.bus.Tx(d.Address, []byte{msb, lsb}, data)
return data[0]
}
// readReg16Bit reads two bytes from the specified address
// and returns it as a uint16
func (d *Device) readReg16Bit(reg uint16) uint16 {
data := []byte{0, 0}
msb := byte((reg >> 8) & 0xFF)
lsb := byte(reg & 0xFF)
d.bus.Tx(d.Address, []byte{msb, lsb}, data)
return readUint(data[0], data[1])
}
// readReg32Bit reads four bytes from the specified address
// and returns it as a uint32
func (d *Device) readReg32Bit(reg uint16) uint32 {
data := make([]byte, 4)
msb := byte((reg >> 8) & 0xFF)
lsb := byte(reg & 0xFF)
d.bus.Tx(d.Address, []byte{msb, lsb}, data)
return readUint32(data)
}
// readUint converts two bytes to uint16
func readUint(msb byte, lsb byte) uint16 {
return (uint16(msb) << 8) | uint16(lsb)
}
// readUint converts four bytes to uint32
func readUint32(data []byte) uint32 {
if len(data) != 4 {
return 0
}
var value uint32
value = uint32(data[0]) << 24
value |= uint32(data[1]) << 16
value |= uint32(data[2]) << 8
value |= uint32(data[3])
return value
}
// encodeTimeout encodes the timeout in the correct format: (LSByte * 2^MSByte) + 1
func encodeTimeout(timeoutMclks uint32) uint16 {
if timeoutMclks == 0 {
return 0
}
msb := 0
lsb := timeoutMclks - 1
for (lsb & 0xFFFFFF00) > 0 {
lsb >>= 1
msb++
}
return uint16(msb<<8) | uint16(lsb&0xFF)
}
// decodeTimeout decodes the timeout from the format: (LSByte * 2^MSByte) + 1
func decodeTimeout(regVal uint16) uint32 {
return (uint32(regVal&0xFF) << (regVal >> 8)) + 1
}
// timeoutMclksToMicroseconds transform from mclks to microseconds
func timeoutMclksToMicroseconds(timeoutMclks uint32, macroPeriodMicroseconds uint32) uint32 {
return uint32((uint64(timeoutMclks)*uint64(macroPeriodMicroseconds) + 0x800) >> 12)
}
// timeoutMicrosecondsToMclks transform from microseconds to mclks
func timeoutMicrosecondsToMclks(timeoutMicroseconds uint32, macroPeriodMicroseconds uint32) uint32 {
return ((timeoutMicroseconds << 12) + (macroPeriodMicroseconds >> 1)) / macroPeriodMicroseconds
}
// calculateMacroPerios calculates the macro period in microsendos from the vcsel period
func (d *Device) calculateMacroPeriod(vcselPeriod uint32) uint32 {
pplPeriodMicroseconds := (uint32(1) << 30) / uint32(d.fastOscillatorFreq)
vcselPeriodPclks := (vcselPeriod + 1) << 1
macroPeriodMicroseconds := 2304 * pplPeriodMicroseconds
macroPeriodMicroseconds >>= 6
macroPeriodMicroseconds *= vcselPeriodPclks
macroPeriodMicroseconds >>= 6
return macroPeriodMicroseconds
}