Initial commit

This commit is contained in:
Andrey Aleksandrov
2026-01-06 14:05:29 +02:00
commit 518c8a6d00
5 changed files with 1066 additions and 0 deletions

625
BME68x_manager.c Normal file
View File

@@ -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 <stdint.h>
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, &reg_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, &reg_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, &reg_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;
}

139
BME68x_manager.h Normal file
View File

@@ -0,0 +1,139 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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

12
CHANGELOG.md Normal file
View File

@@ -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

274
README.md Normal file
View File

@@ -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

16
library.json Normal file
View File

@@ -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"
]
}