From d82c6df57e7e2126026f73599142efcf220ff83b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Thu, 9 Mar 2023 01:34:06 +0100 Subject: [PATCH] Correct BME680 gas calculation and heater_off (#4498) * Fix missing data array * Fix incorrect bit offset * Correct variable types * Do same conversions as in original library * Correct clang-format * Move out float conversion for clarity * Added check for heater stability * Correct clang format * Allow reporting gas resistance when heater is disabled * Correct clang format * Better error reporting by @DAVe3283 * Correct signed operation, range switching error was positive all the time --- esphome/components/bme680/bme680.cpp | 64 ++++++++++++++++++++-------- esphome/components/bme680/bme680.h | 8 ++-- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index 64770ac0d5..e5704a8f9f 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -117,18 +117,24 @@ void BME680Component::setup() { this->calibration_.gh2 = cal2[12] << 8 | cal2[13]; this->calibration_.gh3 = cal2[15]; - if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) { + uint8_t temp_var = 0; + if (!this->read_byte(0x02, &temp_var)) { this->mark_failed(); return; } - if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) { + this->calibration_.res_heat_range = ((temp_var & 0x30) / 16); + + if (!this->read_byte(0x00, &temp_var)) { this->mark_failed(); return; } - if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) { + this->calibration_.res_heat_val = (int8_t) temp_var; + + if (!this->read_byte(0x04, &temp_var)) { this->mark_failed(); return; } + this->calibration_.range_sw_err = ((int8_t) temp_var & (int8_t) 0xf0) / 16; this->calibration_.ambient_temperature = 25; // prime ambient temperature @@ -181,7 +187,7 @@ void BME680Component::setup() { return; } gas0_control &= ~0b00001000; - gas0_control |= heat_off ? 0b100 : 0b000; + gas0_control |= heat_off << 3; if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) { this->mark_failed(); return; @@ -249,12 +255,12 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) { if (temperature > 400) temperature = 400; - const uint8_t ambient_temperature = this->calibration_.ambient_temperature; + const int8_t ambient_temperature = this->calibration_.ambient_temperature; const int8_t gh1 = this->calibration_.gh1; const int16_t gh2 = this->calibration_.gh2; const int8_t gh3 = this->calibration_.gh3; const uint8_t res_heat_range = this->calibration_.res_heat_range; - const uint8_t res_heat_val = this->calibration_.res_heat_val; + const int8_t res_heat_val = this->calibration_.res_heat_val; uint8_t heatr_res; int32_t var1; @@ -293,35 +299,57 @@ uint8_t BME680Component::calc_heater_duration_(uint16_t duration) { void BME680Component::read_data_() { uint8_t data[15]; if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) { + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(NAN); + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(NAN); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(NAN); + if (this->gas_resistance_sensor_ != nullptr) + this->gas_resistance_sensor_->publish_state(NAN); + ESP_LOGW(TAG, "Communication with BME680 failed!"); this->status_set_warning(); return; } + this->status_clear_warning(); uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4); uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4); uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]); - uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6); + uint16_t raw_gas = (uint16_t)((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64)); uint8_t gas_range = data[14] & 0x0F; float temperature = this->calc_temperature_(raw_temperature); float pressure = this->calc_pressure_(raw_pressure); float humidity = this->calc_humidity_(raw_humidity); - float gas_resistance = NAN; - if (data[14] & 0x20) { - gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range); - } + float gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range); + + bool gas_valid = (data[14] >> 5) & 1; + bool heat_stable = (data[14] >> 4) & 1; + if (this->heater_temperature_ == 0 || this->heater_duration_ == 0) + heat_stable = true; // Allow reporting gas resistance when heater is disabled ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure, humidity, gas_resistance); + if (!gas_valid) + ESP_LOGW(TAG, "Gas measurement unsuccessful, reading invalid!"); + if (!heat_stable) + ESP_LOGW(TAG, "Heater unstable, reading invalid! (Normal for a few readings after a power cycle)"); + if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->pressure_sensor_ != nullptr) this->pressure_sensor_->publish_state(pressure); if (this->humidity_sensor_ != nullptr) this->humidity_sensor_->publish_state(humidity); - if (this->gas_resistance_sensor_ != nullptr) - this->gas_resistance_sensor_->publish_state(gas_resistance); - this->status_clear_warning(); + if (this->gas_resistance_sensor_ != nullptr) { + if (gas_valid && heat_stable) { + this->gas_resistance_sensor_->publish_state(gas_resistance); + } else { + this->status_set_warning(); + this->gas_resistance_sensor_->publish_state(NAN); + } + } } float BME680Component::calc_temperature_(uint32_t raw_temperature) { @@ -428,20 +456,22 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) { return calc_hum; } -uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) { +float BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) { float calc_gas_res; float var1 = 0; float var2 = 0; float var3 = 0; + float raw_gas_f = raw_gas; + float range_f = 1U << range; const float range_sw_err = this->calibration_.range_sw_err; var1 = 1340.0f + (5.0f * range_sw_err); var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f); var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f); - calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f)); + calc_gas_res = 1.0f / (var3 * 0.000000125f * range_f * (((raw_gas_f - 512.0f) / var2) + 1.0f)); - return static_cast(calc_gas_res); + return calc_gas_res; } uint32_t BME680Component::calc_meas_duration_() { uint32_t tph_dur; // Calculate in us diff --git a/esphome/components/bme680/bme680.h b/esphome/components/bme680/bme680.h index 6446449742..cfa7aaca20 100644 --- a/esphome/components/bme680/bme680.h +++ b/esphome/components/bme680/bme680.h @@ -59,11 +59,11 @@ struct BME680CalibrationData { int8_t gh3; uint8_t res_heat_range; - uint8_t res_heat_val; - uint8_t range_sw_err; + int8_t res_heat_val; + int8_t range_sw_err; float tfine; - uint8_t ambient_temperature; + int8_t ambient_temperature; }; class BME680Component : public PollingComponent, public i2c::I2CDevice { @@ -117,7 +117,7 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice { /// Calculate the relative humidity in % using the provided raw ADC value. float calc_humidity_(uint16_t raw_humidity); /// Calculate the gas resistance in Ω using the provided raw ADC value. - uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range); + float calc_gas_resistance_(uint16_t raw_gas, uint8_t range); /// Calculate how long the sensor will take until we can retrieve data. uint32_t calc_meas_duration_();