diff --git a/README.md b/README.md index 67f37ed..a0f4410 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,22 @@ # ESP32 ESP-IDF component for BH1750 ambient light sensor -## Wiki - -[EN](WIKI_EN.md) | [RU](WIKI_RU.md) - -## Tested on - -1. [ESP32 ESP-IDF v6.0.1](https://docs.espressif.com/projects/esp-idf/en/v6.0.1/esp32/index.html) - ## SAST Tools [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. ## Features -1. Recorded illuminance values from 1 to 65535 lux. -2. Support some sensors on one device with [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a). +1. I2C interface with configurable frequency up to 400 kHz (Fast Mode). +2. Support for two I2C addresses: 0x23 (ADDR low) and 0x5C (ADDR high). +3. One-time H-resolution measurement with 1 lux step. +4. Built-in error statistics tracking and reset for diagnostics. +5. Automatic lux conversion from raw sensor data. +6. Sensor presence verification during initialization via I2C probe. +7. Support multiple sensors on one device with [zh_pca9548a](https://github.com/aZholtikov/zh_pca9548a). -## Attention +## Note -For correct operation, please enable the following settings in the menuconfig: +Enable the following settings in menuconfig: ```text I2C_ISR_IRAM_SAFE @@ -30,7 +27,7 @@ I2C_MASTER_ISR_HANDLER_IN_IRAM In an existing project, run the following command to install the component: -```text +```bash cd ../your_project/components git clone https://github.com/aZholtikov/zh_bh1750.git ``` @@ -43,14 +40,12 @@ In the application, add the component: ## Example -Reading the sensor: - ```c #include "zh_bh1750.h" #define I2C_PORT (I2C_NUM_MAX - 1) -zh_bh1750_handle_t bh1750_handle = {0}; +zh_bh1750_handle_t *bh1750_handle = NULL; void app_main(void) { @@ -79,62 +74,3 @@ void app_main(void) } } ``` - -3 sensor on one device: - -```c -#include "zh_pca9548a.h" -#include "zh_bh1750.h" - -#define I2C_PORT (I2C_NUM_MAX - 1) - -zh_pca9548a_handle_t pca9548a_handle = {0}; -zh_bh1750_handle_t bh1750_handle_chan_0 = {0}; -zh_bh1750_handle_t bh1750_handle_chan_1 = {0}; -zh_bh1750_handle_t bh1750_handle_chan_2 = {0}; - -void app_main(void) -{ - esp_log_level_set("zh_pca9548a", ESP_LOG_ERROR); - 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; - i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); - zh_pca9548a_init_config_t config = ZH_PCA9548A_INIT_CONFIG_DEFAULT(); - config.i2c_handle = i2c_bus_handle; - config.i2c_address = 0x70; - zh_pca9548a_init(&config, &pca9548a_handle); - zh_bh1750_init_config_t bh1750_config = ZH_BH1750_INIT_CONFIG_DEFAULT(); - bh1750_config.i2c_handle = i2c_bus_handle; - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_0); - zh_bh1750_init(&bh1750_config, &bh1750_handle_chan_0); - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_1); - zh_bh1750_init(&bh1750_config, &bh1750_handle_chan_1); - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_2); - zh_bh1750_init(&bh1750_config, &bh1750_handle_chan_2); - float lux = 0.0; - for (;;) - { - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_0); - zh_bh1750_read(&bh1750_handle_chan_0, &lux); - printf("Sensor 1. Lux %0.2f\n", lux); - lux = 0.0; - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_1); - zh_bh1750_read(&bh1750_handle_chan_1, &lux); - printf("Sensor 2. Lux %0.2f\n", lux); - lux = 0.0; - zh_pca9548a_set(&pca9548a_handle, ZH_PCA9548A_CHAN_NUM_2); - zh_bh1750_read(&bh1750_handle_chan_2, &lux); - printf("Sensor 3. Lux %0.2f\n", lux); - lux = 0.0; - vTaskDelay(5000 / portTICK_PERIOD_MS); - } -} -``` diff --git a/idf_component.yml b/idf_component.yml index 75c1ea7..b21a6d9 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,6 +1,6 @@ description: ESP32 ESP-IDF component for BH1750 ambient light sensor url: https://github.com/aZholtikov/zh_bh1750 -version: "1.0.0" +version: "2.0.0" license: "Apache-2.0" maintainers: - "Alexey Zholtikov " diff --git a/include/zh_bh1750.h b/include/zh_bh1750.h index 07cdfd5..9837389 100644 --- a/include/zh_bh1750.h +++ b/include/zh_bh1750.h @@ -1,5 +1,21 @@ /** * @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 @@ -20,79 +36,105 @@ extern "C" #endif /** - * @brief Structure for initial initialization of BH1750 sensor. + * @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; /*!< Unique I2C bus handle. */ - uint8_t i2c_address; /*!< Sensor I2C address. */ - uint32_t i2c_frequency; /*!< Sensor I2C frequency. */ + 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 BH1750 sensor handle. + * @brief Structure containing sensor error statistics. */ typedef struct { - bool is_initialized; /*!< Sensor initialization flag. */ - i2c_master_dev_handle_t dev_handle; /*!< Unique I2C device handle. */ - } zh_bh1750_handle_t; - - /** - * @brief Structure for error statistics storage. - */ - typedef struct - { - uint32_t i2c_driver_error; /*!< Number of i2c driver error. */ + uint32_t i2c_driver_error; /*!< Counter of I2C driver errors */ } zh_bh1750_stats_t; /** - * @brief Initialize BH1750 sensor. + * @brief Initialize the BH1750 sensor. * - * @param[in] config Pointer to BH1750 initialized configuration structure. Can point to a temporary variable. - * @param[out] handle Pointer to unique BH1750 handle. + * Allocates a handle, validates the provided configuration, and registers + * the sensor as an I2C device. The I2C bus must already be initialized. * - * @attention I2C driver must be initialized first. + * @param[in] config Pointer to the initialization configuration structure (must not be NULL) + * @param[out] handle Pointer to receive the allocated sensor handle (must be NULL) * - * @note Before initialize the sensor recommend initialize zh_bh1750_init_config_t structure with default values. - * - * @code zh_bh1750_init_config_t config = ZH_BH1750_INIT_CONFIG_DEFAULT() @endcode - * - * @return ESP_OK if success or an error code otherwise. + * @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); + esp_err_t zh_bh1750_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t **handle); /** - * @brief Deinitialize PCF8574 expander. + * @brief Deinitialize the BH1750 sensor. * - * @param[in] handle Pointer to unique BH1750 handle. + * Removes the sensor from the I2C bus, frees the allocated handle, + * and sets the handle pointer to NULL. * - * @return ESP_OK if success or an error code otherwise. + * @param[in,out] handle Pointer to the sensor handle to deinitialize (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); + esp_err_t zh_bh1750_deinit(zh_bh1750_handle_t **handle); /** - * @brief Read BH1750 sensor. + * @brief Read ambient light intensity from the BH1750 sensor. * - * @param[in] handle Pointer to unique BH1750 handle. - * @param[out] data Pointer for reading data. + * 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. * - * @return ESP_OK if success or an error code otherwise. + * @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); + esp_err_t zh_bh1750_read(zh_bh1750_handle_t **handle, float *data); /** - * @brief Get error statistics. + * @brief Get pointer to sensor error statistics. * - * @return Pointer to the statistics structure. + * 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 error statistics. + * @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 \ No newline at end of file +#endif diff --git a/version.txt b/version.txt index afaf360..359a5b9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.0 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/zh_bh1750.c b/zh_bh1750.c index 49f2b5d..c2efcd1 100644 --- a/zh_bh1750.c +++ b/zh_bh1750.c @@ -13,44 +13,80 @@ static const char *TAG = "zh_bh1750"; return err; \ } -static const uint8_t _bh1750_read_command = 0x20; // Measurement at 1 lx resolution. -static zh_bh1750_stats_t _stats = {0}; +/** + * @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) +esp_err_t zh_bh1750_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t **handle) // -V2008 { 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(_zh_bh1750_i2c_init(config, handle) == ESP_OK, ESP_FAIL, NULL, "BH1750 initialization failed. Failed to add I2C device."); - handle->is_initialized = true; + 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) +esp_err_t zh_bh1750_deinit(zh_bh1750_handle_t **handle) { ZH_LOGI("BH1750 deinitialization started."); - ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "BH1750 deinitialization failed. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "BH1750 deinitialization failed. BH1750 not initialized."); - ZH_ERROR_CHECK(i2c_master_bus_rm_device(handle->dev_handle) == ESP_OK, ESP_FAIL, NULL, "BH1750 deinitialization failed. I2C remove device failed."); - handle->is_initialized = false; + 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) // -V2008 +esp_err_t zh_bh1750_read(zh_bh1750_handle_t **handle, float *data) // -V2008 { ZH_LOGI("BH1750 read begin."); - ZH_ERROR_CHECK(data != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "BH1750 read fail. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_NOT_FOUND, NULL, "BH1750 read fail. BH1750 not initialized."); + 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), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, + ZH_ERROR_CHECK(i2c_master_transmit((*handle)->dev_handle, &_bh1750_read_command, sizeof(_bh1750_read_command), 1000 / portTICK_PERIOD_MS) == 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), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL, + ZH_ERROR_CHECK(i2c_master_receive((*handle)->dev_handle, sensor_data, sizeof(sensor_data), 1000 / portTICK_PERIOD_MS) == 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("BH1750read completed successfully.");