[vl53l1x] Add VL53L1X time-of-flight distance sensor (#13605)

This commit is contained in:
will-tm
2026-10-20 16:05:46 -10:00
committed by GitHub
parent 3b8b6c6429
commit 41c3bbff25
9 changed files with 732 additions and 0 deletions
+1
View File
@@ -697,6 +697,7 @@ esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core
esphome/components/vevor_dtx/* @Bofr5555
esphome/components/vl53l1x/* @will-tm
esphome/components/voice_assistant/* @jesserockz @kahrendt
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
+36
View File
@@ -0,0 +1,36 @@
The register sequence and the configuration, distance mode and timing budget tables in this
component come from the VL53L1X Ultra Lite Driver (STSW-IMG009) published by STMicroelectronics.
Please see the top-level LICENSE.txt for information about ESPHome's license. ST's license for
the Ultra Lite Driver is included below.
=================================================================
Copyright (c) 2017, STMicroelectronics - All Rights Reserved
This file is part of VL53L1 Core and is dual licensed, either 'STMicroelectronics Proprietary
license' or 'BSD 3-clause "New" or "Revised" License', at your option.
BSD 3-clause "New" or "Revised" License
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.
+1
View File
@@ -0,0 +1 @@
DOMAIN = "vl53l1x"
+170
View File
@@ -0,0 +1,170 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.i2c import CONF_I2C_ID
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_ENABLE_PIN,
CONF_OFFSET,
CONF_PLATFORM,
ICON_ARROW_EXPAND_VERTICAL,
STATE_CLASS_MEASUREMENT,
UNIT_METER,
)
import esphome.final_validate as fv
from esphome.types import ConfigType
from . import DOMAIN
CODEOWNERS = ["@will-tm"]
DEPENDENCIES = ["i2c"]
vl53l1x_ns = cg.esphome_ns.namespace("vl53l1x")
VL53L1XSensor = vl53l1x_ns.class_(
"VL53L1XSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
DistanceMode = vl53l1x_ns.enum("DistanceMode")
DISTANCE_MODE_OPTIONS = DistanceMode.options(
"short", "medium", "long", prefix="DISTANCE_MODE_"
)
CONF_DISTANCE_MODE = "distance_mode"
CONF_TIMING_BUDGET = "timing_budget"
CONF_SIGNAL_RATE_LIMIT = "signal_rate_limit"
CONF_ROI_WIDTH = "roi_width"
CONF_ROI_HEIGHT = "roi_height"
CONF_ROI_CENTER = "roi_center"
DEFAULT_ADDRESS = 0x29
# ROI centre the ULD uses for a ROI wider or taller than 10 SPADs
ROI_CENTER_LARGE = 199
# Timing budgets in ms supported by the ST Ultra Lite Driver tables, per distance mode
TIMING_BUDGETS = {
"short": (15, 20, 33, 50, 100, 200, 500),
"medium": (20, 33, 50, 100, 200, 500),
"long": (20, 33, 50, 100, 200, 500),
}
def validate_address(config: ConfigType) -> ConfigType:
if config[CONF_ADDRESS] != DEFAULT_ADDRESS and CONF_ENABLE_PIN not in config:
raise cv.Invalid(
"Address other than 0x29 requires enable_pin definition to allow sensor "
"re-addressing. If you have multiple VL53L1X sensors on the same I2C bus, "
"each sensor must have an enable_pin defined."
)
return config
def validate_timing_budget(config: ConfigType) -> ConfigType:
mode = config[CONF_DISTANCE_MODE]
budgets = TIMING_BUDGETS[mode]
if config[CONF_TIMING_BUDGET].total_milliseconds not in budgets:
raise cv.Invalid(
f"Timing budget for {mode} distance mode must be one of "
f"{', '.join(f'{b}ms' for b in budgets)}",
path=[CONF_TIMING_BUDGET],
)
return config
def validate_roi(config: ConfigType) -> ConfigType:
if (
(center := config.get(CONF_ROI_CENTER)) is not None
and center != ROI_CENTER_LARGE
and (config[CONF_ROI_WIDTH] > 10 or config[CONF_ROI_HEIGHT] > 10)
):
raise cv.Invalid(
f"roi_center must be {ROI_CENTER_LARGE} when roi_width or roi_height "
"is larger than 10",
path=[CONF_ROI_CENTER],
)
return config
def _final_validate(config: ConfigType) -> ConfigType:
# Every sensor boots at 0x29. With several on one bus each needs its own enable_pin so it
# can be held in reset while the others are re-addressed, and none may stay at 0x29, or a
# later sibling boots at the same address and the re-address write reaches both chips.
full_config = fv.full_config.get()
siblings = [
conf
for conf in full_config.get(sensor.DOMAIN, [])
if conf.get(CONF_PLATFORM) == DOMAIN
and conf[CONF_I2C_ID] == config[CONF_I2C_ID]
]
if len(siblings) < 2:
return config
if CONF_ENABLE_PIN not in config:
raise cv.Invalid(
"Every VL53L1X sensor on a shared I2C bus needs an enable_pin",
path=[CONF_ENABLE_PIN],
)
address = config[CONF_ADDRESS]
if address == DEFAULT_ADDRESS:
raise cv.Invalid(
f"VL53L1X sensors sharing an I2C bus must all be re-addressed away from "
f"0x{DEFAULT_ADDRESS:02X}",
path=[CONF_ADDRESS],
)
if sum(conf[CONF_ADDRESS] == address for conf in siblings) > 1:
raise cv.Invalid(
f"Address 0x{address:02X} is used by more than one VL53L1X sensor on this bus",
path=[CONF_ADDRESS],
)
return config
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
VL53L1XSensor,
unit_of_measurement=UNIT_METER,
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_ARROW_EXPAND_VERTICAL,
)
.extend(
{
cv.Optional(CONF_DISTANCE_MODE, default="long"): cv.enum(
DISTANCE_MODE_OPTIONS, lower=True
),
cv.Optional(
CONF_TIMING_BUDGET, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
# Minimum return signal rate in MCPS (mega counts per second)
cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=1.0): cv.float_range(
min=0.0, max=512.0, min_included=False, max_included=False
),
cv.Optional(CONF_ROI_WIDTH, default=16): cv.int_range(min=4, max=16),
cv.Optional(CONF_ROI_HEIGHT, default=16): cv.int_range(min=4, max=16),
# Defaults to the chip's optical centre, or 199 for a ROI larger than 10x10
cv.Optional(CONF_ROI_CENTER): cv.int_range(min=1, max=255),
cv.Optional(CONF_OFFSET, default=0): cv.int_range(min=-1024, max=1023),
}
)
.extend(cv.polling_component_schema("1s"))
.extend(i2c.i2c_device_schema(DEFAULT_ADDRESS)),
validate_address,
validate_timing_budget,
validate_roi,
)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config: ConfigType) -> None:
var = await i2c.new_i2c_component(config)
await sensor.register_sensor(var, config)
cg.add(var.set_distance_mode(config[CONF_DISTANCE_MODE]))
cg.add(var.set_timing_budget(config[CONF_TIMING_BUDGET].total_milliseconds))
cg.add(var.set_signal_rate_limit(config[CONF_SIGNAL_RATE_LIMIT]))
cg.add(var.set_roi(config[CONF_ROI_WIDTH], config[CONF_ROI_HEIGHT]))
if (roi_center := config.get(CONF_ROI_CENTER)) is not None:
cg.add(var.set_roi_center(roi_center))
cg.add(var.set_offset(config[CONF_OFFSET]))
await cg.add_pin(var.set_enable_pin, config.get(CONF_ENABLE_PIN))
+436
View File
@@ -0,0 +1,436 @@
// VL53L1X time-of-flight distance sensor, ported from ST's VL53L1X Ultra Lite Driver (ULD).
#include "vl53l1x.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <iterator>
namespace esphome::vl53l1x {
ESPHOME_LOG_TAG(TAG, "vl53l1x");
static constexpr uint8_t DEFAULT_ADDRESS = 0x29;
static constexpr uint16_t MODEL_ID = 0xEACC;
static constexpr uint16_t I2C_DEVICE_ADDRESS = 0x0001;
static constexpr uint16_t VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND = 0x0008;
static constexpr uint16_t VHV_CONFIG_INIT = 0x000B;
static constexpr uint16_t ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x001E;
static constexpr uint16_t MM_CONFIG_INNER_OFFSET_MM = 0x0020;
static constexpr uint16_t MM_CONFIG_OUTER_OFFSET_MM = 0x0022;
static constexpr uint16_t DEFAULT_CONFIG_START = 0x002D;
static constexpr uint16_t GPIO_TIO_HV_STATUS = 0x0031;
static constexpr uint16_t PHASECAL_CONFIG_TIMEOUT_MACROP = 0x004B;
static constexpr uint16_t RANGE_CONFIG_TIMEOUT_MACROP_A_HI = 0x005E;
static constexpr uint16_t RANGE_CONFIG_VCSEL_PERIOD_A = 0x0060;
static constexpr uint16_t RANGE_CONFIG_TIMEOUT_MACROP_B_HI = 0x0061;
static constexpr uint16_t RANGE_CONFIG_VCSEL_PERIOD_B = 0x0063;
static constexpr uint16_t RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT_MCPS = 0x0066;
static constexpr uint16_t RANGE_CONFIG_VALID_PHASE_HIGH = 0x0069;
static constexpr uint16_t SD_CONFIG_WOI_SD0 = 0x0078;
static constexpr uint16_t SD_CONFIG_INITIAL_PHASE_SD0 = 0x007A;
static constexpr uint16_t ROI_CONFIG_USER_ROI_CENTRE_SPAD = 0x007F;
static constexpr uint16_t ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE = 0x0080;
static constexpr uint16_t SYSTEM_INTERRUPT_CLEAR = 0x0086;
static constexpr uint16_t SYSTEM_MODE_START = 0x0087;
static constexpr uint16_t RESULT_RANGE_STATUS = 0x0089;
static constexpr uint16_t FIRMWARE_SYSTEM_STATUS = 0x00E5;
static constexpr uint16_t IDENTIFICATION_MODEL_ID = 0x010F;
static constexpr uint16_t ROI_CONFIG_MODE_ROI_CENTRE_SPAD = 0x013E;
static constexpr uint8_t MODE_START_SINGLE_SHOT = 0x10;
// ROI centre for a ROI wider or taller than 10 SPADs (ULD SetROI)
static constexpr uint8_t ROI_CENTER_LARGE = 199;
// RESULT_RANGE_STATUS through RESULT_FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0 (0x0096)
static constexpr size_t RESULT_LENGTH = 15;
// Raw range status that the ULD reports as a valid range
static constexpr uint8_t RANGE_STATUS_VALID = 9;
// ULD VL51L1X_DEFAULT_CONFIGURATION, registers 0x2D to 0x87
static const uint8_t DEFAULT_CONFIG[] PROGMEM = {
0x00, // 0x2D: reserved
0x01, // 0x2E: I2C pads, bit 0 set for pull-up at AVDD
0x01, // 0x2F: GPIO pads, bit 0 set for pull-up at AVDD
0x01, // 0x30: GPIO_HV_MUX_CTRL (bit 4 clear: data ready reads high)
0x02, // 0x31: reserved
0x00, // 0x32: reserved
0x02, // 0x33: reserved
0x08, // 0x34: reserved
0x00, // 0x35: reserved
0x08, // 0x36: reserved
0x10, // 0x37: reserved
0x01, // 0x38: reserved
0x01, // 0x39: reserved
0x00, // 0x3A: reserved
0x00, // 0x3B: reserved
0x00, // 0x3C: reserved
0x00, // 0x3D: reserved
0xFF, // 0x3E: reserved
0x00, // 0x3F: reserved
0x0F, // 0x40: reserved
0x00, // 0x41: reserved
0x00, // 0x42: reserved
0x00, // 0x43: reserved
0x00, // 0x44: reserved
0x00, // 0x45: reserved
0x20, // 0x46: CAL_CONFIG_SPAD_DC_TARGET_COUNT
0x0B, // 0x47: CAL_CONFIG_VCSEL_START
0x00, // 0x48: reserved
0x00, // 0x49: reserved
0x02, // 0x4A: reserved
0x14, // 0x4B: PHASECAL_CONFIG_TIMEOUT_MACROP
0x21, // 0x4C: reserved
0x00, // 0x4D: PHASECAL_CONFIG_OVERRIDE
0x00, // 0x4E: reserved
0x05, // 0x4F: DSS_CONFIG_ROI_MODE_CONTROL
0x00, // 0x50: SYSTEM_THRESH_RATE_HIGH
0x00, // 0x51: SYSTEM_THRESH_RATE_HIGH
0x00, // 0x52: SYSTEM_THRESH_RATE_LOW
0x00, // 0x53: SYSTEM_THRESH_RATE_LOW
0xC8, // 0x54: DSS_CONFIG_MANUAL_EFFECTIVE_SPADS_SELECT (200 << 8)
0x00, // 0x55: DSS_CONFIG_MANUAL_EFFECTIVE_SPADS_SELECT
0x00, // 0x56: reserved
0x38, // 0x57: DSS_CONFIG_APERTURE_ATTENUATION
0xFF, // 0x58: reserved
0x01, // 0x59: reserved
0x00, // 0x5A: reserved
0x08, // 0x5B: reserved
0x00, // 0x5C: reserved
0x00, // 0x5D: reserved
0x01, // 0x5E: RANGE_CONFIG_TIMEOUT_MACROP_A_HI
0xCC, // 0x5F: RANGE_CONFIG_TIMEOUT_MACROP_A_LO
0x0F, // 0x60: RANGE_CONFIG_VCSEL_PERIOD_A
0x01, // 0x61: RANGE_CONFIG_TIMEOUT_MACROP_B_HI
0xF1, // 0x62: RANGE_CONFIG_TIMEOUT_MACROP_B_LO
0x0D, // 0x63: RANGE_CONFIG_VCSEL_PERIOD_B
0x01, // 0x64: RANGE_CONFIG_SIGMA_THRESH
0x68, // 0x65: RANGE_CONFIG_SIGMA_THRESH (360)
0x00, // 0x66: RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT_MCPS
0x80, // 0x67: RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT_MCPS (1.0 MCPS)
0x08, // 0x68: reserved
0xB8, // 0x69: RANGE_CONFIG_VALID_PHASE_HIGH
0x00, // 0x6A: reserved
0x00, // 0x6B: reserved
0x00, // 0x6C: SYSTEM_INTERMEASUREMENT_PERIOD
0x00, // 0x6D: SYSTEM_INTERMEASUREMENT_PERIOD
0x0F, // 0x6E: SYSTEM_INTERMEASUREMENT_PERIOD
0x89, // 0x6F: SYSTEM_INTERMEASUREMENT_PERIOD
0x00, // 0x70: reserved
0x00, // 0x71: SYSTEM_THRESH_HIGH
0x00, // 0x72: SYSTEM_THRESH_HIGH
0x00, // 0x73: SYSTEM_THRESH_LOW
0x00, // 0x74: SYSTEM_THRESH_LOW
0x00, // 0x75: reserved
0x00, // 0x76: reserved
0x01, // 0x77: reserved
0x0F, // 0x78: SD_CONFIG_WOI_SD0
0x0D, // 0x79: SD_CONFIG_WOI_SD1
0x0E, // 0x7A: SD_CONFIG_INITIAL_PHASE_SD0
0x0E, // 0x7B: SD_CONFIG_INITIAL_PHASE_SD1
0x00, // 0x7C: reserved
0x00, // 0x7D: reserved
0x02, // 0x7E: reserved
0xC7, // 0x7F: ROI_CONFIG_USER_ROI_CENTRE_SPAD
0xFF, // 0x80: ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE
0x9B, // 0x81: SYSTEM_SEQUENCE_CONFIG
0x00, // 0x82: reserved
0x00, // 0x83: reserved
0x00, // 0x84: reserved
0x01, // 0x85: reserved
0x00, // 0x86: SYSTEM_INTERRUPT_CLEAR
0x00, // 0x87: SYSTEM_MODE_START
};
// ULD SetDistanceMode values, indexed by DistanceMode. The ULD only knows short and long; the
// medium row is the standard ranging preset of ST's full VL53L1 API.
struct DistanceModeConfig {
uint8_t phasecal_timeout;
uint8_t vcsel_period_a;
uint8_t vcsel_period_b;
uint8_t valid_phase_high;
uint16_t woi_sd0;
uint16_t initial_phase_sd0;
};
static const DistanceModeConfig DISTANCE_MODE_CONFIGS[] PROGMEM = {
{0x14, 0x07, 0x05, 0x38, 0x0705, 0x0606}, // short
{0x0B, 0x0B, 0x09, 0x78, 0x0B09, 0x0A0A}, // medium
{0x0A, 0x0F, 0x0D, 0xB8, 0x0F0D, 0x0E0E}, // long
};
// ULD SetTimingBudgetInMs values
struct TimingBudgetConfig {
uint16_t budget_ms;
uint16_t macrop_a;
uint16_t macrop_b;
};
static const TimingBudgetConfig SHORT_TIMING_BUDGETS[] PROGMEM = {
{15, 0x001D, 0x0027}, {20, 0x0051, 0x006E}, {33, 0x00D6, 0x006E}, {50, 0x01AE, 0x01E8},
{100, 0x02E1, 0x0388}, {200, 0x03E1, 0x0496}, {500, 0x0591, 0x05C1},
};
static const TimingBudgetConfig LONG_TIMING_BUDGETS[] PROGMEM = {
{20, 0x001E, 0x0022}, {33, 0x0060, 0x006E}, {50, 0x00AD, 0x00C6},
{100, 0x01CC, 0x01EA}, {200, 0x02D9, 0x02F8}, {500, 0x048F, 0x04A4},
};
// The ULD has no medium table: these are the long values scaled by the VCSEL period ratio
// (16/12 for A, 14/10 for B), since the macro period grows with the period and the timeouts
// count macro periods. The data-ready poll tolerates the small error this leaves.
static const TimingBudgetConfig MEDIUM_TIMING_BUDGETS[] PROGMEM = {
{20, 0x0028, 0x0030}, {33, 0x0080, 0x009A}, {50, 0x00E7, 0x018B},
{100, 0x0288, 0x02A4}, {200, 0x0390, 0x03AD}, {500, 0x04BE, 0x04E5},
};
// Copies the entry for budget_ms out of flash; false when the mode has no such budget.
static bool find_timing_budget(DistanceMode mode, uint16_t budget_ms, TimingBudgetConfig *out) {
const TimingBudgetConfig *table = LONG_TIMING_BUDGETS;
size_t count = std::size(LONG_TIMING_BUDGETS);
if (mode == DISTANCE_MODE_SHORT) {
table = SHORT_TIMING_BUDGETS;
count = std::size(SHORT_TIMING_BUDGETS);
} else if (mode == DISTANCE_MODE_MEDIUM) {
table = MEDIUM_TIMING_BUDGETS;
count = std::size(MEDIUM_TIMING_BUDGETS);
}
for (size_t i = 0; i < count; i++) {
progmem_memcpy(out, &table[i], sizeof(TimingBudgetConfig));
if (out->budget_ms == budget_ms)
return true;
}
return false;
}
VL53L1XSensor *VL53L1XSensor::first_sensor = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
VL53L1XSensor::VL53L1XSensor() {
this->next_ = VL53L1XSensor::first_sensor;
VL53L1XSensor::first_sensor = this;
}
void VL53L1XSensor::setup() {
// The first sensor to set up holds every sensor with an enable pin in reset, so each one
// comes back at the default address only when its own setup() releases it.
if (VL53L1XSensor::first_sensor != nullptr) {
for (auto *other = VL53L1XSensor::first_sensor; other != nullptr; other = other->next_) {
if (other->enable_pin_ != nullptr) {
other->enable_pin_->setup();
other->enable_pin_->digital_write(false);
}
}
VL53L1XSensor::first_sensor = nullptr;
}
const uint8_t final_address = this->address_;
if (this->enable_pin_ != nullptr) {
this->enable_pin_->digital_write(true);
delay(2);
}
this->set_i2c_address(DEFAULT_ADDRESS);
if (!this->wait_for_boot_()) {
this->fail_at_default_address_(LOG_STR("Boot timeout"));
return;
}
uint16_t model_id;
if (!this->read_word16_(IDENTIFICATION_MODEL_ID, &model_id)) {
this->fail_at_default_address_(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
if (model_id != MODEL_ID) {
ESP_LOGE(TAG, "Wrong model ID 0x%04X", model_id);
this->fail_at_default_address_(LOG_STR("Wrong model ID"));
return;
}
if (final_address != DEFAULT_ADDRESS) {
if (!this->write_byte16_(I2C_DEVICE_ADDRESS, final_address & 0x7F)) {
this->fail_at_default_address_(LOG_STR("Setting address failed"));
return;
}
this->set_i2c_address(final_address);
}
if (!this->configure_()) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
// The ULD ranges once during init to calibrate; loop() finishes that from the first result.
if (!this->start_ranging_()) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
this->measurement_start_ = millis();
}
// A sensor that failed before it was re-addressed is still answering at 0x29, which would
// collide with the next sibling's bring-up, so hold it in reset.
void VL53L1XSensor::fail_at_default_address_(const LogString *message) {
if (this->enable_pin_ != nullptr)
this->enable_pin_->digital_write(false);
this->mark_failed(message);
}
bool VL53L1XSensor::wait_for_boot_() {
for (uint8_t i = 0; i < 100; i++) {
uint8_t status;
if (this->read_byte16_(FIRMWARE_SYSTEM_STATUS, &status) && (status & 0x01))
return true;
delay(1);
}
return false;
}
bool VL53L1XSensor::configure_() {
TimingBudgetConfig budget;
if (!find_timing_budget(this->distance_mode_, this->budget_ms_, &budget)) {
ESP_LOGE(TAG, "Unsupported timing budget %u ms", this->budget_ms_);
return false;
}
DistanceModeConfig mode;
progmem_memcpy(&mode, &DISTANCE_MODE_CONFIGS[this->distance_mode_], sizeof(mode));
uint8_t config[sizeof(DEFAULT_CONFIG)];
progmem_memcpy(config, DEFAULT_CONFIG, sizeof(config));
// ULD SetROI: a ROI of at most 10x10 is centred on the part's calibrated optical centre.
uint8_t center = this->roi_center_;
if (center == 0) {
center = ROI_CENTER_LARGE;
if (this->roi_width_ <= 10 && this->roi_height_ <= 10 &&
!this->read_byte16_(ROI_CONFIG_MODE_ROI_CENTRE_SPAD, &center)) {
return false;
}
}
// The register address auto-increments, so the whole table goes in one write.
return this->write_register16(DEFAULT_CONFIG_START, config, sizeof(config)) == i2c::ERROR_OK &&
// SetDistanceMode
this->write_byte16_(PHASECAL_CONFIG_TIMEOUT_MACROP, mode.phasecal_timeout) &&
this->write_byte16_(RANGE_CONFIG_VCSEL_PERIOD_A, mode.vcsel_period_a) &&
this->write_byte16_(RANGE_CONFIG_VCSEL_PERIOD_B, mode.vcsel_period_b) &&
this->write_byte16_(RANGE_CONFIG_VALID_PHASE_HIGH, mode.valid_phase_high) &&
this->write_word16_(SD_CONFIG_WOI_SD0, mode.woi_sd0) &&
this->write_word16_(SD_CONFIG_INITIAL_PHASE_SD0, mode.initial_phase_sd0) &&
// SetTimingBudgetInMs
this->write_word16_(RANGE_CONFIG_TIMEOUT_MACROP_A_HI, budget.macrop_a) &&
this->write_word16_(RANGE_CONFIG_TIMEOUT_MACROP_B_HI, budget.macrop_b) &&
// SetOffset
this->write_word16_(ALGO_PART_TO_PART_RANGE_OFFSET_MM, static_cast<uint16_t>(this->offset_mm_ * 4)) &&
this->write_word16_(MM_CONFIG_INNER_OFFSET_MM, 0) && this->write_word16_(MM_CONFIG_OUTER_OFFSET_MM, 0) &&
// SetROI and SetROICenter
this->write_byte16_(ROI_CONFIG_USER_ROI_CENTRE_SPAD, center) &&
this->write_byte16_(ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE,
static_cast<uint8_t>(((this->roi_height_ - 1) << 4) | (this->roi_width_ - 1))) &&
// SetSignalThreshold
this->write_word16_(RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT_MCPS, this->signal_rate_limit_);
}
bool VL53L1XSensor::start_ranging_() { return this->write_byte16_(SYSTEM_MODE_START, MODE_START_SINGLE_SHOT); }
void VL53L1XSensor::update() {
if (this->is_in_loop_state()) {
ESP_LOGD(TAG, "Measurement still running");
return;
}
if (!this->start_ranging_()) {
ESP_LOGW(TAG, "Starting measurement failed");
this->status_set_warning();
this->publish_state(NAN);
return;
}
this->measurement_start_ = App.get_loop_component_start_time();
this->enable_loop();
}
void VL53L1XSensor::loop() {
const uint32_t elapsed = App.get_loop_component_start_time() - this->measurement_start_;
if (elapsed < this->budget_ms_)
return;
uint8_t gpio_status;
if (!this->read_byte16_(GPIO_TIO_HV_STATUS, &gpio_status) || (gpio_status & 0x01) == 0) {
if (elapsed > static_cast<uint32_t>(this->budget_ms_) * 2 + 100) {
ESP_LOGW(TAG, "Measurement timed out");
this->status_set_warning();
this->publish_state(NAN);
this->disable_loop();
}
return;
}
uint8_t result[RESULT_LENGTH];
if (this->read_register16(RESULT_RANGE_STATUS, result, sizeof(result)) != i2c::ERROR_OK ||
!this->write_byte16_(SYSTEM_INTERRUPT_CLEAR, 0x01)) {
ESP_LOGW(TAG, "Reading result failed");
this->status_set_warning();
this->publish_state(NAN);
this->disable_loop();
return;
}
this->disable_loop();
if (this->first_result_) {
// ULD SensorInit: after the first ranging, start VHV calibration from the previous temperature.
if (!this->write_byte16_(VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND, 0x09) || !this->write_byte16_(VHV_CONFIG_INIT, 0)) {
this->status_set_warning();
return;
}
this->first_result_ = false;
return;
}
this->status_clear_warning();
const uint8_t range_status = result[0] & 0x1F;
if (range_status != RANGE_STATUS_VALID) {
ESP_LOGD(TAG, "Range status %u", range_status);
this->publish_state(NAN);
return;
}
const uint16_t distance_mm = encode_uint16(result[13], result[14]);
this->publish_state(distance_mm / 1000.0f);
}
// Indexed by DistanceMode
PROGMEM_STRING_TABLE(DistanceModeStrings, "short", "medium", "long");
void VL53L1XSensor::dump_config() {
LOG_SENSOR("", "VL53L1X", this);
LOG_UPDATE_INTERVAL(this);
LOG_I2C_DEVICE(this);
LOG_PIN(" Enable Pin: ", this->enable_pin_);
ESP_LOGCONFIG(TAG,
" Distance Mode: %s\n"
" Timing Budget: %u ms\n"
" ROI: %ux%u\n"
" Offset: %d mm",
LOG_STR_ARG(DistanceModeStrings::get_log_str(this->distance_mode_, DistanceModeStrings::LAST_INDEX)),
this->budget_ms_, this->roi_width_, this->roi_height_, this->offset_mm_);
if (this->roi_center_ != 0) {
ESP_LOGCONFIG(TAG, " ROI Center: %u", this->roi_center_);
}
}
bool VL53L1XSensor::write_byte16_(uint16_t reg, uint8_t value) {
return this->write_register16(reg, &value, 1) == i2c::ERROR_OK;
}
bool VL53L1XSensor::write_word16_(uint16_t reg, uint16_t value) {
const uint8_t data[2] = {static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value)};
return this->write_register16(reg, data, 2) == i2c::ERROR_OK;
}
bool VL53L1XSensor::read_byte16_(uint16_t reg, uint8_t *value) {
return this->read_register16(reg, value, 1) == i2c::ERROR_OK;
}
bool VL53L1XSensor::read_word16_(uint16_t reg, uint16_t *value) {
uint8_t data[2];
if (this->read_register16(reg, data, 2) != i2c::ERROR_OK)
return false;
*value = encode_uint16(data[0], data[1]);
return true;
}
} // namespace esphome::vl53l1x
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
namespace esphome::vl53l1x {
// Values index the distance mode and timing budget tables in the .cpp.
enum DistanceMode : uint8_t {
DISTANCE_MODE_SHORT = 0,
DISTANCE_MODE_MEDIUM = 1,
DISTANCE_MODE_LONG = 2,
};
class VL53L1XSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
VL53L1XSensor();
void setup() override;
void loop() override;
void update() override;
void dump_config() override;
void set_enable_pin(GPIOPin *pin) { this->enable_pin_ = pin; }
void set_distance_mode(DistanceMode mode) { this->distance_mode_ = mode; }
void set_timing_budget(uint16_t budget_ms) { this->budget_ms_ = budget_ms; }
// Rate in MCPS, stored as the register's 9.7 fixed point value.
void set_signal_rate_limit(float limit_mcps) { this->signal_rate_limit_ = static_cast<uint16_t>(limit_mcps * 128); }
void set_offset(int16_t offset_mm) { this->offset_mm_ = offset_mm; }
void set_roi(uint8_t width, uint8_t height) {
this->roi_width_ = width;
this->roi_height_ = height;
}
void set_roi_center(uint8_t center) { this->roi_center_ = center; }
protected:
void fail_at_default_address_(const LogString *message);
bool wait_for_boot_();
bool configure_();
bool start_ranging_();
bool write_byte16_(uint16_t reg, uint8_t value);
bool write_word16_(uint16_t reg, uint16_t value);
bool read_byte16_(uint16_t reg, uint8_t *value);
bool read_word16_(uint16_t reg, uint16_t *value);
// Every instance, so the first setup() can hold all sensors with an enable pin in reset.
static VL53L1XSensor *first_sensor; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
VL53L1XSensor *next_{nullptr};
GPIOPin *enable_pin_{nullptr};
uint32_t measurement_start_{0};
uint16_t budget_ms_{50};
uint16_t signal_rate_limit_{128};
int16_t offset_mm_{0};
uint8_t roi_width_{16};
uint8_t roi_height_{16};
uint8_t roi_center_{0}; // 0 selects the centre the way the ULD does
DistanceMode distance_mode_{DISTANCE_MODE_LONG};
bool first_result_{true};
};
} // namespace esphome::vl53l1x
+17
View File
@@ -0,0 +1,17 @@
sensor:
- platform: vl53l1x
i2c_id: i2c_bus
name: VL53L1X Distance
address: 0x31
enable_pin: GPIO12
- platform: vl53l1x
i2c_id: i2c_bus
name: VL53L1X Short Range
address: 0x30
enable_pin: GPIO13
distance_mode: medium
timing_budget: 33ms
roi_width: 8
roi_height: 8
offset: 5
signal_rate_limit: 2.0
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
vl53l1x: !include common.yaml
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
vl53l1x: !include common.yaml