You are using staging server - a separate instance of the ESP Component Registry that allows you to try distribution tools and processes without affecting the real registry.
# VL53L0X для ESP-IDF (ESP32)
## Оглавление
- [VL53L0X для ESP-IDF (ESP32)](#vl53l0x-для-esp-idf-esp32)
- [Оглавление](#оглавление)
- [Введение](#введение)
- [Калибровка](#калибровка)
- [Что такое SPAD](#что-такое-spad)
- [Какие калибровки нужны](#какие-калибровки-нужны)
- [Рекомендуемый порядок в коде](#рекомендуемый-порядок-в-коде)
- [Когда запускать калибровки](#когда-запускать-калибровки)
- [1. `vl53l0x_perform_ref_spad_management()`](#1-vl53l0x_perform_ref_spad_management)
- [2. `vl53l0x_perform_ref_calibration()`](#2-vl53l0x_perform_ref_calibration)
- [3. `vl53l0x_perform_offset_calibration()`](#3-vl53l0x_perform_offset_calibration)
- [4. `vl53l0x_perform_xtalk_calibration()`](#4-vl53l0x_perform_xtalk_calibration)
- [Использование сохраненных значений](#использование-сохраненных-значений)
- [Когда повторять калибровку](#когда-повторять-калибровку)
- [Минимальный практический сценарий](#минимальный-практический-сценарий)
- [Профили для работы](#профили-для-работы)
- [Доступные профили](#доступные-профили)
- [Как работает профиль](#как-работает-профиль)
- [Примеры использования](#примеры-использования)
- [Стандартный профиль](#стандартный-профиль)
- [Высокая точность](#высокая-точность)
- [Высокая скорость](#высокая-скорость)
- [Большая дистанция](#большая-дистанция)
- [Когда нужен ручной подбор параметров](#когда-нужен-ручной-подбор-параметров)
- [Смена I2C адреса](#смена-i2c-адреса)
- [Как использовать `XSHUT`](#как-использовать-xshut)
- [Пример](#пример)
- [Режимы работы](#режимы-работы)
- [Одиночное измерение (блокирующее, Single ranging)](#одиночное-измерение-блокирующее-single-ranging)
- [Как работает](#как-работает)
- [Когда использовать](#когда-использовать)
- [Пример](#пример-1)
- [Ручной опрос готовности (polling)](#ручной-опрос-готовности-polling)
- [Как работает](#как-работает-1)
- [Когда использовать](#когда-использовать-1)
- [Пример](#пример-2)
- [Что важно помнить](#что-важно-помнить)
- [Одиночное измерение (неблокирующее, по прерыванию)](#одиночное-измерение-неблокирующее-по-прерыванию)
- [Как это работает](#как-это-работает)
- [Пример работы](#пример-работы)
- [Когда использовать](#когда-использовать-2)
- [Что важно помнить](#что-важно-помнить-1)
- [Непрерывное измерение (Continuous ranging)](#непрерывное-измерение-continuous-ranging)
- [Пример работы](#пример-работы-1)
- [Что важно помнить](#что-важно-помнить-2)
- [Непрерывное измерение через интервалы времени (Continuous Timed)](#непрерывное-измерение-через-интервалы-времени-continuous-timed)
- [Что важно помнить](#что-важно-помнить-3)
- [Пороговые измерения по прерыванию](#пороговые-измерения-по-прерыванию)
- [Режимы прерываний](#режимы-прерываний)
- [Что важно учитывать](#что-важно-учитывать)
- [Типовой порядок работы](#типовой-порядок-работы)
- [Пример работы](#пример-работы-2)
- [Как читать этот пример](#как-читать-этот-пример)
- [Когда использовать](#когда-использовать-3)
- [Что важно помнить](#что-важно-помнить-4)
- [API](#api)
- [Инициализация и деинициализация](#инициализация-и-деинициализация)
- [Измерения](#измерения)
- [Калибровка](#калибровка-1)
- [Настройки профиля и параметров](#настройки-профиля-и-параметров)
- [Проверки ограничений](#проверки-ограничений)
- [Настройки прерываний](#настройки-прерываний)
- [Вспомогательные функции](#вспомогательные-функции)
- [Лицензия](#лицензия)
## Введение
`VL53L0X` — ToF-датчик расстояния (Time-of-Flight) от STMicroelectronics (ST). Он измеряет абсолютную дистанцию в миллиметрах и отдает результат по `I2C`.
Эта библиотека предоставляет удобный публичный API для настройки датчика, запуска измерений, чтения результата, прерываний и калибровки.
Что важно знать перед использованием:
- Типичный 7-bit I2C-адрес датчика: `0x29`. В документации ST также может встречаться `0x52` — это тот же адрес в 8-bit представлении (`0x29 << 1`).
- Основные выводы: `SDA`/`SCL` для I2C, `XSHUT` для сброса/сна и `GPIO1` для прерываний.
- **Важно:** На некоторых платах VL53L0X пины `GPIO1` и `XSHUT` уже подтянуты к питанию через резистор **10 кОм** (на плате модуля), но это **не обязательно** — зависит от производителя платы. Если подтяжек нет на вашей плате, их нужно добавить в схему. Пины I2C (`SDA`/`SCL`) всегда требуют внешних подтягивающих резисторов (обычно 4.7 кОм). Например, на плате [VL53L0X breakout](https://www.st.com/resource/en/data_brief/vl53l0x.pdf) подтяжки присутствуют.
- **Прерывания:** Если на вашей плате `GPIO1` подтянут к HIGH (например, через 10 кОм на breakout-плате), для работы прерываний нужно установить `VL53L0X_INTERRUPT_POLARITY_LOW` — активное прерывание будет низкий уровень на пине. Если подтяжек нет, их нужно добавить перед использованием прерываний.
- Угол обзора около **25°**: с ростом дистанции увеличивается область, из которой датчик собирает отраженный сигнал.
- Излучатель VCSEL работает на **940 нм** и относится к лазерному классу безопасности **Class 1**, то есть датчик безопасен для глаз. Такие датчики часто используются в ноутбуках и смартфонах: они определяют, есть ли перед камерой пользователь, и помогают смартфону фокусировать камеру.
- Дальность и стабильность зависят от отражающей способности цели, внешней засветки, выбранного профиля и времени измерения (`timing budget`).
- Оценивать измерение лучше не только по `distance_mm`, но и по признакам качества результата: `valid`, `range_status`, `signal_rate_mcps`.
## Калибровка
Калибровка — это настройка датчика под конкретную оптику, механику и условия работы, чтобы он измерял расстояние корректно. Она обязательна, особенно если при включении датчик показывает неправильное расстояние до предмета.
### Что такое SPAD
`SPAD` — это сокращение от `Single Photon Avalanche Diode`, то есть лавинный фотодиод, который способен зарегистрировать один фотон. Именно такой приемник используется внутри VL53L0X для улавливания отраженного лазерного импульса.
Важно понимать две вещи:
- внутри датчика не один SPAD, а **массив SPAD**;
- во время инициализации и калибровки датчик выбирает из этого массива подходящие **Reference SPAD** — рабочие опорные SPAD, которые будут использоваться для измерений.
У части SPAD перед чувствительной областью есть небольшая оптическая апертура, поэтому в документации встречаются два типа:
- **Non-Aperture SPAD** — более чувствительные;
- **Aperture SPAD** — с оптической маской, которая уменьшает количество принимаемого света и помогает в некоторых оптических условиях.
Именно поэтому в API есть отдельная операция `vl53l0x_perform_ref_spad_management()` — она подбирает количество и тип опорных SPAD для конкретного датчика и его оптики.
Для подготовки VL53L0X к измерениям используйте наш публичный API:
1. `vl53l0x_create(&sensor, bus)` — создать handle и связать его с I2C-шиной.
2. `vl53l0x_init(sensor)` — выполнить базовую инициализацию датчика и перевести его в рабочее состояние.
3. Выполнить нужные калибровки в правильном порядке.
4. Применить сохраненные ранее значения, если они уже есть.
5. Выбрать режим работы через `vl53l0x_set_mode()` и запустить измерения.
### Какие калибровки нужны
У датчика есть несколько независимых калибровок. Часть из них выполняется только один раз для конкретного изделия, а часть — при инициализации или при заметном изменении условий эксплуатации.
| Калибровка | Публичный API | Зачем нужна | Мишень | Как часто выполнять | Что сохранить |
| --- | --- | --- | --- | --- | --- |
| 1. Reference SPAD management | `vl53l0x_perform_ref_spad_management()` / `vl53l0x_set_reference_spads()` | Подбирает число и тип опорных SPAD для датчика | Не нужна; перед датчиком не должно быть отражающих объектов | Обычно один раз для конкретного устройства; можно повторять после изменений оптики или при сборке | `vl53l0x_ref_spad_calibration_t` (`count`, `is_aperture`) |
| 2. Reference calibration | `vl53l0x_perform_ref_calibration()` / `vl53l0x_set_ref_calibration()` | Выполняет калибровку VHV и фазы, чтобы датчик работал корректно | Не нужна; выполняется без цели в стабильных условиях | При первой настройке и затем при заметном изменении температуры, ориентир — около **8°C** | `vl53l0x_ref_calibration_t` (`vhv_settings`, `phase_cal`) |
| 3. Offset calibration | `vl53l0x_perform_offset_calibration()` / `vl53l0x_set_offset_calibration()` | Компенсирует постоянное смещение результата | Белая мишень примерно **88%** отражения, дистанция **100 mm**, темное помещение | Обычно один раз на производстве или при сборке устройства | `offset_um` в микрометрах |
| 4. XTalk calibration | `vl53l0x_perform_xtalk_calibration()` / `vl53l0x_set_xtalk_calibration()` + `vl53l0x_set_xtalk_compensation_enable()` | Компенсирует отражения от защитного стекла, корпуса и других паразитных отражений | Серая мишень примерно **17%** отражения, расстояние подбирается под защитное стекло и механику | Обычно один раз, если датчик работает через окно/стекло; повторять при изменении стекла, зазора или механики | `xtalk_compensation_rate_mcps` |
### Рекомендуемый порядок в коде
Ниже показан рекомендуемый порядок инициализации и калибровок:
```c
// Инициализация датчика и выполнение всех необходимых калибровок
ESP_ERROR_CHECK(vl53l0x_create(&sensor, bus));
ESP_ERROR_CHECK(vl53l0x_init(sensor));
// 1. Reference SPAD management - настройка опорных SPAD (выполняется один раз)
// Сохраняем результат для последующего восстановления без повторной калибровки
vl53l0x_ref_spad_calibration_t ref_spad_calibration = {0};
ESP_ERROR_CHECK(vl53l0x_perform_ref_spad_management(sensor, &ref_spad_calibration));
ESP_ERROR_CHECK(vl53l0x_set_reference_spads(sensor, &ref_spad_calibration));
// 2. Reference calibration - калибровка VHV и фазы (выполняется один раз или при изменении температуры ~8°C)
vl53l0x_ref_calibration_t ref_calibration = {0};
ESP_ERROR_CHECK(vl53l0x_perform_ref_calibration(sensor, &ref_calibration));
ESP_ERROR_CHECK(vl53l0x_set_ref_calibration(sensor, &ref_calibration));
// 3. Offset calibration - компенсация постоянного смещения (выполняется на производстве или при сборке)
// Требуется белая мишень 88% отражения на расстоянии 100 мм
int32_t offset_um = 0;
ESP_ERROR_CHECK(vl53l0x_perform_offset_calibration(sensor, 100.0f, &offset_um));
ESP_ERROR_CHECK(vl53l0x_set_offset_calibration(sensor, offset_um));
// 4. XTalk calibration - компенсация паразитных отражений от стекла/корпуса
// Выполняется один раз, если датчик работает через стекло
float xtalk_compensation_rate_mcps = 0.0f;
ESP_ERROR_CHECK(vl53l0x_perform_xtalk_calibration(
sensor,
calibration_distance_mm,
&xtalk_compensation_rate_mcps));
ESP_ERROR_CHECK(vl53l0x_set_xtalk_calibration(sensor, xtalk_compensation_rate_mcps));
ESP_ERROR_CHECK(vl53l0x_set_xtalk_compensation_enable(sensor, true));
```
### Когда запускать калибровки
#### 1. `vl53l0x_perform_ref_spad_management()`
Это базовая калибровка опорных SPAD. Ее стоит делать после инициализации датчика и до первого измерения. В обычном прикладном сценарии ее не нужно запускать при каждом старте прошивки — достаточно один раз получить значения, сохранить их и затем просто восстановить через `vl53l0x_set_reference_spads()`.
Если устройство уже собрано, оптика и механика не менялись, а вы используете сохраненные значения, то на старте можно сразу вызвать `vl53l0x_set_reference_spads()`.
#### 2. `vl53l0x_perform_ref_calibration()`
Эта калибровка настраивает VHV и фазу. Ее имеет смысл выполнять после SPAD management и перед первым измерением. Это блокирующая операция, поэтому ее запускают до входа в режим ranging.
Если вы уже сохранили `vl53l0x_ref_calibration_t`, то при обычном запуске можно восстановить значения через `vl53l0x_set_ref_calibration()`, а полную калибровку повторять только при изменении температуры или после пересборки устройства.
#### 3. `vl53l0x_perform_offset_calibration()`
Эта калибровка нужна, чтобы убрать постоянное смещение результата относительно реальной дистанции. Обычно она выполняется один раз на производстве или при финальной сборке устройства.
Как делать:
- разместить датчик в финальном корпусе;
- использовать белую мишень примерно `88%` отражения;
- выставить дистанцию `100 mm`;
- держать калибровочную мишень в стабильных условиях;
- сохранить полученный `offset_um` во Flash/NVS хоста;
- на обычном старте применять его через `vl53l0x_set_offset_calibration()`.
Пример:
```c
// Сохраняем offset_um во Flash/NVS для последующего использования
// Позже, при обычном запуске, восстанавливаем его:
ESP_ERROR_CHECK(vl53l0x_set_offset_calibration(sensor, saved_offset_um));
```
#### 4. `vl53l0x_perform_xtalk_calibration()`
Эта калибровка компенсирует паразитные отражения от защитного стекла или окна корпуса. Она нужна, только если датчик работает через стекло или другую оптику, которая добавляет crosstalk.
Как делать:
- использовать финальную механику и то же стекло, что будет в изделии;
- использовать серую мишень примерно `17%` отражения;
- выбрать дистанцию, подобранную под вашу механику и защитное стекло;
- выполнить калибровку один раз, сохранить `xtalk_compensation_rate_mcps`;
- на обычном старте восстановить значение через `vl53l0x_set_xtalk_calibration()` и включить компенсацию через `vl53l0x_set_xtalk_compensation_enable(sensor, true)`.
Если защитного стекла нет, эта калибровка обычно не нужна.
### Использование сохраненных значений
Если калибровка уже была выполнена ранее, при каждом запуске достаточно восстановить сохраненные параметры:
```c
// Пример восстановления калибровочных данных из Flash/NVS (без повторной калибровки)
ESP_ERROR_CHECK(vl53l0x_set_reference_spads(sensor, &saved_ref_spad_calibration));
ESP_ERROR_CHECK(vl53l0x_set_ref_calibration(sensor, &saved_ref_calibration));
ESP_ERROR_CHECK(vl53l0x_set_offset_calibration(sensor, saved_offset_um));
ESP_ERROR_CHECK(vl53l0x_set_xtalk_calibration(sensor, saved_xtalk_mcps));
ESP_ERROR_CHECK(vl53l0x_set_xtalk_compensation_enable(sensor, true));
```
Такой подход позволяет не запускать полную заводскую калибровку при каждом старте устройства.
### Когда повторять калибровку
- После аппаратного reset нужно снова выполнить базовую инициализацию датчика через `vl53l0x_init()`.
- `vl53l0x_perform_ref_calibration()` имеет смысл повторять при заметном изменении температуры, ориентир — около **8°C**.
- `vl53l0x_perform_offset_calibration()` повторяют при изменении механики, питания, оптики, положения датчика или требований к точности.
- `vl53l0x_perform_xtalk_calibration()` повторяют при смене стекла, изменении воздушного зазора, загрязнении окна или другой оптической конфигурации.
- Все калибровки являются блокирующими и должны выполняться до запуска непрерывного измерения или после его остановки через `vl53l0x_stop_measurement()`.
### Минимальный практический сценарий
Если у вас уже есть сохраненные калибровочные данные, то на старте достаточно сделать так:
```c
// Минимальный сценарий при наличии сохраненных калибровочных данных
ESP_ERROR_CHECK(vl53l0x_create(&sensor, bus));
ESP_ERROR_CHECK(vl53l0x_init(sensor));
// Восстанавливаем параметры из Flash/NVS (без повторной калибровки)
ESP_ERROR_CHECK(vl53l0x_set_reference_spads(sensor, &saved_ref_spad_calibration));
ESP_ERROR_CHECK(vl53l0x_set_ref_calibration(sensor, &saved_ref_calibration));
ESP_ERROR_CHECK(vl53l0x_set_offset_calibration(sensor, saved_offset_um));
ESP_ERROR_CHECK(vl53l0x_set_xtalk_calibration(sensor, saved_xtalk_mcps));
ESP_ERROR_CHECK(vl53l0x_set_xtalk_compensation_enable(sensor, true));
// Устанавливаем профиль и режим работы
ESP_ERROR_CHECK(vl53l0x_set_profile(sensor, VL53L0X_PROFILE_DEFAULT));
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
// Устанавливаем интервал между измерениями (100 мс)
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 100));
// Запускаем измерения
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
```
## Профили для работы
В этой библиотеке профиль измерения — это набор параметров, который настраивает баланс между скоростью, точностью и максимальной дистанцией. По смыслу это соответствует примерам из документации STMicroelectronics, а в коде удобнее всего применять готовый пресет через `vl53l0x_set_profile()`.
Все параметры профиля нужно задавать **до** запуска измерения через `vl53l0x_start_measurement()`.
### Доступные профили
| Профиль | Бюджет времени | Типичный сценарий | Особенности |
| --- | ---: | --- | --- |
| `VL53L0X_PROFILE_DEFAULT` | `30000 us` | Стандартные условия | Баланс между скоростью и точностью |
| `VL53L0X_PROFILE_HIGH_ACCURACY` | `200000 us` | Прецизионные измерения | Выше точность, ниже частота обновления |
| `VL53L0X_PROFILE_LONG_RANGE` | `33000 us` | Дальность до ~2 м | Лучше работает при слабой засветке и хорошей оптике |
| `VL53L0X_PROFILE_HIGH_SPEED` | `20000 us` | Частые обновления | Минимальная задержка, точность ниже |
### Как работает профиль
Профиль — это не отдельный режим датчика, а комбинация настроек:
- `timing budget` — сколько времени датчик тратит на одно измерение;
- `limit check` — пороги, по которым фильтруются шумные или слабые результаты.
Для большинства задач достаточно вызвать `vl53l0x_set_profile()`, а затем выбрать режим измерения через `vl53l0x_set_mode()`.
Для `Long range` обычно дополнительно ослабляют ограничения по сигналу и шуму. Это позволяет видеть более слабые отражения на больших дистанциях, но может увеличить разброс значений и количество ошибочных измерений.
### Примеры использования
#### Стандартный профиль
```c
// Установка стандартного профиля (баланс скорости и точности)
ESP_ERROR_CHECK(vl53l0x_set_profile(sensor, VL53L0X_PROFILE_DEFAULT));
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 100));
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
```
#### Высокая точность
```c
// Установка профиля высокой точности (длительное время измерения, меньший разброс)
ESP_ERROR_CHECK(vl53l0x_set_profile(sensor, VL53L0X_PROFILE_HIGH_ACCURACY));
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 100));
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
```
#### Высокая скорость
```c
// Установка профиля высокой скорости (минимальное время измерения, больше разброс)
ESP_ERROR_CHECK(vl53l0x_set_profile(sensor, VL53L0X_PROFILE_HIGH_SPEED));
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 20)); // Более частое обновление
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
```
#### Большая дистанция
```c
// Установка профиля большой дистанции (усиленный прием для слабых отражений)
ESP_ERROR_CHECK(vl53l0x_set_profile(sensor, VL53L0X_PROFILE_LONG_RANGE));
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 100));
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
```
### Когда нужен ручной подбор параметров
Если готовых профилей недостаточно, параметры можно подстраивать вручную. В таком случае ориентируйтесь на две группы настроек:
1. **Measurement timing budget** — чем больше значение, тем обычно выше стабильность результата.
2. **Limit checks** — для дальних измерений их часто ослабляют, особенно для `Long range`.
Пример ручной настройки для дальнего режима:
```c
// Ручная настройка параметров для специфических условий
// Устанавливаем бюджет времени измерения (33000 мкс)
ESP_ERROR_CHECK(vl53l0x_set_timing_budget(sensor, 33000));
// Настраиваем проверки лимитов для дальних измерений
// Включаем проверку по сигналу и устанавливаем более низкий порог (0.1 Mcps)
ESP_ERROR_CHECK(vl53l0x_set_limit_check_enable(sensor, VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, true));
ESP_ERROR_CHECK(vl53l0x_set_limit_check_value(sensor, VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 0.1f));
// Включаем проверку по шуму и устанавливаем порог 60.0
ESP_ERROR_CHECK(vl53l0x_set_limit_check_enable(sensor, VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, true));
ESP_ERROR_CHECK(vl53l0x_set_limit_check_value(sensor, VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, 60.0f));
// После настройки параметров запускаем измерения
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
```
## Смена I2C адреса
Датчик VL53L0X по умолчанию отвечает по адресу `0x52`. Если на одной шине нужно использовать несколько датчиков, каждому из них нужно назначить свой уникальный I2C-адрес.
Смена адреса должна выполняться **сразу после выхода датчика из сброса и его загрузки**, но **до** вызова `vl53l0x_init()`.
Для этого в проекте используется функция `vl53l0x_set_address()` — она уже делает нужную запись в датчик, поэтому отдельные платформенные вызовы для смены адреса не нужны.
### Как использовать `XSHUT`
Пин `XSHUT` — это вход с активным низким уровнем. Он переводит датчик в аппаратный standby, то есть фактически удерживает его в сбросе.
**Примечание:** Наличие подтягивающего резистора на пине `XSHUT` зависит от конкретной платы VL53L0X. На некоторых модулях (например, на breakout-платах ST) резистор уже установлен (обычно 10 кОм), на других — его нет. Если подтяжки нет на вашей плате, подключите внешний резистор 10 кОм между `XSHUT` и `VDD`. Пины I2C (`SDA`/`SCL`) всегда требуют внешних подтягивающих резисторов (обычно 4.7 кОм).
1. Удерживайте **все** датчики в сбросе, подав на все `XSHUT` уровень `LOW`.
2. Подайте `HIGH` только на **один** датчик.
3. Подождите завершения загрузки датчика. Время `tBOOT` — до **1.2 мс**.
4. Создайте объект датчика и вызовите `vl53l0x_set_address()` с новым адресом.
5. Повторите процедуру для следующего датчика.
После назначения уникального адреса каждый датчик можно инициализировать и использовать отдельно на одной общей шине I2C.
### Пример
```c
#define XSHUT_PIN GPIO_NUM_2
// Функция аппаратного сброса датчика через пин XSHUT
static void vl53l0x_hard_reset(void)
{
// Настраиваем пин XSHUT как выход
gpio_set_direction(XSHUT_PIN, GPIO_MODE_OUTPUT);
// Удерживаем в сбросе (LOW) 2 мс
gpio_set_level(XSHUT_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(2));
// Выходим из сброса (HIGH) и ждем 2 мс
gpio_set_level(XSHUT_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(2));
}
// Выполняем аппаратный сброс
vl53l0x_hard_reset();
// Создаем дескриптор датчика и устанавливаем новый I2C-адрес (0x20)
// Адрес должен быть уникален на шине и не конфликтовать с другими устройствами
vl53l0x_handle_t sensor = NULL;
ESP_ERROR_CHECK(vl53l0x_create(&sensor, bus));
ESP_ERROR_CHECK(vl53l0x_set_address(sensor, bus, 0x20));
ESP_ERROR_CHECK(vl53l0x_init(sensor));
```
## Режимы работы
## Одиночное измерение (блокирующее, Single ranging)
Одиночное измерение — это режим, в котором датчик делает один полный цикл замера и сразу возвращает результат в `vl53l0x_data_t`.
В проекте для этого используется метод `vl53l0x_single_measure(sensor, &data)`. Он удобен, когда нужно получить одно значение расстояния «по запросу» и не держать сенсор в непрерывном режиме работы.
### Как работает
`vl53l0x_single_measure()` выполняет измерение как блокирующую операцию: вызов не завершится, пока датчик не подготовит результат. После этого функция заполняет структуру результата и возвращает код ошибки.
Внутри этот вызов сам организует полный цикл одиночного замера:
1. запускает измерение,
2. ждёт готовности данных,
3. считывает результат,
4. сбрасывает состояние измерения для следующего вызова.
### Когда использовать
Этот режим подходит, если:
- нужен простой сценарий «измерил → обработал → подождал → измерил снова»;
- нет необходимости постоянно держать датчик в непрерывном измерении;
- приоритетом является простота кода.
### Пример
```c
// Функция для вывода результата измерения на консоль
static void print_data(const vl53l0x_data_t *data)
{
ESP_LOGI(TAG,
"distance=%u mm, valid=%s, status=%u (%s), signal=%.3f Mcps, ambient=%.3f Mcps",
(unsigned)data->distance_mm,
data->valid ? "true" : "false",
(unsigned)data->range_status,
vl53l0x_range_status_str(data->range_status),
(double)data->signal_rate_mcps,
(double)data->ambient_rate_mcps);
}
// Бесконечный цикл измерений
while (true) {
// Создаем структуру результата и обнуляем её
vl53l0x_data_t r = {0};
// Выполняем одиночное измерение (блокирующая операция)
esp_err_t ret = vl53l0x_single_measure(sensor, &r);
if (ret == ESP_OK) {
// Если измерение успешно, выводим результат
print_data(&r);
}
// Пауза 500 мс перед следующим измерением
vTaskDelay(pdMS_TO_TICKS(500));
}
```
Если нужен непрерывный поток измерений с меньшими накладными расходами, лучше использовать режим `Continuous ranging`.
## Ручной опрос готовности (polling)
### Как работает
В режиме ручного опроса готовности (polling) вы самостоятельно проверяете, готовы ли данные от датчика. Готовность данных проверяется в цикле с помощью функции `vl53l0x_get_ready()`.
Такой подход дает больше контроля над процессом получения данных, но требует постоянного опроса состояния датчика. Это менее эффективный способ по сравнению с использованием прерываний, так как процессор тратит ресурсы на ожидание.
### Когда использовать
Ручной опрос готовности может быть полезен, когда:
- Необходимо выполнять другие задачи во время ожидания измерения
- Нет возможности использовать прерывания (например, из-за ограничений на количество доступных GPIO)
- Требуется синхронизация с другими процессами в системе
Однако, в большинстве случаев рекомендуется использовать прерывания для получения уведомлений о готовности данных, так как это более эффективный подход с точки зрения использования ресурсов процессора.
### Пример
Ниже показан рабочий пример.
```c
#include <stdbool.h>
#include <stdint.h>
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_master.h"
#include "vl53l0x.h"
static const char *TAG = "VL53L0X_EXAMPLE";
// Пины подключения I2C (измените под вашу схему)
#define I2C_PORT_NUM I2C_NUM_0
#define I2C_SDA_PIN GPIO_NUM_1
#define I2C_SCL_PIN GPIO_NUM_0
#define XSHUT_PIN GPIO_NUM_2
// Функция аппаратного сброса датчика через пин XSHUT
static void vl53l0x_hard_reset(void)
{
// Настраиваем пин XSHUT как выход
gpio_set_direction(XSHUT_PIN, GPIO_MODE_OUTPUT);
// Удерживаем в сбросе (LOW) 2 мс
gpio_set_level(XSHUT_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(2));
// Выходим из сброса (HIGH) и ждем 2 мс
gpio_set_level(XSHUT_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(2));
}
// Функция для вывода результата измерения
static void print_data(const vl53l0x_data_t *data)
{
ESP_LOGI(TAG,
"distance=%u mm, valid=%s, status=%u (%s), signal=%.3f Mcps, ambient=%.3f Mcps",
(unsigned)data->distance_mm,
data->valid ? "true" : "false",
(unsigned)data->range_status,
vl53l0x_range_status_str(data->range_status),
(double)data->signal_rate_mcps,
(double)data->ambient_rate_mcps);
}
void app_main(void)
{
// Выполняем аппаратный сброс датчика
vl53l0x_hard_reset();
// Создаем и инициализируем шину I2C
i2c_master_bus_handle_t bus = NULL;
const i2c_master_bus_config_t bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT_NUM,
.sda_io_num = I2C_SDA_PIN,
.scl_io_num = I2C_SCL_PIN,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus));
// Создаем дескриптор датчика и инициализируем его
vl53l0x_handle_t sensor = NULL;
ESP_ERROR_CHECK(vl53l0x_create(&sensor, bus));
ESP_ERROR_CHECK(vl53l0x_init(sensor));
// Выполняем калибровку
ESP_ERROR_CHECK(vl53l0x_perform_ref_calibration(sensor, &(vl53l0x_ref_calibration_t){}));
// Устанавливаем профиль измерения и режим одиночного измерения
ESP_ERROR_CHECK(vl53l0x_set_profile(sensor, VL53L0X_PROFILE_DEFAULT));
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_SINGLE));
// Структура для хранения результата
vl53l0x_data_t data = {0};
// Основной цикл: запускаем измерения с ручным опросом готовности
while (true) {
// Запускаем одиночное измерение
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
bool ready = false;
// Опрос готовности результата
while(true) {
ESP_ERROR_CHECK(vl53l0x_get_ready(sensor, &ready));
if (ready) {
break;
} else {
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// Получение результата измерения
ESP_ERROR_CHECK(vl53l0x_get_data(sensor, &data));
print_data(&data);
// Пауза 1000 мс перед следующим измерением
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
```
### Что важно помнить
- В этом примере используется режим одиночного измерения `VL53L0X_MODE_SINGLE`
- Интервал опроса в `vTaskDelay` можно регулировать в зависимости от требований приложения
- При использовании этого подхода процессор тратит ресурсы на постоянный опрос датчика, что менее эффективно, чем использование прерываний
- Ручной опрос подходит для простых приложений или когда прерывания недоступны
## Одиночное измерение (неблокирующее, по прерыванию)
Этот вариант делает ровно одно измерение, но хост не ждёт результат в активном цикле опроса. Вместо этого датчик поднимает прерывание на `GPIO1`, а задача ESP32 «просыпается» только когда данные готовы.
Такой подход удобен, если нужно сохранить энергию и не занимать процессор ожиданием завершения замера.
### Как это работает
Общий порядок такой:
1. настраивается вывод прерывания датчика через `vl53l0x_set_gpio_config()`;
2. запускается измерение через `vl53l0x_start_measurement(sensor)`;
3. ESP32 ждёт сигнал от `GPIO1`;
4. после прерывания результат читается через `vl53l0x_get_data(sensor, &data)`;
5. прерывание сбрасывается вызовом `vl53l0x_clear_interrupt_mask(sensor)`;
В этом примере для ожидания используется уведомление FreeRTOS-задачи: ISR вызывает `vTaskNotifyGiveFromISR()`, а задача ждёт через `ulTaskNotifyTake()`.
### Пример работы
Ниже показан фрагмент сценария. Он предполагает, что `sensor` уже создан и инициализирован.
```c
#define VL53L0X_GPIO_INT GPIO_NUM_12 // Измените под вашу схему
// Обработчик прерывания от датчика (выполняется в контексте ISR)
static void IRAM_ATTR vl53l0x_gpio_isr(void *arg)
{
// Получаем дескриптор задачи, которую нужно разбудить
TaskHandle_t task = (TaskHandle_t)arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Разбужаем задачу через уведомление FreeRTOS
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
// Если была разбужена задача с более высоким приоритетом,
// запрашиваем переключение контекста
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// Инициализация GPIO для подключения к выводу прерывания датчика
static void gpio_interrupt_init(void)
{
// Получаем дескриптор текущей задачи (её будем будить из ISR)
TaskHandle_t task = xTaskGetCurrentTaskHandle();
// Настраиваем пин прерывания как вход
gpio_set_direction(VL53L0X_GPIO_INT, GPIO_MODE_INPUT);
// Подтяжки отключаем, так как на модуле обычно уже есть внешняя подтяжка
gpio_pullup_dis(VL53L0X_GPIO_INT);
gpio_pulldown_dis(VL53L0X_GPIO_INT);
// Прерывание по спаду сигнала (активный LOW)
gpio_set_intr_type(VL53L0X_GPIO_INT, GPIO_INTR_NEGEDGE);
// Устанавливаем сервис прерываний и регистрируем обработчик
gpio_install_isr_service(0);
gpio_isr_handler_add(VL53L0X_GPIO_INT, vl53l0x_gpio_isr, task);
}
// Функция для вывода результата измерения
static void print_data(const vl53l0x_data_t *data)
{
ESP_LOGI(TAG,
"distance=%u mm, valid=%s, status=%u (%s), signal=%.3f Mcps, ambient=%.3f Mcps",
(unsigned)data->distance_mm,
data->valid ? "true" : "false",
(unsigned)data->range_status,
vl53l0x_range_status_str(data->range_status),
(double)data->signal_rate_mcps,
(double)data->ambient_rate_mcps);
}
void app_main(void)
{
// Структура для хранения результата измерения
vl53l0x_data_t data = {0};
// Устанавливаем режим одиночного измерения
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_SINGLE));
// Настраиваем GPIO1 на генерацию прерывания при готовности нового измерения
// Используем LOW, т.к. GPIO1 подтянут к HIGH на плате датчика
ESP_ERROR_CHECK(vl53l0x_set_gpio_config(
sensor,
VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY,
VL53L0X_INTERRUPT_POLARITY_LOW));
// Инициализация GPIO для обработки прерываний от датчика
gpio_interrupt_init();
while (true) {
// Запускаем измерение (не блокирует выполнение)
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
// Ожидаем прерывания от датчика (задача засыпает до события)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Считываем результат измерения
ESP_ERROR_CHECK(vl53l0x_get_data(sensor, &data));
print_data(&data);
// Сбрасываем флаг прерывания, чтобы можно было получать следующие события
ESP_ERROR_CHECK(vl53l0x_clear_interrupt_mask(sensor));
}
}
```
### Когда использовать
Этот режим подходит, если:
- нужно одно измерение без активного polling;
- задача может спать до готовности данных;
- важно сэкономить время CPU или энергию;
- уже есть обработка внешних прерываний в проекте.
### Что важно помнить
- После `vl53l0x_start_measurement()` датчик должен быть настроен на прерывание по готовности данных.
- После чтения результата обязательно вызывайте `vl53l0x_clear_interrupt_mask(sensor)`, иначе следующее прерывание может не прийти.
- В этом примере измерение всё равно одиночное: после каждого события цикл запускается заново.
## Непрерывное измерение (Continuous ranging)
Этот режим подходит, когда нужен поток измерений без повторного запуска каждого замера вручную. В отличие от одиночного режима, датчик сам продолжает выдавать новые результаты, а хост только:
1. запускает непрерывный режим через `vl53l0x_start_measurement(sensor)`;
2. ждёт событие готовности данных;
3. читает результат через `vl53l0x_get_data(sensor, &data)`;
4. сбрасывает прерывание через `vl53l0x_clear_interrupt_mask(sensor)`;
5. ждёт следующее измерение.
Важно: в этом режиме датчик фактически "молотит" постоянно — он непрерывно выполняет новые измерения, а ИК-лазер (эмиттер) остаётся активным почти всё время. Это удобно для быстрого потока данных, но может заметно увеличить потребление энергии по сравнению с одиночными режимами.
В примере ниже GPIO1 датчика настроен на событие `VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY`, а задача ESP32 просыпается через уведомление FreeRTOS. Это удобно, если нужно минимизировать polling и получать результат только тогда, когда он уже готов.
### Пример работы
Ниже показан фрагмент сценария. Он предполагает, что `sensor` уже создан и инициализирован.
```c
#define VL53L0X_GPIO_INT GPIO_NUM_12 // Измените под вашу схему
// Обработчик прерывания от датчика (выполняется в контексте ISR)
static void IRAM_ATTR vl53l0x_gpio_isr(void *arg)
{
// Получаем дескриптор задачи, которую нужно разбудить
TaskHandle_t task = (TaskHandle_t)arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Разбужаем задачу через уведомление FreeRTOS
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
// Если была разбужена задача с более высоким приоритетом,
// запрашиваем переключение контекста
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// Инициализация GPIO для подключения к выводу прерывания датчика
static void gpio_interrupt_init(void)
{
// Получаем дескриптор текущей задачи (её будем будить из ISR)
TaskHandle_t task = xTaskGetCurrentTaskHandle();
// Настраиваем пин прерывания как вход
gpio_set_direction(VL53L0X_GPIO_INT, GPIO_MODE_INPUT);
// Подтяжки отключаем, так как на модуле обычно уже есть внешняя подтяжка
gpio_pullup_dis(VL53L0X_GPIO_INT);
gpio_pulldown_dis(VL53L0X_GPIO_INT);
// Прерывание по спаду сигнала (активный LOW)
gpio_set_intr_type(VL53L0X_GPIO_INT, GPIO_INTR_NEGEDGE);
// Устанавливаем сервис прерываний и регистрируем обработчик
gpio_install_isr_service(0);
gpio_isr_handler_add(VL53L0X_GPIO_INT, vl53l0x_gpio_isr, task);
}
// Функция для вывода результата измерения
static void print_data(const vl53l0x_data_t *data)
{
ESP_LOGI(TAG,
"distance=%u mm, valid=%s, status=%u (%s), signal=%.3f Mcps, ambient=%.3f Mcps",
(unsigned)data->distance_mm,
data->valid ? "true" : "false",
(unsigned)data->range_status,
vl53l0x_range_status_str(data->range_status),
(double)data->signal_rate_mcps,
(double)data->ambient_rate_mcps);
}
void app_main(void)
{
// Структура для хранения результата
vl53l0x_data_t data = {0};
// Включаем непрерывный режим
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS));
// GPIO1 будет сигнализировать о готовности нового измерения
ESP_ERROR_CHECK(vl53l0x_set_gpio_config(
sensor,
VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY,
VL53L0X_INTERRUPT_POLARITY_LOW));
// Настраиваем обработку прерывания от GPIO1
gpio_interrupt_init();
// Запускаем поток измерений
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
while (true) {
// Ждём прерывание от датчика (задача спит до события)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Читаем готовый результат
ESP_ERROR_CHECK(vl53l0x_get_data(sensor, &data));
print_data(&data);
// Сбрасываем флаг прерывания, чтобы получить следующее событие
ESP_ERROR_CHECK(vl53l0x_clear_interrupt_mask(sensor));
}
}
```
### Что важно помнить
- `vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS)` включает непрерывный поток измерений.
- `vl53l0x_set_gpio_config(..., VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY, ...)` делает GPIO1 сигналом готовности нового результата.
- После чтения результата обязательно вызывайте `vl53l0x_clear_interrupt_mask(sensor)`, иначе следующее событие может не прийти.
- Если нужен фиксированный период между измерениями, чаще подходит `Continuous Timed`.
## Непрерывное измерение через интервалы времени (Continuous Timed)
Режим `Continuous Timed` похож на обычный `Continuous`, но измерения запускаются **через заданный интервал времени**. Это удобно, когда нужен стабильный период опроса без ручного перезапуска каждого замера.
Главная практическая разница — **ИК-лазер включается только на время измерительных окон**, а между ними датчик ждёт следующий интервал. За счёт этого режим обычно экономичнее, чем непрерывное измерение без пауз.
Схема работы такая:
1. инициализировать датчик и I2C;
2. выбрать профиль и режим `VL53L0X_MODE_CONTINUOUS_TIMED`;
3. задать период между измерениями через `vl53l0x_set_inter_measurement(sensor, ...)`;
4. настроить GPIO1 на событие `VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY`;
5. запустить измерения;
6. ждать прерывание, читать результат и очищать маску прерывания.
Ниже показан фрагмент сценария. Он предполагает, что `sensor` уже создан и инициализирован.
```c
#define VL53L0X_GPIO_INT GPIO_NUM_12 // Измените под вашу схему
// Обработчик прерывания от датчика (выполняется в контексте ISR)
static void IRAM_ATTR vl53l0x_gpio_isr(void *arg)
{
// Получаем дескриптор задачи, которую нужно разбудить
TaskHandle_t task = (TaskHandle_t)arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Разбужаем задачу через уведомление FreeRTOS
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
// Если была разбужена задача с более высоким приоритетом,
// запрашиваем переключение контекста
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// Инициализация GPIO для подключения к выводу прерывания датчика
static void gpio_interrupt_init(void)
{
// Получаем дескриптор текущей задачи (её будем будить из ISR)
TaskHandle_t task = xTaskGetCurrentTaskHandle();
// Настраиваем пин прерывания как вход
gpio_set_direction(VL53L0X_GPIO_INT, GPIO_MODE_INPUT);
// Подтяжки отключаем, так как на модуле обычно уже есть внешняя подтяжка
gpio_pullup_dis(VL53L0X_GPIO_INT);
gpio_pulldown_dis(VL53L0X_GPIO_INT);
// Прерывание по спаду сигнала (активный LOW)
gpio_set_intr_type(VL53L0X_GPIO_INT, GPIO_INTR_NEGEDGE);
// Устанавливаем сервис прерываний и регистрируем обработчик
gpio_install_isr_service(0);
gpio_isr_handler_add(VL53L0X_GPIO_INT, vl53l0x_gpio_isr, task);
}
// Функция для вывода результата измерения
static void print_data(const vl53l0x_data_t *data)
{
ESP_LOGI(TAG,
"distance=%u mm, valid=%s, status=%u (%s), signal=%.3f Mcps, ambient=%.3f Mcps",
(unsigned)data->distance_mm,
data->valid ? "true" : "false",
(unsigned)data->range_status,
vl53l0x_range_status_str(data->range_status),
(double)data->signal_rate_mcps,
(double)data->ambient_rate_mcps);
}
void app_main(void)
{
// Структура для хранения результата
vl53l0x_data_t data = {0};
// Включаем режим непрерывных измерений с интервалом
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
// Интервал между измерениями: 1000 мс
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 1000));
// GPIO1 будет сигнализировать о готовности нового измерения
ESP_ERROR_CHECK(vl53l0x_set_gpio_config(
sensor,
VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY,
VL53L0X_INTERRUPT_POLARITY_LOW));
// Настраиваем обработку прерывания от GPIO1
gpio_interrupt_init();
// Запускаем измерения
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
while (true) {
// Ждём прерывание от датчика
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Читаем готовый результат
ESP_ERROR_CHECK(vl53l0x_get_data(sensor, &data));
print_data(&data);
// Сбрасываем флаг прерывания
ESP_ERROR_CHECK(vl53l0x_clear_interrupt_mask(sensor));
}
}
```
### Что важно помнить
- `vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED)` включает непрерывные измерения с заданным интервалом.
- `vl53l0x_set_inter_measurement(sensor, 1000)` задаёт период между измерениями в миллисекундах.
- `vl53l0x_set_gpio_config(..., VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY, ...)` позволяет будить задачу по готовности нового результата.
- После чтения результата обязательно вызывайте `vl53l0x_clear_interrupt_mask(sensor)`, иначе следующее событие может не прийти.
Если нужен поток измерений, но с паузой между ними, `Continuous Timed` обычно удобнее и экономичнее, чем обычный `Continuous`.
## Пороговые измерения по прерыванию
Этот режим нужен, когда датчик должен не просто сообщать о готовности нового измерения, а подавать сигнал только при достижении заданных границ расстояния. Тогда хост может «спать» и просыпаться только в нужный момент: например, когда объект подошёл ближе заданного порога, вышел из допустимого окна или когда нужно обработать новое измерение без постоянного polling.
Для этого используются две настройки:
1. `vl53l0x_set_interrupt_thresholds(sensor, low_mm, high_mm)` — задаёт нижнюю и верхнюю границы.
2. `vl53l0x_set_gpio_config(sensor, function, polarity)` — выбирает, при каком событии будет активен вывод `GPIO1`.
### Режимы прерываний
В библиотеке доступны несколько вариантов работы вывода прерывания:
- `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_LOW` — прерывание по нижнему порогу. Срабатывает, когда измеренная дистанция становится меньше `thresh_low`.
- `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_HIGH` — прерывание по верхнему порогу. Срабатывает, когда дистанция становится больше `thresh_high`.
- `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_OUT` — прерывание «вне окна». Срабатывает, если значение меньше нижней границы или больше верхней: `value < thresh_low || value > thresh_high`.
- `VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY` — прерывание по готовности нового измерения. Этот режим полезен, если нужно просто получать сигнал о завершении очередного замера.
Обычно для пороговой логики используют именно `THRESHOLD_CROSSED_LOW`, `THRESHOLD_CROSSED_HIGH` или `THRESHOLD_CROSSED_OUT`.
### Что важно учитывать
- В режиме одиночного измерения (`VL53L0X_MODE_SINGLE`) максимальное программируемое значение порога ограничено **254 мм**.
- В непрерывных режимах (`VL53L0X_MODE_CONTINUOUS` и `VL53L0X_MODE_CONTINUOUS_TIMED`) пороги могут быть больше 254 мм.
- Если выбран режим по верхнему порогу, а цель не обнаружена, прерывание может не прийти.
- После обработки события прерывание нужно обязательно сбросить через `vl53l0x_clear_interrupt_mask(sensor)`, иначе следующее событие не сработает.
- Если используется внешний обработчик на `GPIO1`, сам датчик должен быть уже переведён в нужный режим и запущено измерение.
### Типовой порядок работы
1. Инициализировать I2C и создать объект датчика.
2. Выполнить калибровки и выбрать профиль измерения.
3. Задать пороги через `vl53l0x_set_interrupt_thresholds()`.
4. Настроить `GPIO1` через `vl53l0x_set_gpio_config()`.
5. Поднять обработчик прерывания на стороне ESP32.
6. Запустить измерение через `vl53l0x_start_measurement()`.
7. Дождаться сигнала от датчика.
8. Считать результат через `vl53l0x_get_data()`.
9. Сбросить флаг прерывания через `vl53l0x_clear_interrupt_mask()`.
### Пример работы
Ниже показан фрагмент сценария. Он предполагает, что `sensor` уже создан и инициализирован.
```c
#define VL53L0X_GPIO_INT GPIO_NUM_12 // Измените под вашу схему
// Обработчик прерывания от датчика (выполняется в контексте ISR)
static void IRAM_ATTR vl53l0x_gpio_isr(void *arg)
{
// Получаем дескриптор задачи, которую нужно разбудить
TaskHandle_t task = (TaskHandle_t)arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Разбужаем задачу через уведомление FreeRTOS
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
// Если была разбужена задача с более высоким приоритетом,
// запрашиваем переключение контекста
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// Инициализация GPIO для подключения к выводу прерывания датчика
static void gpio_interrupt_init(void)
{
// Получаем дескриптор текущей задачи (её будем будить из ISR)
TaskHandle_t task = xTaskGetCurrentTaskHandle();
// Настраиваем пин прерывания как вход
gpio_set_direction(VL53L0X_GPIO_INT, GPIO_MODE_INPUT);
// Подтяжки отключаем, так как на модуле обычно уже есть внешняя подтяжка
gpio_pullup_dis(VL53L0X_GPIO_INT);
gpio_pulldown_dis(VL53L0X_GPIO_INT);
// Прерывание по спаду сигнала (активный LOW)
gpio_set_intr_type(VL53L0X_GPIO_INT, GPIO_INTR_NEGEDGE);
// Устанавливаем сервис прерываний и регистрируем обработчик
gpio_install_isr_service(0);
gpio_isr_handler_add(VL53L0X_GPIO_INT, vl53l0x_gpio_isr, task);
}
// Функция для вывода результата измерения
static void print_data(const vl53l0x_data_t *data)
{
ESP_LOGI(TAG,
"distance=%u mm, valid=%s, status=%u (%s), signal=%.3f Mcps, ambient=%.3f Mcps",
(unsigned)data->distance_mm,
data->valid ? "true" : "false",
(unsigned)data->range_status,
vl53l0x_range_status_str(data->range_status),
(double)data->signal_rate_mcps,
(double)data->ambient_rate_mcps);
}
void app_main(void)
{
// Структура для хранения результата
vl53l0x_data_t data = {0};
// Непрерывный режим с интервалом 250 мс
ESP_ERROR_CHECK(vl53l0x_set_mode(sensor, VL53L0X_MODE_CONTINUOUS_TIMED));
ESP_ERROR_CHECK(vl53l0x_set_inter_measurement(sensor, 250));
// Нижний порог 400 мм: событие при дистанции меньше порога
ESP_ERROR_CHECK(vl53l0x_set_interrupt_thresholds(sensor, 400.0, 0.0));
// GPIO1 активируется при пересечении нижней границы
ESP_ERROR_CHECK(vl53l0x_set_gpio_config(
sensor,
VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_LOW,
VL53L0X_INTERRUPT_POLARITY_LOW));
// Настраиваем обработку прерывания от GPIO1
gpio_interrupt_init();
// Запускаем измерения
ESP_ERROR_CHECK(vl53l0x_start_measurement(sensor));
while (true) {
// Ждём срабатывание порогового события
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Читаем результат измерения
ESP_ERROR_CHECK(vl53l0x_get_data(sensor, &data));
print_data(&data);
// Сбрасываем флаг прерывания
ESP_ERROR_CHECK(vl53l0x_clear_interrupt_mask(sensor));
}
}
```
### Как читать этот пример
- `vl53l0x_set_interrupt_thresholds(sensor, 400.0, 0.0)` задаёт нижний порог срабатывания на уровне 400 мм.
- `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_LOW` включает реакцию на пересечение нижней границы.
- `VL53L0X_INTERRUPT_POLARITY_LOW` выбран потому, что вывод прерывания `GPIO1` активируется низким уровнем. Это возможно, потому что на большинстве плат VL53L0X пин `GPIO1` уже подтянут к питанию через резистор 10 кОм (если на вашей плате подтяжки нет, её нужно добавить в схему).
### Когда использовать
Этот режим подходит, если:
- нужно отслеживать приближение или удаление объекта по заданной границе;
- система должна спать до наступления важного события;
- polling не подходит из-за нагрузки на CPU или из-за требований к энергопотреблению;
- важно обрабатывать только значимые изменения, а не каждое измерение подряд.
### Что важно помнить
- Для логики «меньше порога» используйте `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_LOW`.
- Для логики «больше порога» используйте `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_HIGH`.
- Для контроля внутри/вне окна используйте `VL53L0X_GPIO_FUNCTION_THRESHOLD_CROSSED_OUT`.
- После обработки события всегда вызывайте `vl53l0x_clear_interrupt_mask(sensor)`.
- Если нужен просто сигнал о готовности данных, а не пороговая логика, выбирайте `VL53L0X_GPIO_FUNCTION_NEW_MEASURE_READY`.
## API
### Инициализация и деинициализация
- `esp_err_t vl53l0x_create(vl53l0x_handle_t *out_sensor, i2c_master_bus_handle_t bus)` - Инициализация и калибровка экземпляра VL53L0X
- `esp_err_t vl53l0x_init(vl53l0x_handle_t sensor)` - Инициализация датчика
- `esp_err_t vl53l0x_destroy(vl53l0x_handle_t sensor)` - Остановка измерений (если выполняются) и уничтожение дескриптора
- `esp_err_t vl53l0x_reset(vl53l0x_handle_t sensor)` - Сброс датчика и ожидание завершения перезагрузки устройства
- `esp_err_t vl53l0x_set_address(vl53l0x_handle_t sensor, i2c_master_bus_handle_t bus, uint8_t address)` - Установка I2C адреса датчика
### Измерения
- `esp_err_t vl53l0x_single_measure(vl53l0x_handle_t sensor, vl53l0x_data_t *data)` - Выполнение одиночного измерения расстояния (блокирующая функция)
- `esp_err_t vl53l0x_set_mode(vl53l0x_handle_t sensor, vl53l0x_mode_t mode)` - Установка режима измерения (одиночный, непрерывный, временной)
- `esp_err_t vl53l0x_start_measurement(vl53l0x_handle_t sensor)` - Начало измерения (неблокирующая функция)
- `esp_err_t vl53l0x_stop_measurement(vl53l0x_handle_t sensor, uint32_t timeout_ms)` - Остановка текущего измерения и ожидание завершения остановки
- `esp_err_t vl53l0x_get_ready(vl53l0x_handle_t sensor, bool *ready)` - Проверка готовности данных измерения
- `esp_err_t vl53l0x_get_data(vl53l0x_handle_t sensor, vl53l0x_data_t *data)` - Получение данных измерения расстояния
### Калибровка
- `esp_err_t vl53l0x_perform_ref_spad_management(vl53l0x_handle_t sensor, vl53l0x_ref_spad_calibration_t *out)` - Выполнение блокирующего управления ссылочными SPAD и возврат значений для хранения на хосте
- `esp_err_t vl53l0x_set_reference_spads(vl53l0x_handle_t sensor, const vl53l0x_ref_spad_calibration_t *calibration)` - Применение ранее сохраненных данных калибровки ссылочных SPAD
- `esp_err_t vl53l0x_get_reference_spads(vl53l0x_handle_t sensor, vl53l0x_ref_spad_calibration_t *out)` - Чтение текущих данных калибровки ссылочных SPAD
- `esp_err_t vl53l0x_perform_ref_calibration(vl53l0x_handle_t sensor, vl53l0x_ref_calibration_t *out)` - Выполнение блокирующей VHV/фазовой калибровки и возврат значений для хранения на хосте
- `esp_err_t vl53l0x_set_ref_calibration(vl53l0x_handle_t sensor, const vl53l0x_ref_calibration_t *calibration)` - Применение ранее сохраненных данных VHV/фазовой калибровки
- `esp_err_t vl53l0x_get_ref_calibration(vl53l0x_handle_t sensor, vl53l0x_ref_calibration_t *out)` - Чтение текущих данных VHV/фазовой калибровки
- `esp_err_t vl53l0x_perform_offset_calibration(vl53l0x_handle_t sensor, float calibration_distance_mm, int32_t *out_offset_um)` - Выполнение блокирующей калибровки смещения на известном расстоянии до цели
- `esp_err_t vl53l0x_set_offset_calibration(vl53l0x_handle_t sensor, int32_t offset_um)` - Применение ранее сохраненной калибровки смещения измерения в микрометрах
- `esp_err_t vl53l0x_get_offset_calibration(vl53l0x_handle_t sensor, int32_t *offset_um)` - Чтение текущей калибровки смещения измерения в микрометрах
- `esp_err_t vl53l0x_perform_xtalk_calibration(vl53l0x_handle_t sensor, float calibration_distance_mm, float *out_xtalk_compensation_rate_mcps)` - Выполнение блокирующей калибровки перекрестных помех на известном расстоянии до цели
- `esp_err_t vl53l0x_set_xtalk_calibration(vl53l0x_handle_t sensor, float xtalk_compensation_rate_mcps)` - Применение ранее сохраненной скорости компенсации перекрестных помех в MCPS и включение компенсации
- `esp_err_t vl53l0x_get_xtalk_calibration(vl53l0x_handle_t sensor, float *xtalk_compensation_rate_mcps)` - Чтение текущей скорости компенсации перекрестных помех в MCPS
- `esp_err_t vl53l0x_set_xtalk_compensation_enable(vl53l0x_handle_t sensor, bool enable)` - Включение или отключение компенсации перекрестных помех
- `esp_err_t vl53l0x_get_xtalk_compensation_enable(vl53l0x_handle_t sensor, bool *enabled)` - Чтение состояния включения компенсации перекрестных помех
### Настройки профиля и параметров
- `esp_err_t vl53l0x_set_profile(vl53l0x_handle_t sensor, vl53l0x_profile_t profile)` - Применение одного из предустановленных профилей
- `esp_err_t vl53l0x_set_timing_budget(vl53l0x_handle_t sensor, uint32_t us)` - Установка бюджета времени измерения в микросекундах
- `esp_err_t vl53l0x_get_timing_budget(vl53l0x_handle_t sensor, uint32_t *us)` - Получение текущего бюджета времени измерения в микросекундах
- `esp_err_t vl53l0x_set_inter_measurement(vl53l0x_handle_t sensor, uint32_t ms)` - Установка периода между измерениями в миллисекундах
- `esp_err_t vl53l0x_get_inter_measurement(vl53l0x_handle_t sensor, uint32_t *ms)` - Получение периода между измерениями в миллисекундах
### Проверки ограничений
- `esp_err_t vl53l0x_set_limit_check_enable(vl53l0x_handle_t sensor, vl53l0x_limit_check_id_t id, bool enable)` - Включение/отключение выбранной проверки ограничений
- `esp_err_t vl53l0x_get_limit_check_enable(vl53l0x_handle_t sensor, vl53l0x_limit_check_id_t id, bool *enabled)` - Чтение состояния включения выбранной проверки ограничений
- `esp_err_t vl53l0x_set_limit_check_value(vl53l0x_handle_t sensor, vl53l0x_limit_check_id_t id, float value)` - Установка значения выбранной проверки ограничений (в float единицах)
- `esp_err_t vl53l0x_get_limit_check_value(vl53l0x_handle_t sensor, vl53l0x_limit_check_id_t id, float *value)` - Получение настроенного значения выбранной проверки ограничений
- `esp_err_t vl53l0x_get_limit_check_current(vl53l0x_handle_t sensor, vl53l0x_limit_check_id_t id, float *value)` - Получение текущего измеренного значения выбранной проверки ограничений
- `esp_err_t vl53l0x_get_limit_check_status(vl53l0x_handle_t sensor, vl53l0x_limit_check_id_t id, bool *passed)` - Получение статуса прохождения/непрохождения выбранной проверки ограничений для последнего образца
- `esp_err_t vl53l0x_set_range_ignore_threshold(vl53l0x_handle_t sensor, bool enable, float threshold_mcps)` - Удобная оболочка для управления порогом игнорирования диапазона
- `esp_err_t vl53l0x_get_range_ignore_threshold(vl53l0x_handle_t sensor, bool *enabled, float *threshold_mcps)` - Чтение состояния и значения порога игнорирования диапазона
### Настройки прерываний
- `esp_err_t vl53l0x_clear_interrupt_mask(vl53l0x_handle_t sensor)` - Очистка маски прерываний
- `esp_err_t vl53l0x_set_interrupt_thresholds(vl53l0x_handle_t sensor, float threshold_low_mm, float threshold_high_mm)` - Установка порогов прерываний (нижний и верхний) в миллиметрах
- `esp_err_t vl53l0x_set_gpio_config(vl53l0x_handle_t sensor, vl53l0x_gpio_function_t functionality, vl53l0x_interrupt_polarity_t polarity)` - Настройка конфигурации GPIO прерываний
### Вспомогательные функции
- `const char *vl53l0x_range_status_str(uint8_t status)` - Преобразование кода статуса диапазона в человекочитаемый текст
## Лицензия
[MIT](./LICENSE)
idf.py add-dependency "pkolt/vl53l0x^0.0.1"