625 lines
21 KiB
C
625 lines
21 KiB
C
#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, ®_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;
|
|
} |