diff --git a/BH1750 datasheet.pdf b/BH1750 datasheet.pdf new file mode 100755 index 0000000..bc752b6 Binary files /dev/null and b/BH1750 datasheet.pdf differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5c51..3788be4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1,6 @@ -idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") \ No newline at end of file +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}) \ No newline at end of file diff --git a/README.md b/README.md index 3df3e4d..d8e7155 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,136 @@ -# esp_component_template +# ESP32 ESP-IDF component for BH1750 ambient light sensor -esp_component_template \ No newline at end of file +## 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). + +## Attention + +For correct operation, please enable the following settings in the menuconfig: + +```text +I2C_ISR_IRAM_SAFE +I2C_MASTER_ISR_HANDLER_IN_IRAM +``` + +## Using + +In an existing project, run the following command to install the component: + +```text +cd ../your_project/components +git clone https://github.com/aZholtikov/zh_bh1750.git +``` + +In the application, add the component: + +```c +#include "zh_bh1750.h" +``` + +## Example + +Reading the sensor: + +```c +#include "zh_bh1750.h" + +#define I2C_PORT (I2C_NUM_MAX - 1) + +zh_bh1750_handle_t bh1750_handle = {0}; + +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; + 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; + for (;;) + { + zh_bh1750_read(&bh1750_handle, &lux); + printf("Lux %0.2f\n", lux); + const zh_bh1750_stats_t *stats = zh_bh1750_get_stats(); + printf("Number of i2c driver error: %ld.\n", stats->i2c_driver_error); + vTaskDelay(5000 / portTICK_PERIOD_MS); + } +} +``` + +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/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/zh_bh1750.h b/include/zh_bh1750.h new file mode 100644 index 0000000..88b32f0 --- /dev/null +++ b/include/zh_bh1750.h @@ -0,0 +1,99 @@ +/** + * @file zh_bh1750.h + */ + +#pragma once + +#include "esp_err.h" +#include "esp_log.h" +#include "driver/i2c_master.h" +#include "freertos/FreeRTOS.h" + +#define ZH_BH1750_INIT_CONFIG_DEFAULT() \ + { \ + .i2c_frequency = 400000, \ + .i2c_address = 0x23} + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Structure for initial initialization of BH1750 sensor. + */ + 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. */ + } zh_bh1750_init_config_t; + + /** + * @brief BH1750 sensor handle. + */ + typedef struct + { + bool is_initialized; /*!< Sensor initialization flag. */ + uint8_t sensivity; /*!< Sensor sensivity. */ + 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. */ + } zh_bh1750_stats_t; + + /** + * @brief Initialize 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. + * + * @attention I2C driver must be initialized first. + * + * @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. + */ + esp_err_t zh_bh1750_init(const zh_bh1750_init_config_t *config, zh_bh1750_handle_t *handle); + + /** + * @brief Deinitialize PCF8574 expander. + * + * @param[in] handle Pointer to unique BH1750 handle. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_bh1750_deinit(zh_bh1750_handle_t *handle); + + /** + * @brief Read BH1750 sensor. + * + * @param[in] handle Pointer to unique BH1750 handle. + * @param[out] data Pointer for reading data. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_bh1750_read(zh_bh1750_handle_t *handle, float *data); + + /** + * @brief Get error statistics. + * + * @return Pointer to the statistics structure. + */ + const zh_bh1750_stats_t *zh_bh1750_get_stats(void); + + /** + * @brief Reset error statistics. + */ + void zh_bh1750_reset_stats(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/main.c b/main.c deleted file mode 100644 index e69de29..0000000 diff --git a/version.txt b/version.txt index e69de29..afaf360 100644 --- a/version.txt +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/zh_bh1750.c b/zh_bh1750.c new file mode 100644 index 0000000..e70ec17 --- /dev/null +++ b/zh_bh1750.c @@ -0,0 +1,95 @@ +#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; \ + } + +static const uint8_t _bh1750_read_command = 0x20; // Measurement at 1 lx resolution. +static zh_bh1750_stats_t _stats = {0}; + +static esp_err_t _zh_bh1750_validate_config(const zh_bh1750_init_config_t *config); +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(_zh_bh1750_i2c_init(config, handle) == ESP_OK, ESP_FAIL, NULL, "BH1750 initialization failed. Failed to add I2C device."); + handle->sensivity = 69; + handle->is_initialized = true; + 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, 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->sensivity = 69; + handle->is_initialized = false; + ZH_LOGI("BH1750 deinitialization completed successfully."); + return ESP_OK; +} + +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."); + 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, + ++_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, + ++_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."); + 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, 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, 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_ERR_NOT_FOUND, + ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed."), "Sensor not connected or not responded."); + handle->dev_handle = _dev_handle; + return ESP_OK; +} \ No newline at end of file