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.

pkolt/vl53l0x

0.0.1

Latest
uploaded 4 hours ago
ESP-IDF driver for the VL53L0X ToF sensor featuring configurable ranging profiles, continuous measurements, and GPIO interrupt support.

Readme (ru)

# 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)

Links

Supports all targets

To add this component to your project, run:

idf.py add-dependency "pkolt/vl53l0x^0.0.1"

download archive

Stats

  • Archive size
    Archive size ~ 124.67 KB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

pkolt/vl53l0x version: 0.0.1
|