# Описание компонента zh_bh1750 ## Общие сведения **zh_bh1750** — это компонент ESP-IDF, реализующий драйвер цифрового датчика освещённости BH1750, подключаемого по шине I2C. Компонент предоставляет простой API для инициализации, чтения освещённости в люксах и диагностики ошибок. ## Основные возможности 1. **Интерфейс I2C** — поддержка стандартного и быстрого режимов с частотой до 400 кГц. 2. **Два I2C-адреса** — `0x23` (ADDR на GND) и `0x5C` (ADDR на VCC). 3. **Однократное измерение H-разрешения** — шаг 1 лк без внешней калибровки. 4. **Автоматический пересчёт в люксы** — из сырых данных по формуле `lux = raw / 1.2`. 5. **Проверка присутствия датчика** — I2C-пробирование при инициализации. 6. **Сбор статистики ошибок** — счётчик ошибок драйвера I2C со сбросом для диагностики. 7. **Несколько датчиков на одном устройстве** — через мультиплексор [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a). --- ## Внутреннее устройство ### Структура `zh_bh1750_handle_t` Непрозрачная структура, описывающая экземпляр датчика. Объявлена как `struct _zh_bh1750_handle_t`, доступна пользователю только через указатель. | Поле | Тип | Описание | |------|-----|----------| | `dev_handle` | `i2c_master_dev_handle_t` | Уникальный дескриптор I2C-устройства, полученный при регистрации на шине | ### Структура `zh_bh1750_init_config_t` Параметры инициализации, передаваемые пользователем. | Поле | Тип | Описание | | ------ | ----- | ---------- | | `i2c_handle` | `i2c_master_bus_handle_t` | Дескриптор уже инициализированной I2C-шины | | `i2c_address` | `uint8_t` | I2C-адрес датчика (`0x23` или `0x5C`) | | `i2c_frequency` | `uint32_t` | Частота шины I2C в герцах (до 400000) | Для подстановки значений по умолчанию (`400000` Гц, `0x23`) служит макрос `ZH_BH1750_INIT_CONFIG_DEFAULT()`. ### Структура `zh_bh1750_stats_t` Глобальный накопитель диагностической статистики. | Поле | Тип | Описание | |------|-----|----------| | `i2c_driver_error` | `uint32_t` | Счётчик ошибок передачи/приёма драйвера I2C | ### Управление памятью - Дескриптор датчика выделяется одной операцией `heap_caps_calloc()` с флагом `MALLOC_CAP_8BIT` и обнуляется. Это гарантирует, что при частичной инициализации все поля равны нулю. - При любой ошибке после выделения памяти дескриптор освобождается тем же `heap_caps_free()`, а пользовательский указатель обнуляется — утечка исключена. - `zh_bh1750_deinit()` снимает устройство с шины и освобождает дескриптор. - Статистика `_stats` — статическая глобальная переменная уровня модуля, не требует аллокации и живёт всё время работы программы. ### Вспомогательные данные и макросы - `_bh1750_read_command` (`uint8_t` = `0x20`) — команда однократного измерения H-разрешения, отправляемая перед каждым чтением. - `_stats` — глобальный экземпляр `zh_bh1750_stats_t`, накапливающий ошибки I2C-драйвера. - Макросы `ZH_ERROR_CHECK` / `ZH_ERROR_CHECK_CONT` — инкапсулируют проверку условия, логирование ошибки с именем функции и строкой, выполнение очистки и возврат кода ошибки. --- ## API (интерфейс) ### Инициализация и освобождение | Функция | Описание | |---------|----------| | `zh_bh1750_init()` | Валидирует конфигурацию, выделяет дескриптор, регистрирует датчик на I2C-шине и проверяет его доступность пробированием. Требует `*handle == NULL`. | | `zh_bh1750_deinit()` | Удаляет устройство с I2C-шины, освобождает дескриптор и устанавливает пользовательский указатель в `NULL`. | ### Чтение данных | Функция | Описание | |---------|----------| | `zh_bh1750_read()` | Отправляет команду однократного измерения, ожидает ~180 мс, считывает 2 байта и возвращает освещённость в люксах. Блокирующий вызов. | ### Получение информации и сброс статистики | Функция | Описание | |---------|----------| | `zh_bh1750_get_stats()` | Возвращает read-only указатель на глобальную структуру статистики ошибок. | | `zh_bh1750_reset_stats()` | Обнуляет все счётчики в структуре статистики. | --- ## Потокобезопасность Компонент **не** использует механизмы синхронизации (мьютексы, критические секции, атомарные операции). 1. **Дескриптор датчика** защищён только на уровне инициализации: `zh_bh1750_init()` требует `*handle == NULL` и возвращает `ESP_ERR_INVALID_STATE` при повторном вызове, что предотвращает двойную регистрацию устройства. 2. **Глобальная статистика** `_stats` не имеет защиты — инкремент `i2c_driver_error` из разных задач может привести к гонке данных. 3. **Чтение** `zh_bh1750_read()` не атомарно: одновременные вызовы из разных задач для одного дескриптора недопустимы. Подробности — в разделе «Ограничения». --- ## Алгоритмы ### Валидация конфигурации (`_zh_bh1750_validate_config`) Проверяются три условия, при нарушении любого возвращается `ESP_ERR_INVALID_ARG`: - `i2c_address` равен `0x23` или `0x5C`; - `0 < i2c_frequency <= 400000`; - `i2c_handle != NULL`. ### Регистрация устройства и пробирование (`_zh_bh1750_i2c_init`) 1. Формируется `i2c_device_config_t` с 7-битным адресом и заданной частотой. 2. Устройство добавляется на шину через `i2c_master_bus_add_device()`. 3. Выполняется `i2c_master_probe()` на адресе датчика. 4. Если пробирование не удалось, только что добавленное устройство снимается с шины (`i2c_master_bus_rm_device`), и возвращается `ESP_ERR_NOT_FOUND` — «висящего» устройства на шине не остаётся. ### Чтение освещённости (`zh_bh1750_read`) 1. Отправка команды `_bh1750_read_command` (`0x20`). 2. Ожидание `vTaskDelay(180 / portTICK_PERIOD_MS)` — время преобразования для H-разрешения. 3. Приём 2 байт сырых данных. 4. Пересчёт: `lux = (data[0] << 8 | data[1]) / 1.2`. --- ## Примеры использования ### Базовый пример ```c #include "zh_bh1750.h" zh_bh1750_handle_t *bh1750_handle = NULL; void app_main(void) { // Инициализируем I2C-шину i2c_master_bus_config_t i2c_bus_config = { .clk_source = I2C_CLK_SRC_DEFAULT, .i2c_port = I2C_NUM_MAX - 1, .scl_io_num = GPIO_NUM_22, .sda_io_num = GPIO_NUM_21, .glitch_ignore_cnt = 7, .flags.enable_internal_pullup = true, }; i2c_master_bus_handle_t i2c_bus_handle; i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); // Инициализируем датчик конфигурацией по умолчанию zh_bh1750_init_config_t config = ZH_BH1750_INIT_CONFIG_DEFAULT(); config.i2c_handle = i2c_bus_handle; zh_bh1750_init(&config, &bh1750_handle); // Читаем освещённость float lux = 0.0; zh_bh1750_read(&bh1750_handle, &lux); printf("Lux %0.2f\n", lux); // Освобождаем ресурсы zh_bh1750_deinit(&bh1750_handle); } ``` ### Работа со статистикой ошибок ```c #include "zh_bh1750.h" void check_sensor_health(zh_bh1750_handle_t **handle) { float lux = 0.0; if (zh_bh1750_read(handle, &lux) != ESP_OK) { // Считываем накопленную статистику сбоев I2C const zh_bh1750_stats_t *stats = zh_bh1750_get_stats(); printf("I2C driver errors: %lu\n", (unsigned long)stats->i2c_driver_error); } // Сбрасываем счётчики перед новым циклом измерений zh_bh1750_reset_stats(); } ``` ### Подключение датчика с адресом 0x5C ```c #include "zh_bh1750.h" void init_addr_high(i2c_master_bus_handle_t bus, zh_bh1750_handle_t **handle) { // Датчик с ADDR на VCC использует адрес 0x5C zh_bh1750_init_config_t config = { .i2c_handle = bus, .i2c_address = 0x5C, .i2c_frequency = 100000, // Стандартный режим I2C }; zh_bh1750_init(&config, handle); // ... работа с датчиком ... zh_bh1750_deinit(handle); } ``` --- ## Логирование Компонент использует ESP-IDF логирование с тегом `"zh_bh1750"`. Уровень по умолчанию позволяет видеть ход инициализации и чтения (`ESP_LOGI`), а также ошибки с указанием файла, строки и имени функции ошибки (`ESP_LOGE`). ```c // Приглушить informational-сообщения, оставить только ошибки esp_log_level_set("zh_bh1750", ESP_LOG_ERROR); ``` --- ## Ошибки | Код | Описание | | ----- | ---------- | | `ESP_OK` | Операция выполнена успешно | | `ESP_ERR_INVALID_ARG` | Неверный аргумент: NULL-указатель, недопустимый адрес/частота, неинициализированная шина | | `ESP_ERR_INVALID_STATE` | Датчик уже инициализирован (`*handle != NULL` при вызове `zh_bh1750_init()`) | | `ESP_ERR_NO_MEM` | Не удалось выделить память под дескриптор | | `ESP_ERR_NOT_FOUND` | Датчик не отвечает на пробировании I2C (не подключён или неверный адрес) | | `ESP_FAIL` | Ошибка регистрации устройства на шине либо сбой передачи/приёма I2C | --- ## Ограничения 1. **Отсутствие потокобезопасности** — компонент не использует мьютексы; одновременные вызовы `zh_bh1750_read()` и работа со статистикой из разных задач приводят к гонке данных. 2. **Глобальная статистика** — `_stats` общая для всех экземпляров датчиков: счётчик `i2c_driver_error` суммирует ошибки всех устройств. 3. **Два допустимых адреса** — аппаратное ограничение BH1750: принимаются только `0x23` и `0x5C`. 4. **Частота I2C не выше 400 кГц** — ограничение Fast Mode datasheet BH1750. 5. **Блокирующее чтение** — `zh_bh1750_read()` задерживает задачу примерно на 180 мс (`vTaskDelay`) ради преобразования H-разрешения. 6. **Только H-разрешение** — жёстко зашита команда `0x20` (однократное измерение); режимы H-Res 2, L-Res и автоматического измерения не поддерживаются. 7. **Предварительная инициализация шины** — `i2c_handle` в конфигурации должен быть получен через `i2c_new_master_bus()` до вызова `zh_bh1750_init()`. --- ## Требования для сборки - ESP-IDF версии **5.0** и выше. - Зависимость от компонента I2C-драйвера: `driver` (ESP-IDF 5.x) либо `esp_driver_i2c` (ESP-IDF 6.x) — выбирается автоматически в `CMakeLists.txt`. - FreeRTOS (входит в состав ESP-IDF) — используется `vTaskDelay`. --- ## Установка ```bash cd ../your_project/components git clone https://github.com/aZholtikov/zh_bh1750.git ``` ```c #include "zh_bh1750.h" ```