Skip to content

Commit

Permalink
BME280/BME680 reduce bandwidth usage (#6645) (#401)
Browse files Browse the repository at this point in the history
* bme680: select mode once

Signed-off-by: Timofey Titovets <[email protected]>

* bme280.py: iir_filter mask input value

Signed-off-by: Timofey Titovets <[email protected]>

* bme280.py: drop unused max_sample_time

Signed-off-by: Timofey Titovets <[email protected]>

* bme280: use periodic mode for BM[PE]280

Signed-off-by: Timofey Titovets <[email protected]>

* bme680: measure gas VOC once a while

Signed-off-by: Timofey Titovets <[email protected]>

---------

Signed-off-by: Timofey Titovets <[email protected]>
Co-authored-by: Timofey Titovets <[email protected]>
  • Loading branch information
rogerlz and nefelim4ag authored Oct 29, 2024
1 parent a266b62 commit 52b7b87
Showing 1 changed file with 105 additions and 45 deletions.
150 changes: 105 additions & 45 deletions klippy/extras/bme280.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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)
Expand All @@ -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":
Expand All @@ -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":
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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])
Expand All @@ -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:
Expand Down Expand Up @@ -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}
Expand Down

0 comments on commit 52b7b87

Please sign in to comment.