Compare commits

..
10 Commits
Author SHA1 Message Date
aZholtikov b6e49ca147 doc: added wiki 2026-10-18 06:31:28 +03:00
aZholtikov 7672975ba4 doc: updated readme 2026-10-18 06:31:10 +03:00
aZholtikov 81cf96aa16 perf: updated error checks 2026-10-11 20:49:00 +03:00
aZholtikov c5affea438 style: updated comments 2026-09-12 12:56:14 +03:00
aZholtikov ffe0c5e376 feat!: main structure of handle changed to double pointer 2026-09-06 19:49:26 +03:00
aZholtikov 9749186fc1 style: updated examples 2026-06-13 08:15:21 +03:00
aZholtikov f0d5e579ab doc: added idf_component.yml 2026-06-12 11:30:30 +03:00
aZholtikov 2ac63a930c doc: added wiki 2026-06-12 11:30:10 +03:00
aZholtikov 10cb89b02b style: removed unused variable 2026-06-12 11:29:42 +03:00
aZholtikov ac973f18a6 feat: initial 2026-05-17 21:42:28 +03:00
10 changed files with 755 additions and 3 deletions

No files matched your search

BIN
View File
Binary file not shown.
+6 -1
View File
@@ -1 +1,6 @@
idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") if(${IDF_VERSION_MAJOR} STREQUAL 6)
set(requires esp_driver_i2c)
else()
set(requires driver)
endif()
idf_component_register(SRCS "zh_bh1750.c" INCLUDE_DIRS "include" REQUIRES ${requires})
+202 -2
View File
@@ -1,3 +1,203 @@
# esp_component_template # ESP32 ESP-IDF component for BH1750 ambient light sensor
esp_component_template ## Features
1. Measurement of ambient light intensity in lux.
2. I2C interface with frequency up to 400 kHz (Fast Mode).
3. Support for two I2C addresses: 0x23 (ADDR low) and 0x5C (ADDR high).
4. One-time H-resolution measurement with 1 lux step.
5. Automatic lux conversion from raw sensor data.
6. Sensor presence verification during initialization via I2C probe.
7. Built-in I2C error statistics tracking and reset for diagnostics.
## Attention
1. The I2C bus must be initialized before the component.
2. Only one-time H-resolution measurement mode is supported.
3. Each read is a blocking call of approximately 180 ms.
4. Error statistics are global for all sensor instances on the device.
## Using
In an existing project, run the following command to install the component:
```bash
cd ../your_project/components
git clone https://github.com/aZholtikov/zh_bh1750
```
In the application, add the component:
```c
#include "zh_bh1750.h"
```
## Examples
### Basic Read Example
```c
#include "zh_bh1750.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_bh1750_handle_t *bh1750_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_bh1750", ESP_LOG_ERROR);
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
.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 = NULL;
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
if (ret != ESP_OK)
{
printf("I2C bus initialization error\n");
return;
}
zh_bh1750_init_config_t config = ZH_BH1750_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
ret = zh_bh1750_init(&config, &bh1750_handle);
if (ret != ESP_OK)
{
printf("BH1750 initialization error: %s\n", esp_err_to_name(ret));
return;
}
float lux = 0.0;
for (;;)
{
ret = zh_bh1750_read(&bh1750_handle, &lux);
if (ret == ESP_OK)
{
printf("Lux: %0.2f\n", lux);
}
else
{
printf("Read error: %s\n", esp_err_to_name(ret));
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
```
---
### Alternative Address Example
```c
#include "zh_bh1750.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_bh1750_handle_t *bh1750_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_bh1750", ESP_LOG_ERROR);
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
.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 = NULL;
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
if (ret != ESP_OK)
{
printf("I2C bus initialization error\n");
return;
}
zh_bh1750_init_config_t config = ZH_BH1750_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x5C;
ret = zh_bh1750_init(&config, &bh1750_handle);
if (ret != ESP_OK)
{
printf("BH1750 initialization error: %s\n", esp_err_to_name(ret));
return;
}
float lux = 0.0;
ret = zh_bh1750_read(&bh1750_handle, &lux);
if (ret == ESP_OK)
{
printf("Lux: %0.2f\n", lux);
}
else
{
printf("Read error: %s\n", esp_err_to_name(ret));
}
ret = zh_bh1750_deinit(&bh1750_handle);
if (ret != ESP_OK)
{
printf("Deinitialization error: %s\n", esp_err_to_name(ret));
}
}
```
---
### Error Statistics Example
```c
#include "zh_bh1750.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_bh1750_handle_t *bh1750_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_bh1750", ESP_LOG_ERROR);
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
.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 = NULL;
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
if (ret != ESP_OK)
{
printf("I2C bus initialization error\n");
return;
}
zh_bh1750_init_config_t config = ZH_BH1750_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
ret = zh_bh1750_init(&config, &bh1750_handle);
if (ret != ESP_OK)
{
printf("BH1750 initialization error: %s\n", esp_err_to_name(ret));
return;
}
float lux = 0.0;
for (int i = 0; i < 10; i++)
{
ret = zh_bh1750_read(&bh1750_handle, &lux);
if (ret == ESP_OK)
{
printf("Lux: %0.2f\n", lux);
}
else
{
printf("Read error: %s\n", esp_err_to_name(ret));
}
const zh_bh1750_stats_t *stats = zh_bh1750_get_stats();
printf("Number of I2C driver errors: %lu\n", (unsigned long)stats->i2c_driver_error);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
zh_bh1750_reset_stats();
const zh_bh1750_stats_t *stats = zh_bh1750_get_stats();
printf("Number of I2C driver errors after reset: %lu\n", (unsigned long)stats->i2c_driver_error);
zh_bh1750_deinit(&bh1750_handle);
}
```
+8
View File
@@ -0,0 +1,8 @@
description: ESP32 ESP-IDF component for BH1750 ambient light sensor
url: https://github.com/aZholtikov/zh_bh1750
version: "2.0.0"
license: "Apache-2.0"
maintainers:
- "Alexey Zholtikov <alexey.zholtikov@yandex.ru>"
dependencies:
idf: ">=5.0"
View File
Whitespace-only changes.
+142
View File
@@ -0,0 +1,142 @@
/**
* @file zh_bh1750.h
*
* @brief Driver for BH1750 digital ambient light sensor over I2C.
*
* This module provides an abstraction layer for the BH1750 ambient light
* intensity sensor, which communicates via the I2C bus. The sensor converts
* ambient light to a digital value with high resolution, providing a direct
* lux reading without the need for external calibration.
*
* Key features:
* - I2C interface with support for standard and fast modes
* - Automatic lux calculation from raw sensor data
* - Error statistics tracking for diagnostics
* - Thread-safe initialization and deinitialization
*
* @note Supported I2C addresses: 0x23 (default), 0x5C (ADDR pin high).
* @warning Ensure the I2C bus is initialized before calling zh_bh1750_init().
*/
#pragma once
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define ZH_BH1750_INIT_CONFIG_DEFAULT() \
{ \
.i2c_frequency = 400000, \
.i2c_address = 0x23}
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Opaque handle representing a BH1750 sensor instance.
*
* This handle is allocated by zh_bh1750_init() and must be passed to
* all subsequent API functions. It is freed by zh_bh1750_deinit().
*/
typedef struct _zh_bh1750_handle_t zh_bh1750_handle_t;
/**
* @brief Configuration structure for BH1750 sensor initialization.
*
* This structure defines the I2C bus parameters required to communicate
* with the BH1750 sensor. The I2C address defaults to 0x23 when the ADDR
* pin is connected to GND, or 0x5C when the ADDR pin is connected to VCC.
*
* @note The I2C bus handle must be initialized before passing it here.
* @warning I2C frequency must not exceed 400000 Hz (Fast Mode).
*/
typedef struct
{
i2c_master_bus_handle_t i2c_handle; /*!< I2C bus handle for sensor communication */
uint8_t i2c_address; /*!< Sensor I2C address (0x23 or 0x5C) */
uint32_t i2c_frequency; /*!< I2C clock frequency in Hz (max 400000) */
} zh_bh1750_init_config_t;
/**
* @brief Structure containing sensor error statistics.
*/
typedef struct
{
uint32_t i2c_driver_error; /*!< Counter of I2C driver errors */
} zh_bh1750_stats_t;
/**
* @brief Initialize the BH1750 sensor.
*
* Allocates a handle, validates the provided configuration, and registers
* the sensor as an I2C device. The I2C bus must already be initialized.
*
* @param[in] config Pointer to the initialization configuration structure
* @param[out] handle Pointer to receive the allocated sensor handle
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if config or handle is NULL, or parameters are invalid
* @return ESP_ERR_NO_MEM if memory allocation for the handle fails
* @return ESP_FAIL if I2C device registration fails
* @return ESP_ERR_NOT_FOUND if the sensor does not respond on the specified address
*/
esp_err_t zh_bh1750_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t **handle);
/**
* @brief Deinitialize the BH1750 sensor.
*
* Removes the sensor from the I2C bus, frees the allocated handle,
* and sets the handle pointer to NULL.
*
* @param handle Pointer to the sensor handle (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle or *handle is NULL
*
* @note After this call, the handle is invalid and must not be used.
*/
esp_err_t zh_bh1750_deinit(zh_bh1750_handle_t **handle);
/**
* @brief Read ambient light intensity from the BH1750 sensor.
*
* Issues a one-time H-resolution measurement command, waits for the
* conversion period (~180 ms), and reads the raw data. The raw value
* is converted to lux using the formula: lux = raw_value / 1.2.
*
* @param[in] handle Pointer to the sensor handle (must not be NULL)
* @param[out] data Pointer to receive the light intensity in lux (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle or data is NULL
* @return ESP_FAIL if any I2C transmission or reception fails
*
* @note This is a blocking call that delays for approximately 180 ms.
* @warning Ensure sufficient stack space for the vTaskDelay call.
*/
esp_err_t zh_bh1750_read(zh_bh1750_handle_t **handle, float *data);
/**
* @brief Get pointer to sensor error statistics.
*
* Returns read-only pointer to global statistics structure.
*
* @return Pointer to statistics structure (valid until reset)
*/
const zh_bh1750_stats_t *zh_bh1750_get_stats(void);
/**
* @brief Reset all error statistics to zero.
*
* Clears all error counters tracked in the global statistics structure.
*/
void zh_bh1750_reset_stats(void);
#ifdef __cplusplus
}
#endif
View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
2.0.0
+260
View File
@@ -0,0 +1,260 @@
# Описание компонента 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"
```
+136
View File
@@ -0,0 +1,136 @@
#include "zh_bh1750.h"
static const char *TAG = "zh_bh1750";
#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__)
#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__)
#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \
if (!(cond)) \
{ \
ZH_LOGE(msg, err, ##__VA_ARGS__); \
cleanup; \
return err; \
}
#define ZH_ERROR_CHECK_CONT(cond, cleanup, msg, ...) \
if (!(cond)) \
{ \
ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \
cleanup; \
}
/**
* @brief Handle structure for BH1750 sensor.
*
* Internal structure managing the I2C device connection.
*/
struct _zh_bh1750_handle_t
{
i2c_master_dev_handle_t dev_handle; /*!< Unique I2C device handle */
};
static const uint8_t _bh1750_read_command = 0x20; /*!< One-time H-resolution measurement command (1 lx step) */
static zh_bh1750_stats_t _stats = {0}; /*!< Global error statistics accumulator */
/**
* @brief Validate the initialization configuration parameters.
*
* Checks that the I2C address is valid (0x23 or 0x5C), the frequency
* does not exceed 400000 Hz, and the I2C bus handle is not NULL.
*
* @param config Pointer to the configuration to validate
*
* @return ESP_OK if all parameters are valid
* @return ESP_ERR_INVALID_ARG if any parameter is out of range or NULL
*/
static esp_err_t _zh_bh1750_validate_config(const zh_bh1750_init_config_t *config);
/**
* @brief Register the BH1750 as an I2C device and verify connectivity.
*
* Configures the I2C device parameters, adds the device to the bus,
* and probes the sensor to confirm it is physically connected and responds.
*
* @param config Pointer to the initialization configuration
* @param handle Pointer to the allocated handle to store the device handle
*
* @return ESP_OK on successful setup and probe
* @return ESP_FAIL if device registration fails
* @return ESP_ERR_NOT_FOUND if the sensor does not respond to a probe
*/
static esp_err_t _zh_bh1750_i2c_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t *handle);
esp_err_t zh_bh1750_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t **handle)
{
ZH_LOGI("BH1750 initialization begin.");
ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "BH1750 initialization failed. Invalid argument.");
ZH_ERROR_CHECK(_zh_bh1750_validate_config(config) == ESP_OK, ESP_FAIL, NULL, "BH1750 initialization failed. Initial configuration check failed.");
ZH_ERROR_CHECK(*handle == NULL, ESP_ERR_INVALID_STATE, NULL, "BH1750 initialization failed. BH1750 is already initialized.");
*handle = heap_caps_calloc(1, sizeof(zh_bh1750_handle_t), MALLOC_CAP_8BIT);
ZH_ERROR_CHECK(*handle != NULL, ESP_ERR_NO_MEM, NULL, "BH1750 initialization failed. Failed to allocate BH1750 handle.");
ZH_ERROR_CHECK(_zh_bh1750_i2c_init(config, *handle) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "BH1750 initialization failed. Failed to add I2C device.");
ZH_LOGI("BH1750 initialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_bh1750_deinit(zh_bh1750_handle_t **handle)
{
ZH_LOGI("BH1750 deinitialization started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "BH1750 deinitialization failed. Invalid argument.");
ZH_ERROR_CHECK(i2c_master_bus_rm_device((*handle)->dev_handle) == ESP_OK, ESP_FAIL, NULL, "BH1750 deinitialization failed. I2C remove device failed.");
heap_caps_free(*handle);
*handle = NULL;
ZH_LOGI("BH1750 deinitialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_bh1750_read(zh_bh1750_handle_t **handle, float *data)
{
ZH_LOGI("BH1750 read begin.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && data != NULL, ESP_ERR_INVALID_ARG, NULL, "BH1750 read fail. Invalid argument.");
uint8_t sensor_data[2] = {0};
ZH_ERROR_CHECK(i2c_master_transmit((*handle)->dev_handle, &_bh1750_read_command, sizeof(_bh1750_read_command), 100) == ESP_OK, ESP_FAIL,
++_stats.i2c_driver_error, "BH1750 read fail. I2C driver error.");
vTaskDelay(180 / portTICK_PERIOD_MS);
ZH_ERROR_CHECK(i2c_master_receive((*handle)->dev_handle, sensor_data, sizeof(sensor_data), 100) == ESP_OK, ESP_FAIL,
++_stats.i2c_driver_error, "BH1750 read fail. I2C driver error.");
*data = (sensor_data[0] << 8 | sensor_data[1]) / 1.2;
ZH_LOGI("BH1750 read completed successfully.");
return ESP_OK;
}
const zh_bh1750_stats_t *zh_bh1750_get_stats(void)
{
return &_stats;
}
void zh_bh1750_reset_stats(void)
{
ZH_LOGI("Error statistic reset started.");
_stats.i2c_driver_error = 0;
ZH_LOGI("Error statistic reset successfully.");
}
static esp_err_t _zh_bh1750_validate_config(const zh_bh1750_init_config_t *config)
{
ZH_ERROR_CHECK(config->i2c_address == 0x23 || config->i2c_address == 0x5C, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C address.");
ZH_ERROR_CHECK(config->i2c_frequency <= 400000 && config->i2c_frequency > 0, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C frequency.");
ZH_ERROR_CHECK(config->i2c_handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C handle.");
return ESP_OK;
}
static esp_err_t _zh_bh1750_i2c_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t *handle)
{
i2c_device_config_t bh1750_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = config->i2c_address,
.scl_speed_hz = config->i2c_frequency,
};
i2c_master_dev_handle_t _dev_handle = NULL;
ZH_ERROR_CHECK(i2c_master_bus_add_device(config->i2c_handle, &bh1750_config, &_dev_handle) == ESP_OK, ESP_FAIL, NULL, "Add I2C device failed.");
ZH_ERROR_CHECK(i2c_master_probe(config->i2c_handle, config->i2c_address, 100) == ESP_OK, ESP_ERR_NOT_FOUND,
ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, NULL, "I2C remove device failed."), "Sensor not connected or not responded.");
handle->dev_handle = _dev_handle;
return ESP_OK;
}