* VL530LX

* VL53L0X

* Updates

* License

* Lint
This commit is contained in:
Otto Winter 2019-10-20 17:56:57 +02:00
parent 268387f829
commit b70a0325c5
No known key found for this signature in database
GPG Key ID: DB66C0BE6013F97E
8 changed files with 656 additions and 0 deletions

View File

@ -3,3 +3,4 @@ include README.md
include esphome/dashboard/templates/*.html
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
recursive-include esphome *.cpp *.h *.tcc
recursive-include esphome LICENSE.txt

View File

@ -208,5 +208,30 @@ void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; }
uint8_t next_i2c_bus_num_ = 0;
#endif
I2CRegister &I2CRegister::operator=(uint8_t value) {
this->parent_->write_byte(this->register_, value);
return *this;
}
I2CRegister &I2CRegister::operator&=(uint8_t value) {
this->parent_->write_byte(this->register_, this->get() & value);
return *this;
}
I2CRegister &I2CRegister::operator|=(uint8_t value) {
this->parent_->write_byte(this->register_, this->get() | value);
return *this;
}
uint8_t I2CRegister::get() {
uint8_t value = 0x00;
this->parent_->read_byte(this->register_, &value);
return value;
}
I2CRegister &I2CRegister::operator=(const std::vector<uint8_t> &value) {
this->parent_->write_bytes(this->register_, value);
return *this;
}
} // namespace i2c
} // namespace esphome

View File

@ -134,6 +134,24 @@ class I2CComponent : public Component {
extern uint8_t next_i2c_bus_num_;
#endif
class I2CDevice;
class I2CRegister {
public:
I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {}
I2CRegister &operator=(uint8_t value);
I2CRegister &operator=(const std::vector<uint8_t> &value);
I2CRegister &operator&=(uint8_t value);
I2CRegister &operator|=(uint8_t value);
uint8_t get();
protected:
I2CDevice *parent_;
uint8_t register_;
};
/** All components doing communication on the I2C bus should subclass I2CDevice.
*
* This class stores 1. the address of the i2c device and has a helper function to allow
@ -153,6 +171,8 @@ class I2CDevice {
/// Manually set the parent i2c bus for this device.
void set_i2c_parent(I2CComponent *parent);
I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
/** Read len amount of bytes from a register into data. Optionally with a conversion time after
* writing the register value to the bus.
*

View File

@ -0,0 +1,80 @@
Most of the code in this integration is based on the VL53L0x library
by Pololu (Pololu Corporation), which in turn is based on the VL53L0X
API from ST. The code has been adapted to work with ESPHome's i2c APIs.
Please see the top-level LICENSE.txt for information about ESPHome's license.
The licenses for Pololu's and ST's software are included below.
Orignally taken from https://github.com/pololu/vl53l0x-arduino (accessed 20th october 2019).
=================================================================
Copyright (c) 2017 Pololu Corporation. For more information, see
https://www.pololu.com/
https://forum.pololu.com/
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
=================================================================
Most of the functionality of this library is based on the VL53L0X
API provided by ST (STSW-IMG005), and some of the explanatory
comments are quoted or paraphrased from the API source code, API
user manual (UM2039), and the VL53L0X datasheet.
The following applies to source code reproduced or derived from
the API:
-----------------------------------------------------------------
Copyright © 2016, STMicroelectronics International N.V. All
rights reserved.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of STMicroelectronics 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, FITNESS FOR A PARTICULAR PURPOSE, AND
NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED.
IN NO EVENT SHALL STMICROELECTRONICS INTERNATIONAL N.V. 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.
-----------------------------------------------------------------

View File

View File

@ -0,0 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import CONF_ID, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL
DEPENDENCIES = ['i2c']
vl53l0x_ns = cg.esphome_ns.namespace('vl53l0x')
VL53L0XSensor = vl53l0x_ns.class_('VL53L0XSensor', sensor.Sensor, cg.PollingComponent,
i2c.I2CDevice)
CONF_SIGNAL_RATE_LIMIT = 'signal_rate_limit'
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, 2).extend({
cv.GenerateID(): cv.declare_id(VL53L0XSensor),
cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=0.25): cv.float_range(
min=0.0, max=512.0, min_included=False, max_included=False)
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x29))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield sensor.register_sensor(var, config)
yield i2c.register_i2c_device(var, config)

View File

@ -0,0 +1,249 @@
#include "vl53l0x_sensor.h"
#include "esphome/core/log.h"
/*
* Most of the code in this integration is based on the VL53L0x library
* by Pololu (Pololu Corporation), which in turn is based on the VL53L0X
* API from ST.
*
* For more information about licensing, please view the included LICENSE.txt file
* in the vl53l0x integration directory.
*/
namespace esphome {
namespace vl53l0x {
static const char *TAG = "vl53l0x";
void VL53L0XSensor::dump_config() {
LOG_SENSOR("", "VL53L0X", this);
LOG_UPDATE_INTERVAL(this);
LOG_I2C_DEVICE(this);
}
void VL53L0XSensor::setup() {
reg(0x89) |= 0x01;
reg(0x88) = 0x00;
reg(0x80) = 0x01;
reg(0xFF) = 0x01;
reg(0x00) = 0x00;
stop_variable_ = reg(0x91).get();
reg(0x00) = 0x01;
reg(0xFF) = 0x00;
reg(0x80) = 0x00;
reg(0x60) |= 0x12;
auto rate_value = static_cast<uint16_t>(signal_rate_limit_ * 128);
write_byte_16(0x44, rate_value);
reg(0x01) = 0xFF;
// getSpadInfo()
reg(0x80) = 0x01;
reg(0xFF) = 0x01;
reg(0x00) = 0x00;
reg(0xFF) = 0x06;
reg(0x83) |= 0x04;
reg(0xFF) = 0x07;
reg(0x81) = 0x01;
reg(0x80) = 0x01;
reg(0x94) = 0x6B;
reg(0x83) = 0x00;
while (reg(0x83).get() == 0x00)
yield();
reg(0x83) = 0x01;
uint8_t tmp = reg(0x92).get();
uint8_t spad_count = tmp & 0x7F;
bool spad_type_is_aperture = tmp & 0x80;
reg(0x81) = 0x00;
reg(0xFF) = 0x06;
reg(0x83) &= ~0x04;
reg(0xFF) = 0x01;
reg(0x00) = 0x01;
reg(0xFF) = 0x00;
reg(0x80) = 0x00;
uint8_t ref_spad_map[6];
this->read_bytes(0xB0, ref_spad_map, 6);
reg(0xFF) = 0x01;
reg(0x4F) = 0x00;
reg(0x4E) = 0x2C;
reg(0xFF) = 0x00;
reg(0xB6) = 0xB4;
uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0;
uint8_t spads_enabled = 0;
for (int i = 0; i < 48; i++) {
uint8_t &val = ref_spad_map[i / 8];
uint8_t mask = 1 << (i % 8);
if (i < first_spad_to_enable || spads_enabled == spad_count)
val &= ~mask;
else if (val & mask)
spads_enabled += 1;
}
this->write_bytes(0xB0, ref_spad_map, 6);
reg(0xFF) = 0x01;
reg(0x00) = 0x00;
reg(0xFF) = 0x00;
reg(0x09) = 0x00;
reg(0x10) = 0x00;
reg(0x11) = 0x00;
reg(0x24) = 0x01;
reg(0x25) = 0xFF;
reg(0x75) = 0x00;
reg(0xFF) = 0x01;
reg(0x4E) = 0x2C;
reg(0x48) = 0x00;
reg(0x30) = 0x20;
reg(0xFF) = 0x00;
reg(0x30) = 0x09;
reg(0x54) = 0x00;
reg(0x31) = 0x04;
reg(0x32) = 0x03;
reg(0x40) = 0x83;
reg(0x46) = 0x25;
reg(0x60) = 0x00;
reg(0x27) = 0x00;
reg(0x50) = 0x06;
reg(0x51) = 0x00;
reg(0x52) = 0x96;
reg(0x56) = 0x08;
reg(0x57) = 0x30;
reg(0x61) = 0x00;
reg(0x62) = 0x00;
reg(0x64) = 0x00;
reg(0x65) = 0x00;
reg(0x66) = 0xA0;
reg(0xFF) = 0x01;
reg(0x22) = 0x32;
reg(0x47) = 0x14;
reg(0x49) = 0xFF;
reg(0x4A) = 0x00;
reg(0xFF) = 0x00;
reg(0x7A) = 0x0A;
reg(0x7B) = 0x00;
reg(0x78) = 0x21;
reg(0xFF) = 0x01;
reg(0x23) = 0x34;
reg(0x42) = 0x00;
reg(0x44) = 0xFF;
reg(0x45) = 0x26;
reg(0x46) = 0x05;
reg(0x40) = 0x40;
reg(0x0E) = 0x06;
reg(0x20) = 0x1A;
reg(0x43) = 0x40;
reg(0xFF) = 0x00;
reg(0x34) = 0x03;
reg(0x35) = 0x44;
reg(0xFF) = 0x01;
reg(0x31) = 0x04;
reg(0x4B) = 0x09;
reg(0x4C) = 0x05;
reg(0x4D) = 0x04;
reg(0xFF) = 0x00;
reg(0x44) = 0x00;
reg(0x45) = 0x20;
reg(0x47) = 0x08;
reg(0x48) = 0x28;
reg(0x67) = 0x00;
reg(0x70) = 0x04;
reg(0x71) = 0x01;
reg(0x72) = 0xFE;
reg(0x76) = 0x00;
reg(0x77) = 0x00;
reg(0xFF) = 0x01;
reg(0x0D) = 0x01;
reg(0xFF) = 0x00;
reg(0x80) = 0x01;
reg(0x01) = 0xF8;
reg(0xFF) = 0x01;
reg(0x8E) = 0x01;
reg(0x00) = 0x01;
reg(0xFF) = 0x00;
reg(0x80) = 0x00;
reg(0x0A) = 0x04;
reg(0x84) &= ~0x10;
reg(0x0B) = 0x01;
measurement_timing_budget_us_ = get_measurement_timing_budget_();
reg(0x01) = 0xE8;
set_measurement_timing_budget_(measurement_timing_budget_us_);
reg(0x01) = 0x01;
if (!perform_single_ref_calibration_(0x40)) {
ESP_LOGW(TAG, "1st reference calibration failed!");
this->mark_failed();
return;
}
reg(0x01) = 0x02;
if (!perform_single_ref_calibration_(0x00)) {
ESP_LOGW(TAG, "2nd reference calibration failed!");
this->mark_failed();
return;
}
reg(0x01) = 0xE8;
}
void VL53L0XSensor::update() {
if (this->initiated_read_ || this->waiting_for_interrupt_) {
this->publish_state(NAN);
this->status_set_warning();
}
// initiate single shot measurement
reg(0x80) = 0x01;
reg(0xFF) = 0x01;
reg(0x00) = 0x00;
reg(0x91) = stop_variable_;
reg(0x00) = 0x01;
reg(0xFF) = 0x00;
reg(0x80) = 0x00;
reg(0x00) = 0x01;
this->waiting_for_interrupt_ = false;
this->initiated_read_ = true;
// wait for timeout
}
void VL53L0XSensor::loop() {
if (this->initiated_read_) {
if (reg(0x00).get() & 0x01) {
// waiting
} else {
// done
// wait until reg(0x13) & 0x07 is set
this->initiated_read_ = false;
this->waiting_for_interrupt_ = true;
}
}
if (this->waiting_for_interrupt_) {
if (reg(0x13).get() & 0x07) {
uint16_t range_mm;
this->read_byte_16(0x14 + 10, &range_mm);
reg(0x0B) = 0x01;
this->waiting_for_interrupt_ = false;
if (range_mm >= 8190) {
ESP_LOGW(TAG, "'%s' - Distance is out of range, please move the target closer", this->name_.c_str());
this->publish_state(NAN);
return;
}
float range_m = range_mm / 1e3f;
ESP_LOGD(TAG, "'%s' - Got distance %.3f m", this->name_.c_str(), range_m);
this->publish_state(range_m);
}
}
}
} // namespace vl53l0x
} // namespace esphome

