451 lines
16 KiB
Python
451 lines
16 KiB
Python
# The MIT License (MIT)
|
|
#
|
|
# Copyright (c) 2017 ladyada for Adafruit Industries
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
"""
|
|
`adafruit_bmp280` - Adafruit BMP280 - Temperature & Barometic Pressure Sensor
|
|
===============================================================================
|
|
|
|
CircuitPython driver from BMP280 Temperature and Barometic Pressure sensor
|
|
|
|
* Author(s): ladyada
|
|
"""
|
|
import math
|
|
from time import sleep
|
|
|
|
try:
|
|
import struct
|
|
except ImportError:
|
|
import ustruct as struct
|
|
from micropython import const
|
|
|
|
__version__ = "0.0.0-auto.0"
|
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BMP280.git"
|
|
|
|
# I2C ADDRESS/BITS/SETTINGS
|
|
# -----------------------------------------------------------------------
|
|
_CHIP_ID = const(0x58)
|
|
|
|
_REGISTER_CHIPID = const(0xD0)
|
|
_REGISTER_DIG_T1 = const(0x88)
|
|
_REGISTER_SOFTRESET = const(0xE0)
|
|
_REGISTER_STATUS = const(0xF3)
|
|
_REGISTER_CTRL_MEAS = const(0xF4)
|
|
_REGISTER_CONFIG = const(0xF5)
|
|
_REGISTER_PRESSUREDATA = const(0xF7)
|
|
_REGISTER_TEMPDATA = const(0xFA)
|
|
|
|
_BMP280_PRESSURE_MIN_HPA = const(300)
|
|
_BMP280_PRESSURE_MAX_HPA = const(1100)
|
|
|
|
|
|
"""iir_filter values"""
|
|
IIR_FILTER_DISABLE = const(0)
|
|
IIR_FILTER_X2 = const(0x01)
|
|
IIR_FILTER_X4 = const(0x02)
|
|
IIR_FILTER_X8 = const(0x03)
|
|
IIR_FILTER_X16 = const(0x04)
|
|
|
|
_BMP280_IIR_FILTERS = (
|
|
IIR_FILTER_DISABLE,
|
|
IIR_FILTER_X2,
|
|
IIR_FILTER_X4,
|
|
IIR_FILTER_X8,
|
|
IIR_FILTER_X16,
|
|
)
|
|
|
|
"""overscan values for temperature, pressure, and humidity"""
|
|
OVERSCAN_DISABLE = const(0x00)
|
|
OVERSCAN_X1 = const(0x01)
|
|
OVERSCAN_X2 = const(0x02)
|
|
OVERSCAN_X4 = const(0x03)
|
|
OVERSCAN_X8 = const(0x04)
|
|
OVERSCAN_X16 = const(0x05)
|
|
|
|
_BMP280_OVERSCANS = {
|
|
OVERSCAN_DISABLE: 0,
|
|
OVERSCAN_X1: 1,
|
|
OVERSCAN_X2: 2,
|
|
OVERSCAN_X4: 4,
|
|
OVERSCAN_X8: 8,
|
|
OVERSCAN_X16: 16,
|
|
}
|
|
|
|
"""mode values"""
|
|
MODE_SLEEP = const(0x00)
|
|
MODE_FORCE = const(0x01)
|
|
MODE_NORMAL = const(0x03)
|
|
|
|
_BMP280_MODES = (MODE_SLEEP, MODE_FORCE, MODE_NORMAL)
|
|
"""
|
|
standby timeconstant values
|
|
TC_X[_Y] where X=milliseconds and Y=tenths of a millisecond
|
|
"""
|
|
STANDBY_TC_0_5 = const(0x00) # 0.5ms
|
|
STANDBY_TC_10 = const(0x06) # 10ms
|
|
STANDBY_TC_20 = const(0x07) # 20ms
|
|
STANDBY_TC_62_5 = const(0x01) # 62.5ms
|
|
STANDBY_TC_125 = const(0x02) # 125ms
|
|
STANDBY_TC_250 = const(0x03) # 250ms
|
|
STANDBY_TC_500 = const(0x04) # 500ms
|
|
STANDBY_TC_1000 = const(0x05) # 1000ms
|
|
|
|
_BMP280_STANDBY_TCS = (
|
|
STANDBY_TC_0_5,
|
|
STANDBY_TC_10,
|
|
STANDBY_TC_20,
|
|
STANDBY_TC_62_5,
|
|
STANDBY_TC_125,
|
|
STANDBY_TC_250,
|
|
STANDBY_TC_500,
|
|
STANDBY_TC_1000,
|
|
)
|
|
|
|
|
|
class Adafruit_BMP280: # pylint: disable=invalid-name
|
|
"""Base BMP280 object. Use `Adafruit_BMP280_I2C` or `Adafruit_BMP280_SPI` instead of this. This
|
|
checks the BMP280 was found, reads the coefficients and enables the sensor for continuous
|
|
reads"""
|
|
|
|
def __init__(self):
|
|
# Check device ID.
|
|
chip_id = self._read_byte(_REGISTER_CHIPID)
|
|
if _CHIP_ID != chip_id:
|
|
raise RuntimeError("Failed to find BMP280! Chip ID 0x%x" % chip_id)
|
|
# Set some reasonable defaults.
|
|
self._iir_filter = IIR_FILTER_DISABLE
|
|
self._overscan_temperature = OVERSCAN_X2
|
|
self._overscan_pressure = OVERSCAN_X16
|
|
self._t_standby = STANDBY_TC_0_5
|
|
self._mode = MODE_SLEEP
|
|
self._reset()
|
|
self._read_coefficients()
|
|
self._write_ctrl_meas()
|
|
self._write_config()
|
|
self.sea_level_pressure = 1013.25
|
|
"""Pressure in hectoPascals at sea level. Used to calibrate `altitude`."""
|
|
self._t_fine = None
|
|
|
|
def _read_temperature(self):
|
|
# perform one measurement
|
|
if self.mode != MODE_NORMAL:
|
|
self.mode = MODE_FORCE
|
|
# Wait for conversion to complete
|
|
while self._get_status() & 0x08:
|
|
sleep(0.002)
|
|
raw_temperature = (
|
|
self._read24(_REGISTER_TEMPDATA) / 16
|
|
) # lowest 4 bits get dropped
|
|
# print("raw temp: ", UT)
|
|
var1 = (
|
|
raw_temperature / 16384.0 - self._temp_calib[0] / 1024.0
|
|
) * self._temp_calib[1]
|
|
# print(var1)
|
|
var2 = (
|
|
(raw_temperature / 131072.0 - self._temp_calib[0] / 8192.0)
|
|
* (raw_temperature / 131072.0 - self._temp_calib[0] / 8192.0)
|
|
) * self._temp_calib[2]
|
|
# print(var2)
|
|
|
|
self._t_fine = int(var1 + var2)
|
|
# print("t_fine: ", self.t_fine)
|
|
|
|
def _reset(self):
|
|
"""Soft reset the sensor"""
|
|
self._write_register_byte(_REGISTER_SOFTRESET, 0xB6)
|
|
sleep(0.004) # Datasheet says 2ms. Using 4ms just to be safe
|
|
|
|
def _write_ctrl_meas(self):
|
|
"""
|
|
Write the values to the ctrl_meas register in the device
|
|
ctrl_meas sets the pressure and temperature data acquistion options
|
|
"""
|
|
self._write_register_byte(_REGISTER_CTRL_MEAS, self._ctrl_meas)
|
|
|
|
def _get_status(self):
|
|
"""Get the value from the status register in the device """
|
|
return self._read_byte(_REGISTER_STATUS)
|
|
|
|
def _read_config(self):
|
|
"""Read the value from the config register in the device """
|
|
return self._read_byte(_REGISTER_CONFIG)
|
|
|
|
def _write_config(self):
|
|
"""Write the value to the config register in the device """
|
|
normal_flag = False
|
|
if self._mode == MODE_NORMAL:
|
|
# Writes to the config register may be ignored while in Normal mode
|
|
normal_flag = True
|
|
self.mode = MODE_SLEEP # So we switch to Sleep mode first
|
|
self._write_register_byte(_REGISTER_CONFIG, self._config)
|
|
if normal_flag:
|
|
self.mode = MODE_NORMAL
|
|
|
|
@property
|
|
def mode(self):
|
|
"""
|
|
Operation mode
|
|
Allowed values are set in the MODE enum class
|
|
"""
|
|
return self._mode
|
|
|
|
@mode.setter
|
|
def mode(self, value):
|
|
if not value in _BMP280_MODES:
|
|
raise ValueError("Mode '%s' not supported" % (value))
|
|
self._mode = value
|
|
self._write_ctrl_meas()
|
|
|
|
@property
|
|
def standby_period(self):
|
|
"""
|
|
Control the inactive period when in Normal mode
|
|
Allowed standby periods are set the STANDBY enum class
|
|
"""
|
|
return self._t_standby
|
|
|
|
@standby_period.setter
|
|
def standby_period(self, value):
|
|
if not value in _BMP280_STANDBY_TCS:
|
|
raise ValueError("Standby Period '%s' not supported" % (value))
|
|
if self._t_standby == value:
|
|
return
|
|
self._t_standby = value
|
|
self._write_config()
|
|
|
|
@property
|
|
def overscan_temperature(self):
|
|
"""
|
|
Temperature Oversampling
|
|
Allowed values are set in the OVERSCAN enum class
|
|
"""
|
|
return self._overscan_temperature
|
|
|
|
@overscan_temperature.setter
|
|
def overscan_temperature(self, value):
|
|
if not value in _BMP280_OVERSCANS:
|
|
raise ValueError("Overscan value '%s' not supported" % (value))
|
|
self._overscan_temperature = value
|
|
self._write_ctrl_meas()
|
|
|
|
@property
|
|
def overscan_pressure(self):
|
|
"""
|
|
Pressure Oversampling
|
|
Allowed values are set in the OVERSCAN enum class
|
|
"""
|
|
return self._overscan_pressure
|
|
|
|
@overscan_pressure.setter
|
|
def overscan_pressure(self, value):
|
|
if not value in _BMP280_OVERSCANS:
|
|
raise ValueError("Overscan value '%s' not supported" % (value))
|
|
self._overscan_pressure = value
|
|
self._write_ctrl_meas()
|
|
|
|
@property
|
|
def iir_filter(self):
|
|
"""
|
|
Controls the time constant of the IIR filter
|
|
Allowed values are set in the IIR_FILTER enum class
|
|
"""
|
|
return self._iir_filter
|
|
|
|
@iir_filter.setter
|
|
def iir_filter(self, value):
|
|
if not value in _BMP280_IIR_FILTERS:
|
|
raise ValueError("IIR Filter '%s' not supported" % (value))
|
|
self._iir_filter = value
|
|
self._write_config()
|
|
|
|
@property
|
|
def _config(self):
|
|
"""Value to be written to the device's config register """
|
|
config = 0
|
|
if self.mode == MODE_NORMAL:
|
|
config += self._t_standby << 5
|
|
if self._iir_filter:
|
|
config += self._iir_filter << 2
|
|
return config
|
|
|
|
@property
|
|
def _ctrl_meas(self):
|
|
"""Value to be written to the device's ctrl_meas register """
|
|
ctrl_meas = self.overscan_temperature << 5
|
|
ctrl_meas += self.overscan_pressure << 2
|
|
ctrl_meas += self.mode
|
|
return ctrl_meas
|
|
|
|
@property
|
|
def measurement_time_typical(self):
|
|
"""Typical time in milliseconds required to complete a measurement in normal mode"""
|
|
meas_time_ms = 1
|
|
if self.overscan_temperature != OVERSCAN_DISABLE:
|
|
meas_time_ms += 2 * _BMP280_OVERSCANS.get(self.overscan_temperature)
|
|
if self.overscan_pressure != OVERSCAN_DISABLE:
|
|
meas_time_ms += 2 * _BMP280_OVERSCANS.get(self.overscan_pressure) + 0.5
|
|
return meas_time_ms
|
|
|
|
@property
|
|
def measurement_time_max(self):
|
|
"""Maximum time in milliseconds required to complete a measurement in normal mode"""
|
|
meas_time_ms = 1.25
|
|
if self.overscan_temperature != OVERSCAN_DISABLE:
|
|
meas_time_ms += 2.3 * _BMP280_OVERSCANS.get(self.overscan_temperature)
|
|
if self.overscan_pressure != OVERSCAN_DISABLE:
|
|
meas_time_ms += 2.3 * _BMP280_OVERSCANS.get(self.overscan_pressure) + 0.575
|
|
return meas_time_ms
|
|
|
|
@property
|
|
def temperature(self):
|
|
"""The compensated temperature in degrees celsius."""
|
|
self._read_temperature()
|
|
return self._t_fine / 5120.0
|
|
|
|
@property
|
|
def pressure(self):
|
|
"""
|
|
The compensated pressure in hectoPascals.
|
|
returns None if pressure measurement is disabled
|
|
"""
|
|
self._read_temperature()
|
|
|
|
# Algorithm from the BMP280 driver
|
|
# https://github.com/BoschSensortec/BMP280_driver/blob/master/bmp280.c
|
|
adc = self._read24(_REGISTER_PRESSUREDATA) / 16 # lowest 4 bits get dropped
|
|
var1 = float(self._t_fine) / 2.0 - 64000.0
|
|
var2 = var1 * var1 * self._pressure_calib[5] / 32768.0
|
|
var2 = var2 + var1 * self._pressure_calib[4] * 2.0
|
|
var2 = var2 / 4.0 + self._pressure_calib[3] * 65536.0
|
|
var3 = self._pressure_calib[2] * var1 * var1 / 524288.0
|
|
var1 = (var3 + self._pressure_calib[1] * var1) / 524288.0
|
|
var1 = (1.0 + var1 / 32768.0) * self._pressure_calib[0]
|
|
if not var1:
|
|
return _BMP280_PRESSURE_MIN_HPA
|
|
pressure = 1048576.0 - adc
|
|
pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
|
|
var1 = self._pressure_calib[8] * pressure * pressure / 2147483648.0
|
|
var2 = pressure * self._pressure_calib[7] / 32768.0
|
|
pressure = pressure + (var1 + var2 + self._pressure_calib[6]) / 16.0
|
|
pressure /= 100
|
|
if pressure < _BMP280_PRESSURE_MIN_HPA:
|
|
return _BMP280_PRESSURE_MIN_HPA
|
|
if pressure > _BMP280_PRESSURE_MAX_HPA:
|
|
return _BMP280_PRESSURE_MAX_HPA
|
|
return pressure
|
|
|
|
@property
|
|
def altitude(self):
|
|
"""The altitude based on the sea level pressure (`sea_level_pressure`) - which you must
|
|
enter ahead of time)"""
|
|
p = self.pressure # in Si units for hPascal
|
|
return 44330 * (1.0 - math.pow(p / self.sea_level_pressure, 0.1903))
|
|
|
|
####################### Internal helpers ################################
|
|
def _read_coefficients(self):
|
|
"""Read & save the calibration coefficients"""
|
|
coeff = self._read_register(_REGISTER_DIG_T1, 24)
|
|
coeff = list(struct.unpack("<HhhHhhhhhhhh", bytes(coeff)))
|
|
coeff = [float(i) for i in coeff]
|
|
# The temp_calib lines up with DIG_T# registers.
|
|
self._temp_calib = coeff[:3]
|
|
self._pressure_calib = coeff[3:]
|
|
# print("%d %d %d" % (self._temp_calib[0], self._temp_calib[1], self._temp_calib[2]))
|
|
# print("%d %d %d" % (self._pressure_calib[0], self._pressure_calib[1],
|
|
# self._pressure_calib[2]))
|
|
# print("%d %d %d" % (self._pressure_calib[3], self._pressure_calib[4],
|
|
# self._pressure_calib[5]))
|
|
# print("%d %d %d" % (self._pressure_calib[6], self._pressure_calib[7],
|
|
# self._pressure_calib[8]))
|
|
|
|
def _read_byte(self, register):
|
|
"""Read a byte register value and return it"""
|
|
return self._read_register(register, 1)[0]
|
|
|
|
def _read24(self, register):
|
|
"""Read an unsigned 24-bit value as a floating point and return it."""
|
|
ret = 0.0
|
|
for b in self._read_register(register, 3):
|
|
ret *= 256.0
|
|
ret += float(b & 0xFF)
|
|
return ret
|
|
|
|
def _read_register(self, register, length):
|
|
"""Low level register reading, not implemented in base class"""
|
|
raise NotImplementedError()
|
|
|
|
def _write_register_byte(self, register, value):
|
|
"""Low level register writing, not implemented in base class"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
class Adafruit_BMP280_I2C(Adafruit_BMP280): # pylint: disable=invalid-name
|
|
"""Driver for I2C connected BMP280. Default address is 0x77 but another address can be passed
|
|
in as an argument"""
|
|
|
|
def __init__(self, i2c, address=0x77):
|
|
import adafruit_bus_device.i2c_device as i2c_device # pylint: disable=import-outside-toplevel
|
|
|
|
self._i2c = i2c_device.I2CDevice(i2c, address)
|
|
super().__init__()
|
|
|
|
def _read_register(self, register, length):
|
|
"""Low level register reading over I2C, returns a list of values"""
|
|
with self._i2c as i2c:
|
|
i2c.write(bytes([register & 0xFF]))
|
|
result = bytearray(length)
|
|
i2c.readinto(result)
|
|
# print("$%02X => %s" % (register, [hex(i) for i in result]))
|
|
return result
|
|
|
|
def _write_register_byte(self, register, value):
|
|
"""Low level register writing over I2C, writes one 8-bit value"""
|
|
with self._i2c as i2c:
|
|
i2c.write(bytes([register & 0xFF, value & 0xFF]))
|
|
# print("$%02X <= 0x%02X" % (register, value))
|
|
|
|
|
|
class Adafruit_BMP280_SPI(Adafruit_BMP280):
|
|
"""Driver for SPI connected BMP280. Default clock rate is 100000 but can be changed with
|
|
'baudrate'"""
|
|
|
|
def __init__(self, spi, cs, baudrate=100000):
|
|
import adafruit_bus_device.spi_device as spi_device # pylint: disable=import-outside-toplevel
|
|
|
|
self._spi = spi_device.SPIDevice(spi, cs, baudrate=baudrate)
|
|
super().__init__()
|
|
|
|
def _read_register(self, register, length):
|
|
"""Low level register reading over SPI, returns a list of values"""
|
|
register = (register | 0x80) & 0xFF # Read single, bit 7 high.
|
|
with self._spi as spi:
|
|
# pylint: disable=no-member
|
|
spi.write(bytearray([register]))
|
|
result = bytearray(length)
|
|
spi.readinto(result)
|
|
# print("$%02X => %s" % (register, [hex(i) for i in result]))
|
|
return result
|
|
|
|
def _write_register_byte(self, register, value):
|
|
"""Low level register writing over SPI, writes one 8-bit value"""
|
|
register &= 0x7F # Write, bit 7 low.
|
|
with self._spi as spi:
|
|
# pylint: disable=no-member
|
|
spi.write(bytes([register, value & 0xFF]))
|