From 518c8a6d00598c767ab183a1a7e6765789fa621c Mon Sep 17 00:00:00 2001 From: Andrey Aleksandrov Date: Tue, 6 Jan 2026 14:05:29 +0200 Subject: [PATCH] Initial commit --- BME68x_manager.c | 625 +++++++++++++++++++++++++++++++++++++++++++++++ BME68x_manager.h | 139 +++++++++++ CHANGELOG.md | 12 + README.md | 274 +++++++++++++++++++++ library.json | 16 ++ 5 files changed, 1066 insertions(+) create mode 100644 BME68x_manager.c create mode 100644 BME68x_manager.h create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 library.json diff --git a/BME68x_manager.c b/BME68x_manager.c new file mode 100644 index 0000000..db60b6c --- /dev/null +++ b/BME68x_manager.c @@ -0,0 +1,625 @@ +#include "BME68x_manager.h" +#include "driver/i2c.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "config.h" +#include + +static const char *TAG = "BME68x_Manager"; + +// BME68x calibration coefficients structure +typedef struct +{ + uint16_t par_t1; + int16_t par_t2; + int8_t par_t3; + uint16_t par_p1; + int16_t par_p2; + int8_t par_p3; + int16_t par_p4; + int16_t par_p5; + int8_t par_p6; + int8_t par_p7; + int16_t par_p8; + int16_t par_p9; + uint8_t par_p10; + uint16_t par_h1; + uint16_t par_h2; + int8_t par_h3; + int8_t par_h4; + int8_t par_h5; + uint8_t par_h6; + int8_t par_h7; + int8_t par_gh1; + int16_t par_gh2; + int8_t par_gh3; + uint8_t res_heat_range; + int8_t res_heat_val; + int8_t range_sw_err; + int32_t t_fine; +} BME68x_calib_data_t; + +// Global calibration data and current I2C address +static BME68x_calib_data_t calib_data; +static uint8_t current_i2c_addr = BME68x_I2C_ADDR_PRIMARY; + +// Static function declarations +static esp_err_t BME68x_register_read(uint8_t reg_addr, uint8_t *data, size_t len); +static esp_err_t BME68x_register_write_byte(uint8_t reg_addr, uint8_t data); +static esp_err_t BME68x_get_calib_data(void); +static float BME68x_calc_temperature(uint32_t temp_adc); +static float BME68x_calc_pressure(uint32_t pres_adc); +static float BME68x_calc_humidity(uint16_t hum_adc); +static float BME68x_calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range); +static uint8_t BME68x_calc_heater_res(uint16_t temp); +static uint8_t BME68x_calc_air_quality_score(float gas_resistance, float humidity); + +/** + * @brief i2c master initialization + */ +esp_err_t bme68x_i2c_init(void) +{ + int i2c_master_port = I2C_MASTER_NUM; + + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .scl_io_num = I2C_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = I2C_MASTER_FREQ_HZ, + }; + + esp_err_t ret = i2c_param_config(i2c_master_port, &conf); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "I2C param config failed: %s", esp_err_to_name(ret)); + return ret; + } + + ret = i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "I2C driver install failed: %s", esp_err_to_name(ret)); + } + + return ret; +} + +/** + * @brief Read a sequence of bytes from a BME68x sensor registers + */ +static esp_err_t BME68x_register_read(uint8_t reg_addr, uint8_t *data, size_t len) +{ + return i2c_master_write_read_device(I2C_MASTER_NUM, current_i2c_addr, ®_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS); +} + +/** + * @brief Write a byte to a BME68x sensor register + */ +static esp_err_t BME68x_register_write_byte(uint8_t reg_addr, uint8_t data) +{ + uint8_t write_buf[2] = {reg_addr, data}; + return i2c_master_write_to_device(I2C_MASTER_NUM, current_i2c_addr, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS); +} + +/** + * @brief Perform simple I2C bus recovery + */ +esp_err_t bme68x_i2c_recovery(void) +{ + ESP_LOGI(TAG, "Performing I2C bus recovery..."); + + // Simple approach: just reset the pins briefly + gpio_reset_pin(I2C_MASTER_SDA_IO); + gpio_reset_pin(I2C_MASTER_SCL_IO); + + // Configure pins as outputs temporarily + gpio_set_direction(I2C_MASTER_SCL_IO, GPIO_MODE_OUTPUT); + gpio_set_direction(I2C_MASTER_SDA_IO, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(I2C_MASTER_SCL_IO, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(I2C_MASTER_SDA_IO, GPIO_PULLUP_ONLY); + + // Set both pins high + gpio_set_level(I2C_MASTER_SCL_IO, 1); + gpio_set_level(I2C_MASTER_SDA_IO, 1); + vTaskDelay(pdMS_TO_TICKS(10)); + + // Send some clock pulses + for (int i = 0; i < 9; i++) + { + gpio_set_level(I2C_MASTER_SCL_IO, 0); + vTaskDelay(pdMS_TO_TICKS(1)); + gpio_set_level(I2C_MASTER_SCL_IO, 1); + vTaskDelay(pdMS_TO_TICKS(1)); + } + + // Reset pins back to default + gpio_reset_pin(I2C_MASTER_SDA_IO); + gpio_reset_pin(I2C_MASTER_SCL_IO); + + vTaskDelay(pdMS_TO_TICKS(50)); + + ESP_LOGI(TAG, "I2C bus recovery completed"); + return ESP_OK; +} + +/** + * @brief Scan I2C bus for devices + */ +void bme68x_i2c_scan(void) +{ + ESP_LOGI(TAG, "Scanning I2C bus..."); + printf("Scanning I2C bus for devices:\n"); + + int devices_found = 0; + + // Scan only common I2C addresses to avoid null address errors + uint8_t common_addresses[] = {0x76, 0x77, 0x3C, 0x3D, 0x48, 0x49, 0x4A, 0x4B, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75}; + int num_addresses = sizeof(common_addresses) / sizeof(common_addresses[0]); + + for (int i = 0; i < num_addresses; i++) + { + uint8_t addr = common_addresses[i]; + esp_err_t ret = i2c_master_write_to_device(I2C_MASTER_NUM, addr, NULL, 0, 100 / portTICK_PERIOD_MS); + if (ret == ESP_OK) + { + printf(" Device found at address 0x%02X\n", addr); + ESP_LOGI(TAG, "I2C device found at address 0x%02X", addr); + devices_found++; + } + } + + if (devices_found == 0) + { + printf(" No I2C devices found at common addresses!\n"); + ESP_LOGW(TAG, "No I2C devices found at common addresses"); + printf(" (Scanned: 0x76, 0x77, 0x3C, 0x3D, 0x48-0x4B, 0x68-0x75)\n"); + } + else + { + printf(" Found %d I2C device(s)\n", devices_found); + ESP_LOGI(TAG, "Found %d I2C device(s) on bus", devices_found); + } +} + +/** + * @brief Software reset BME68x sensor + */ +esp_err_t bme68x_soft_reset(void) +{ + ESP_LOGI(TAG, "Performing BME68x soft reset..."); + + // BME68x soft reset command + esp_err_t ret = BME68x_register_write_byte(0xE0, 0xB6); // Reset register with reset command + if (ret == ESP_OK) + { + vTaskDelay(pdMS_TO_TICKS(10)); // Wait for reset to complete + ESP_LOGI(TAG, "BME68x soft reset completed"); + } + else + { + ESP_LOGW(TAG, "BME68x soft reset failed, but continuing..."); + } + + return ret; +} + +/** + * @brief Test BME68x sensor connection and read chip ID with recovery attempts + */ +esp_err_t bme68x_test_connection(void) +{ + uint8_t chip_id; + esp_err_t ret; + int retry_count = 0; + const int max_retries = 3; + + while (retry_count < max_retries) + { + ESP_LOGI(TAG, "BME68x connection attempt %d/%d", retry_count + 1, max_retries); + + // Try primary address first + current_i2c_addr = BME68x_I2C_ADDR_PRIMARY; + ret = BME68x_register_read(BME68x_REG_CHIP_ID, &chip_id, 1); + + if (ret != ESP_OK) + { + ESP_LOGI(TAG, "Failed to read from primary address 0x%02X, trying secondary address...", BME68x_I2C_ADDR_PRIMARY); + + // Try secondary address + current_i2c_addr = BME68x_I2C_ADDR_SECONDARY; + ret = BME68x_register_read(BME68x_REG_CHIP_ID, &chip_id, 1); + } + + if (ret == ESP_OK) + { + if (chip_id == BME68x_CHIP_ID) + { + ESP_LOGI(TAG, "BME68x sensor found! Chip ID: 0x%02X (Address: 0x%02X)", chip_id, current_i2c_addr); + return ESP_OK; + } + else + { + ESP_LOGW(TAG, "Unexpected chip ID: 0x%02X (expected 0x%02X)", chip_id, BME68x_CHIP_ID); + ret = ESP_FAIL; + } + } + else + { + ESP_LOGE(TAG, "Failed to communicate with BME68x sensor (attempt %d)", retry_count + 1); + } + + // If communication failed, try recovery + if (ret != ESP_OK && retry_count < max_retries - 1) + { + ESP_LOGI(TAG, "Attempting I2C bus recovery..."); + bme68x_i2c_recovery(); + vTaskDelay(pdMS_TO_TICKS(100)); // Wait after recovery + } + + retry_count++; + } + + ESP_LOGE(TAG, "Failed to communicate with BME68x sensor after %d attempts", max_retries); + return ret; +} + +/** + * @brief Read calibration coefficients from BME68x + */ +static esp_err_t BME68x_get_calib_data(void) +{ + uint8_t coeff_array[41]; + esp_err_t ret; + + // Read first set of coefficients (0x89 to 0xA1) + ret = BME68x_register_read(BME68x_COEFF_ADDR1, coeff_array, 25); + if (ret != ESP_OK) + return ret; + + // Read second set of coefficients (0xE1 to 0xF0) + ret = BME68x_register_read(BME68x_COEFF_ADDR2, &coeff_array[25], 16); + if (ret != ESP_OK) + return ret; + + // Temperature coefficients + calib_data.par_t1 = (uint16_t)(((uint16_t)coeff_array[34] << 8) | coeff_array[33]); + calib_data.par_t2 = (int16_t)(((uint16_t)coeff_array[2] << 8) | coeff_array[1]); + calib_data.par_t3 = (int8_t)coeff_array[3]; + + // Pressure coefficients + calib_data.par_p1 = (uint16_t)(((uint16_t)coeff_array[6] << 8) | coeff_array[5]); + calib_data.par_p2 = (int16_t)(((uint16_t)coeff_array[8] << 8) | coeff_array[7]); + calib_data.par_p3 = (int8_t)coeff_array[9]; + calib_data.par_p4 = (int16_t)(((uint16_t)coeff_array[12] << 8) | coeff_array[11]); + calib_data.par_p5 = (int16_t)(((uint16_t)coeff_array[14] << 8) | coeff_array[13]); + calib_data.par_p6 = (int8_t)coeff_array[16]; + calib_data.par_p7 = (int8_t)coeff_array[15]; + calib_data.par_p8 = (int16_t)(((uint16_t)coeff_array[20] << 8) | coeff_array[19]); + calib_data.par_p9 = (int16_t)(((uint16_t)coeff_array[22] << 8) | coeff_array[21]); + calib_data.par_p10 = (uint8_t)coeff_array[23]; + + // Humidity coefficients + calib_data.par_h1 = (uint16_t)(((uint16_t)coeff_array[27] << 4) | (coeff_array[26] & 0x0F)); + calib_data.par_h2 = (uint16_t)(((uint16_t)coeff_array[25] << 4) | (coeff_array[26] >> 4)); + calib_data.par_h3 = (int8_t)coeff_array[28]; + calib_data.par_h4 = (int8_t)coeff_array[29]; + calib_data.par_h5 = (int8_t)coeff_array[30]; + calib_data.par_h6 = (uint8_t)coeff_array[31]; + calib_data.par_h7 = (int8_t)coeff_array[32]; + + // Gas sensor coefficients + calib_data.par_gh1 = (int8_t)coeff_array[37]; + calib_data.par_gh2 = (int16_t)(((uint16_t)coeff_array[36] << 8) | coeff_array[35]); + calib_data.par_gh3 = (int8_t)coeff_array[38]; + + // Other coefficients for gas sensor + uint8_t temp_var; + ret = BME68x_register_read(0x02, &temp_var, 1); + if (ret != ESP_OK) + return ret; + calib_data.res_heat_range = ((temp_var & 0x30) >> 4); + + ret = BME68x_register_read(0x00, &temp_var, 1); + if (ret != ESP_OK) + return ret; + calib_data.res_heat_val = (int8_t)temp_var; + + ret = BME68x_register_read(0x04, &temp_var, 1); + if (ret != ESP_OK) + return ret; + calib_data.range_sw_err = ((int8_t)temp_var & 0xF0) >> 4; + + return ESP_OK; +} + +/** + * @brief Calculate temperature from raw ADC value + */ +static float BME68x_calc_temperature(uint32_t temp_adc) +{ + int64_t var1, var2, var3; + float calc_temp; + + var1 = ((int32_t)temp_adc >> 3) - ((int32_t)calib_data.par_t1 << 1); + var2 = (var1 * (int32_t)calib_data.par_t2) >> 11; + var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; + var3 = ((var3) * ((int32_t)calib_data.par_t3 << 4)) >> 14; + + calib_data.t_fine = (int32_t)(var2 + var3); + calc_temp = (((float)calib_data.t_fine * 5) + 128) / 256; + + return calc_temp / 100.0f; +} + +/** + * @brief Calculate pressure from raw ADC value + */ +static float BME68x_calc_pressure(uint32_t pres_adc) +{ + int32_t var1, var2, var3; + uint32_t calc_pres; + + var1 = (((int32_t)calib_data.t_fine) >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * (int32_t)calib_data.par_p6) >> 2; + var2 = var2 + ((var1 * (int32_t)calib_data.par_p5) << 1); + var2 = (var2 >> 2) + ((int32_t)calib_data.par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * ((int32_t)calib_data.par_p3 << 5)) >> 3) + (((int32_t)calib_data.par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * (int32_t)calib_data.par_p1) >> 15; + calc_pres = 1048576 - pres_adc; + calc_pres = ((calc_pres - (var2 >> 12)) * ((uint32_t)3125)); + + if (calc_pres >= (1 << 30)) + calc_pres = (calc_pres / var1) * 2; + else + calc_pres = (calc_pres * 2) / var1; + + var1 = ((int32_t)calib_data.par_p9 * (int32_t)(((calc_pres >> 3) * (calc_pres >> 3)) >> 13)) >> 12; + var2 = ((int32_t)(calc_pres >> 2) * (int32_t)calib_data.par_p8) >> 13; + var3 = ((int32_t)(calc_pres >> 8) * (int32_t)(calc_pres >> 8) * (int32_t)(calc_pres >> 8) * (int32_t)calib_data.par_p10) >> 17; + + calc_pres = (calc_pres) + ((var1 + var2 + var3 + ((int32_t)calib_data.par_p7 << 7)) >> 4); + + return (float)calc_pres / 100.0f; // Convert Pa to hPa +} + +/** + * @brief Calculate humidity from raw ADC value + */ +static float BME68x_calc_humidity(uint16_t hum_adc) +{ + int32_t var1, var2, var3, var4, var5, var6, temp_scaled, calc_hum; + + temp_scaled = (((int32_t)calib_data.t_fine * 5) + 128) >> 8; + var1 = (int32_t)(hum_adc - ((int32_t)((int32_t)calib_data.par_h1 * 16))) - (((temp_scaled * (int32_t)calib_data.par_h3) / ((int32_t)100)) >> 1); + var2 = ((int32_t)calib_data.par_h2 * (((temp_scaled * (int32_t)calib_data.par_h4) / ((int32_t)100)) + (((temp_scaled * ((temp_scaled * (int32_t)calib_data.par_h5) / ((int32_t)100))) >> 6) / ((int32_t)100)) + (int32_t)(1 << 14))) >> 10; + var3 = var1 * var2; + var4 = (int32_t)calib_data.par_h6 << 7; + var4 = ((var4) + ((temp_scaled * (int32_t)calib_data.par_h7) / ((int32_t)100))) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + calc_hum = (((var3 + var6) >> 10) * ((int32_t)1000)) >> 12; + + if (calc_hum > 100000) /* Cap at 100%rH */ + calc_hum = 100000; + else if (calc_hum < 0) + calc_hum = 0; + + return calc_hum / 1000.0f; +} + +/** + * @brief Calculate gas resistance from raw ADC value + */ +static float BME68x_calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range) +{ + int64_t var1; + uint64_t var2; + int64_t var3; + uint32_t calc_gas_res; + uint32_t lookupTable1[16] = {UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2130303777), + UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2143188679), UINT32_C(2136746228), + UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2147483647)}; + uint32_t lookupTable2[16] = {UINT32_C(4096000000), UINT32_C(2048000000), UINT32_C(1024000000), UINT32_C(512000000), + UINT32_C(255744255), UINT32_C(127110228), UINT32_C(64000000), UINT32_C(32258064), + UINT32_C(16016016), UINT32_C(8000000), UINT32_C(4000000), UINT32_C(2000000), + UINT32_C(1000000), UINT32_C(500000), UINT32_C(250000), UINT32_C(125000)}; + + var1 = (int64_t)((1340 + (5 * (int64_t)calib_data.range_sw_err)) * ((int64_t)lookupTable1[gas_range])) >> 16; + var2 = (((int64_t)((int64_t)gas_res_adc << 15) - (int64_t)(16777216)) + var1); + var3 = (((int64_t)lookupTable2[gas_range] * (int64_t)var1) >> 9); + calc_gas_res = (uint32_t)((var3 + ((int64_t)var2 >> 1)) / (int64_t)var2); + + return (float)calc_gas_res; +} + +/** + * @brief Calculate heater resistance for target temperature + */ +static uint8_t BME68x_calc_heater_res(uint16_t temp) +{ + float var1, var2, var3, var4, var5; + uint8_t res_heat; + + if (temp < 200) /* Cap temperature */ + temp = 200; + else if (temp > 400) + temp = 400; + + var1 = (((float)calib_data.par_gh1 / (16.0f)) + 49.0f); + var2 = ((((float)calib_data.par_gh2 / (32768.0f)) * (0.0005f)) + 0.00235f); + var3 = ((float)calib_data.par_gh3 / (1024.0f)); + var4 = (var1 * (1.0f + (var2 * (float)temp))); + var5 = (var4 + (var3 * (float)25.0f)); + res_heat = (uint8_t)(3.4f * ((var5 * (4 / (4 + (float)calib_data.res_heat_range)) * + (1 / (1 + ((float)calib_data.res_heat_val * 0.002f)))) - + 25)); + + return res_heat; +} + +/** + * @brief Calculate air quality score based on gas resistance + */ +static uint8_t BME68x_calc_air_quality_score(float gas_resistance, float humidity) +{ + float hum_baseline = 40.0f, hum_weighting = 0.15f; + float gas_baseline = 80000.0f, gas_weighting = 0.85f; + + // Calculate humidity contribution to IAQ index + float hum_score = 0.0f; + if (humidity >= 38 && humidity <= 42) + hum_score = hum_weighting * 100; // Humidity +/-5% around optimum + else + { + if (humidity < 38) + hum_score = (hum_weighting / hum_baseline) * humidity * 100; + else + hum_score = hum_weighting * 100 * (1.0f - ((humidity - 42) / (100 - 42))); + if (hum_score < 0) + hum_score = 0; // Don't go negative + } + + // Calculate gas contribution to IAQ index + float gas_score = (gas_weighting / gas_baseline) * gas_resistance * 100; + + // Combine results for the final IAQ index value (0-100% where 100% is good quality air) + float air_quality_score = hum_score + gas_score; + + if (air_quality_score > 100) + air_quality_score = 100; + if (air_quality_score < 0) + air_quality_score = 0; + + return (uint8_t)air_quality_score; +} + +/** + * @brief Initialize BME68x sensor + */ +esp_err_t bme68x_init(void) +{ + esp_err_t ret; + uint8_t reg_data; + + // Read calibration data + ret = BME68x_get_calib_data(); + if (ret != ESP_OK) + return ret; + + // Set humidity oversampling to 2x + ret = BME68x_register_write_byte(BME68x_REG_CTRL_HUM, BME68x_OS_2X << BME68x_OSH_SEL); + if (ret != ESP_OK) + return ret; + + // Set temperature oversampling to 8x and pressure oversampling to 4x + reg_data = (BME68x_OS_8X << BME68x_OST_SEL) | (BME68x_OS_4X << BME68x_OSP_SEL); + ret = BME68x_register_write_byte(BME68x_REG_CTRL_MEAS, reg_data); + if (ret != ESP_OK) + return ret; + + // Set IIR filter to 3 + ret = BME68x_register_write_byte(BME68x_REG_CONFIG, BME68x_FILTER_SIZE_3 << BME68x_FILTER_SEL); + if (ret != ESP_OK) + return ret; + + // Enable gas sensor + uint8_t heater_res = BME68x_calc_heater_res(320); // Set heater to 320°C + ret = BME68x_register_write_byte(BME68x_REG_RES_HEAT_0, heater_res); + if (ret != ESP_OK) + return ret; + + // Set gas wait time (approx 150ms) + ret = BME68x_register_write_byte(BME68x_REG_GAS_WAIT_0, 0x59); + if (ret != ESP_OK) + return ret; + + // Enable gas measurements + reg_data = (BME68x_ENABLE_GAS_MEAS << BME68x_RUN_GAS_SEL) | (0 << BME68x_NB_CONV_SEL); + ret = BME68x_register_write_byte(BME68x_REG_CTRL_GAS_1, reg_data); + if (ret != ESP_OK) + return ret; + + return ESP_OK; +} + +/** + * @brief Trigger measurement and read sensor data + */ +esp_err_t bme68x_read_data(BME68x_data_t *data) +{ + esp_err_t ret; + uint8_t reg_data; + uint8_t buff[8]; + uint32_t adc_temp, adc_pres; + uint16_t adc_hum; + + // Trigger forced mode measurement + ret = BME68x_register_read(BME68x_REG_CTRL_MEAS, ®_data, 1); + if (ret != ESP_OK) + return ret; + + reg_data = (reg_data & 0xFC) | 0x01; // Set mode to forced + ret = BME68x_register_write_byte(BME68x_REG_CTRL_MEAS, reg_data); + if (ret != ESP_OK) + return ret; + + // Wait for measurement to complete + do + { + vTaskDelay(pdMS_TO_TICKS(10)); + ret = BME68x_register_read(BME68x_REG_MEAS_STATUS_0, ®_data, 1); + if (ret != ESP_OK) + return ret; + } while (reg_data & 0x80); // Wait for measuring bit to clear + + // Read measurement data + ret = BME68x_register_read(BME68x_REG_PRESS_MSB, buff, 8); + if (ret != ESP_OK) + return ret; + + // Extract ADC values + adc_pres = (uint32_t)buff[0] << 12 | (uint32_t)buff[1] << 4 | (uint32_t)buff[2] >> 4; + adc_temp = (uint32_t)buff[3] << 12 | (uint32_t)buff[4] << 4 | (uint32_t)buff[5] >> 4; + adc_hum = (uint16_t)buff[6] << 8 | (uint16_t)buff[7]; + + // Read gas sensor data + uint8_t gas_buff[2]; + uint16_t adc_gas; + uint8_t gas_range; + + ret = BME68x_register_read(BME68x_REG_GAS_R_MSB, gas_buff, 2); + if (ret != ESP_OK) + return ret; + + adc_gas = (uint16_t)gas_buff[0] << 2 | (uint16_t)gas_buff[1] >> 6; + gas_range = gas_buff[1] & 0x0F; + + // Check gas measurement validity + data->gas_valid = (gas_buff[1] & 0x20) >> 5; + data->heat_stab = (gas_buff[1] & 0x10) >> 4; + + // Calculate actual values + data->temperature = BME68x_calc_temperature(adc_temp) + TEMP_OFFSET; + data->pressure = BME68x_calc_pressure(adc_pres); + data->humidity = BME68x_calc_humidity(adc_hum); + data->gas_resistance = BME68x_calc_gas_resistance(adc_gas, gas_range); + + // Calculate air quality score + data->air_quality_score = BME68x_calc_air_quality_score(data->gas_resistance, data->humidity); + + return ESP_OK; +} + +/** + * @brief Get current I2C address being used + */ +uint8_t bme68x_get_current_address(void) +{ + return current_i2c_addr; +} \ No newline at end of file diff --git a/BME68x_manager.h b/BME68x_manager.h new file mode 100644 index 0000000..54d5b1e --- /dev/null +++ b/BME68x_manager.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include "esp_err.h" +#include "config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +// I2C configuration - can be overridden in config.h +#ifndef I2C_MASTER_SCL_IO +#define I2C_MASTER_SCL_IO 7 /*!< Default GPIO for I2C master clock */ +#endif +#ifndef I2C_MASTER_SDA_IO +#define I2C_MASTER_SDA_IO 6 /*!< Default GPIO for I2C master data */ +#endif +#ifndef I2C_MASTER_NUM +#define I2C_MASTER_NUM I2C_NUM_0 /*!< Default I2C port number */ +#endif +#ifndef I2C_MASTER_FREQ_HZ +#define I2C_MASTER_FREQ_HZ 100000 /*!< Default I2C frequency */ +#endif +#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ +#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ +#define I2C_MASTER_TIMEOUT_MS 1000 + +// BME68x I2C address (can be 0x76 or 0x77 depending on SDO pin) +#define BME68x_I2C_ADDR_PRIMARY 0x76 +#define BME68x_I2C_ADDR_SECONDARY 0x77 + +// BME68x register addresses +#define BME68x_REG_CHIP_ID 0xD0 +#define BME68x_CHIP_ID 0x61 +#define BME68x_REG_CTRL_MEAS 0x74 +#define BME68x_REG_CTRL_HUM 0x72 +#define BME68x_REG_CONFIG 0x75 +#define BME68x_REG_MEAS_STATUS_0 0x1D +#define BME68x_REG_PRESS_MSB 0x1F +#define BME68x_REG_TEMP_MSB 0x22 +#define BME68x_REG_HUM_MSB 0x25 +#define BME68x_REG_GAS_R_MSB 0x2A +#define BME68x_REG_GAS_R_LSB 0x2B +#define BME68x_REG_CTRL_GAS_1 0x71 +#define BME68x_REG_GAS_WAIT_0 0x64 +#define BME68x_REG_RES_HEAT_0 0x5A + +// Calibration coefficient addresses +#define BME68x_COEFF_ADDR1 0x89 +#define BME68x_COEFF_ADDR2 0xE1 + +// BME68x oversampling definitions +#define BME68x_OS_NONE 0x00 +#define BME68x_OS_1X 0x01 +#define BME68x_OS_2X 0x02 +#define BME68x_OS_4X 0x03 +#define BME68x_OS_8X 0x04 +#define BME68x_OS_16X 0x05 + +// BME68x field bit positions +#define BME68x_OST_SEL 5 +#define BME68x_OSP_SEL 2 +#define BME68x_OSH_SEL 0 +#define BME68x_FILTER_SEL 2 + +// BME68x IIR filter definitions +#define BME68x_FILTER_SIZE_3 0x02 + +// Gas sensor definitions +#define BME68x_DISABLE_GAS_MEAS 0x00 +#define BME68x_ENABLE_GAS_MEAS 0x01 +#define BME68x_RUN_GAS_SEL 4 +#define BME68x_NB_CONV_SEL 0 + + // BME68x sensor data structure + typedef struct + { + float temperature; + float pressure; + float humidity; + float gas_resistance; + uint8_t gas_valid; + uint8_t heat_stab; + uint8_t air_quality_score; + } BME68x_data_t; + + /** + * @brief Initialize I2C master interface + * @return ESP_OK on success, error code on failure + */ + esp_err_t bme68x_i2c_init(void); + + /** + * @brief Perform I2C bus recovery + * @return ESP_OK on success, error code on failure + */ + esp_err_t bme68x_i2c_recovery(void); + + /** + * @brief Scan I2C bus for devices + */ + void bme68x_i2c_scan(void); + + /** + * @brief Test BME68x sensor connection and read chip ID + * @return ESP_OK on success, error code on failure + */ + esp_err_t bme68x_test_connection(void); + + /** + * @brief Perform soft reset of BME68x sensor + * @return ESP_OK on success, error code on failure + */ + esp_err_t bme68x_soft_reset(void); + + /** + * @brief Initialize BME68x sensor + * @return ESP_OK on success, error code on failure + */ + esp_err_t bme68x_init(void); + + /** + * @brief Read sensor data from BME68x + * @param data Pointer to BME68x_data_t structure to store the data + * @return ESP_OK on success, error code on failure + */ + esp_err_t bme68x_read_data(BME68x_data_t *data); + + /** + * @brief Get current I2C address being used + * @return Current I2C address (0x76 or 0x77) + */ + uint8_t bme68x_get_current_address(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e040b0e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v1.0.0] - 2025-06-01 + +### Added + +- Repository diff --git a/README.md b/README.md new file mode 100644 index 0000000..63de591 --- /dev/null +++ b/README.md @@ -0,0 +1,274 @@ +# BME68x Manager Library + +A comprehensive ESP-IDF library for interfacing with the Bosch BME68x environmental sensor series (BME680, BME688). This library provides easy-to-use functions for reading temperature, humidity, pressure, and gas resistance measurements with built-in air quality calculations. + +## Features + +- **Complete BME68x Support**: Works with BME680 and BME688 sensors +- **I2C Communication**: Robust I2C implementation with automatic address detection +- **Auto Recovery**: Built-in I2C bus recovery mechanisms +- **Air Quality Index**: Calculates air quality scores based on gas resistance and humidity +- **Error Handling**: Comprehensive error checking and recovery +- **Easy Integration**: Simple API for quick integration into ESP32 projects + +## Hardware Connections + +| BME68x Pin | ESP32-C6 Pin | Description | +| ---------- | ------------ | ---------------------------------------------- | +| VCC | 3.3V | Power supply | +| GND | GND | Ground | +| SDA | GPIO6 | I2C Data line | +| SCL | GPIO7 | I2C Clock line | +| SDO | GND or 3.3V | I2C Address select (0x76 if GND, 0x77 if 3.3V) | + +## Installation + +1. Copy the `BME68x_manager` folder to your project's `lib/` directory: + + ``` + your_project/ + ├── lib/ + │ └── BME68x_manager/ + │ ├── BME68x_manager.h + │ └── BME68x_manager.c + ├── src/ + └── platformio.ini + ``` + +2. Include the library in your source files: + + ```c + #include "BME68x_manager.h" + ``` + +3. Configure I2C pins and settings in your `config.h` file: + + ```c + // Temperature calibration offset + #define TEMP_OFFSET -2.0f // Adjust based on your sensor calibration + + // I2C Configuration (optional - defaults shown) + #define I2C_MASTER_SCL_IO 7 // SCL GPIO pin + #define I2C_MASTER_SDA_IO 6 // SDA GPIO pin + #define I2C_MASTER_NUM I2C_NUM_0 // I2C port number + #define I2C_MASTER_FREQ_HZ 100000 // I2C frequency (100kHz) + ``` + +## API Reference + +### Data Structures + +```c +typedef struct { + float temperature; // Temperature in °C + float pressure; // Pressure in hPa + float humidity; // Humidity in % + float gas_resistance; // Gas resistance in Ω + uint8_t gas_valid; // 1 if gas measurement is valid + uint8_t heat_stab; // 1 if heater is stable + uint8_t air_quality_score; // Air quality score (0-100) +} BME68x_data_t; +``` + +### Core Functions + +#### `esp_err_t bme68x_i2c_init(void)` + +Initializes the I2C master interface. + +**Returns:** + +- `ESP_OK` on success +- Error code on failure + +--- + +#### `esp_err_t bme68x_test_connection(void)` + +Tests communication with the BME68x sensor and automatically detects the I2C address. + +**Returns:** + +- `ESP_OK` if sensor found and communication successful +- Error code on failure + +--- + +#### `esp_err_t bme68x_init(void)` + +Initializes the BME68x sensor with optimal settings for air quality monitoring. + +**Configuration:** + +- Temperature oversampling: 8x +- Pressure oversampling: 4x +- Humidity oversampling: 2x +- IIR filter: 3 +- Gas sensor: Enabled (320°C, 150ms heating) + +**Returns:** + +- `ESP_OK` on success +- Error code on failure + +--- + +#### `esp_err_t bme68x_read_data(BME68x_data_t *data)` + +Reads sensor data and calculates air quality score. + +**Parameters:** + +- `data`: Pointer to BME68x_data_t structure to store results + +**Returns:** + +- `ESP_OK` on success +- Error code on failure + +### Utility Functions + +#### `esp_err_t bme68x_i2c_recovery(void)` + +Performs I2C bus recovery by sending clock pulses. + +#### `void bme68x_i2c_scan(void)` + +Scans the I2C bus and prints found devices to console. + +#### `esp_err_t bme68x_soft_reset(void)` + +Performs a software reset of the BME68x sensor. + +#### `uint8_t bme68x_get_current_address(void)` + +Returns the current I2C address being used (0x76 or 0x77). + +## Usage Example + +```c +#include "BME68x_manager.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "BME68x_Example"; + +void app_main(void) +{ + // Initialize I2C + ESP_ERROR_CHECK(bme68x_i2c_init()); + + // Test sensor connection + if (bme68x_test_connection() != ESP_OK) { + ESP_LOGE(TAG, "BME68x sensor not found!"); + return; + } + + // Initialize sensor + ESP_ERROR_CHECK(bme68x_init()); + + ESP_LOGI(TAG, "BME68x sensor initialized successfully"); + + // Main measurement loop + while (1) { + BME68x_data_t sensor_data; + + if (bme68x_read_data(&sensor_data) == ESP_OK) { + ESP_LOGI(TAG, "Temperature: %.1f°C", sensor_data.temperature); + ESP_LOGI(TAG, "Pressure: %.1f hPa", sensor_data.pressure); + ESP_LOGI(TAG, "Humidity: %.1f%%", sensor_data.humidity); + + if (sensor_data.gas_valid && sensor_data.heat_stab) { + ESP_LOGI(TAG, "Gas Resistance: %.0f Ω", sensor_data.gas_resistance); + ESP_LOGI(TAG, "Air Quality Score: %d%%", sensor_data.air_quality_score); + } else { + ESP_LOGI(TAG, "Gas sensor stabilizing..."); + } + } else { + ESP_LOGE(TAG, "Failed to read sensor data"); + } + + vTaskDelay(pdMS_TO_TICKS(5000)); // Read every 5 seconds + } +} +``` + +## Air Quality Scoring + +The library calculates an air quality score (0-100) based on: + +- **Gas Resistance**: Higher resistance indicates cleaner air +- **Humidity**: Optimal range is 38-42% RH + +### Air Quality Interpretation + +| Score | Quality | Gas Resistance Range | Description | +| ------ | --------- | -------------------- | -------------------------------------- | +| 90-100 | Excellent | >50kΩ | Fresh outdoor air quality | +| 60-89 | Good | 10-50kΩ | Normal indoor air | +| 30-59 | Fair | 5-10kΩ | Moderate pollution (cooking, cleaning) | +| 0-29 | Poor | <5kΩ | High pollution (smoke, chemicals) | + +## Configuration Options + +All configuration is done in your project's `config.h` file. The library provides sensible defaults if settings are not specified. + +### I2C Settings (Optional) + +```c +// Override default I2C configuration in config.h +#define I2C_MASTER_SCL_IO 7 // SCL GPIO pin (default: 7) +#define I2C_MASTER_SDA_IO 6 // SDA GPIO pin (default: 6) +#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number (default: I2C_NUM_0) +#define I2C_MASTER_FREQ_HZ 100000 // I2C frequency (default: 100kHz) +``` + +### Temperature Calibration (Required) + +```c +#define TEMP_OFFSET -2.0f // Temperature offset for calibration +``` + +### Sensor Addresses + +```c +#define BME68x_I2C_ADDR_PRIMARY 0x76 // SDO connected to GND +#define BME68x_I2C_ADDR_SECONDARY 0x77 // SDO connected to VCC +``` + +## Troubleshooting + +### Sensor Not Found + +1. Check wiring connections +2. Verify power supply (3.3V) +3. Run `bme68x_i2c_scan()` to see detected devices +4. Try different I2C pull-up resistor values (4.7kΩ recommended) + +### Invalid Gas Readings + +- Gas sensor requires heating time (~60 seconds for first stable reading) +- Check that `gas_valid` and `heat_stab` flags are set +- Ensure adequate power supply for heating element + +### Inconsistent Readings + +- Allow sensor to stabilize in target environment +- Consider temperature calibration offset in `config.h` +- Shield sensor from direct airflow or heat sources + +## Dependencies + +- ESP-IDF framework +- FreeRTOS (for delays) +- I2C driver (`driver/i2c.h`) +- GPIO driver (`driver/gpio.h`) + +## License + +This library is provided as-is for educational and commercial use. Based on Bosch BME68x sensor specifications and ESP-IDF examples. + +## Version History + +- **v1.0.0** - Initial release with full BME68x support and air quality calculations diff --git a/library.json b/library.json new file mode 100644 index 0000000..9f16dab --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "BME68x_manager", + "version": "1.0.0", + "description": "Custom BME68x sensor library for ESP32", + "keywords": "bme68x, temperature, humidity, pressure, gas, sensor, esp32", + "authors": { + "name": "ESP32C6-BME68x Project" + }, + "license": "MIT", + "frameworks": [ + "espidf" + ], + "platforms": [ + "espressif32" + ] +} \ No newline at end of file