|
1 | 1 | # SPDX-FileCopyrightText: 2017 ladyada for Adafruit Industries
|
2 | 2 | #
|
3 |
| -# SPDX-License-Identifier: MIT |
| 3 | +# SPDX-License-Identifier: MIT AND BSD-3-Clause |
4 | 4 |
|
5 | 5 | # We have a lot of attributes for this complex sensor.
|
6 | 6 | # pylint: disable=too-many-instance-attributes
|
| 7 | +# pylint: disable=no_self_use |
| 8 | +# pylint: disable=consider-using-f-string |
7 | 9 |
|
8 | 10 | """
|
9 | 11 | `adafruit_bme680`
|
|
14 | 16 |
|
15 | 17 | * Author(s): Limor Fried
|
16 | 18 |
|
| 19 | +
|
| 20 | +original Adafruit_BME680 with addition of set_gas_heater(). Other new members are private. |
| 21 | +
|
| 22 | +
|
17 | 23 | Implementation Notes
|
18 | 24 | --------------------
|
19 | 25 |
|
|
33 | 39 | import math
|
34 | 40 | from micropython import const
|
35 | 41 |
|
| 42 | + |
| 43 | +def delay_microseconds(nusec): |
| 44 | + """HELP must be same as dev->delay_us""" |
| 45 | + time.sleep(nusec / 1000000.0) |
| 46 | + |
| 47 | + |
36 | 48 | try:
|
37 | 49 | # Used only for type annotations.
|
38 | 50 |
|
|
49 | 61 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BME680.git"
|
50 | 62 |
|
51 | 63 |
|
| 64 | +# garberw added begin =========================== |
| 65 | +# I2C ADDRESS/BITS/SETTINGS NEW |
| 66 | +# ----------------------------------------------------------------------- |
| 67 | +_BME68X_ENABLE_HEATER = const(0x00) |
| 68 | +_BME68X_DISABLE_HEATER = const(0x01) |
| 69 | +_BME68X_DISABLE_GAS_MEAS = const(0x00) |
| 70 | +_BME68X_ENABLE_GAS_MEAS_L = const(0x01) |
| 71 | +_BME68X_ENABLE_GAS_MEAS_H = const(0x02) |
| 72 | +_BME68X_SLEEP_MODE = const(0) |
| 73 | +_BME68X_FORCED_MODE = const(1) |
| 74 | +_BME68X_VARIANT_GAS_LOW = const(0x00) |
| 75 | +_BME68X_VARIANT_GAS_HIGH = const(0x01) |
| 76 | +_BME68X_HCTRL_MSK = const(0x08) |
| 77 | +_BME68X_HCTRL_POS = const(3) |
| 78 | +_BME68X_NBCONV_MSK = const(0x0F) |
| 79 | +_BME68X_RUN_GAS_MSK = const(0x30) |
| 80 | +_BME68X_RUN_GAS_POS = const(4) |
| 81 | +_BME68X_MODE_MSK = const(0x03) |
| 82 | +_BME68X_PERIOD_POLL = const(10000) |
| 83 | +_BME68X_REG_CTRL_GAS_0 = const(0x70) |
| 84 | +_BME68X_REG_CTRL_GAS_1 = const(0x71) |
| 85 | + |
| 86 | + |
| 87 | +# garberw added end =========================== |
52 | 88 | # I2C ADDRESS/BITS/SETTINGS
|
53 | 89 | # -----------------------------------------------------------------------
|
54 | 90 | _BME680_CHIPID = const(0x61)
|
|
116 | 152 | )
|
117 | 153 |
|
118 | 154 |
|
| 155 | +# garberw added begin =========================== |
| 156 | +INT32 = int |
| 157 | +INT16 = int |
| 158 | +INT8 = int |
| 159 | +UINT32 = int |
| 160 | +UINT16 = int |
| 161 | +UINT8 = int |
| 162 | + |
| 163 | + |
| 164 | +def bme_set_bits(reg_data, bitname_msk, bitname_pos, data): |
| 165 | + """ |
| 166 | + Macro to set bits |
| 167 | + data2 = data << bitname_pos |
| 168 | + set masked bits from data2 in reg_data |
| 169 | + """ |
| 170 | + return (reg_data & ~bitname_msk) | ((data << bitname_pos) & bitname_msk) |
| 171 | + |
| 172 | + |
| 173 | +def bme_set_bits_pos_0(reg_data, bitname_msk, data): |
| 174 | + """ |
| 175 | + Macro to set bits starting from position 0 |
| 176 | + set masked bits from data in reg_data |
| 177 | + """ |
| 178 | + return (reg_data & ~bitname_msk) | (data & bitname_msk) |
| 179 | + |
| 180 | + |
| 181 | +class GasHeaterException(Exception): |
| 182 | + """ |
| 183 | + Error during set_gas_heater() |
| 184 | + """ |
| 185 | + |
| 186 | + def __init__(self, msg="GasHeaterException default"): |
| 187 | + self.msg = msg |
| 188 | + super().__init__(msg) |
| 189 | + |
| 190 | + |
| 191 | +# garberw added end =========================== |
119 | 192 | def _read24(arr: ReadableBuffer) -> float:
|
120 | 193 | """Parse an unsigned 24-bit value as a floating point and return it."""
|
121 | 194 | ret = 0.0
|
@@ -171,6 +244,12 @@ def __init__(self, *, refresh_rate: int = 10) -> None:
|
171 | 244 | self._last_reading = 0
|
172 | 245 | self._min_refresh_time = 1 / refresh_rate
|
173 | 246 |
|
| 247 | + # garberw added begin =========================== |
| 248 | + self._amb_temp = 25 # Copy required parameters from reference bme68x_dev struct |
| 249 | + self.set_gas_heater(320, 150) # heater 320 deg C for 150 msec |
| 250 | + |
| 251 | + # garberw added end =========================== |
| 252 | + |
174 | 253 | @property
|
175 | 254 | def pressure_oversample(self) -> int:
|
176 | 255 | """The oversampling for pressure sensor"""
|
@@ -403,6 +482,180 @@ def _read(self, register: int, length: int) -> bytearray:
|
403 | 482 | def _write(self, register: int, values: bytearray) -> None:
|
404 | 483 | raise NotImplementedError()
|
405 | 484 |
|
| 485 | + # garberw added begin =========================== |
| 486 | + def set_gas_heater(self, heater_temp: UINT16, heater_time: UINT16) -> bool: |
| 487 | + """ |
| 488 | + * @brief Enable and configure gas reading + heater |
| 489 | + * @param heater_temp |
| 490 | + * Desired temperature in degrees Centigrade |
| 491 | + * @param heater_time |
| 492 | + * Time to keep heater on in milliseconds |
| 493 | + * @return True on success, False on failure |
| 494 | + """ |
| 495 | + if (heater_temp == 0) or (heater_time == 0): |
| 496 | + return False |
| 497 | + # enable = BME68X_ENABLE |
| 498 | + try: |
| 499 | + self._set_heatr_conf(heater_temp, heater_time) |
| 500 | + except GasHeaterException: |
| 501 | + return False |
| 502 | + return True |
| 503 | + |
| 504 | + def _set_heatr_conf(self, heater_temp: UINT16, heater_time: UINT16) -> None: |
| 505 | + # restrict to BME68X_FORCED_MODE |
| 506 | + op_mode: UINT8 = _BME68X_FORCED_MODE |
| 507 | + # restrict to enable = True |
| 508 | + enable: bool = True |
| 509 | + nb_conv: UINT8 = 0 |
| 510 | + hctrl: UINT8 = _BME68X_ENABLE_HEATER |
| 511 | + run_gas: UINT8 = 0 |
| 512 | + ctrl_gas_data_0: UINT8 = 0 |
| 513 | + ctrl_gas_data_1: UINT8 = 0 |
| 514 | + ctrl_gas_addr_0: UINT8 = _BME68X_REG_CTRL_GAS_0 |
| 515 | + ctrl_gas_addr_1: UINT8 = _BME68X_REG_CTRL_GAS_1 |
| 516 | + try: |
| 517 | + self._set_op_mode(_BME68X_SLEEP_MODE) |
| 518 | + self._set_conf(heater_temp, heater_time, op_mode) |
| 519 | + ctrl_gas_data_0 = self._read_byte(ctrl_gas_addr_0) |
| 520 | + ctrl_gas_data_1 = self._read_byte(ctrl_gas_addr_1) |
| 521 | + if enable: |
| 522 | + hctrl = _BME68X_ENABLE_HEATER |
| 523 | + if self._chip_variant == _BME68X_VARIANT_GAS_HIGH: |
| 524 | + run_gas = _BME68X_ENABLE_GAS_MEAS_H |
| 525 | + else: |
| 526 | + run_gas = _BME68X_ENABLE_GAS_MEAS_L |
| 527 | + else: |
| 528 | + hctrl = _BME68X_DISABLE_HEATER |
| 529 | + run_gas = _BME68X_DISABLE_GAS_MEAS |
| 530 | + |
| 531 | + ctrl_gas_data_0 = bme_set_bits( |
| 532 | + ctrl_gas_data_0, _BME68X_HCTRL_MSK, _BME68X_HCTRL_POS, hctrl |
| 533 | + ) |
| 534 | + ctrl_gas_data_1 = bme_set_bits_pos_0( |
| 535 | + ctrl_gas_data_1, _BME68X_NBCONV_MSK, nb_conv |
| 536 | + ) |
| 537 | + ctrl_gas_data_1 = bme_set_bits( |
| 538 | + ctrl_gas_data_1, _BME68X_RUN_GAS_MSK, _BME68X_RUN_GAS_POS, run_gas |
| 539 | + ) |
| 540 | + self._write(ctrl_gas_addr_0, [ctrl_gas_data_0]) |
| 541 | + self._write(ctrl_gas_addr_1, [ctrl_gas_data_1]) |
| 542 | + # HELP check this |
| 543 | + self._set_op_mode(_BME68X_FORCED_MODE) |
| 544 | + except GasHeaterException as exc: |
| 545 | + self._set_op_mode(_BME68X_FORCED_MODE) |
| 546 | + raise exc |
| 547 | + |
| 548 | + def _set_op_mode(self, op_mode: UINT8) -> None: |
| 549 | + """ |
| 550 | + * @brief This API is used to set the operation mode of the sensor |
| 551 | + """ |
| 552 | + tmp_pow_mode: UINT8 = 0 |
| 553 | + pow_mode: UINT8 = _BME68X_FORCED_MODE |
| 554 | + reg_addr: UINT8 = _BME680_REG_CTRL_MEAS |
| 555 | + # Call until in sleep |
| 556 | + try: |
| 557 | + # was a do {} while() loop |
| 558 | + while pow_mode != _BME68X_SLEEP_MODE: |
| 559 | + tmp_pow_mode = self._read_byte(_BME680_REG_CTRL_MEAS) |
| 560 | + # Put to sleep before changing mode |
| 561 | + pow_mode = tmp_pow_mode & _BME68X_MODE_MSK |
| 562 | + if pow_mode != _BME68X_SLEEP_MODE: |
| 563 | + tmp_pow_mode &= ~_BME68X_MODE_MSK # Set to sleep |
| 564 | + self._write(reg_addr, [tmp_pow_mode]) |
| 565 | + # dev->delay_us(_BME68X_PERIOD_POLL, dev->intf_ptr) # HELP |
| 566 | + delay_microseconds(_BME68X_PERIOD_POLL) |
| 567 | + # Already in sleep |
| 568 | + if op_mode != _BME68X_SLEEP_MODE: |
| 569 | + tmp_pow_mode = (tmp_pow_mode & ~_BME68X_MODE_MSK) | ( |
| 570 | + op_mode & _BME68X_MODE_MSK |
| 571 | + ) |
| 572 | + self._write(reg_addr, [tmp_pow_mode]) |
| 573 | + except GasHeaterException as exc: |
| 574 | + raise exc |
| 575 | + |
| 576 | + def _set_conf( |
| 577 | + self, heater_temp: UINT16, heater_time: UINT16, op_mode: UINT8 |
| 578 | + ) -> None: |
| 579 | + """ |
| 580 | + This internal API is used to set heater configurations |
| 581 | + """ |
| 582 | + try: |
| 583 | + if op_mode != _BME68X_FORCED_MODE: |
| 584 | + raise GasHeaterException("_set_conf not forced mode") |
| 585 | + rh_reg_addr: UINT8 = _BME680_BME680_RES_HEAT_0 |
| 586 | + rh_reg_data: UINT8 = self._calc_res_heat(heater_temp) |
| 587 | + gw_reg_addr: UINT8 = _BME680_BME680_GAS_WAIT_0 |
| 588 | + gw_reg_data: UINT8 = self._calc_gas_wait(heater_time) |
| 589 | + self._write(rh_reg_addr, [rh_reg_data]) |
| 590 | + self._write(gw_reg_addr, [gw_reg_data]) |
| 591 | + except GasHeaterException as exc: |
| 592 | + raise exc |
| 593 | + |
| 594 | + def _calc_res_heat(self, temp: UINT16) -> UINT8: |
| 595 | + """ |
| 596 | + This internal API is used to calculate the heater resistance value using float |
| 597 | + """ |
| 598 | + gh1: INT8 = self._gas_calibration[0] |
| 599 | + gh2: INT16 = self._gas_calibration[1] |
| 600 | + gh3: INT8 = self._gas_calibration[2] |
| 601 | + htr: UINT8 = self._heat_range |
| 602 | + htv: INT8 = self._heat_val |
| 603 | + amb: UINT8 = self._amb_temp |
| 604 | + |
| 605 | + temp = min(temp, 400) # Cap temperature |
| 606 | + |
| 607 | + var1: INT32 = ((INT32(amb) * gh3) / 1000) * 256 |
| 608 | + var2: INT32 = (gh1 + 784) * ( |
| 609 | + ((((gh2 + 154009) * temp * 5) / 100) + 3276800) / 10 |
| 610 | + ) |
| 611 | + var3: INT32 = var1 + (var2 / 2) |
| 612 | + var4: INT32 = var3 / (htr + 4) |
| 613 | + var5: INT32 = (131 * htv) + 65536 |
| 614 | + heatr_res_x100: INT32 = INT32(((var4 / var5) - 250) * 34) |
| 615 | + heatr_res: UINT8 = UINT8((heatr_res_x100 + 50) / 100) |
| 616 | + |
| 617 | + return heatr_res |
| 618 | + |
| 619 | + def _calc_res_heat(self, temp: UINT16) -> UINT8: |
| 620 | + """ |
| 621 | + This internal API is used to calculate the heater resistance value |
| 622 | + """ |
| 623 | + gh1: float = float(self._gas_calibration[0]) |
| 624 | + gh2: float = float(self._gas_calibration[1]) |
| 625 | + gh3: float = float(self._gas_calibration[2]) |
| 626 | + htr: float = float(self._heat_range) |
| 627 | + htv: float = float(self._heat_val) |
| 628 | + amb: float = float(self._amb_temp) |
| 629 | + |
| 630 | + temp = min(temp, 400) # Cap temperature |
| 631 | + |
| 632 | + var1: float = (gh1 / (16.0)) + 49.0 |
| 633 | + var2: float = ((gh2 / (32768.0)) * (0.0005)) + 0.00235 |
| 634 | + var3: float = gh3 / (1024.0) |
| 635 | + var4: float = var1 * (1.0 + (var2 * float(temp))) |
| 636 | + var5: float = var4 + (var3 * amb) |
| 637 | + res_heat: UINT8 = UINT8( |
| 638 | + 3.4 * ((var5 * (4 / (4 + htr)) * (1 / (1 + (htv * 0.002)))) - 25) |
| 639 | + ) |
| 640 | + return res_heat |
| 641 | + |
| 642 | + def _calc_gas_wait(self, dur: UINT16) -> UINT8: |
| 643 | + """ |
| 644 | + This internal API is used to calculate the gas wait |
| 645 | + """ |
| 646 | + factor: UINT8 = 0 |
| 647 | + durval: UINT8 = 0xFF # Max duration |
| 648 | + |
| 649 | + if dur < 0xFC0: |
| 650 | + return durval |
| 651 | + while dur > 0x3F: |
| 652 | + dur = dur / 4 |
| 653 | + factor += 1 |
| 654 | + durval = UINT8(dur + (factor * 64)) |
| 655 | + return durval |
| 656 | + |
| 657 | + # garberw added end =========================== |
| 658 | + |
406 | 659 |
|
407 | 660 | class Adafruit_BME680_I2C(Adafruit_BME680):
|
408 | 661 | """Driver for I2C connected BME680.
|
|
0 commit comments