Skip to content

Commit d7909fe

Browse files
authored
Merge pull request adafruit#61 from garberw/main
add set_gas_heater(); Was available on arduino but not CircuitPython
2 parents d02eaf5 + 72dfc38 commit d7909fe

File tree

3 files changed

+301
-1
lines changed

3 files changed

+301
-1
lines changed

LICENSES/BSD-3-Clause.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
BOSCH LICENSE
2+
3+
Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved.
4+
5+
BSD-3-Clause
6+
7+
Redistribution and use in source and binary forms, with or without
8+
modification, are permitted provided that the following conditions are met:
9+
10+
1. Redistributions of source code must retain the above copyright
11+
notice, this list of conditions and the following disclaimer.
12+
13+
2. Redistributions in binary form must reproduce the above copyright
14+
notice, this list of conditions and the following disclaimer in the
15+
documentation and/or other materials provided with the distribution.
16+
17+
3. Neither the name of the copyright holder nor the names of its
18+
contributors may be used to endorse or promote products derived from
19+
this software without specific prior written permission.
20+
21+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30+
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
31+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
POSSIBILITY OF SUCH DAMAGE.
33+
34+
@file bme68x_defs.h
35+
@date 2021-05-24
36+
@version v4.4.6

adafruit_bme680.py

Lines changed: 254 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# SPDX-FileCopyrightText: 2017 ladyada for Adafruit Industries
22
#
3-
# SPDX-License-Identifier: MIT
3+
# SPDX-License-Identifier: MIT AND BSD-3-Clause
44

55
# We have a lot of attributes for this complex sensor.
66
# pylint: disable=too-many-instance-attributes
7+
# pylint: disable=no_self_use
8+
# pylint: disable=consider-using-f-string
79

810
"""
911
`adafruit_bme680`
@@ -14,6 +16,10 @@
1416
1517
* Author(s): Limor Fried
1618
19+
20+
original Adafruit_BME680 with addition of set_gas_heater(). Other new members are private.
21+
22+
1723
Implementation Notes
1824
--------------------
1925
@@ -33,6 +39,12 @@
3339
import math
3440
from micropython import const
3541

42+
43+
def delay_microseconds(nusec):
44+
"""HELP must be same as dev->delay_us"""
45+
time.sleep(nusec / 1000000.0)
46+
47+
3648
try:
3749
# Used only for type annotations.
3850

@@ -49,6 +61,30 @@
4961
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BME680.git"
5062

5163

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 ===========================
5288
# I2C ADDRESS/BITS/SETTINGS
5389
# -----------------------------------------------------------------------
5490
_BME680_CHIPID = const(0x61)
@@ -116,6 +152,43 @@
116152
)
117153

118154

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 ===========================
119192
def _read24(arr: ReadableBuffer) -> float:
120193
"""Parse an unsigned 24-bit value as a floating point and return it."""
121194
ret = 0.0
@@ -171,6 +244,12 @@ def __init__(self, *, refresh_rate: int = 10) -> None:
171244
self._last_reading = 0
172245
self._min_refresh_time = 1 / refresh_rate
173246

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+
174253
@property
175254
def pressure_oversample(self) -> int:
176255
"""The oversampling for pressure sensor"""
@@ -403,6 +482,180 @@ def _read(self, register: int, length: int) -> bytearray:
403482
def _write(self, register: int, values: bytearray) -> None:
404483
raise NotImplementedError()
405484

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+
406659

407660
class Adafruit_BME680_I2C(Adafruit_BME680):
408661
"""Driver for I2C connected BME680.

contributors.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: 2017 ladyada for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
Contributors to Adafruit_CircuitPython_BME680_modified
6+
=============================================================
7+
8+
Limor (Ladyada) Fried
9+
10+
William Garber
11+
many others

0 commit comments

Comments
 (0)