View File

@ -0,0 +1,257 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace vl53l0x {
struct SequenceStepEnables {
bool tcc, msrc, dss, pre_range, final_range;
};
struct SequenceStepTimeouts {
uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks;
uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks;
uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us;
};
class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void update() override;
void loop() override;
void set_signal_rate_limit(float signal_rate_limit) { signal_rate_limit_ = signal_rate_limit; }
protected:
uint32_t get_measurement_timing_budget_() {
SequenceStepEnables enables{};
SequenceStepTimeouts timeouts{};
uint16_t start_overhead = 1910;
uint16_t end_overhead = 960;
uint16_t msrc_overhead = 660;
uint16_t tcc_overhead = 590;
uint16_t dss_overhead = 690;
uint16_t pre_range_overhead = 660;
uint16_t final_range_overhead = 550;
// "Start and end overhead times always present"
uint32_t budget_us = start_overhead + end_overhead;
get_sequence_step_enables_(&enables);
get_sequence_step_timeouts_(&enables, &timeouts);
if (enables.tcc)
budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
if (enables.dss)
budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
else if (enables.msrc)
budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
if (enables.pre_range)
budget_us += (timeouts.pre_range_us + pre_range_overhead);
if (enables.final_range)
budget_us += (timeouts.final_range_us + final_range_overhead);
measurement_timing_budget_us_ = budget_us; // store for internal reuse
return budget_us;
}
bool set_measurement_timing_budget_(uint32_t budget_us) {
SequenceStepEnables enables{};
SequenceStepTimeouts timeouts{};
uint16_t start_overhead = 1320; // note that this is different than the value in get_
uint16_t end_overhead = 960;
uint16_t msrc_overhead = 660;
uint16_t tcc_overhead = 590;
uint16_t dss_overhead = 690;
uint16_t pre_range_overhead = 660;
uint16_t final_range_overhead = 550;
uint32_t min_timing_budget = 20000;
if (budget_us < min_timing_budget) {
return false;
}
uint32_t used_budget_us = start_overhead + end_overhead;
get_sequence_step_enables_(&enables);
get_sequence_step_timeouts_(&enables, &timeouts);
if (enables.tcc) {
used_budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
}
if (enables.dss) {
used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
} else if (enables.msrc) {
used_budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
}
if (enables.pre_range) {
used_budget_us += (timeouts.pre_range_us + pre_range_overhead);
}
if (enables.final_range) {
used_budget_us += final_range_overhead;
// "Note that the final range timeout is determined by the timing
// budget and the sum of all other timeouts within the sequence.
// If there is no room for the final range timeout, then an error
// will be set. Otherwise the remaining time will be applied to
// the final range."
if (used_budget_us > budget_us) {
// "Requested timeout too big."
return false;
}
uint32_t final_range_timeout_us = budget_us - used_budget_us;
// set_sequence_step_timeout() begin
// (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
// "For the final range timeout, the pre-range timeout
// must be added. To do this both final and pre-range
// timeouts must be expressed in macro periods MClks
// because they have different vcsel periods."
uint16_t final_range_timeout_mclks =
timeout_microseconds_to_mclks_(final_range_timeout_us, timeouts.final_range_vcsel_period_pclks);
if (enables.pre_range) {
final_range_timeout_mclks += timeouts.pre_range_mclks;
}
write_byte_16(0x71, encode_timeout_(final_range_timeout_mclks));
// set_sequence_step_timeout() end
measurement_timing_budget_us_ = budget_us; // store for internal reuse
}
return true;
}
void get_sequence_step_enables_(SequenceStepEnables *enables) {
uint8_t sequence_config = reg(0x01).get();
enables->tcc = (sequence_config >> 4) & 0x1;
enables->dss = (sequence_config >> 3) & 0x1;
enables->msrc = (sequence_config >> 2) & 0x1;
enables->pre_range = (sequence_config >> 6) & 0x1;
enables->final_range = (sequence_config >> 7) & 0x1;
}
enum VcselPeriodType { VCSEL_PERIOD_PRE_RANGE, VCSEL_PERIOD_FINAL_RANGE };
void get_sequence_step_timeouts_(SequenceStepEnables const *enables, SequenceStepTimeouts *timeouts) {
timeouts->pre_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_PRE_RANGE);
timeouts->msrc_dss_tcc_mclks = reg(0x46).get() + 1;
timeouts->msrc_dss_tcc_us =
timeout_mclks_to_microseconds_(timeouts->msrc_dss_tcc_mclks, timeouts->pre_range_vcsel_period_pclks);
uint16_t value;
read_byte_16(0x51, &value);
timeouts->pre_range_mclks = decode_timeout_(value);
timeouts->pre_range_us =
timeout_mclks_to_microseconds_(timeouts->pre_range_mclks, timeouts->pre_range_vcsel_period_pclks);
timeouts->final_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_FINAL_RANGE);
read_byte_16(0x71, &value);
timeouts->final_range_mclks = decode_timeout_(value);
if (enables->pre_range) {
timeouts->final_range_mclks -= timeouts->pre_range_mclks;
}
timeouts->final_range_us =
timeout_mclks_to_microseconds_(timeouts->final_range_mclks, timeouts->final_range_vcsel_period_pclks);
}
uint8_t get_vcsel_pulse_period_(VcselPeriodType type) {
uint8_t vcsel;
if (type == VCSEL_PERIOD_PRE_RANGE)
vcsel = reg(0x50).get();
else if (type == VCSEL_PERIOD_FINAL_RANGE)
vcsel = reg(0x70).get();
else
return 255;
return (vcsel + 1) << 1;
}
uint32_t get_macro_period_(uint8_t vcsel_period_pclks) {
return ((2304UL * vcsel_period_pclks * 1655UL) + 500UL) / 1000UL;
}
uint32_t timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) {
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns / 2)) / 1000;
}
uint32_t timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) {
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
}
uint16_t decode_timeout_(uint16_t reg_val) {
// format: "(LSByte * 2^MSByte) + 1"
uint8_t msb = (reg_val >> 8) & 0xFF;
uint8_t lsb = (reg_val >> 0) & 0xFF;
return (uint16_t(lsb) << msb) + 1;
}
uint16_t encode_timeout_(uint16_t timeout_mclks) {
// format: "(LSByte * 2^MSByte) + 1"
uint32_t ls_byte = 0;
uint16_t ms_byte = 0;
if (timeout_mclks <= 0)
return 0;
ls_byte = timeout_mclks - 1;
while ((ls_byte & 0xFFFFFF00) > 0) {
ls_byte >>= 1;
ms_byte++;
}
return (ms_byte << 8) | (ls_byte & 0xFF);
}
bool perform_single_ref_calibration_(uint8_t vhv_init_byte) {
reg(0x00) = 0x01 | vhv_init_byte; // VL53L0X_REG_SYSRANGE_MODE_START_STOP
uint32_t start = millis();
while ((reg(0x13).get() & 0x07) == 0) {
if (millis() - start > 1000)
return false;
yield();
}
reg(0x0B) = 0x01;
reg(0x00) = 0x00;
return true;
}
float signal_rate_limit_;
uint32_t measurement_timing_budget_us_;
bool initiated_read_{false};
bool waiting_for_interrupt_{false};
uint8_t stop_variable_;
};
} // namespace vl53l0x
} // namespace esphome