Skip to content

Commit e967d93

Browse files
authored
feat(vl53l): Add espp::Vl53l component for interacting with VL53LXX Time-of-Flight (ToF) Distance Sensors (#409)
* feat(vl53l): Add `espp::Vl53l` component for interacting with VL53LXX Time-of-Flight (ToF) Distance Sensors * Add component * Add example * Update docs * Update CI I have a dev board for the VL53L0X ToF distance sensor. It isnt perfect, but its really fun to play with. This component allows others to play with it too. * Build and run `vl53l/example` on a QtPy ESP32S3 conected to the sensor dev board. * fix sa * update to properly use 16bit register addresses * update readme * implement proper timing control * cleanup some more
1 parent 71390f8 commit e967d93

File tree

14 files changed

+1116
-0
lines changed

14 files changed

+1116
-0
lines changed

.github/workflows/build.yml

+2
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ jobs:
157157
target: esp32
158158
- path: 'components/tt21100/example'
159159
target: esp32s3
160+
- path: 'components/vl53l/example'
161+
target: esp32s3
160162
- path: 'components/wifi/example'
161163
target: esp32
162164
- path: 'components/wrover-kit/example'

components/vl53l/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
idf_component_register(
2+
INCLUDE_DIRS "include"
3+
REQUIRES "base_peripheral"
4+
)
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# The following lines of boilerplate have to be in your project's CMakeLists
2+
# in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
7+
# add the component directories that we want to use
8+
set(EXTRA_COMPONENT_DIRS
9+
"../../../components/"
10+
)
11+
12+
set(
13+
COMPONENTS
14+
"main esptool_py timer logger vl53l i2c"
15+
CACHE STRING
16+
"List of components to include"
17+
)
18+
19+
project(vl53l_example)
20+
21+
set(CMAKE_CXX_STANDARD 20)

components/vl53l/example/README.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# VL53LXX Example
2+
3+
This example shows the use of the `Vl53l` component to communicate with a VL53L0X
4+
time-of-flight distance sensor.
5+
6+
The example initializes the sensor, configures it, and then reads the distance
7+
from the sensor every 50ms. The distance is then printed to the console.
8+
9+
![CleanShot 2025-03-19 at 08 40 32](https://github.com/user-attachments/assets/93dc9340-8be6-4b9b-b592-958caa26570b)
10+
11+
## Hardware Required
12+
13+
This example is designed to work with any ESP32 which has I2C pins exposed, but
14+
is pre-configured to run on QtPy boards such as the QtPy ESP32 Pico and QtPy
15+
ESP32-S3.
16+
17+
It requires that you have a time of flight distance sensor dev board, such as
18+
the [VL53L0X dev board from Adafruit](https://www.adafruit.com/product/3317).
19+
20+
![image](https://github.com/user-attachments/assets/47f7f64a-ad55-4529-9851-5283fc57bcb8)
21+
22+
## How to use example
23+
24+
### Build and Flash
25+
26+
Build the project and flash it to the board, then run monitor tool to view serial output:
27+
28+
```
29+
idf.py -p PORT flash monitor
30+
```
31+
32+
(Replace PORT with the name of the serial port to use.)
33+
34+
(To exit the serial monitor, type ``Ctrl-]``.)
35+
36+
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
37+
38+
## Example Output
39+
40+
![CleanShot 2025-03-19 at 08 38 47](https://github.com/user-attachments/assets/63d059ac-41e5-4692-9fc5-f35ebdb455a2)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRC_DIRS "."
2+
INCLUDE_DIRS ".")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
menu "Example Configuration"
2+
3+
choice EXAMPLE_HARDWARE
4+
prompt "Hardware"
5+
default EXAMPLE_HARDWARE_QTPY
6+
help
7+
Select the hardware to run this example on.
8+
9+
config EXAMPLE_HARDWARE_QTPY
10+
bool "QtPy ESP32 Pico or QtPy ESP32-S3"
11+
12+
config EXAMPLE_HARDWARE_CUSTOM
13+
bool "Custom"
14+
endchoice
15+
16+
config EXAMPLE_I2C_SCL_GPIO
17+
int "SCL GPIO Num"
18+
range 0 50
19+
default 40 if EXAMPLE_HARDWARE_QTPY && IDF_TARGET_ESP32S3
20+
default 19 if EXAMPLE_HARDWARE_QTPY && IDF_TARGET_ESP32
21+
default 19 if EXAMPLE_HARDWARE_CUSTOM
22+
help
23+
GPIO number for I2C Master clock line.
24+
25+
config EXAMPLE_I2C_SDA_GPIO
26+
int "SDA GPIO Num"
27+
range 0 50
28+
default 41 if EXAMPLE_HARDWARE_QTPY && IDF_TARGET_ESP32S3
29+
default 22 if EXAMPLE_HARDWARE_QTPY && IDF_TARGET_ESP32
30+
default 22 if EXAMPLE_HARDWARE_CUSTOM
31+
help
32+
GPIO number for I2C Master data line.
33+
34+
config EXAMPLE_I2C_CLOCK_SPEED_HZ
35+
int "I2C Clock Speed"
36+
range 100 1000000
37+
default 400000
38+
help
39+
I2C clock speed in Hz.
40+
41+
endmenu
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <chrono>
2+
#include <vector>
3+
4+
#include "i2c.hpp"
5+
#include "logger.hpp"
6+
#include "timer.hpp"
7+
#include "vl53l.hpp"
8+
9+
using namespace std::chrono_literals;
10+
11+
extern "C" void app_main(void) {
12+
static espp::Logger logger({.tag = "VL53L4CX example", .level = espp::Logger::Verbosity::INFO});
13+
// This example shows using the i2c adc (vl53l)
14+
{
15+
logger.info("Starting example!");
16+
17+
//! [vl53l example]
18+
// make the i2c we'll use to communicate
19+
static constexpr auto i2c_port = I2C_NUM_0;
20+
static constexpr auto i2c_clock_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ;
21+
static constexpr gpio_num_t i2c_sda = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO;
22+
static constexpr gpio_num_t i2c_scl = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO;
23+
logger.info("Creating I2C on port {} with SDA {} and SCL {}", i2c_port, i2c_sda, i2c_scl);
24+
logger.info("I2C clock speed: {} Hz", i2c_clock_speed);
25+
espp::I2c i2c({.port = i2c_port,
26+
.sda_io_num = i2c_sda,
27+
.scl_io_num = i2c_scl,
28+
.sda_pullup_en = GPIO_PULLUP_ENABLE,
29+
.scl_pullup_en = GPIO_PULLUP_ENABLE,
30+
.clk_speed = i2c_clock_speed});
31+
32+
// make the actual test object
33+
espp::Vl53l vl53l(
34+
espp::Vl53l::Config{.device_address = espp::Vl53l::DEFAULT_ADDRESS,
35+
.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1,
36+
std::placeholders::_2, std::placeholders::_3),
37+
.read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1,
38+
std::placeholders::_2, std::placeholders::_3),
39+
.log_level = espp::Logger::Verbosity::WARN});
40+
41+
std::error_code ec;
42+
// set the timing budget to 10ms, which must be shorter than the
43+
// inter-measurement period. We'll log every 20ms so this guarantees we get
44+
// new data every time
45+
if (!vl53l.set_timing_budget_ms(10, ec)) {
46+
logger.error("Failed to set inter measurement period: {}", ec.message());
47+
return;
48+
}
49+
// set the inter-measurement period to 10ms, so we should be sure to get new
50+
// data each measurement
51+
if (!vl53l.set_inter_measurement_period_ms(10, ec)) {
52+
logger.error("Failed to set inter measurement period: {}", ec.message());
53+
return;
54+
}
55+
// tell it to start ranging
56+
if (!vl53l.start_ranging(ec)) {
57+
logger.error("Failed to start ranging: {}", ec.message());
58+
return;
59+
}
60+
61+
// make the task which will read the vl53l
62+
fmt::print("%time (s), distance (m)\n");
63+
auto read_task_fn = [&vl53l]() {
64+
auto now = esp_timer_get_time();
65+
static auto start = now;
66+
float elapsed = (float)(now - start) / 1e6;
67+
std::error_code ec;
68+
// wait for the data to be ready
69+
while (!vl53l.is_data_ready(ec)) {
70+
std::this_thread::sleep_for(1ms);
71+
}
72+
// clear the interrupt so we can get another reading
73+
if (!vl53l.clear_interrupt(ec)) {
74+
logger.error("Failed to clear interrupt: {}", ec.message());
75+
return false;
76+
}
77+
auto meters = vl53l.get_distance_meters(ec);
78+
if (ec) {
79+
logger.error("Failed to get distance: {}", ec.message());
80+
return false;
81+
}
82+
fmt::print("{:.3f}, {:.3f}\n", elapsed, meters);
83+
// we don't want to stop, so return false
84+
return false;
85+
};
86+
87+
espp::Timer timer({.period = 20ms,
88+
.callback = read_task_fn,
89+
.task_config =
90+
{
91+
.name = "VL53L4CX",
92+
.stack_size_bytes{4 * 1024},
93+
},
94+
.log_level = espp::Logger::Verbosity::INFO});
95+
//! [vl53l example]
96+
97+
while (true) {
98+
std::this_thread::sleep_for(100ms);
99+
}
100+
}
101+
102+
logger.info("Example complete!");
103+
104+
while (true) {
105+
std::this_thread::sleep_for(1s);
106+
}
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CONFIG_IDF_TARGET="esp32s3"
2+
3+
CONFIG_FREERTOS_HZ=1000
4+
5+
# set compiler optimization level to -O2 (compile for performance)
6+
CONFIG_COMPILER_OPTIMIZATION_PERF=y
7+
8+
# ESP32-specific
9+
#
10+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
11+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
12+
13+
# Common ESP-related
14+
#
15+
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
16+
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
17+
18+
# Set esp-timer task stack size to 6KB
19+
CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144

0 commit comments

Comments
 (0)