ESPHome 2025.5.0
Loading...
Searching...
No Matches
vl53l0x_sensor.cpp
Go to the documentation of this file.
1#include "vl53l0x_sensor.h"
2#include "esphome/core/log.h"
3
4/*
5 * Most of the code in this integration is based on the VL53L0x library
6 * by Pololu (Pololu Corporation), which in turn is based on the VL53L0X
7 * API from ST.
8 *
9 * For more information about licensing, please view the included LICENSE.txt file
10 * in the vl53l0x integration directory.
11 */
12
13namespace esphome {
14namespace vl53l0x {
15
16static const char *const TAG = "vl53l0x";
17
18std::list<VL53L0XSensor *> VL53L0XSensor::vl53_sensors; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
19bool VL53L0XSensor::enable_pin_setup_complete = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
20
22
24 LOG_SENSOR("", "VL53L0X", this);
25 LOG_UPDATE_INTERVAL(this);
26 LOG_I2C_DEVICE(this);
27 if (this->enable_pin_ != nullptr) {
28 LOG_PIN(" Enable Pin: ", this->enable_pin_);
29 }
30 ESP_LOGCONFIG(TAG, " Timeout: %u%s", this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)");
31 ESP_LOGCONFIG(TAG, " Timing Budget %uus ", this->measurement_timing_budget_us_);
32}
33
35 ESP_LOGD(TAG, "'%s' - setup BEGIN", this->name_.c_str());
36
38 for (auto &vl53_sensor : vl53_sensors) {
39 if (vl53_sensor->enable_pin_ != nullptr) {
40 // Set enable pin as OUTPUT and disable the enable pin to force vl53 to HW Standby mode
41 vl53_sensor->enable_pin_->setup();
42 vl53_sensor->enable_pin_->digital_write(false);
43 }
44 }
46 }
47
48 if (this->enable_pin_ != nullptr) {
49 // Enable the enable pin to cause FW boot (to get back to 0x29 default address)
50 this->enable_pin_->digital_write(true);
52 }
53
54 // Save the i2c address we want and force it to use the default 0x29
55 // until we finish setup, then re-address to final desired address.
56 uint8_t final_address = address_;
57 this->set_i2c_address(0x29);
58
59 reg(0x89) |= 0x01;
60 reg(0x88) = 0x00;
61
62 reg(0x80) = 0x01;
63 reg(0xFF) = 0x01;
64 reg(0x00) = 0x00;
65 this->stop_variable_ = reg(0x91).get();
66
67 reg(0x00) = 0x01;
68 reg(0xFF) = 0x00;
69 reg(0x80) = 0x00;
70 reg(0x60) |= 0x12;
71 if (this->long_range_)
72 this->signal_rate_limit_ = 0.1;
73 auto rate_value = static_cast<uint16_t>(signal_rate_limit_ * 128);
74 write_byte_16(0x44, rate_value);
75
76 reg(0x01) = 0xFF;
77
78 // getSpadInfo()
79 reg(0x80) = 0x01;
80 reg(0xFF) = 0x01;
81 reg(0x00) = 0x00;
82 reg(0xFF) = 0x06;
83 reg(0x83) |= 0x04;
84 reg(0xFF) = 0x07;
85 reg(0x81) = 0x01;
86 reg(0x80) = 0x01;
87 reg(0x94) = 0x6B;
88 reg(0x83) = 0x00;
89
90 this->timeout_start_us_ = micros();
91 while (reg(0x83).get() == 0x00) {
92 if (this->timeout_us_ > 0 && ((uint16_t) (micros() - this->timeout_start_us_) > this->timeout_us_)) {
93 ESP_LOGE(TAG, "'%s' - setup timeout", this->name_.c_str());
94 this->mark_failed();
95 return;
96 }
97 yield();
98 }
99
100 reg(0x83) = 0x01;
101 uint8_t tmp = reg(0x92).get();
102 uint8_t spad_count = tmp & 0x7F;
103 bool spad_type_is_aperture = tmp & 0x80;
104
105 reg(0x81) = 0x00;
106 reg(0xFF) = 0x06;
107 reg(0x83) &= ~0x04;
108 reg(0xFF) = 0x01;
109 reg(0x00) = 0x01;
110 reg(0xFF) = 0x00;
111 reg(0x80) = 0x00;
112
113 uint8_t ref_spad_map[6] = {};
114 this->read_bytes(0xB0, ref_spad_map, 6);
115
116 reg(0xFF) = 0x01;
117 reg(0x4F) = 0x00;
118 reg(0x4E) = 0x2C;
119 reg(0xFF) = 0x00;
120 reg(0xB6) = 0xB4;
121
122 uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0;
123 uint8_t spads_enabled = 0;
124 for (int i = 0; i < 48; i++) {
125 uint8_t &val = ref_spad_map[i / 8];
126 uint8_t mask = 1 << (i % 8);
127
128 if (i < first_spad_to_enable || spads_enabled == spad_count) {
129 val &= ~mask;
130 } else if (val & mask) {
131 spads_enabled += 1;
132 }
133 }
134
135 this->write_bytes(0xB0, ref_spad_map, 6);
136
137 reg(0xFF) = 0x01;
138 reg(0x00) = 0x00;
139 reg(0xFF) = 0x00;
140 reg(0x09) = 0x00;
141 reg(0x10) = 0x00;
142 reg(0x11) = 0x00;
143 reg(0x24) = 0x01;
144 reg(0x25) = 0xFF;
145 reg(0x75) = 0x00;
146 reg(0xFF) = 0x01;
147 reg(0x4E) = 0x2C;
148 reg(0x48) = 0x00;
149 reg(0x30) = 0x20;
150 reg(0xFF) = 0x00;
151 if (this->long_range_) {
152 reg(0x30) = 0x07; // WAS 0x09
153 } else {
154 reg(0x30) = 0x09;
155 }
156 reg(0x54) = 0x00;
157 reg(0x31) = 0x04;
158 reg(0x32) = 0x03;
159 reg(0x40) = 0x83;
160 reg(0x46) = 0x25;
161 reg(0x60) = 0x00;
162 reg(0x27) = 0x00;
163 reg(0x50) = 0x06;
164 reg(0x51) = 0x00;
165 reg(0x52) = 0x96;
166 reg(0x56) = 0x08;
167 if (this->long_range_) {
168 reg(0x57) = 0x50; // was 0x30
169 } else {
170 reg(0x57) = 0x30;
171 }
172 reg(0x61) = 0x00;
173 reg(0x62) = 0x00;
174 reg(0x64) = 0x00;
175 reg(0x65) = 0x00;
176 reg(0x66) = 0xA0;
177 reg(0xFF) = 0x01;
178 reg(0x22) = 0x32;
179 reg(0x47) = 0x14;
180 reg(0x49) = 0xFF;
181 reg(0x4A) = 0x00;
182 reg(0xFF) = 0x00;
183 reg(0x7A) = 0x0A;
184 reg(0x7B) = 0x00;
185 reg(0x78) = 0x21;
186 reg(0xFF) = 0x01;
187 reg(0x23) = 0x34;
188 reg(0x42) = 0x00;
189 reg(0x44) = 0xFF;
190 reg(0x45) = 0x26;
191 reg(0x46) = 0x05;
192 reg(0x40) = 0x40;
193 reg(0x0E) = 0x06;
194 reg(0x20) = 0x1A;
195 reg(0x43) = 0x40;
196 reg(0xFF) = 0x00;
197 reg(0x34) = 0x03;
198 reg(0x35) = 0x44;
199 reg(0xFF) = 0x01;
200 reg(0x31) = 0x04;
201 reg(0x4B) = 0x09;
202 reg(0x4C) = 0x05;
203 reg(0x4D) = 0x04;
204 reg(0xFF) = 0x00;
205 reg(0x44) = 0x00;
206 reg(0x45) = 0x20;
207 reg(0x47) = 0x08;
208 if (this->long_range_) {
209 reg(0x48) = 0x48; // was 0x28
210 } else {
211 reg(0x48) = 0x28;
212 }
213 reg(0x67) = 0x00;
214 reg(0x70) = 0x04;
215 reg(0x71) = 0x01;
216 reg(0x72) = 0xFE;
217 reg(0x76) = 0x00;
218 reg(0x77) = 0x00;
219 reg(0xFF) = 0x01;
220 reg(0x0D) = 0x01;
221 reg(0xFF) = 0x00;
222 reg(0x80) = 0x01;
223 reg(0x01) = 0xF8;
224 reg(0xFF) = 0x01;
225 reg(0x8E) = 0x01;
226 reg(0x00) = 0x01;
227 reg(0xFF) = 0x00;
228 reg(0x80) = 0x00;
229
230 reg(0x0A) = 0x04;
231 reg(0x84) &= ~0x10;
232 reg(0x0B) = 0x01;
233
234 if (this->measurement_timing_budget_us_ == 0) {
236 }
237
238 reg(0x01) = 0xE8;
240 reg(0x01) = 0x01;
241
243 ESP_LOGW(TAG, "1st reference calibration failed!");
244 this->mark_failed();
245 return;
246 }
247 reg(0x01) = 0x02;
249 ESP_LOGW(TAG, "2nd reference calibration failed!");
250 this->mark_failed();
251 return;
252 }
253 reg(0x01) = 0xE8;
254
255 // Set the sensor to the desired final address
256 // The following is different for VL53L0X vs VL53L1X
257 // I2C_SXXXX_DEVICE_ADDRESS = 0x8A for VL53L0X
258 // I2C_SXXXX__DEVICE_ADDRESS = 0x0001 for VL53L1X
259 reg(0x8A) = final_address & 0x7F;
260 this->set_i2c_address(final_address);
261
262 ESP_LOGD(TAG, "'%s' - setup END", this->name_.c_str());
263}
264
266 if (this->initiated_read_ || this->waiting_for_interrupt_) {
267 this->publish_state(NAN);
268 this->status_momentary_warning("update", 5000);
269 ESP_LOGW(TAG, "%s - update called before prior reading complete - initiated:%d waiting_for_interrupt:%d",
270 this->name_.c_str(), this->initiated_read_, this->waiting_for_interrupt_);
271 }
272
273 // initiate single shot measurement
274 reg(0x80) = 0x01;
275 reg(0xFF) = 0x01;
276
277 reg(0x00) = 0x00;
278 reg(0x91) = this->stop_variable_;
279 reg(0x00) = 0x01;
280 reg(0xFF) = 0x00;
281 reg(0x80) = 0x00;
282
283 reg(0x00) = 0x01;
284 this->waiting_for_interrupt_ = false;
285 this->initiated_read_ = true;
286 // wait for timeout
287}
288
290 if (this->initiated_read_) {
291 if (reg(0x00).get() & 0x01) {
292 // waiting
293 } else {
294 // done
295 // wait until reg(0x13) & 0x07 is set
296 this->initiated_read_ = false;
297 this->waiting_for_interrupt_ = true;
298 }
299 }
300 if (this->waiting_for_interrupt_) {
301 if (reg(0x13).get() & 0x07) {
302 uint16_t range_mm = 0;
303 this->read_byte_16(0x14 + 10, &range_mm);
304 reg(0x0B) = 0x01;
305 this->waiting_for_interrupt_ = false;
306
307 if (range_mm >= 8190) {
308 ESP_LOGD(TAG, "'%s' - Distance is out of range, please move the target closer", this->name_.c_str());
309 this->publish_state(NAN);
310 return;
311 }
312
313 float range_m = range_mm / 1e3f;
314 ESP_LOGD(TAG, "'%s' - Got distance %.3f m", this->name_.c_str(), range_m);
315 this->publish_state(range_m);
316 }
317 }
318}
319
321 SequenceStepEnables enables{};
322 SequenceStepTimeouts timeouts{};
323
324 uint16_t start_overhead = 1910;
325 uint16_t end_overhead = 960;
326 uint16_t msrc_overhead = 660;
327 uint16_t tcc_overhead = 590;
328 uint16_t dss_overhead = 690;
329 uint16_t pre_range_overhead = 660;
330 uint16_t final_range_overhead = 550;
331
332 // "Start and end overhead times always present"
333 uint32_t budget_us = start_overhead + end_overhead;
334
336 get_sequence_step_timeouts_(&enables, &timeouts);
337
338 if (enables.tcc)
339 budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
340
341 if (enables.dss) {
342 budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
343 } else if (enables.msrc) {
344 budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
345 }
346
347 if (enables.pre_range)
348 budget_us += (timeouts.pre_range_us + pre_range_overhead);
349
350 if (enables.final_range)
351 budget_us += (timeouts.final_range_us + final_range_overhead);
352
353 measurement_timing_budget_us_ = budget_us; // store for internal reuse
354 return budget_us;
355}
356
358 SequenceStepEnables enables{};
359 SequenceStepTimeouts timeouts{};
360
361 uint16_t start_overhead = 1320; // note that this is different than the value in get_
362 uint16_t end_overhead = 960;
363 uint16_t msrc_overhead = 660;
364 uint16_t tcc_overhead = 590;
365 uint16_t dss_overhead = 690;
366 uint16_t pre_range_overhead = 660;
367 uint16_t final_range_overhead = 550;
368
369 uint32_t min_timing_budget = 20000;
370
371 if (budget_us < min_timing_budget) {
372 return false;
373 }
374
375 uint32_t used_budget_us = start_overhead + end_overhead;
376
378 get_sequence_step_timeouts_(&enables, &timeouts);
379
380 if (enables.tcc) {
381 used_budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
382 }
383
384 if (enables.dss) {
385 used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
386 } else if (enables.msrc) {
387 used_budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
388 }
389
390 if (enables.pre_range) {
391 used_budget_us += (timeouts.pre_range_us + pre_range_overhead);
392 }
393
394 if (enables.final_range) {
395 used_budget_us += final_range_overhead;
396
397 // "Note that the final range timeout is determined by the timing
398 // budget and the sum of all other timeouts within the sequence.
399 // If there is no room for the final range timeout, then an error
400 // will be set. Otherwise the remaining time will be applied to
401 // the final range."
402
403 if (used_budget_us > budget_us) {
404 // "Requested timeout too big."
405 return false;
406 }
407
408 uint32_t final_range_timeout_us = budget_us - used_budget_us;
409
410 // set_sequence_step_timeout() begin
411 // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
412
413 // "For the final range timeout, the pre-range timeout
414 // must be added. To do this both final and pre-range
415 // timeouts must be expressed in macro periods MClks
416 // because they have different vcsel periods."
417
418 uint16_t final_range_timeout_mclks =
419 timeout_microseconds_to_mclks_(final_range_timeout_us, timeouts.final_range_vcsel_period_pclks);
420
421 if (enables.pre_range) {
422 final_range_timeout_mclks += timeouts.pre_range_mclks;
423 }
424
425 write_byte_16(0x71, encode_timeout_(final_range_timeout_mclks));
426
427 // set_sequence_step_timeout() end
428
429 measurement_timing_budget_us_ = budget_us; // store for internal reuse
430 }
431 return true;
432}
433
435 uint8_t sequence_config = reg(0x01).get();
436 enables->tcc = (sequence_config >> 4) & 0x1;
437 enables->dss = (sequence_config >> 3) & 0x1;
438 enables->msrc = (sequence_config >> 2) & 0x1;
439 enables->pre_range = (sequence_config >> 6) & 0x1;
440 enables->final_range = (sequence_config >> 7) & 0x1;
441}
442
445
446 timeouts->msrc_dss_tcc_mclks = reg(0x46).get() + 1;
447 timeouts->msrc_dss_tcc_us =
449
450 uint16_t value;
451 read_byte_16(0x51, &value);
452 timeouts->pre_range_mclks = decode_timeout_(value);
453 timeouts->pre_range_us =
455
457
458 read_byte_16(0x71, &value);
459 timeouts->final_range_mclks = decode_timeout_(value);
460
461 if (enables->pre_range) {
462 timeouts->final_range_mclks -= timeouts->pre_range_mclks;
463 }
464
465 timeouts->final_range_us =
467}
468
470 uint8_t vcsel;
472 vcsel = reg(0x50).get();
473 } else if (type == VCSEL_PERIOD_FINAL_RANGE) {
474 vcsel = reg(0x70).get();
475 } else {
476 return 255;
477 }
478
479 return (vcsel + 1) << 1;
480}
481
482uint32_t VL53L0XSensor::get_macro_period_(uint8_t vcsel_period_pclks) {
483 return ((2304UL * vcsel_period_pclks * 1655UL) + 500UL) / 1000UL;
484}
485
486uint32_t VL53L0XSensor::timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) {
487 uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
488 return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns / 2)) / 1000;
489}
490
491uint32_t VL53L0XSensor::timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) {
492 uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
493 return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
494}
495
496uint16_t VL53L0XSensor::decode_timeout_(uint16_t reg_val) {
497 // format: "(LSByte * 2^MSByte) + 1"
498 uint8_t msb = (reg_val >> 8) & 0xFF;
499 uint8_t lsb = (reg_val >> 0) & 0xFF;
500 return (uint16_t(lsb) << msb) + 1;
501}
502
503uint16_t VL53L0XSensor::encode_timeout_(uint16_t timeout_mclks) {
504 // format: "(LSByte * 2^MSByte) + 1"
505 uint32_t ls_byte = 0;
506 uint16_t ms_byte = 0;
507
508 if (timeout_mclks <= 0)
509 return 0;
510
511 ls_byte = timeout_mclks - 1;
512
513 while ((ls_byte & 0xFFFFFF00) > 0) {
514 ls_byte >>= 1;
515 ms_byte++;
516 }
517
518 return (ms_byte << 8) | (ls_byte & 0xFF);
519}
520
522 reg(0x00) = 0x01 | vhv_init_byte; // VL53L0X_REG_SYSRANGE_MODE_START_STOP
523
524 uint32_t start = millis();
525 while ((reg(0x13).get() & 0x07) == 0) {
526 if (millis() - start > 1000)
527 return false;
528 yield();
529 }
530
531 reg(0x0B) = 0x01;
532 reg(0x00) = 0x00;
533
534 return true;
535}
536
537} // namespace vl53l0x
538} // namespace esphome
virtual void mark_failed()
Mark this component as failed.
void status_momentary_warning(const std::string &name, uint32_t length=5000)
virtual void digital_write(bool value)=0
constexpr const char * c_str() const
Definition string_ref.h:68
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop=true)
Definition i2c.h:252
uint8_t address_
store the address of the device on the bus
Definition i2c.h:273
bool read_byte_16(uint8_t a_register, uint16_t *data)
Definition i2c.h:250
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:153
void set_i2c_address(uint8_t address)
We store the address of the device on the bus.
Definition i2c.h:140
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:216
bool write_byte_16(uint8_t a_register, uint16_t data)
Definition i2c.h:270
uint8_t get() const
returns the register value
Definition i2c.cpp:75
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
uint32_t timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks)
void get_sequence_step_enables_(SequenceStepEnables *enables)
bool perform_single_ref_calibration_(uint8_t vhv_init_byte)
uint16_t decode_timeout_(uint16_t reg_val)
uint32_t timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks)
static std::list< VL53L0XSensor * > vl53_sensors
void get_sequence_step_timeouts_(SequenceStepEnables const *enables, SequenceStepTimeouts *timeouts)
bool set_measurement_timing_budget_(uint32_t budget_us)
uint32_t get_macro_period_(uint8_t vcsel_period_pclks)
uint8_t get_vcsel_pulse_period_(VcselPeriodType type)
uint16_t encode_timeout_(uint16_t timeout_mclks)
uint8_t type
mopeka_std_values val[4]
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:30
void IRAM_ATTR HOT yield()
Definition core.cpp:26
uint32_t IRAM_ATTR HOT micros()
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27