From 52b7b8709419184f25e757b731ac7d23c1cd8200 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Tue, 29 Oct 2024 10:50:44 +0000 Subject: [PATCH] BME280/BME680 reduce bandwidth usage (#6645) (#401) * bme680: select mode once Signed-off-by: Timofey Titovets * bme280.py: iir_filter mask input value Signed-off-by: Timofey Titovets * bme280.py: drop unused max_sample_time Signed-off-by: Timofey Titovets * bme280: use periodic mode for BM[PE]280 Signed-off-by: Timofey Titovets * bme680: measure gas VOC once a while Signed-off-by: Timofey Titovets --------- Signed-off-by: Timofey Titovets Co-authored-by: Timofey Titovets --- klippy/extras/bme280.py | 150 ++++++++++++++++++++++++++++------------ 1 file changed, 105 insertions(+), 45 deletions(-) diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index bfc88996d..ebd99d1ff 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -113,6 +113,7 @@ STATUS_MEASURING = 1 << 3 STATUS_IM_UPDATE = 1 MODE = 1 +MODE_PERIODIC = 3 RUN_GAS = 1 << 4 NB_CONV_0 = 0 EAS_NEW_DATA = 1 << 7 @@ -183,6 +184,7 @@ def __init__(self, config): ) ) logging.info("BMxx80: IIR: %dx" % (pow(2, self.iir_filter) - 1)) + self.iir_filter = self.iir_filter & 0x07 self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0.0 self.min_temp = self.max_temp = self.range_switching_error = 0.0 @@ -196,6 +198,7 @@ def __init__(self, config): self.printer.register_event_handler( "klippy:connect", self.handle_connect ) + self.last_gas_time = 0 def handle_connect(self): self._init_bmxx80() @@ -328,7 +331,7 @@ def read_calibration_data_bmp180(calib_data_1): ) # Reset chip - self.write_register("RESET", [RESET_CHIP_VALUE]) + self.write_register("RESET", [RESET_CHIP_VALUE], wait=True) self.reactor.pause(self.reactor.monotonic() + 0.5) # Make sure non-volatile memory has been copied to registers @@ -340,17 +343,18 @@ def read_calibration_data_bmp180(calib_data_1): status = self.read_register("STATUS", 1)[0] if self.chip_type == "BME680": - self.max_sample_time = 0.5 + self.max_sample_time = ( + 1.25 + + (2.3 * self.os_temp) + + ((2.3 * self.os_pres) + 0.575) + + ((2.3 * self.os_hum) + 0.575) + ) / 1000 self.sample_timer = self.reactor.register_timer(self._sample_bme680) self.chip_registers = BME680_REGS elif self.chip_type == "BMP180": - self.max_sample_time = ( - 1.25 + ((2.3 * self.os_pres) + 0.575) - ) / 1000 self.sample_timer = self.reactor.register_timer(self._sample_bmp180) self.chip_registers = BMP180_REGS elif self.chip_type == "BMP388": - self.max_sample_time = 0.5 self.chip_registers = BMP388_REGS self.write_register( "PWR_CTRL", @@ -367,7 +371,7 @@ def read_calibration_data_bmp180(calib_data_1): self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN]) self.sample_timer = self.reactor.register_timer(self._sample_bmp388) - else: + elif self.chip_type == "BME280": self.max_sample_time = ( 1.25 + (2.3 * self.os_temp) @@ -376,9 +380,12 @@ def read_calibration_data_bmp180(calib_data_1): ) / 1000 self.sample_timer = self.reactor.register_timer(self._sample_bme280) self.chip_registers = BME280_REGS - - if self.chip_type in ("BME680", "BME280"): - self.write_register("CONFIG", (self.iir_filter & 0x07) << 2) + else: + self.max_sample_time = ( + 1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + 0.575) + ) / 1000 + self.sample_timer = self.reactor.register_timer(self._sample_bme280) + self.chip_registers = BME280_REGS # Read out and calculate the trimming parameters if self.chip_type == "BMP180": @@ -399,22 +406,64 @@ def read_calibration_data_bmp180(calib_data_1): elif self.chip_type == "BMP388": self.dig = read_calibration_data_bmp388(cal_1) - def _sample_bme280(self, eventtime): - # Enter forced mode - if self.chip_type == "BME280": - self.write_register("CTRL_HUM", self.os_hum) - meas = self.os_temp << 5 | self.os_pres << 2 | MODE - self.write_register("CTRL_MEAS", meas) + if self.chip_type in ("BME280", "BMP280"): + max_standby_time = REPORT_TIME - self.max_sample_time + # 0.5 ms + t_sb = 0 + if self.chip_type == "BME280": + if max_standby_time > 1: + t_sb = 5 + elif max_standby_time > 0.5: + t_sb = 4 + elif max_standby_time > 0.25: + t_sb = 3 + elif max_standby_time > 0.125: + t_sb = 2 + elif max_standby_time > 0.0625: + t_sb = 1 + elif max_standby_time > 0.020: + t_sb = 7 + elif max_standby_time > 0.010: + t_sb = 6 + else: + if max_standby_time > 4: + t_sb = 7 + elif max_standby_time > 2: + t_sb = 6 + elif max_standby_time > 1: + t_sb = 5 + elif max_standby_time > 0.5: + t_sb = 4 + elif max_standby_time > 0.25: + t_sb = 3 + elif max_standby_time > 0.125: + t_sb = 2 + elif max_standby_time > 0.0625: + t_sb = 1 + + cfg = t_sb << 5 | self.iir_filter << 2 + self.write_register("CONFIG", cfg) + if self.chip_type == "BME280": + self.write_register("CTRL_HUM", self.os_hum) + # Enter normal (periodic) mode + meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC + self.write_register("CTRL_MEAS", meas, wait=True) - try: - # wait until results are ready - status = self.read_register("STATUS", 1)[0] - while status & STATUS_MEASURING: - self.reactor.pause( - self.reactor.monotonic() + self.max_sample_time - ) - status = self.read_register("STATUS", 1)[0] + if self.chip_type == "BME680": + self.write_register("CONFIG", self.iir_filter << 2) + # Should be set once and reused on every mode register write + self.write_register("CTRL_HUM", self.os_hum & 0x07) + gas_wait_0 = self._calc_gas_heater_duration(self.gas_heat_duration) + self.write_register("GAS_WAIT_0", [gas_wait_0]) + res_heat_0 = self._calc_gas_heater_resistance(self.gas_heat_temp) + self.write_register("RES_HEAT_0", [res_heat_0]) + # Set initial heater current to reach Gas heater target on start + self.write_register("IDAC_HEAT_0", 96) + def _sample_bme280(self, eventtime): + # In normal mode data shadowing is performed + # So reading can be done while measurements are in process + try: if self.chip_type == "BME280": data = self.read_register("PRESSURE_MSB", 8) elif self.chip_type == "BMP280": @@ -518,37 +567,41 @@ def _sample_bmp388_press(self): return comp_press def _sample_bme680(self, eventtime): - self.write_register("CTRL_HUM", self.os_hum & 0x07) - meas = self.os_temp << 5 | self.os_pres << 2 - self.write_register("CTRL_MEAS", [meas]) - - gas_wait_0 = self._calculate_gas_heater_duration(self.gas_heat_duration) - self.write_register("GAS_WAIT_0", [gas_wait_0]) - res_heat_0 = self._calculate_gas_heater_resistance(self.gas_heat_temp) - self.write_register("RES_HEAT_0", [res_heat_0]) - gas_config = RUN_GAS | NB_CONV_0 - self.write_register("CTRL_GAS_1", [gas_config]) - - def data_ready(stat): + def data_ready(stat, run_gas): new_data = stat & EAS_NEW_DATA gas_done = not (stat & GAS_DONE) meas_done = not (stat & MEASURE_DONE) + if not run_gas: + gas_done = True return new_data and gas_done and meas_done + run_gas = False + # Check VOC once a while + if self.reactor.monotonic() - self.last_gas_time > 3: + gas_config = RUN_GAS | NB_CONV_0 + self.write_register("CTRL_GAS_1", [gas_config]) + run_gas = True + # Enter forced mode - meas = meas | MODE - self.write_register("CTRL_MEAS", meas) + meas = self.os_temp << 5 | self.os_pres << 2 | MODE + self.write_register("CTRL_MEAS", meas, wait=True) + max_sample_time = self.max_sample_time + if run_gas: + max_sample_time += self.gas_heat_duration / 1000 + self.reactor.pause(self.reactor.monotonic() + max_sample_time) try: # wait until results are ready status = self.read_register("EAS_STATUS_0", 1)[0] - while not data_ready(status): + while not data_ready(status, run_gas): self.reactor.pause( self.reactor.monotonic() + self.max_sample_time ) status = self.read_register("EAS_STATUS_0", 1)[0] data = self.read_register("PRESSURE_MSB", 8) - gas_data = self.read_register("GAS_R_MSB", 2) + gas_data = [0, 0] + if run_gas: + gas_data = self.read_register("GAS_R_MSB", 2) except Exception: logging.exception("BME680: Error reading data") self.temp = self.pressure = self.humidity = self.gas = 0.0 @@ -573,6 +626,10 @@ def data_ready(stat): gas_raw = (gas_data[0] << 2) | ((gas_data[1] & 0xC0) >> 6) gas_range = gas_data[1] & 0x0F self.gas = self._compensate_gas(gas_raw, gas_range) + # Disable gas measurement on success + gas_config = NB_CONV_0 + self.write_register("CTRL_GAS_1", [gas_config]) + self.last_gas_time = self.reactor.monotonic() if self.temp < self.min_temp or self.temp > self.max_temp: self.printer.invoke_shutdown( @@ -726,7 +783,7 @@ def _compensate_gas(self, gas_raw, gas_range): ) return gas - def _calculate_gas_heater_resistance(self, target_temp): + def _calc_gas_heater_resistance(self, target_temp): amb_temp = self.temp heater_data = self.read_register("RES_HEAT_VAL", 3) res_heat_val = get_signed_byte(heater_data[0]) @@ -747,7 +804,7 @@ def _calculate_gas_heater_resistance(self, target_temp): ) return int(res_heat) - def _calculate_gas_heater_duration(self, duration_ms): + def _calc_gas_heater_duration(self, duration_ms): if duration_ms >= 4032: duration_reg = 0xFF else: @@ -808,12 +865,15 @@ def read_register(self, reg_name, read_len): params = self.i2c.i2c_read(regs, read_len) return bytearray(params["response"]) - def write_register(self, reg_name, data): - if not isinstance(data, list): + def write_register(self, reg_name, data, wait=False): + if type(data) is not list: data = [data] reg = self.chip_registers[reg_name] data.insert(0, reg) - self.i2c.i2c_write(data) + if not wait: + self.i2c.i2c_write(data) + else: + self.i2c.i2c_write_wait_ack(data) def get_status(self, eventtime): data = {"temperature": round(self.temp, 2), "pressure": self.pressure}