Files
EmbeddedESP/managed_components/k0i05__esp_bme680/bme680.c
2025-04-22 20:35:29 +02:00

1350 lines
57 KiB
C

/*
* The MIT License (MIT)
*
* Copyright (c) 2024 Eric Gionet (gionet.c.eric@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file bme680.c
*
* ESP-IDF driver for BME680 temperature, humidity, pressure, and gas sensor
*
* Ported from esp-open-rtos
*
* https://github.com/boschsensortec/BME68x_SensorAPI/blob/master/bme68x.c
*
* iaq: https://github.com/3KUdelta/heltec_wifi_kit_32_BME680/blob/master/Wifi_Kit_32_BME680.ino
*
* Copyright (c) 2024 Eric Gionet (gionet.c.eric@gmail.com)
*
* MIT Licensed as described in the file LICENSE
*/
#include "include/bme680.h"
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <sdkconfig.h>
#include <esp_types.h>
#include <esp_log.h>
#include <esp_check.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
/**
* possible BME680 registers
*/
#define BME680_REG_STATUS0 UINT8_C(0x1D)
#define BME680_REG_RESET UINT8_C(0xE0) /*!< reset value: 0xB6 */
#define BME680_REG_ID UINT8_C(0xD0)
#define BME680_REG_CONFIG UINT8_C(0x75)
#define BME680_REG_CTRL_MEAS UINT8_C(0x74)
#define BME680_REG_CTRL_HUMI UINT8_C(0x72)
#define BME680_REG_CTRL_GAS1 UINT8_C(0x71)
#define BME680_REG_CTRL_GAS0 UINT8_C(0x70)
#define BME680_REG_GAS_WAIT UINT8_C(0x64) /*!< gas_wait_x: 0x64 to 0x6D */
#define BME680_REG_RES_HEAT UINT8_C(0x5A) /*!< res_heat_x: 0x5A to 0x63 */
#define BME680_REG_IDAC_HEAT UINT8_C(0x50) /*!< idac_heat_x: 0x50 to 0x59 */
#define BME680_REG_GAS_R_LSB UINT8_C(0x2B)
#define BME680_REG_GAS_R_MSB UINT8_C(0x2A)
#define BME680_REG_GAS_R UINT8_C(BME680_REG_GAS_R_MSB)
#define BME680_REG_HUMI_LSB UINT8_C(0x26)
#define BME680_REG_HUMI_MSB UINT8_C(0x25)
#define BME680_REG_HUMI UINT8_C(BME680_REG_HUMI_MSB)
#define BME680_REG_TEMP_XLSB UINT8_C(0x24)
#define BME680_REG_TEMP_LSB UINT8_C(0x23)
#define BME680_REG_TEMP_MSB UINT8_C(0x22)
#define BME680_REG_TEMP UINT8_C(BME680_REG_TEMP_MSB)
#define BME680_REG_PRESS_XLSB UINT8_C(0x21)
#define BME680_REG_PRESS_LSB UINT8_C(0x20)
#define BME680_REG_PRESS_MSB UINT8_C(0x1F)
#define BME680_REG_PRESS UINT8_C(BME680_REG_PRESS_MSB)
#define BME680_RESET_VALUE UINT8_C(0xB6)
#define BME680_REG_SHD_HEATR_DUR UINT8_C(0x6E) /* Shared heating duration address */
#define BME680_REG_VARIANT_ID UINT8_C(0xF0)
#define BME680_VARIANT_GAS_LOW UINT8_C(0x00) /* Low Gas variant */
#define BME680_VARIANT_GAS_HIGH UINT8_C(0x01) /* High Gas variant */
#define BME680_ENABLE_GAS_MEAS_L UINT8_C(0x01) /* Enable gas measurement low */
#define BME680_ENABLE_GAS_MEAS_H UINT8_C(0x02) /* Enable gas measurement high */
// field data 1 registers (not documented, used in SEQUENTIAL_MODE)
#define BME680_REG_MEAS_STATUS_1 UINT8_C(0x2e)
#define BME680_REG_MEAS_INDEX_1 UINT8_C(0x2f)
// field data 2 registers (not documented, used in SEQUENTIAL_MODE)
#define BME680_REG_MEAS_STATUS_2 UINT8_C(0x3f)
#define BME680_REG_MEAS_INDEX_2 UINT8_C(0x40)
#define BME680_CHIP_ID UINT8_C(0x61)
#define BME680_AQI_TEMP_CORR (-3) // Calibration offset - calibrate yourself the temp reading --> humidity will
// be automatically adjusted using the August-Roche-Magnus approximation
// http://bmcnoldy.rsmas.miami.edu/Humidity.html
#define BME680_DATA_POLL_TIMEOUT_MS UINT16_C(250) // ? see datasheet tables 13 and 14, standby-time could be 2-seconds (2000ms)
#define BME680_DATA_READY_DELAY_MS UINT16_C(1)
#define BME680_POWERUP_DELAY_MS UINT16_C(25)
#define BME680_APPSTART_DELAY_MS UINT16_C(25)
#define BME680_RESET_DELAY_MS UINT16_C(25)
#define BME680_CMD_DELAY_MS UINT16_C(5)
#define BME680_TX_RX_DELAY_MS UINT16_C(10)
/*
* macro definitions
*/
#define ESP_TIMEOUT_CHECK(start, len) ((uint64_t)(esp_timer_get_time() - (start)) >= (len))
#define ESP_ARG_CHECK(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
/*
* static constant declarations
*/
static const char *TAG = "bme680";
/**
* @brief BME680 I2C read from register address transaction. This is a write and then read process.
*
* @param handle BME680 device handle.
* @param reg_addr BME680 register address to read from.
* @param buffer BME680 read transaction return buffer.
* @param size Length of buffer to store results from read transaction.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_i2c_read_from(bme680_handle_t handle, const uint8_t reg_addr, uint8_t *buffer, const uint8_t size) {
const bit8_uint8_buffer_t tx = { reg_addr };
/* validate arguments */
ESP_ARG_CHECK( handle );
ESP_RETURN_ON_ERROR( i2c_master_transmit_receive(handle->i2c_handle, tx, BIT8_UINT8_BUFFER_SIZE, buffer, size, I2C_XFR_TIMEOUT_MS), TAG, "bme680_i2c_read_from failed" );
return ESP_OK;
}
/**
* @brief BME680 I2C read halfword from register address transaction.
*
* @param handle BME680 device handle.
* @param reg_addr BME680 register address to read from.
* @param halfword BME680 read transaction return halfword.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_i2c_read_word_from(bme680_handle_t handle, const uint8_t reg_addr, uint16_t *const word) {
const bit8_uint8_buffer_t tx = { reg_addr };
bit16_uint8_buffer_t rx = { 0 };
/* validate arguments */
ESP_ARG_CHECK( handle );
ESP_RETURN_ON_ERROR( i2c_master_transmit_receive(handle->i2c_handle, tx, BIT8_UINT8_BUFFER_SIZE, rx, BIT16_UINT8_BUFFER_SIZE, I2C_XFR_TIMEOUT_MS), TAG, "bme680_i2c_read_word_from failed" );
/* set output parameter */
*word = (uint16_t)rx[0] | ((uint16_t)rx[1] << 8);
return ESP_OK;
}
/**
* @brief BME680 I2C read byte from register address transaction.
*
* @param handle BME680 device handle.
* @param reg_addr BME680 register address to read from.
* @param byte BME680 read transaction return byte.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_i2c_read_byte_from(bme680_handle_t handle, const uint8_t reg_addr, uint8_t *const byte) {
const bit8_uint8_buffer_t tx = { reg_addr };
bit8_uint8_buffer_t rx = { 0 };
/* validate arguments */
ESP_ARG_CHECK( handle );
ESP_RETURN_ON_ERROR( i2c_master_transmit_receive(handle->i2c_handle, tx, BIT8_UINT8_BUFFER_SIZE, rx, BIT8_UINT8_BUFFER_SIZE, I2C_XFR_TIMEOUT_MS), TAG, "bme680_i2c_read_byte_from failed" );
/* set output parameter */
*byte = rx[0];
return ESP_OK;
}
/**
* @brief BME680 I2C write byte to register address transaction.
*
* @param handle BME680 device handle.
* @param reg_addr BME680 register address to write to.
* @param byte BME680 write transaction input byte.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_i2c_write_byte_to(bme680_handle_t handle, const uint8_t reg_addr, const uint8_t byte) {
const bit16_uint8_buffer_t tx = { reg_addr, byte };
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( i2c_master_transmit(handle->i2c_handle, tx, BIT16_UINT8_BUFFER_SIZE, I2C_XFR_TIMEOUT_MS), TAG, "i2c_master_transmit, i2c write failed" );
return ESP_OK;
}
/**
* @brief Calculates dew-point temperature from air temperature and relative humidity.
*
* @param[in] temperature air temperature in degrees Celsius.
* @param[in] humidity relative humidity in percent.
* @return float calculated dew-point temperature in degrees Celsius.
*/
static inline float bme680_calculate_dewpoint(const float temperature, const float humidity) {
// validate parameters
if(temperature > 80.0f || temperature < -40.0f) return ESP_ERR_INVALID_ARG;
if(humidity > 100.0f || humidity < 0.0f) return ESP_ERR_INVALID_ARG;
// calculate dew-point temperature
float H = (log10f(humidity)-2.0f)/0.4343f + (17.62f*temperature)/(243.12f+temperature);
return 243.12f*H/(17.62f-H);
}
/**
* @brief Temperature compensation algorithm is taken from datasheet. See datasheet for details.
*
* @param[in] handle BME680 device handle.
* @param[in] adc_temperature raw adc temperature.
* @return float Temperature in degrees Celsius.
*/
static inline float bme680_compensate_temperature(bme680_handle_t handle, const uint32_t adc_temperature) {
/* calculate var1 data */
float var1 = ((((float)adc_temperature / 16384.0f) - ((float)handle->dev_cal_factors->par_T1 / 1024.0f)) * ((float)handle->dev_cal_factors->par_T2));
/* calculate var2 data */
float var2 =
(((((float)adc_temperature / 131072.0f) - ((float)handle->dev_cal_factors->par_T1 / 8192.0f)) *
(((float)adc_temperature / 131072.0f) - ((float)handle->dev_cal_factors->par_T1 / 8192.0f))) * ((float)handle->dev_cal_factors->par_T3 * 16.0f));
/* t_fine value*/
handle->dev_cal_factors->temperature_fine = (var1 + var2);
/* compensated temperature data*/
return ((handle->dev_cal_factors->temperature_fine) / 5120.0f);
}
/**
* @brief Humidity compensation algorithm is taken from datasheet. See datasheet for details.
*
* @param handle BME680 device handle.
* @param adc_humidity Raw ADC humidity.
* @return float Humidity in percentage.
*/
static inline float bme680_compensate_humidity(bme680_handle_t handle, const uint16_t adc_humidity) {
/* compensated temperature data*/
float temp_comp = ((handle->dev_cal_factors->temperature_fine) / 5120.0f);
float var1 = (float)((float)adc_humidity) -
(((float)handle->dev_cal_factors->par_H1 * 16.0f) + (((float)handle->dev_cal_factors->par_H3 / 2.0f) * temp_comp));
float var2 = var1 *
((float)(((float)handle->dev_cal_factors->par_H2 / 262144.0f) *
(1.0f + (((float)handle->dev_cal_factors->par_H4 / 16384.0f) * temp_comp) +
(((float)handle->dev_cal_factors->par_H5 / 1048576.0f) * temp_comp * temp_comp))));
float var3 = (float)handle->dev_cal_factors->par_H6 / 16384.0f;
float var4 = (float)handle->dev_cal_factors->par_H7 / 2097152.0f;
float calc_hum = var2 + ((var3 + (var4 * temp_comp)) * var2 * var2);
if (calc_hum > 100.0f) {
calc_hum = 100.0f;
} else if (calc_hum < 0.0f) {
calc_hum = 0.0f;
}
return calc_hum;
}
/**
* @brief Pressure compensation algorithm is taken from datasheet. see datasheet for details.
*
* @param[in] handle BME680 device handle.
* @param[in] adc_pressure raw adc pressure.
* @return float Pressure in Pascal. Divide by 100 for Hecto-Pascals.
*/
static inline float bme680_compensate_pressure(bme680_handle_t handle, const uint32_t adc_pressure) {
float var1 = (((float)handle->dev_cal_factors->temperature_fine / 2.0f) - 64000.0f);
float var2 = var1 * var1 * (((float)handle->dev_cal_factors->par_P6) / (131072.0f));
var2 = var2 + (var1 * ((float)handle->dev_cal_factors->par_P5) * 2.0f);
var2 = (var2 / 4.0f) + (((float)handle->dev_cal_factors->par_P4) * 65536.0f);
var1 = (((((float)handle->dev_cal_factors->par_P3 * var1 * var1) / 16384.0f) + ((float)handle->dev_cal_factors->par_P2 * var1)) / 524288.0f);
var1 = ((1.0f + (var1 / 32768.0f)) * ((float)handle->dev_cal_factors->par_P1));
float calc_pres = (1048576.0f - ((float)adc_pressure));
/* Avoid exception caused by division by zero */
if ((int)var1 != 0) {
calc_pres = (((calc_pres - (var2 / 4096.0f)) * 6250.0f) / var1);
var1 = (((float)handle->dev_cal_factors->par_P9) * calc_pres * calc_pres) / 2147483648.0f;
var2 = calc_pres * (((float)handle->dev_cal_factors->par_P8) / 32768.0f);
float var3 = ((calc_pres / 256.0f) * (calc_pres / 256.0f) * (calc_pres / 256.0f) * (handle->dev_cal_factors->par_P10 / 131072.0f));
calc_pres = (calc_pres + (var1 + var2 + var3 + ((float)handle->dev_cal_factors->par_P7 * 128.0f)) / 16.0f);
} else {
calc_pres = 0;
}
return calc_pres;
}
static inline float bme680_compensate_gas_resistance(bme680_handle_t handle, uint16_t adc_gas_res, uint8_t gas_range) {
const float lookup_k1_range[16] = {
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.99f, 1.0f, 0.992f, 1.0f, 1.0f, 0.998f, 0.995f, 1.0f, 0.99f, 1.0f, 1.0f
};
const float lookup_k2_range[16] = {
8000000.0f, 4000000.0f, 2000000.0f, 1000000.0f, 499500.4995f, 248262.1648f, 125000.0f, 63004.03226f,
31281.28128f, 15625.0f, 7812.5f, 3906.25f, 1953.125f, 976.5625f, 488.28125f, 244.140625f
};
float var1 = (1340.0f + 5.0f * handle->dev_cal_factors->range_switching_error) * lookup_k1_range[gas_range];
return var1 * lookup_k2_range[gas_range] / (adc_gas_res - 512.0f + var1);
}
static inline uint8_t bme680_compensate_heater_resistance(bme680_handle_t handle, uint16_t temperature) {
/* Cap temperature */
if (temperature > 400) {
temperature = 400;
}
float var1 = (((float)handle->dev_cal_factors->par_G1 / (16.0f)) + 49.0f);
float var2 = ((((float)handle->dev_cal_factors->par_G2 / (32768.0f)) * (0.0005f)) + 0.00235f);
float var3 = ((float)handle->dev_cal_factors->par_G3 / (1024.0f));
float var4 = (var1 * (1.0f + (var2 * (float)temperature)));
float var5 = (var4 + (var3 * (float)handle->ambient_temperature));
uint8_t res_heat =
(uint8_t)(3.4f *
((var5 * (4 / (4 + (float)handle->dev_cal_factors->res_heat_range)) *
(1 / (1 + ((float)handle->dev_cal_factors->res_heat_val * 0.002f)))) -
25.0f));
return res_heat;
}
/**
* @brief Calculates the gas wait time.
*
* @param duration
* @return uint8_t Gas wait time duration.
*/
static inline uint8_t bme680_compute_gas_wait(const uint16_t duration) {
uint8_t factor = 0;
uint8_t durval;
uint16_t dura = duration;
if (dura >= 0xfc0) {
durval = 0xff; /* Max duration*/
} else {
while (dura > 0x3F) {
dura = dura / 4;
factor += 1;
}
durval = (uint8_t)(dura + (factor * 64));
}
return durval;
}
static inline uint8_t bme680_compute_heater_shared_duration(const uint16_t duration) {
uint8_t factor = 0;
uint8_t heatdurval;
uint16_t dura = duration;
if (dura >= 0x783) {
heatdurval = 0xff; /* Max duration */
} else {
/* Step size of 0.477ms */
dura = (uint16_t)(((uint32_t)dura * 1000) / 477);
while (dura > 0x3F) {
dura = dura >> 2;
factor += 1;
}
heatdurval = (uint8_t)(dura + (factor * 64));
}
return heatdurval;
}
/**
* @brief Gets an estimated measurement duration in milliseconds.
*
* @param handle bmp280 device handle.
* @return uint32_t Estimated measurement duration in milliseconds.
*/
static inline uint32_t bme680_get_measurement_duration(bme680_handle_t handle) {
const uint8_t os_to_meas_cycles[6] = { 0, 1, 2, 4, 8, 16 };
uint32_t meas_dur = 0; /* Calculate in us */
uint32_t meas_cycles;
/* validate arguments */
if((handle) != NULL) {
meas_cycles = os_to_meas_cycles[handle->dev_config.temperature_oversampling];
meas_cycles += os_to_meas_cycles[handle->dev_config.pressure_oversampling];
meas_cycles += os_to_meas_cycles[handle->dev_config.humidity_oversampling];
/* TPH measurement duration */
meas_dur = meas_cycles * (uint32_t)(1963);
meas_dur += (uint32_t)(477 * 4); /* TPH switching duration */
meas_dur += (uint32_t)(477 * 5); /* Gas measurement duration */
if (handle->dev_config.power_mode != BME680_POWER_MODE_PARALLEL) {
meas_dur += (uint32_t)(1000); /* Wake up duration of 1ms */
}
}
return meas_dur;
}
/**
* @brief Gets the calibration factors onboard the bme680. see datasheet for details.
*
* @param[in] bme680_handle bmp280 device handle.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_get_cal_factors(bme680_handle_t handle) {
bit16_uint8_buffer_t rx;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* bme680 attempt to request T1-T3 calibration values from device */
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0xe9, &handle->dev_cal_factors->par_T1) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x8a, (uint16_t *)&handle->dev_cal_factors->par_T2) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x8c, (uint8_t *)&handle->dev_cal_factors->par_T3) );
/* bme680 attempt to request H1-H7 calibration values from device */
ESP_ERROR_CHECK( bme680_i2c_read_from(handle, 0xe2, rx, BIT16_UINT8_BUFFER_SIZE) );
handle->dev_cal_factors->par_H1 = (uint16_t)(((uint16_t)rx[1] << 4) | (rx[0] & 0x0F));
ESP_ERROR_CHECK( bme680_i2c_read_from(handle, 0xe1, rx, BIT16_UINT8_BUFFER_SIZE) );
handle->dev_cal_factors->par_H2 = (uint16_t)(((uint16_t)rx[0] << 4) | (rx[1] >> 4));
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xe4, (uint8_t *)&handle->dev_cal_factors->par_H3) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xe5, (uint8_t *)&handle->dev_cal_factors->par_H4) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xe6, (uint8_t *)&handle->dev_cal_factors->par_H5) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xe7, &handle->dev_cal_factors->par_H6) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xe8, (uint8_t *)&handle->dev_cal_factors->par_H7) );
/* bme680 attempt to request P1-P10 calibration values from device */
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x8e, &handle->dev_cal_factors->par_P1) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x90, (uint16_t *)&handle->dev_cal_factors->par_P2) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x92, (uint8_t *)&handle->dev_cal_factors->par_P3) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x94, (uint16_t *)&handle->dev_cal_factors->par_P4) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x96, (uint16_t *)&handle->dev_cal_factors->par_P5) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x99, (uint8_t *)&handle->dev_cal_factors->par_P6) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x98, (uint8_t *)&handle->dev_cal_factors->par_P7) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x9c, (uint16_t *)&handle->dev_cal_factors->par_P8) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0x9e, (uint16_t *)&handle->dev_cal_factors->par_P9) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xa0, &handle->dev_cal_factors->par_P10) );
/* bme680 attempt to request G1-G3 calibration values from device */
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xed, (uint8_t *)&handle->dev_cal_factors->par_G1) );
ESP_ERROR_CHECK( bme680_i2c_read_word_from(handle, 0xeb, (uint16_t *)&handle->dev_cal_factors->par_G2) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0xee, (uint8_t *)&handle->dev_cal_factors->par_G3) );
/* bme680 attempt to request gas range and switching error values from device */
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x02, &handle->dev_cal_factors->res_heat_range) );
handle->dev_cal_factors->res_heat_range = (handle->dev_cal_factors->res_heat_range & 0x30) / 16;
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x00, (uint8_t *)&handle->dev_cal_factors->res_heat_val) );
ESP_ERROR_CHECK( bme680_i2c_read_byte_from(handle, 0x04, (uint8_t *)&handle->dev_cal_factors->range_switching_error) );
handle->dev_cal_factors->range_switching_error = (handle->dev_cal_factors->range_switching_error & 0xf0) / 16;
/*
ESP_LOGD(TAG, "Calibration data received:");
//
ESP_LOGD(TAG, "par_T1=%u", handle->dev_cal_factors->par_T1);
ESP_LOGD(TAG, "par_T2=%d", handle->dev_cal_factors->par_T2);
ESP_LOGD(TAG, "par_T3=%d", handle->dev_cal_factors->par_T3);
//
ESP_LOGD(TAG, "par_P1=%u", handle->dev_cal_factors->par_P1);
ESP_LOGD(TAG, "par_P2=%d", handle->dev_cal_factors->par_P2);
ESP_LOGD(TAG, "par_P3=%d", handle->dev_cal_factors->par_P3);
ESP_LOGD(TAG, "par_P4=%d", handle->dev_cal_factors->par_P4);
ESP_LOGD(TAG, "par_P5=%d", handle->dev_cal_factors->par_P5);
ESP_LOGD(TAG, "par_P6=%d", handle->dev_cal_factors->par_P6);
ESP_LOGD(TAG, "par_P7=%d", handle->dev_cal_factors->par_P7);
ESP_LOGD(TAG, "par_P8=%d", handle->dev_cal_factors->par_P8);
ESP_LOGD(TAG, "par_P9=%d", handle->dev_cal_factors->par_P9);
ESP_LOGD(TAG, "par_P10=%d", handle->dev_cal_factors->par_P10);
*/
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
/**
* @brief Setup and configuration of BME680 gas and heater profile registers.
*
* @param handle BME680 device handle.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_setup_heater_profiles(bme680_handle_t handle, bme680_heater_setpoints_t *const heater_setpoint) {
uint8_t i;
uint8_t shared_dur;
uint8_t write_len = 0;
uint8_t rh_reg_addr[BME680_HEATER_PROFILE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t rh_reg_data[BME680_HEATER_PROFILE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t gw_reg_addr[BME680_HEATER_PROFILE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t gw_reg_data[BME680_HEATER_PROFILE_SIZE] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
/* validate arguments */
ESP_ARG_CHECK( handle );
switch(handle->dev_config.power_mode) {
case BME680_POWER_MODE_FORCED:
rh_reg_addr[0] = BME680_REG_RES_HEAT;
rh_reg_data[0] = bme680_compensate_heater_resistance(handle, handle->dev_config.heater_temperature);
gw_reg_addr[0] = BME680_REG_GAS_WAIT;
gw_reg_data[0] = bme680_compute_gas_wait(handle->dev_config.heater_duration);
(*heater_setpoint) = handle->dev_config.heater_profile_size - 1;
write_len = handle->dev_config.heater_profile_size;;
break;
case BME680_POWER_MODE_SEQUENTIAL:
if ((handle->dev_config.heater_profile_size == 0) || (handle->dev_config.heater_profile_size > 10)) {
ESP_RETURN_ON_FALSE( false, ESP_ERR_INVALID_ARG, TAG, "heater duration or temperature profile are empty and cannot be larger than 10, setup heater setpoints failed");
}
for (i = 0; i < handle->dev_config.heater_profile_size; i++) {
rh_reg_addr[i] = BME680_REG_RES_HEAT + i;
rh_reg_data[i] = bme680_compensate_heater_resistance(handle, handle->dev_config.heater_temperature_profile[i]);
gw_reg_addr[i] = BME680_REG_GAS_WAIT + i;
gw_reg_data[i] = bme680_compute_gas_wait(handle->dev_config.heater_duration_profile[i]);
}
(*heater_setpoint) = handle->dev_config.heater_profile_size - 1;
write_len = handle->dev_config.heater_profile_size;
break;
case BME680_POWER_MODE_PARALLEL:
if ((handle->dev_config.heater_profile_size == 0) || (handle->dev_config.heater_profile_size > 10)) {
ESP_RETURN_ON_FALSE( false, ESP_ERR_INVALID_ARG, TAG, "heater duration or temperature profile are empty and cannot be larger than 10, setup heater setpoints failed");
}
if(handle->dev_config.heater_shared_duration == 0) {
ESP_RETURN_ON_FALSE( false, ESP_ERR_INVALID_ARG, TAG, "heater shared duration must be greater than 0, setup heater setpoints failed");
}
for (i = 0; i < handle->dev_config.heater_profile_size; i++) {
rh_reg_addr[i] = BME680_REG_RES_HEAT + i;
rh_reg_data[i] = bme680_compensate_heater_resistance(handle, handle->dev_config.heater_temperature_profile[i]);
gw_reg_addr[i] = BME680_REG_GAS_WAIT + i;
gw_reg_data[i] = (uint8_t)handle->dev_config.heater_duration_profile[i];
}
(*heater_setpoint) = handle->dev_config.heater_profile_size - 1;
write_len = handle->dev_config.heater_profile_size;
shared_dur = bme680_compute_heater_shared_duration(handle->dev_config.heater_shared_duration);
ESP_RETURN_ON_ERROR(bme680_i2c_write_byte_to(handle, BME680_REG_SHD_HEATR_DUR, shared_dur), TAG, "unable to write shared heater duration, setup heater setpoints failed");
break;
case BME680_POWER_MODE_SLEEP:
return ESP_ERR_INVALID_ARG;
};
/* attempt to write resistance heater and gas wait profile */
for (i = 0; i < write_len; i++) {
ESP_RETURN_ON_ERROR(bme680_i2c_write_byte_to(handle, rh_reg_addr[i], rh_reg_data[i]), TAG, "unable to write resistance heater profile, setup heater setpoints failed");
ESP_RETURN_ON_ERROR(bme680_i2c_write_byte_to(handle, gw_reg_addr[i], gw_reg_data[i]), TAG, "unable to write gas wait profile, setup heater setpoints failed");
ESP_LOGI(TAG, "bme680_setup_heater_profiles: rh_reg_data %d | gw_reg_data %d", rh_reg_data[i], gw_reg_data[i]);
}
return ESP_OK;
}
/**
* @brief Setup and configuration of BME680 gas and heater registers.
*
* @param handle BME680 device handle.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_setup_heater(bme680_handle_t handle) {
/* validate arguments */
ESP_ARG_CHECK( handle );
bme680_heater_setpoints_t heater_setpoint;
bme680_control_gas0_register_t ctrl_gas0_reg;
bme680_control_gas1_register_t ctrl_gas1_reg;
ESP_RETURN_ON_ERROR(bme680_setup_heater_profiles(handle, &heater_setpoint), TAG, "setup heater profiles for setup heater failed");
/* attempt to read control gas 0 register */
ESP_RETURN_ON_ERROR(bme680_get_control_gas0_register(handle, &ctrl_gas0_reg), TAG, "read control gas 0 register for setup heater failed");
/* attempt to read control gas 1 register */
ESP_RETURN_ON_ERROR(bme680_get_control_gas1_register(handle, &ctrl_gas1_reg), TAG, "read control gas 1 register for setup heater failed");
/* initialize control gas 0 and 1 from configuration params */
if(handle->dev_config.gas_enabled) {
ctrl_gas0_reg.bits.heater_disabled = false;
ctrl_gas1_reg.bits.gas_conversion_enabled = true;
ctrl_gas1_reg.bits.heater_setpoint = heater_setpoint;
} else {
ctrl_gas1_reg.bits.gas_conversion_enabled = false;
ctrl_gas0_reg.bits.heater_disabled = true;
}
/* attempt to write control gas 0 register */
ESP_RETURN_ON_ERROR(bme680_set_control_gas0_register(handle, ctrl_gas0_reg), TAG, "write control gas 0 register for setup heater failed");
/* attempt to write control gas 1 register */
ESP_RETURN_ON_ERROR(bme680_set_control_gas1_register(handle, ctrl_gas1_reg), TAG, "write control gas 1 register for setup heater failed");
return ESP_OK;
}
/**
* @brief Setup and configuration of BME680 context and registers.
*
* @param handle BME680 device handle.
* @return esp_err_t ESP_OK on success.
*/
static inline esp_err_t bme680_setup(bme680_handle_t handle) {
/* validate arguments */
ESP_ARG_CHECK( handle );
bme680_control_measurement_register_t ctrl_meas_reg;
bme680_control_humidity_register_t ctrl_humi_reg;
bme680_config_register_t config_reg;
/* set ambient temperature of sensor to default value (25 degree C) */
handle->ambient_temperature = 25;
/* attempt to read calibration factors from device */
ESP_RETURN_ON_ERROR(bme680_get_cal_factors(handle), TAG, "read calibration factors for setup failed" );
/* attempt to read variant identifier register from device */
ESP_RETURN_ON_ERROR(bme680_get_variant_id_register(handle, &handle->variant_id), TAG, "read variant identifier register for setup failed" );
/* attempt to read control humidity register */
ESP_RETURN_ON_ERROR(bme680_get_control_humidity_register(handle, &ctrl_humi_reg), TAG, "read control humidity register for setup failed");
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR(bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for setup failed");
/* attempt to read configuration register */
ESP_RETURN_ON_ERROR(bme680_get_configuration_register(handle, &config_reg), TAG, "read configuration register for setup failed");
/* initialize configuration register from configuration params */
config_reg.bits.iir_filter = handle->dev_config.iir_filter;
config_reg.bits.spi_enabled = false;
/* initialize control measurement register from configuration params */
ctrl_meas_reg.bits.power_mode = handle->dev_config.power_mode;
ctrl_meas_reg.bits.temperature_oversampling = handle->dev_config.temperature_oversampling;
ctrl_meas_reg.bits.pressure_oversampling = handle->dev_config.pressure_oversampling;
/* initialize control humidity register from configuration params */
ctrl_humi_reg.bits.humidity_oversampling = handle->dev_config.humidity_oversampling;
ctrl_humi_reg.bits.spi_irq_enabled = false;
/* attempt to write control humidity register */
ESP_RETURN_ON_ERROR(bme680_set_control_humidity_register(handle, ctrl_humi_reg), TAG, "write control humidity register for setup failed");
/* attempt to write control measurement register */
ESP_RETURN_ON_ERROR(bme680_set_control_measurement_register(handle, ctrl_meas_reg), TAG, "write control measurement register for setup failed");
/* attempt to write configuration register */
ESP_RETURN_ON_ERROR(bme680_set_configuration_register(handle, config_reg), TAG, "write configuration register for setup failed");
return ESP_OK;
}
/**
* @brief IAQ calculations following Dr. Julie Riggs, The IAQ Rating Index, www.iaquk.org.uk.
*
* - Weighting: Temperature, Humidity each one tenth of the rating ==> 6.5 points max each
* giving gas resistance readings 8 tenths of the rating ==> 52 points max
*
* @param handle BME680 device handle.
* @param data BME680 data structure, air temperature, dew-point temperature, and
* relative humidity parameters must be initialized as a minimum.
* @return esp_err_t ESP_OK on success.
*/
static inline void bme680_compute_iaq(bme680_data_t *const data) {
float adjusted_temp = data->air_temperature + BME680_AQI_TEMP_CORR;
// August-Roche-Magnus approximation (http://bmcnoldy.rsmas.miami.edu/Humidity.html)
float adjusted_humi = 100 * (exp((17.625 * data->dewpoint_temperature) / (243.04 + data->dewpoint_temperature)) / exp((17.625 * adjusted_temp) / (243.04 + adjusted_temp)));
uint32_t gas = data->gas_resistance;
// Calculate humidity score
if (adjusted_humi >= 40 && adjusted_humi <= 60) data->humidity_score = 6.5; //ideal condition, full points
else if ((adjusted_humi >= 30 && adjusted_humi < 40) || (adjusted_humi > 60 && adjusted_humi <= 70)) data->humidity_score = 5.2; //20% less
else if ((adjusted_humi >= 20 && adjusted_humi < 30) || (adjusted_humi > 70 && adjusted_humi <= 80)) data->humidity_score = 3.9; //40% less
else if ((adjusted_humi >= 10 && adjusted_humi < 20) || (adjusted_humi > 80 && adjusted_humi <= 90)) data->humidity_score = 2.6; //60% less
else if (adjusted_humi < 10 && adjusted_humi > 90) data->humidity_score = 1.3; //80% less
// Calculate temperature score
int compare_temp = adjusted_temp; // round to full degree
if (compare_temp >= 18 && compare_temp <= 22) data->temperature_score = 6.5; //ideal condition, full points
else if (compare_temp == 17 || compare_temp == 23) data->temperature_score = 5.2; //20% less
else if (compare_temp == 16 || compare_temp == 24) data->temperature_score = 3.9; //40% less
else if (compare_temp == 15 || compare_temp == 25) data->temperature_score = 2.6; //60% less
else if (compare_temp == 14 || compare_temp == 26) data->temperature_score = 1.3; //80% less
else if (compare_temp < 14 || compare_temp > 26) data->temperature_score = 0; //100% less
// Calculate gas score on resistance values - best practices
if (gas >= 430000) data->gas_score = 52; //ideal condition, full points
else if (gas >= 210000 && gas < 430000) data->gas_score = 43;
else if (gas >= 100000 && gas < 210000) data->gas_score = 35;
else if (gas >= 55000 && gas < 100000) data->gas_score = 26;
else if (gas >= 27000 && gas < 55000) data->gas_score = 18;
else if (gas >= 13500 && gas > 9000) data->gas_score = 9;
else if (gas < 9000) data->gas_score = 0;
// calculate iaq score
data->iaq_score = data->humidity_score + data->temperature_score + data->gas_score;
}
esp_err_t bme680_get_chip_id_register(bme680_handle_t handle, uint8_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_ID, reg), TAG, "read chip identifier register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_variant_id_register(bme680_handle_t handle, uint8_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_VARIANT_ID, reg), TAG, "read variant identifier register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_status0_register(bme680_handle_t handle, bme680_status0_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_STATUS0, &reg->reg), TAG, "read status register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_gas_lsb_register(bme680_handle_t handle, bme680_gas_lsb_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_GAS_R_LSB, &reg->reg), TAG, "read gas lsb register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_set_gas_lsb_register(bme680_handle_t handle, const bme680_gas_lsb_register_t reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_GAS_R_LSB, reg.reg), TAG, "write control measurement register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_control_measurement_register(bme680_handle_t handle, bme680_control_measurement_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_CTRL_MEAS, &reg->reg), TAG, "read control measurement register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_set_control_measurement_register(bme680_handle_t handle, const bme680_control_measurement_register_t reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_CTRL_MEAS, reg.reg), TAG, "write control measurement register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_control_humidity_register(bme680_handle_t handle, bme680_control_humidity_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_CTRL_HUMI, &reg->reg), TAG, "read control humidity register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_set_control_humidity_register(bme680_handle_t handle, const bme680_control_humidity_register_t reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
bme680_control_humidity_register_t ctrl_hum = { .reg = reg.reg };
ctrl_hum.bits.reserved1 = 0;
ctrl_hum.bits.reserved2 = 0;
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_CTRL_HUMI, ctrl_hum.reg), TAG, "write control humidity register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_control_gas0_register(bme680_handle_t handle, bme680_control_gas0_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_CTRL_GAS0, &reg->reg), TAG, "read control gas 0 register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_set_control_gas0_register(bme680_handle_t handle, const bme680_control_gas0_register_t reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
bme680_control_gas0_register_t gas0 = { .reg = reg.reg };
gas0.bits.reserved1 = 0;
gas0.bits.reserved2 = 0;
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_CTRL_GAS0, gas0.reg), TAG, "write control gas 0 register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_control_gas1_register(bme680_handle_t handle, bme680_control_gas1_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_CTRL_GAS1, &reg->reg), TAG, "read control gas 1 register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_set_control_gas1_register(bme680_handle_t handle, const bme680_control_gas1_register_t reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
bme680_control_gas1_register_t gas1 = { .reg = reg.reg };
gas1.bits.reserved = 0;
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_CTRL_GAS1, gas1.reg), TAG, "write control gas 1 register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_configuration_register(bme680_handle_t handle, bme680_config_register_t *const reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c read transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_read_byte_from(handle, BME680_REG_CONFIG, &reg->reg), TAG, "read configuration register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_set_configuration_register(bme680_handle_t handle, const bme680_config_register_t reg) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* copy register */
bme680_config_register_t config = { .reg = reg.reg };
/* set reserved to 0 */
config.bits.reserved1 = 0;
config.bits.reserved2 = 0;
/* attempt i2c write transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_CONFIG, config.reg), TAG, "write configuration register failed" );
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_init(i2c_master_bus_handle_t master_handle, const bme680_config_t *bme680_config, bme680_handle_t *bme680_handle) {
/* validate arguments */
ESP_ARG_CHECK( master_handle && bme680_config );
/* delay task before i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_POWERUP_DELAY_MS));
/* validate device exists on the master bus */
esp_err_t ret = i2c_master_probe(master_handle, bme680_config->i2c_address, I2C_XFR_TIMEOUT_MS);
ESP_GOTO_ON_ERROR(ret, err, TAG, "device does not exist at address 0x%02x, bmp280 device handle initialization failed", bme680_config->i2c_address);
/* validate memory availability for handle */
bme680_handle_t out_handle;
out_handle = (bme680_handle_t)calloc(1, sizeof(*out_handle));
ESP_GOTO_ON_FALSE(out_handle, ESP_ERR_NO_MEM, err, TAG, "no memory for i2c0 bmp280 device for init");
/* validate memory availability for handle calibration factors */
out_handle->dev_cal_factors = (bme680_cal_factors_t*)calloc(1, sizeof(bme680_cal_factors_t));
ESP_GOTO_ON_FALSE(out_handle->dev_cal_factors, ESP_ERR_NO_MEM, err_handle, TAG, "no memory for i2c bmp280 device calibration factors for init");
/* copy configuration */
out_handle->dev_config = *bme680_config;
/* set i2c device configuration */
const i2c_device_config_t i2c_dev_conf = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = out_handle->dev_config.i2c_address,
.scl_speed_hz = out_handle->dev_config.i2c_clock_speed,
};
/* validate device handle */
if (out_handle->i2c_handle == NULL) {
ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(master_handle, &i2c_dev_conf, &out_handle->i2c_handle), err_handle, TAG, "i2c0 new bus failed for init");
}
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
/* read and validate device type */
ESP_GOTO_ON_ERROR(bme680_get_chip_id_register(out_handle, &out_handle->chip_id), err_handle, TAG, "read chip identifier for init failed");
if(out_handle->chip_id != BME680_CHIP_ID) {
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_VERSION, err_handle, TAG, "detected an invalid chip type for init, got: %02x", out_handle->chip_id);
}
/* attempt to reset the device and initialize registers */
ESP_GOTO_ON_ERROR(bme680_reset(out_handle), err_handle, TAG, "soft-reset and initialize registers for init failed");
/* copy configuration */
*bme680_handle = out_handle;
/* delay task before i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_APPSTART_DELAY_MS));
return ESP_OK;
err_handle:
if (out_handle && out_handle->i2c_handle) {
i2c_master_bus_rm_device(out_handle->i2c_handle);
}
free(out_handle);
err:
return ret;
}
char *bme680_air_quality_to_string(float iaq_score) {
if (iaq_score < 25) return "Hazardous";
else if (iaq_score >= 25 && iaq_score <= 38) return "Unhealthy";
else if (iaq_score > 38 && iaq_score <= 51) return "Moderate";
else if (iaq_score > 51 && iaq_score <= 60) return "Good";
else if (iaq_score > 60 && iaq_score <= 65) return "Excellent";
else return "Unknown";
}
esp_err_t bme680_get_adc_signals(bme680_handle_t handle, bme680_adc_data_t *const data) {
esp_err_t ret = ESP_OK;
bool data_is_ready = false;
uint8_t gas_index = 0;
uint16_t adc_gas_r;
uint32_t adc_press;
uint16_t adc_humi;
uint32_t adc_temp;
bit104_uint8_buffer_t rx;
bme680_status0_register_t status0_reg;
/* validate arguments */
ESP_ARG_CHECK( handle && data );
/* trigger measurement when in forced mode */
if(handle->dev_config.power_mode == BME680_POWER_MODE_FORCED) {
bme680_set_power_mode(handle, BME680_POWER_MODE_FORCED);
}
/* set start time for timeout monitoring */
uint64_t start_time = esp_timer_get_time();
/* attempt to poll until data is available or timeout */
do {
/* attempt to check if data is ready */
//ESP_GOTO_ON_ERROR( bme680_get_data_status(handle, &data_is_ready), err, TAG, "data ready for get adc signals failed." );
ESP_GOTO_ON_ERROR( bme680_get_status0_register(handle, &status0_reg), err, TAG, "status 0 register for get adc signals failed." );
data_is_ready = status0_reg.bits.new_data;
gas_index = status0_reg.bits.gas_measurement_index;
/* delay task before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_DATA_READY_DELAY_MS));
/* validate timeout condition */
if (ESP_TIMEOUT_CHECK(start_time, BME680_DATA_POLL_TIMEOUT_MS * 1000))
return ESP_ERR_TIMEOUT;
} while (data_is_ready == false);
// need to read in one sequence to ensure they match.
ESP_GOTO_ON_ERROR( bme680_i2c_read_from(handle, BME680_REG_PRESS, rx, BIT104_UINT8_BUFFER_SIZE), err, TAG, "read adc data failed" );
/* instantiate gas lsb register */
bme680_gas_lsb_register_t gas_lsb_reg = { .reg = rx[12] };
/* concat parameters */
adc_press = ((uint32_t)rx[0] << 12) | ((uint32_t)rx[1] << 4) | ((uint32_t)rx[2] >> 4);
adc_temp = ((uint32_t)rx[3] << 12) | ((uint32_t)rx[4] << 4) | ((uint32_t)rx[5] >> 4);
adc_humi = ((uint16_t)rx[6] << 8) | (uint16_t)rx[7];
adc_gas_r = ((uint16_t)rx[11] << 2) | ((uint16_t)rx[12] >> 6);
ESP_LOGD(TAG, "ADC humidity: %" PRIu16, adc_humi);
ESP_LOGD(TAG, "ADC temperature: %" PRIu32, adc_temp);
ESP_LOGD(TAG, "ADC pressure: %" PRIu32, adc_press);
ESP_LOGD(TAG, "ADC gas: %" PRIu16, adc_gas_r);
/* initialize data structure */
data->temperature = adc_temp;
data->humidity = adc_humi;
data->pressure = adc_press;
data->gas = adc_gas_r;
data->gas_index = gas_index;
data->gas_range = gas_lsb_reg.bits.gas_range;
data->heater_stable = gas_lsb_reg.bits.heater_stable;
data->gas_valid = gas_lsb_reg.bits.gas_valid;
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
err:
return ret;
}
esp_err_t bme680_get_data(bme680_handle_t handle, bme680_data_t *const data) {
bme680_adc_data_t adc_data;
/* validate arguments */
ESP_ARG_CHECK( handle && data );
/* attempt to read adc signals */
ESP_RETURN_ON_ERROR( bme680_get_adc_signals(handle, &adc_data), TAG, "read adc signals failed" );
/* initialize data structure */
data->air_temperature = bme680_compensate_temperature(handle, adc_data.temperature);
data->relative_humidity = bme680_compensate_humidity(handle, adc_data.humidity);
data->dewpoint_temperature = bme680_calculate_dewpoint(data->air_temperature, data->relative_humidity);
data->barometric_pressure = bme680_compensate_pressure(handle, adc_data.pressure);
data->gas_resistance = bme680_compensate_gas_resistance(handle, adc_data.gas, adc_data.gas_range);
data->gas_range = adc_data.gas_range;
data->heater_stable = adc_data.heater_stable;
data->gas_valid = adc_data.gas_valid;
data->raw_data = adc_data;
/* compute scores */
bme680_compute_iaq(data);
/* delay before next i2c transaction */
vTaskDelay(pdMS_TO_TICKS(BME680_CMD_DELAY_MS));
return ESP_OK;
}
esp_err_t bme680_get_data_status(bme680_handle_t handle, bool *const ready) {
bme680_status0_register_t status0_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read device status register */
ESP_RETURN_ON_ERROR( bme680_get_status0_register(handle, &status0_reg), TAG, "read status register (data ready state) failed" );
/* set ready state */
*ready = status0_reg.bits.new_data;
return ESP_OK;
}
esp_err_t bme680_get_gas_measurement_index(bme680_handle_t handle, uint8_t *const index) {
bme680_status0_register_t status0_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read device status register */
ESP_RETURN_ON_ERROR( bme680_get_status0_register(handle, &status0_reg), TAG, "read status register (gas measurement index) failed" );
/* set gas measurement index */
*index = status0_reg.bits.gas_measurement_index;
return ESP_OK;
}
esp_err_t bme680_get_power_mode(bme680_handle_t handle, bme680_power_modes_t *const power_mode) {
bme680_control_measurement_register_t ctrl_meas_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR( bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for get power mode failed" );
/* set power mode */
*power_mode = ctrl_meas_reg.bits.power_mode;
return ESP_OK;
}
esp_err_t bme680_set_power_mode(bme680_handle_t handle, const bme680_power_modes_t power_mode) {
bme680_control_measurement_register_t ctrl_meas_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR( bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for get power mode failed" );
/* initialize control measurement register */
ctrl_meas_reg.bits.power_mode = power_mode;
/* attempt to write control measurement register */
ESP_RETURN_ON_ERROR( bme680_set_control_measurement_register(handle, ctrl_meas_reg), TAG, "write control measurement register for set power mode failed" );
return ESP_OK;
}
esp_err_t bme680_get_pressure_oversampling(bme680_handle_t handle, bme680_pressure_oversampling_t *const oversampling) {
bme680_control_measurement_register_t ctrl_meas_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR( bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for get pressure oversampling failed" );
/* set oversampling */
*oversampling = ctrl_meas_reg.bits.pressure_oversampling;
return ESP_OK;
}
esp_err_t bme680_set_pressure_oversampling(bme680_handle_t handle, const bme680_pressure_oversampling_t oversampling) {
bme680_control_measurement_register_t ctrl_meas_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR( bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for get pressure oversampling failed" );
/* initialize control measurement register */
ctrl_meas_reg.bits.pressure_oversampling = oversampling;
/* attempt to write control measurement register */
ESP_RETURN_ON_ERROR( bme680_set_control_measurement_register(handle, ctrl_meas_reg), TAG, "write control measurement register for set pressure oversampling failed" );
return ESP_OK;
}
esp_err_t bme680_get_temperature_oversampling(bme680_handle_t handle, bme680_temperature_oversampling_t *const oversampling) {
bme680_control_measurement_register_t ctrl_meas_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR( bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for get temperature oversampling failed" );
/* set oversampling */
*oversampling = ctrl_meas_reg.bits.temperature_oversampling;
return ESP_OK;
}
esp_err_t bme680_set_temperature_oversampling(bme680_handle_t handle, const bme680_temperature_oversampling_t oversampling) {
bme680_control_measurement_register_t ctrl_meas_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control measurement register */
ESP_RETURN_ON_ERROR( bme680_get_control_measurement_register(handle, &ctrl_meas_reg), TAG, "read control measurement register for get temperature oversampling failed" );
/* initialize control measurement register */
ctrl_meas_reg.bits.temperature_oversampling = oversampling;
/* attempt to write control measurement register */
ESP_RETURN_ON_ERROR( bme680_set_control_measurement_register(handle, ctrl_meas_reg), TAG, "write control measurement register for set temperature oversampling failed" );
return ESP_OK;
}
esp_err_t bme680_get_humidity_oversampling(bme680_handle_t handle, bme680_humidity_oversampling_t *const oversampling) {
bme680_control_humidity_register_t ctrl_humi_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control humidity register */
ESP_RETURN_ON_ERROR( bme680_get_control_humidity_register(handle, &ctrl_humi_reg), TAG, "read control humidity register for get humidity oversampling failed" );
/* set oversampling */
*oversampling = ctrl_humi_reg.bits.humidity_oversampling;
return ESP_OK;
}
esp_err_t bme680_set_humidity_oversampling(bme680_handle_t handle, const bme680_humidity_oversampling_t oversampling) {
bme680_control_humidity_register_t ctrl_humi_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read control humidity register */
ESP_RETURN_ON_ERROR( bme680_get_control_humidity_register(handle, &ctrl_humi_reg), TAG, "read control humidity register for get humidity oversampling failed" );
/* set oversampling */
ctrl_humi_reg.bits.humidity_oversampling = oversampling;
/* attempt to write control humidity register */
ESP_RETURN_ON_ERROR( bme680_set_control_humidity_register(handle, ctrl_humi_reg), TAG, "write control humidity register for get humidity oversampling failed" );
return ESP_OK;
}
esp_err_t bme680_get_iir_filter(bme680_handle_t handle, bme680_iir_filters_t *const iir_filter) {
bme680_config_register_t config_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read configuration register */
ESP_RETURN_ON_ERROR( bme680_get_configuration_register(handle, &config_reg), TAG, "read configuration register for get IIR filter failed" );
/* set standby time */
*iir_filter = config_reg.bits.iir_filter;
return ESP_OK;
}
esp_err_t bme680_set_iir_filter(bme680_handle_t handle, const bme680_iir_filters_t iir_filter) {
bme680_config_register_t config_reg;
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt to read configuration register */
ESP_RETURN_ON_ERROR( bme680_get_configuration_register(handle, &config_reg), TAG, "read configuration register for get IIR filter failed" );
/* initialize configuration register */
config_reg.bits.iir_filter = iir_filter;
/* attempt to write configuration register */
ESP_RETURN_ON_ERROR( bme680_set_configuration_register(handle, config_reg), TAG, "write configuration register for set IIR filter failed" );
return ESP_OK;
}
esp_err_t bme680_reset(bme680_handle_t handle) {
/* validate arguments */
ESP_ARG_CHECK( handle );
/* attempt i2c transaction */
ESP_RETURN_ON_ERROR( bme680_i2c_write_byte_to(handle, BME680_REG_RESET, BME680_RESET_VALUE), TAG, "write reset register for reset failed" );
/* wait until finished copying NVP data */
// forced delay before next transaction - see datasheet for details
vTaskDelay(pdMS_TO_TICKS(BME680_RESET_DELAY_MS)); // check is busy in timeout loop...
/* attempt to setup device */
ESP_RETURN_ON_ERROR( bme680_setup(handle), TAG, "setup device for reset failed" );
/* attempt to setup device heaters */
ESP_RETURN_ON_ERROR( bme680_setup_heater(handle), TAG, "setup device heaters for reset failed" );
return ESP_OK;
}
esp_err_t bme680_remove(bme680_handle_t handle) {
/* validate arguments */
ESP_ARG_CHECK( handle );
return i2c_master_bus_rm_device(handle->i2c_handle);
}
esp_err_t bme680_delete(bme680_handle_t handle){
/* validate arguments */
ESP_ARG_CHECK( handle );
/* remove device from master bus */
ESP_RETURN_ON_ERROR( bme680_remove(handle), TAG, "unable to remove device from i2c master bus, delete handle failed" );
/* validate handle instance and free handles */
if(handle->i2c_handle) {
free(handle->i2c_handle);
free(handle);
}
return ESP_OK;
}
const char* bme680_get_fw_version(void) {
return (const char*)BME680_FW_VERSION_STR;
}
int32_t bme680_get_fw_version_number(void) {
return BME680_FW_VERSION_INT32;
}