Pico driver for vl53l1x Time of Flight breakout

pull/68/head
Simon Reap 2021-02-22 21:46:48 +00:00 zatwierdzone przez Phil Howard
rodzic 53f3539c3a
commit 0166208a52
9 zmienionych plików z 2544 dodań i 0 usunięć

Wyświetl plik

@ -1,3 +1,4 @@
add_subdirectory(st7789)
add_subdirectory(msa301)
add_subdirectory(rv3028)
add_subdirectory(vl53l1x)

Wyświetl plik

@ -0,0 +1,10 @@
add_library(vl53l1x INTERFACE)
target_sources(vl53l1x INTERFACE
${CMAKE_CURRENT_LIST_DIR}/vl53l1x.cpp
)
target_include_directories(vl53l1x INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(vl53l1x INTERFACE pico_stdlib hardware_i2c)

Wyświetl plik

@ -0,0 +1,42 @@
Most of the functionality of this library is based on the VL53L1X API provided
provided by ST (STSW-IMG007), and some of the explanatory comments are quoted
or paraphrased from the API source code, API user manual (UM2356), and VL53L1X
datasheet. Therefore, the license terms for the API source code (BSD 3-clause
"New" or "Revised" License) also apply to this derivative work, as specified
below.
For more information, see
https://www.pololu.com/
https://forum.pololu.com/
--------------------------------------------------------------------------------
Copyright (c) 2017, STMicroelectronics
Copyright (c) 2018-2020, Pololu Corporation
All Rights Reserved
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Wyświetl plik

@ -0,0 +1,10 @@
add_library(vl53l1x INTERFACE)
target_sources(vl53l1x INTERFACE
${CMAKE_CURRENT_LIST_DIR}/vl53l1x.cpp
)
target_include_directories(vl53l1x INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(vl53l1x INTERFACE pico_stdlib hardware_i2c)

Wyświetl plik

@ -0,0 +1,811 @@
// Most of the functionality of this library is based on the VL53L1X API
// provided by ST (STSW-IMG007), and some of the explanatory comments are quoted
// or paraphrased from the API source code, API user manual (UM2356), and
// VL53L1X datasheet. Therefore, the license terms for the API source code
// (BSD 3-clause "New" or "Revised" License) also apply to this derivative work.
// Based on the code in https://github.com/pololu/vl53l1x-arduino
// Modified by https://github.com/simon3270/driver-vl53l1x
#include "vl53l1x.hpp"
// Constructors ////////////////////////////////////////////////////////////////
namespace pimoroni {
// Public Methods //////////////////////////////////////////////////////////////
// Initialize sensor using settings taken mostly from VL53L1_DataInit() and
// VL53L1_StaticInit().
// We are running a breakout, so will definitely configure the sensor for 2V8 mode
uint16_t VL53L1X::getid() {
return readReg16Bit(IDENTIFICATION__MODEL_ID);
}
uint16_t VL53L1X::getosc() {
return readReg16Bit(OSC_MEASURED__FAST_OSC__FREQUENCY);
}
void VL53L1X::setosc(uint16_t value) {
writeReg16Bit(OSC_MEASURED__FAST_OSC__FREQUENCY, value);
}
bool VL53L1X::init(bool io_2v8)
{
// Set some defaults
setTimeout(0);
did_timeout = false;
calibrated = true;
saved_vhv_init = 0;
saved_vhv_timeout = 0;
// distance_mode = 1;
// Initialise I2C connection
i2c_init(i2c, 400000);
gpio_set_function(sda, GPIO_FUNC_I2C); gpio_pull_up(sda);
gpio_set_function(scl, GPIO_FUNC_I2C); gpio_pull_up(scl);
last_status = 0;
// check model ID and module type registers (values specified in datasheet)
if (readReg16Bit(IDENTIFICATION__MODEL_ID) != 0xEACC) { return false; }
// VL53L1_software_reset() begin
writeReg(SOFT_RESET, 0x00);
sleep_us(100);
writeReg(SOFT_RESET, 0x01);
// give it some time to boot; otherwise the sensor NACKs during the readReg()
// call below and the Arduino 101 doesn't seem to handle that well
sleep_ms(1000);
// VL53L1_poll_for_boot_completion() begin
startTimeout();
// check last_status in case we still get a NACK to try to deal with it correctly
while ((readReg(FIRMWARE__SYSTEM_STATUS) & 0x01) == 0 || last_status != 0)
{
if (checkTimeoutExpired())
{
did_timeout = true;
return false;
}
}
// VL53L1_poll_for_boot_completion() end
// VL53L1_software_reset() end
// VL53L1_DataInit() begin
// sensor uses 1V8 mode for I/O by default; switch to 2V8 mode if necessary
if (io_2v8)
{
writeReg(PAD_I2C_HV__EXTSUP_CONFIG,
readReg(PAD_I2C_HV__EXTSUP_CONFIG) | 0x01);
}
// store oscillator info for later use
fast_osc_frequency = readReg16Bit(OSC_MEASURED__FAST_OSC__FREQUENCY);
osc_calibrate_val = readReg16Bit(RESULT__OSC_CALIBRATE_VAL);
// VL53L1_DataInit() end
// VL53L1_StaticInit() begin
// Note that the API does not actually apply the configuration settings below
// when VL53L1_StaticInit() is called: it keeps a copy of the sensor's
// register contents in memory and doesn't actually write them until a
// measurement is started. Writing the configuration here means we don't have
// to keep it all in memory and avoids a lot of redundant writes later.
// the API sets the preset mode to LOWPOWER_AUTONOMOUS here:
// VL53L1_set_preset_mode() begin
// VL53L1_preset_mode_standard_ranging() begin
// values labeled "tuning parm default" are from vl53l1_tuning_parm_defaults.h
// (API uses these in VL53L1_init_tuning_parm_storage_struct())
// static config
// API resets PAD_I2C_HV__EXTSUP_CONFIG here, but maybe we don't want to do
// that? (seems like it would disable 2V8 mode)
writeReg16Bit(DSS_CONFIG__TARGET_TOTAL_RATE_MCPS, TargetRate); // should already be this value after reset
writeReg(GPIO__TIO_HV_STATUS, 0x02);
writeReg(SIGMA_ESTIMATOR__EFFECTIVE_PULSE_WIDTH_NS, 8); // tuning parm default
writeReg(SIGMA_ESTIMATOR__EFFECTIVE_AMBIENT_WIDTH_NS, 16); // tuning parm default
writeReg(ALGO__CROSSTALK_COMPENSATION_VALID_HEIGHT_MM, 0x01);
writeReg(ALGO__RANGE_IGNORE_VALID_HEIGHT_MM, 0xFF);
writeReg(ALGO__RANGE_MIN_CLIP, 0); // tuning parm default
writeReg(ALGO__CONSISTENCY_CHECK__TOLERANCE, 2); // tuning parm default
// general config
writeReg16Bit(SYSTEM__THRESH_RATE_HIGH, 0x0000);
writeReg16Bit(SYSTEM__THRESH_RATE_LOW, 0x0000);
writeReg(DSS_CONFIG__APERTURE_ATTENUATION, 0x38);
// timing config
// most of these settings will be determined later by distance and timing
// budget configuration
writeReg16Bit(RANGE_CONFIG__SIGMA_THRESH, 360); // tuning parm default
writeReg16Bit(RANGE_CONFIG__MIN_COUNT_RATE_RTN_LIMIT_MCPS, 192); // tuning parm default
// dynamic config
writeReg(SYSTEM__GROUPED_PARAMETER_HOLD_0, 0x01);
writeReg(SYSTEM__GROUPED_PARAMETER_HOLD_1, 0x01);
writeReg(SD_CONFIG__QUANTIFIER, 2); // tuning parm default
// VL53L1_preset_mode_standard_ranging() end
// from VL53L1_preset_mode_timed_ranging_*
// GPH is 0 after reset, but writing GPH0 and GPH1 above seem to set GPH to 1,
// and things don't seem to work if we don't set GPH back to 0 (which the API
// does here).
writeReg(SYSTEM__GROUPED_PARAMETER_HOLD, 0x00);
writeReg(SYSTEM__SEED_CONFIG, 1); // tuning parm default
// from VL53L1_config_low_power_auto_mode
writeReg(SYSTEM__SEQUENCE_CONFIG, 0x8B); // VHV, PHASECAL, DSS1, RANGE
writeReg16Bit(DSS_CONFIG__MANUAL_EFFECTIVE_SPADS_SELECT, 200 << 8);
writeReg(DSS_CONFIG__ROI_MODE_CONTROL, 2); // REQUESTED_EFFFECTIVE_SPADS
// VL53L1_set_preset_mode() end
// default to long range, 50 ms timing budget
// note that this is different than what the API defaults to
setDistanceMode(Long);
setMeasurementTimingBudget(50000);
// VL53L1_StaticInit() end
// the API triggers this change in VL53L1_init_and_start_range() once a
// measurement is started; assumes MM1 and MM2 are disabled
writeReg16Bit(ALGO__PART_TO_PART_RANGE_OFFSET_MM,
readReg16Bit(MM_CONFIG__OUTER_OFFSET_MM) * 4);
return true;
}
// Write an 8-bit register
void VL53L1X::writeReg(uint16_t reg, uint8_t value)
{
uint8_t buffer[3] = {(reg >> 8) & 0xFF, reg & 0xFF, value};
i2c_write_blocking(i2c, address, buffer, 3, false);
}
// Write a 16-bit register
void VL53L1X::writeReg16Bit(uint16_t reg, uint16_t value)
{
uint8_t buffer[4] = {(reg >> 8) & 0xFF, reg & 0xFF, (value >> 8) & 0xFF, value & 0xFF};
i2c_write_blocking(i2c, address, buffer, 4, false);
}
// Write a 32-bit register
void VL53L1X::writeReg32Bit(uint16_t reg, uint32_t value)
{
uint8_t buffer[6] = {(reg >> 8) & 0xFF, reg & 0xFF,
(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF};
i2c_write_blocking(i2c, address, buffer, 6, false);
}
// Read an 8-bit register
uint8_t VL53L1X::readReg(regAddr reg)
{
uint8_t regbuf[2] = {((uint8_t)reg >> 8) & 0xFF, (uint8_t)reg & 0xFF};
uint8_t buffer[1];
uint8_t value;
i2c_write_blocking(i2c, address, regbuf, 2, true);
i2c_read_blocking(i2c, address, buffer, 1, false);
value = buffer[0];
return value;
}
// Read a 16-bit register
uint16_t VL53L1X::readReg16Bit(uint16_t reg)
{
uint8_t regbuf[2] = {(reg >> 8) & 0xFF, reg & 0xFF};
uint8_t buffer[2];
uint16_t value;
reg= (reg << 8) + (reg >> 8);
i2c_write_blocking(i2c, address, regbuf, 2, true);
i2c_read_blocking(i2c, address, buffer, 2, false);
value= (buffer[0] << 8) + buffer[1];
return value;
}
// Read a 32-bit register
uint32_t VL53L1X::readReg32Bit(uint16_t reg)
{
uint8_t regbuf[2] = {(reg >> 8) & 0xFF, reg & 0xFF};
uint8_t buffer[4];
uint32_t value;
reg= (reg << 8) + (reg >> 8);
i2c_write_blocking(i2c, address, regbuf, 2, true);
i2c_read_blocking(i2c, address, buffer, 4, false);
value= (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
return value;
}
// set distance mode to Short, Medium, or Long
// based on VL53L1_SetDistanceMode()
bool VL53L1X::setDistanceMode(DistanceMode mode)
{
// save existing timing budget
uint32_t budget_us = getMeasurementTimingBudget();
switch (mode)
{
case Short:
// from VL53L1_preset_mode_standard_ranging_short_range()
// timing config
writeReg(RANGE_CONFIG__VCSEL_PERIOD_A, 0x07);
writeReg(RANGE_CONFIG__VCSEL_PERIOD_B, 0x05);
writeReg(RANGE_CONFIG__VALID_PHASE_HIGH, 0x38);
// dynamic config
writeReg(SD_CONFIG__WOI_SD0, 0x07);
writeReg(SD_CONFIG__WOI_SD1, 0x05);
writeReg(SD_CONFIG__INITIAL_PHASE_SD0, 6); // tuning parm default
writeReg(SD_CONFIG__INITIAL_PHASE_SD1, 6); // tuning parm default
break;
case Medium:
// from VL53L1_preset_mode_standard_ranging()
// timing config
writeReg(RANGE_CONFIG__VCSEL_PERIOD_A, 0x0B);
writeReg(RANGE_CONFIG__VCSEL_PERIOD_B, 0x09);
writeReg(RANGE_CONFIG__VALID_PHASE_HIGH, 0x78);
// dynamic config
writeReg(SD_CONFIG__WOI_SD0, 0x0B);
writeReg(SD_CONFIG__WOI_SD1, 0x09);
writeReg(SD_CONFIG__INITIAL_PHASE_SD0, 10); // tuning parm default
writeReg(SD_CONFIG__INITIAL_PHASE_SD1, 10); // tuning parm default
break;
case Long: // long
// from VL53L1_preset_mode_standard_ranging_long_range()
// timing config
writeReg(RANGE_CONFIG__VCSEL_PERIOD_A, 0x0F);
writeReg(RANGE_CONFIG__VCSEL_PERIOD_B, 0x0D);
writeReg(RANGE_CONFIG__VALID_PHASE_HIGH, 0xB8);
// dynamic config
writeReg(SD_CONFIG__WOI_SD0, 0x0F);
writeReg(SD_CONFIG__WOI_SD1, 0x0D);
writeReg(SD_CONFIG__INITIAL_PHASE_SD0, 14); // tuning parm default
writeReg(SD_CONFIG__INITIAL_PHASE_SD1, 14); // tuning parm default
break;
default:
// unrecognized mode - do nothing
return false;
}
// reapply timing budget
setMeasurementTimingBudget(budget_us);
// save mode so it can be returned by getDistanceMode()
distance_mode = mode;
return true;
}
bool VL53L1X::setDistanceModeInt(uint8_t mode)
{
// Map the mode here to the internal Enum - must be a better way!
switch (mode)
{
case 0:
// Do nothing
break;
case 1:
setDistanceMode(Short);
break;
case 2:
setDistanceMode(Medium);
break;
case 3:
setDistanceMode(Long);
break;
}
return true;
}
// Set the measurement timing budget in microseconds, which is the time allowed
// for one measurement. A longer timing budget allows for more accurate
// measurements.
// based on VL53L1_SetMeasurementTimingBudgetMicroSeconds()
bool VL53L1X::setMeasurementTimingBudget(uint32_t budget_us)
{
// assumes PresetMode is LOWPOWER_AUTONOMOUS
if (budget_us <= TimingGuard) { return false; }
uint32_t range_config_timeout_us = budget_us -= TimingGuard;
if (range_config_timeout_us > 1100000) { return false; } // FDA_MAX_TIMING_BUDGET_US * 2
range_config_timeout_us /= 2;
// VL53L1_calc_timeout_register_values() begin
uint32_t macro_period_us;
// "Update Macro Period for Range A VCSEL Period"
macro_period_us = calcMacroPeriod(readReg(RANGE_CONFIG__VCSEL_PERIOD_A));
// "Update Phase timeout - uses Timing A"
// Timeout of 1000 is tuning parm default (TIMED_PHASECAL_CONFIG_TIMEOUT_US_DEFAULT)
// via VL53L1_get_preset_mode_timing_cfg().
uint32_t phasecal_timeout_mclks = timeoutMicrosecondsToMclks(1000, macro_period_us);
if (phasecal_timeout_mclks > 0xFF) { phasecal_timeout_mclks = 0xFF; }
writeReg(PHASECAL_CONFIG__TIMEOUT_MACROP, phasecal_timeout_mclks);
// "Update MM Timing A timeout"
// Timeout of 1 is tuning parm default (LOWPOWERAUTO_MM_CONFIG_TIMEOUT_US_DEFAULT)
// via VL53L1_get_preset_mode_timing_cfg(). With the API, the register
// actually ends up with a slightly different value because it gets assigned,
// retrieved, recalculated with a different macro period, and reassigned,
// but it probably doesn't matter because it seems like the MM ("mode
// mitigation"?) sequence steps are disabled in low power auto mode anyway.
writeReg16Bit(MM_CONFIG__TIMEOUT_MACROP_A, encodeTimeout(
timeoutMicrosecondsToMclks(1, macro_period_us)));
// "Update Range Timing A timeout"
writeReg16Bit(RANGE_CONFIG__TIMEOUT_MACROP_A, encodeTimeout(
timeoutMicrosecondsToMclks(range_config_timeout_us, macro_period_us)));
// "Update Macro Period for Range B VCSEL Period"
macro_period_us = calcMacroPeriod(readReg(RANGE_CONFIG__VCSEL_PERIOD_B));
// "Update MM Timing B timeout"
// (See earlier comment about MM Timing A timeout.)
writeReg16Bit(MM_CONFIG__TIMEOUT_MACROP_B, encodeTimeout(
timeoutMicrosecondsToMclks(1, macro_period_us)));
// "Update Range Timing B timeout"
writeReg16Bit(RANGE_CONFIG__TIMEOUT_MACROP_B, encodeTimeout(
timeoutMicrosecondsToMclks(range_config_timeout_us, macro_period_us)));
// VL53L1_calc_timeout_register_values() end
return true;
}
// Get the measurement timing budget in microseconds
// based on VL53L1_SetMeasurementTimingBudgetMicroSeconds()
uint32_t VL53L1X::getMeasurementTimingBudget()
{
// assumes PresetMode is LOWPOWER_AUTONOMOUS and these sequence steps are
// enabled: VHV, PHASECAL, DSS1, RANGE
// VL53L1_get_timeouts_us() begin
// "Update Macro Period for Range A VCSEL Period"
uint32_t macro_period_us = calcMacroPeriod(readReg(RANGE_CONFIG__VCSEL_PERIOD_A));
// "Get Range Timing A timeout"
uint32_t range_config_timeout_us = timeoutMclksToMicroseconds(decodeTimeout(
readReg16Bit(RANGE_CONFIG__TIMEOUT_MACROP_A)), macro_period_us);
// VL53L1_get_timeouts_us() end
return 2 * range_config_timeout_us + TimingGuard;
}
// Start continuous ranging measurements, with the given inter-measurement
// period in milliseconds determining how often the sensor takes a measurement.
void VL53L1X::startContinuous(uint32_t period_ms)
{
// from VL53L1_set_inter_measurement_period_ms()
writeReg32Bit(SYSTEM__INTERMEASUREMENT_PERIOD, period_ms * osc_calibrate_val);
writeReg(SYSTEM__INTERRUPT_CLEAR, 0x01); // sys_interrupt_clear_range
writeReg(SYSTEM__MODE_START, 0x40); // mode_range__timed
}
// Stop continuous measurements
// based on VL53L1_stop_range()
void VL53L1X::stopContinuous()
{
writeReg(SYSTEM__MODE_START, 0x80); // mode_range__abort
// VL53L1_low_power_auto_data_stop_range() begin
calibrated = false;
// "restore vhv configs"
if (saved_vhv_init != 0)
{
writeReg(VHV_CONFIG__INIT, saved_vhv_init);
}
if (saved_vhv_timeout != 0)
{
writeReg(VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND, saved_vhv_timeout);
}
// "remove phasecal override"
writeReg(PHASECAL_CONFIG__OVERRIDE, 0x00);
// VL53L1_low_power_auto_data_stop_range() end
}
// Returns a range reading in millimeters when continuous mode is active. If
// blocking is true (the default), this function waits for a new measurement to
// be available. If blocking is false, it will try to return data immediately.
// (readSingle() also calls this function after starting a single-shot range
// measurement)
uint16_t VL53L1X::read(bool blocking)
{
if (blocking)
{
startTimeout();
while (!dataReady())
{
if (checkTimeoutExpired())
{
did_timeout = true;
return 0;
}
sleep_us(100);
}
}
readResults();
if (!calibrated)
{
setupManualCalibration();
calibrated = true;
}
updateDSS();
getRangingData();
writeReg(SYSTEM__INTERRUPT_CLEAR, 0x01); // sys_interrupt_clear_range
return ranging_data.range_mm;
}
// Starts a single-shot range measurement. If blocking is true (the default),
// this function waits for the measurement to finish and returns the reading.
// Otherwise, it returns 0 immediately.
uint16_t VL53L1X::readSingle(bool blocking)
{
writeReg(SYSTEM__INTERRUPT_CLEAR, 0x01); // sys_interrupt_clear_range
writeReg(SYSTEM__MODE_START, 0x10); // mode_range__single_shot
if (blocking)
{
return read(true);
}
else
{
return 0;
}
}
// convert a RangeStatus to a readable string
// Note that on an AVR, these strings are stored in RAM (dynamic memory), which
// makes working with them easier but uses up 200+ bytes of RAM (many AVR-based
// Arduinos only have about 2000 bytes of RAM). You can avoid this memory usage
// if you do not call this function in your sketch.
const char * VL53L1X::rangeStatusToString(RangeStatus status)
{
switch (status)
{
case RangeValid:
return "range valid";
case SigmaFail:
return "sigma fail";
case SignalFail:
return "signal fail";
case RangeValidMinRangeClipped:
return "range valid, min range clipped";
case OutOfBoundsFail:
return "out of bounds fail";
case HardwareFail:
return "hardware fail";
case RangeValidNoWrapCheckFail:
return "range valid, no wrap check fail";
case WrapTargetFail:
return "wrap target fail";
case XtalkSignalFail:
return "xtalk signal fail";
case SynchronizationInt:
return "synchronization int";
case MinRangeFail:
return "min range fail";
case None:
return "no update";
default:
return "unknown status";
}
}
// Did a timeout occur in one of the read functions since the last call to
// timeoutOccurred()?
bool VL53L1X::timeoutOccurred()
{
bool tmp = did_timeout;
did_timeout = false;
return tmp;
}
// Private Methods /////////////////////////////////////////////////////////////
// "Setup ranges after the first one in low power auto mode by turning off
// FW calibration steps and programming static values"
// based on VL53L1_low_power_auto_setup_manual_calibration()
void VL53L1X::setupManualCalibration()
{
// "save original vhv configs"
saved_vhv_init = readReg(VHV_CONFIG__INIT);
saved_vhv_timeout = readReg(VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND);
// "disable VHV init"
writeReg(VHV_CONFIG__INIT, saved_vhv_init & 0x7F);
// "set loop bound to tuning param"
writeReg(VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND,
(saved_vhv_timeout & 0x03) + (3 << 2)); // tuning parm default (LOWPOWERAUTO_VHV_LOOP_BOUND_DEFAULT)
// "override phasecal"
writeReg(PHASECAL_CONFIG__OVERRIDE, 0x01);
writeReg(CAL_CONFIG__VCSEL_START, readReg(PHASECAL_RESULT__VCSEL_START));
}
// read measurement results into buffer
void VL53L1X::readResults()
{
uint16_t reg = RESULT__RANGE_STATUS;
uint8_t regbuf[2] = {(reg >> 8) & 0xFF, reg & 0xFF};
uint8_t buffer[17];
i2c_write_blocking(i2c, address, regbuf, 2, true);
i2c_read_blocking(i2c, address, buffer, 17, false);
results.range_status = buffer[0];
// bus->read(); // report_status: not used
results.stream_count = buffer[2];
results.dss_actual_effective_spads_sd0 = (uint16_t)buffer[3] << 8; // high byte
results.dss_actual_effective_spads_sd0 |= buffer[4]; // low byte
// bus->read(); // peak_signal_count_rate_mcps_sd0: not used
// bus->read();
results.ambient_count_rate_mcps_sd0 = (uint16_t)buffer[7] << 8; // high byte
results.ambient_count_rate_mcps_sd0 |= buffer[8]; // low byte
// bus->read(); // sigma_sd0: not used
// bus->read();
// bus->read(); // phase_sd0: not used
// bus->read();
results.final_crosstalk_corrected_range_mm_sd0 = (uint16_t)buffer[13] << 8; // high byte
results.final_crosstalk_corrected_range_mm_sd0 |= buffer[14]; // low byte
results.peak_signal_count_rate_crosstalk_corrected_mcps_sd0 = (uint16_t)buffer[15] << 8; // high byte
results.peak_signal_count_rate_crosstalk_corrected_mcps_sd0 |= buffer[16]; // low byte
}
// perform Dynamic SPAD Selection calculation/update
// based on VL53L1_low_power_auto_update_DSS()
void VL53L1X::updateDSS()
{
uint16_t spadCount = results.dss_actual_effective_spads_sd0;
if (spadCount != 0)
{
// "Calc total rate per spad"
uint32_t totalRatePerSpad =
(uint32_t)results.peak_signal_count_rate_crosstalk_corrected_mcps_sd0 +
results.ambient_count_rate_mcps_sd0;
// "clip to 16 bits"
if (totalRatePerSpad > 0xFFFF) { totalRatePerSpad = 0xFFFF; }
// "shift up to take advantage of 32 bits"
totalRatePerSpad <<= 16;
totalRatePerSpad /= spadCount;
if (totalRatePerSpad != 0)
{
// "get the target rate and shift up by 16"
uint32_t requiredSpads = ((uint32_t)TargetRate << 16) / totalRatePerSpad;
// "clip to 16 bit"
if (requiredSpads > 0xFFFF) { requiredSpads = 0xFFFF; }
// "override DSS config"
writeReg16Bit(DSS_CONFIG__MANUAL_EFFECTIVE_SPADS_SELECT, requiredSpads);
// DSS_CONFIG__ROI_MODE_CONTROL should already be set to REQUESTED_EFFFECTIVE_SPADS
return;
}
}
// If we reached this point, it means something above would have resulted in a
// divide by zero.
// "We want to gracefully set a spad target, not just exit with an error"
// "set target to mid point"
writeReg16Bit(DSS_CONFIG__MANUAL_EFFECTIVE_SPADS_SELECT, 0x8000);
}
// get range, status, rates from results buffer
// based on VL53L1_GetRangingMeasurementData()
void VL53L1X::getRangingData()
{
// VL53L1_copy_sys_and_core_results_to_range_results() begin
uint16_t range = results.final_crosstalk_corrected_range_mm_sd0;
// "apply correction gain"
// gain factor of 2011 is tuning parm default (VL53L1_TUNINGPARM_LITE_RANGING_GAIN_FACTOR_DEFAULT)
// Basically, this appears to scale the result by 2011/2048, or about 98%
// (with the 1024 added for proper rounding).
ranging_data.range_mm = ((uint32_t)range * 2011 + 0x0400) / 0x0800;
// VL53L1_copy_sys_and_core_results_to_range_results() end
// set range_status in ranging_data based on value of RESULT__RANGE_STATUS register
// mostly based on ConvertStatusLite()
switch(results.range_status)
{
case 17: // MULTCLIPFAIL
case 2: // VCSELWATCHDOGTESTFAILURE
case 1: // VCSELCONTINUITYTESTFAILURE
case 3: // NOVHVVALUEFOUND
// from SetSimpleData()
ranging_data.range_status = HardwareFail;
break;
case 13: // USERROICLIP
// from SetSimpleData()
ranging_data.range_status = MinRangeFail;
break;
case 18: // GPHSTREAMCOUNT0READY
ranging_data.range_status = SynchronizationInt;
break;
case 5: // RANGEPHASECHECK
ranging_data.range_status = OutOfBoundsFail;
break;
case 4: // MSRCNOTARGET
ranging_data.range_status = SignalFail;
break;
case 6: // SIGMATHRESHOLDCHECK
ranging_data.range_status = SigmaFail;
break;
case 7: // PHASECONSISTENCY
ranging_data.range_status = WrapTargetFail;
break;
case 12: // RANGEIGNORETHRESHOLD
ranging_data.range_status = XtalkSignalFail;
break;
case 8: // MINCLIP
ranging_data.range_status = RangeValidMinRangeClipped;
break;
case 9: // RANGECOMPLETE
// from VL53L1_copy_sys_and_core_results_to_range_results()
if (results.stream_count == 0)
{
ranging_data.range_status = RangeValidNoWrapCheckFail;
}
else
{
ranging_data.range_status = RangeValid;
}
break;
default:
ranging_data.range_status = None;
}
// from SetSimpleData()
ranging_data.peak_signal_count_rate_MCPS =
countRateFixedToFloat(results.peak_signal_count_rate_crosstalk_corrected_mcps_sd0);
ranging_data.ambient_count_rate_MCPS =
countRateFixedToFloat(results.ambient_count_rate_mcps_sd0);
}
// Decode sequence step timeout in MCLKs from register value
// based on VL53L1_decode_timeout()
uint32_t VL53L1X::decodeTimeout(uint16_t reg_val)
{
return ((uint32_t)(reg_val & 0xFF) << (reg_val >> 8)) + 1;
}
// Encode sequence step timeout register value from timeout in MCLKs
// based on VL53L1_encode_timeout()
uint16_t VL53L1X::encodeTimeout(uint32_t timeout_mclks)
{
// encoded format: "(LSByte * 2^MSByte) + 1"
uint32_t ls_byte = 0;
uint16_t ms_byte = 0;
if (timeout_mclks > 0)
{
ls_byte = timeout_mclks - 1;
while ((ls_byte & 0xFFFFFF00) > 0)
{
ls_byte >>= 1;
ms_byte++;
}
return (ms_byte << 8) | (ls_byte & 0xFF);
}
else { return 0; }
}
// Convert sequence step timeout from macro periods to microseconds with given
// macro period in microseconds (12.12 format)
// based on VL53L1_calc_timeout_us()
uint32_t VL53L1X::timeoutMclksToMicroseconds(uint32_t timeout_mclks, uint32_t macro_period_us)
{
return ((uint64_t)timeout_mclks * macro_period_us + 0x800) >> 12;
}
// Convert sequence step timeout from microseconds to macro periods with given
// macro period in microseconds (12.12 format)
// based on VL53L1_calc_timeout_mclks()
uint32_t VL53L1X::timeoutMicrosecondsToMclks(uint32_t timeout_us, uint32_t macro_period_us)
{
return (((uint32_t)timeout_us << 12) + (macro_period_us >> 1)) / macro_period_us;
}
// Calculate macro period in microseconds (12.12 format) with given VCSEL period
// assumes fast_osc_frequency has been read and stored
// based on VL53L1_calc_macro_period_us()
uint32_t VL53L1X::calcMacroPeriod(uint8_t vcsel_period)
{
// from VL53L1_calc_pll_period_us()
// fast osc frequency in 4.12 format; PLL period in 0.24 format
uint32_t pll_period_us = ((uint32_t)0x01 << 30) / fast_osc_frequency;
// from VL53L1_decode_vcsel_period()
uint8_t vcsel_period_pclks = (vcsel_period + 1) << 1;
// VL53L1_MACRO_PERIOD_VCSEL_PERIODS = 2304
uint32_t macro_period_us = (uint32_t)2304 * pll_period_us;
macro_period_us >>= 6;
macro_period_us *= vcsel_period_pclks;
macro_period_us >>= 6;
return macro_period_us;
}
}

Plik diff jest za duży Load Diff

Wyświetl plik

@ -5,3 +5,4 @@ add_subdirectory(pico_scroll)
add_subdirectory(pico_explorer)
add_subdirectory(pico_rgb_keypad)
add_subdirectory(pico_rtc_display)
add_subdirectory(pico_tof_display)

Wyświetl plik

@ -0,0 +1,14 @@
add_executable(
tof_display
demo.cpp
)
# Pull in pico libraries that we need
target_link_libraries(tof_display pico_stdlib pico_explorer pico_display vl53l1x)
pico_enable_stdio_uart(tof_display 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(tof_display)
#example_auto_set_url(tof_display)

Wyświetl plik

@ -0,0 +1,242 @@
#include <string.h>
// **************************************************************************
// Demonstrate the Pimoroni ToF module (VL53L1X)
// Assumes that a Pico Display Pack (a 1.14inch IPS LCD screen with four
// useful buttons) is installed, and that the VL53L1X I2C module has
// sda, scl and int on GPIO 20, 21 and 22
// Displays the current distance (in mm) and mode
// Button B holds the reading until released
// Button X toggles metric/imperial display
// Button Y increments the mode (Auto, Short, Medium, Long)
// (There are on-screen reminders of the active buttons)
// **************************************************************************
// To use PicoExplorer rather than PicoDisplay, uncomment the following line
// #define USE_PICO_EXPLORER 1
// This:
// - Includes pico_explorer.hpp rather than pico_display.hpp
// - Replaces all PicoDisplay references with PicoExplorer
// - Leaves out the .set_led() calls in flash_led()
#ifdef USE_PICO_EXPLORER
#include "pico_explorer.hpp"
#else
#include "pico_display.hpp"
#endif
#include "vl53l1x.hpp"
using namespace pimoroni;
#ifdef USE_PICO_EXPLORER
uint16_t buffer[PicoExplorer::WIDTH * PicoExplorer::HEIGHT];
PicoExplorer pico_display(buffer);
uint16_t screen_width = PicoExplorer::WIDTH;
uint16_t screen_height = PicoExplorer::HEIGHT;
uint16_t disptext_reminder_size = 2;
uint16_t disptext_b_reminder_xoff = 5;
uint16_t disptext_b_reminder_yoff = 210;
uint16_t disptext_x_reminder_xoff = 165;
uint16_t disptext_x_reminder_yoff = 2;
uint16_t disptext_y_reminder_xoff = 165;
uint16_t disptext_y_reminder_yoff = 210;
uint16_t disptext_mode_xoff = 10;
uint16_t disptext_mode_yoff = 30;
uint16_t disptext_mode_size = 3;
uint16_t disptext_dist_xoff = 10;
uint16_t disptext_dist_yoff = 90;
uint16_t disptext_dist_size = 6;
#else
uint16_t buffer[PicoDisplay::WIDTH * PicoDisplay::HEIGHT];
PicoDisplay pico_display(buffer);
uint16_t screen_width = PicoDisplay::WIDTH;
uint16_t screen_height = PicoDisplay::HEIGHT;
uint16_t disptext_reminder_size = 2;
uint16_t disptext_b_reminder_xoff = 2;
uint16_t disptext_b_reminder_yoff = 110;
uint16_t disptext_x_reminder_xoff = 175;
uint16_t disptext_x_reminder_yoff = 2;
uint16_t disptext_y_reminder_xoff = 175;
uint16_t disptext_y_reminder_yoff = 110;
uint16_t disptext_mode_xoff = 12;
uint16_t disptext_mode_yoff = 15;
uint16_t disptext_mode_size = 3;
uint16_t disptext_dist_xoff = 10;
uint16_t disptext_dist_yoff = 45;
uint16_t disptext_dist_size = 4;
#endif
#define MM_TO_INCH 25.4
VL53L1X vl53l1x;
char * mode_to_text[4];
#define LOW_COUNT_MOD 40
#define HIGH_COUNT_MOD 20
bool repeat_count_reached(uint16_t curr_count) {
// Check whether the current counter means that a key has repeated
if (curr_count <= 10*LOW_COUNT_MOD) {
return (0 == (curr_count % LOW_COUNT_MOD));
} else {
return (0 == (curr_count % HIGH_COUNT_MOD));
}
}
#define FLASH_MOD 20
void flash_led(uint32_t curr_count) {
// Flash the LED based on the current loop counter
// curr_count=0 will turn LED off
#ifndef USE_PICO_EXPLORER
if ((curr_count % FLASH_MOD) < (FLASH_MOD / 2)) {
// value less than half modded number - LED off
pico_display.set_led(0, 0, 0);
} else {
// value more than half modded number - LED on
pico_display.set_led(128, 128, 128);
}
#endif
}
int main() {
bool vl53_present = false;
uint16_t vl53_mode = 1;
pico_display.init();
vl53_present = vl53l1x.init();
// Use these variables to make the buttons single-shot
// Counts number of loops pressed, 0 if not pressed
// Only for x and y - a and b are single-shot
uint16_t a_pressed = 0;
uint16_t b_pressed = 0;
uint16_t x_pressed = 0;
uint16_t y_pressed = 0;
mode_to_text[0] = "Auto";
mode_to_text[1] = "Short";
mode_to_text[2] = "Medium";
mode_to_text[3] = "Long";
struct pt {
float x;
float y;
uint8_t r;
float dx;
float dy;
uint16_t pen;
};
uint32_t i = 0;
char buf[256];
if (vl53_present) {
vl53l1x.startContinuous(1000);
vl53l1x.setMeasurementTimingBudget(50000);
vl53l1x.setDistanceModeInt(vl53_mode);
}
// The distance (in millimetres)
uint16_t dist = 0;
// True if units in metric, false for Imperial (feet and inches)
bool units_metric = true;
// Whether the display is being held
bool dist_held = false;
while(true) {
if (a_pressed == 0 && pico_display.is_pressed(pico_display.A)) {
a_pressed = 1;
} else if (a_pressed >= 1 && !pico_display.is_pressed(pico_display.A)) {
a_pressed = 0;
}
if (b_pressed == 0 && pico_display.is_pressed(pico_display.B)) {
b_pressed = 1;
dist_held = true;
} else if (b_pressed >= 1 && !pico_display.is_pressed(pico_display.B)) {
b_pressed = 0;
dist_held = false;
}
if (x_pressed == 0 && pico_display.is_pressed(pico_display.X)) {
x_pressed = 1;
units_metric = !units_metric;
} else if (x_pressed >= 1 && pico_display.is_pressed(pico_display.X)) {
// Button still pressed - check if has reached repeat count
if (repeat_count_reached(x_pressed++)) {
// Do nothing for now - may need this later!
}
} else if (x_pressed >= 1 && !pico_display.is_pressed(pico_display.X)) {
x_pressed = 0;
}
if (y_pressed == 0 && pico_display.is_pressed(pico_display.Y)) {
y_pressed = 1;
if (vl53_present) {
vl53_mode++;
if (vl53_mode > 3) vl53_mode = 1;
vl53l1x.setDistanceModeInt(vl53_mode);
}
} else if (y_pressed >= 1 && pico_display.is_pressed(pico_display.Y)) {
// Button still pressed - check if has reached repeat count
if (repeat_count_reached(y_pressed++)) {
// Do nothing for now - may need this later!
}
} else if (y_pressed >= 1 && !pico_display.is_pressed(pico_display.Y)) {
y_pressed = 0;
}
Rect text_box(5, 5, screen_width-10, screen_height-10);
pico_display.set_pen(55, 65, 75);
pico_display.rectangle(text_box);
// text_box.deflate(10);
pico_display.set_clip(text_box);
pico_display.set_pen(255, 255, 255);
// Show the current distance
flash_led(0);
if (vl53_present) {
pico_display.text("Units",
Point(text_box.x+disptext_x_reminder_xoff,
text_box.y+disptext_x_reminder_yoff), 230, disptext_reminder_size);
pico_display.text("+Mode",
Point(text_box.x+disptext_y_reminder_xoff,
text_box.y+disptext_y_reminder_yoff), 230, disptext_reminder_size);
pico_display.text("Hold",
Point(text_box.x+disptext_b_reminder_xoff,
text_box.y+disptext_b_reminder_yoff), 230, disptext_reminder_size);
sprintf(buf, "Mode: %s", mode_to_text[vl53_mode]);
pico_display.text(buf,
Point(text_box.x+disptext_mode_xoff,
text_box.y+disptext_mode_yoff), 230, disptext_mode_size);
// Get the distance (use previous distance if number is held)
if (!dist_held) dist = vl53l1x.read();
if (units_metric) {
sprintf(buf, "%dmm", dist);
} else {
uint16_t ft = ((uint16_t)(dist/MM_TO_INCH))/12;
sprintf(buf, "%dft %.1fin", ft,
((float)dist/MM_TO_INCH)-ft*12.0);
}
pico_display.text(buf,
Point(text_box.x+disptext_dist_xoff,
text_box.y+disptext_dist_yoff), 120, disptext_dist_size);
} else {
pico_display.text("VL53L1X Missing",
Point(text_box.x+disptext_dist_xoff,
text_box.y+disptext_dist_yoff), 230, disptext_dist_size);
}
pico_display.remove_clip();
// update screen
pico_display.update();
i++;
}
return 0;
}