Source code for bmp384

# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`bmp384`
================================================================================

CircuitPython Driver for the Bosch BMP384 Pressure and Temperature sensor


* Author: Jose D. Montoya


"""

from micropython import const
from adafruit_bus_device import i2c_device
from adafruit_register.i2c_struct import ROUnaryStruct, Struct
from adafruit_register.i2c_bits import RWBits, ROBits
from adafruit_register.i2c_bit import RWBit

try:
    from busio import I2C
except ImportError:
    pass

__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/CircuitPython_BMP384.git"

_REG_WHOAMI = const(0x00)
_PWR_CTRL = const(0x1B)
_OSR_CONFIG = const(0x1C)
_ODR_CONFIG = const(0x1D)
_CONFIG = const(0x1F)
_REGISTER_CAL_DATA = const(0x31)

SLEEP_MODE = const(0b00)
FORCED_MODE = const(0b10)
NORMAL_MODE = const(0b11)
power_mode_values = (SLEEP_MODE, FORCED_MODE, NORMAL_MODE)

PRESS_DISABLE = const(0x00)
PRESS_ENABLE = const(0x01)
pressure_mode_values = (PRESS_DISABLE, PRESS_ENABLE)

# Filter Coefficients
IIR_FILTER_DISABLE = const(0x00)
IIR_FILTER_X2 = const(0x01)
IIR_FILTER_X4 = const(0x02)
IIR_FILTER_X8 = const(0x03)
IIR_FILTER_X16 = const(0x04)
IIR_FILTER_X32 = const(0x05)
IIR_FILTER_X64 = const(0x06)
IIR_FILTER_X128 = const(0x07)
filter_coefficients_values = (
    IIR_FILTER_DISABLE,
    IIR_FILTER_X2,
    IIR_FILTER_X4,
    IIR_FILTER_X8,
    IIR_FILTER_X16,
    IIR_FILTER_X32,
    IIR_FILTER_X64,
    IIR_FILTER_X128,
)

TEMP_DISABLE = const(0x00)
TEMP_ENABLE = const(0x01)
temperature_mode_values = (TEMP_DISABLE, TEMP_ENABLE)

# Oversample
OVERSAMPLE_DISABLE = const(0x00)
OVERSAMPLE_X2 = const(0x01)
OVERSAMPLE_X4 = const(0x02)
OVERSAMPLE_X8 = const(0x03)
OVERSAMPLE_X16 = const(0x04)
OVERSAMPLE_X32 = const(0x05)
pressure_oversample_values = (
    OVERSAMPLE_DISABLE,
    OVERSAMPLE_X2,
    OVERSAMPLE_X4,
    OVERSAMPLE_X8,
    OVERSAMPLE_X16,
    OVERSAMPLE_X32,
)
temperature_oversample_values = (
    OVERSAMPLE_DISABLE,
    OVERSAMPLE_X2,
    OVERSAMPLE_X4,
    OVERSAMPLE_X8,
    OVERSAMPLE_X16,
    OVERSAMPLE_X32,
)


[docs] class BMP384: """Driver for the BMP384 Sensor connected over I2C. :param ~busio.I2C i2c_bus: The I2C bus the BMP384 is connected to. :param int address: The I2C device address. Defaults to :const:`0x77` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`BMP384` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import bmp384 Once this is done you can define your `board.I2C` object and define your sensor object .. code-block:: python i2c = board.I2C() # uses board.SCL and board.SDA bmp = bmp384.BMP384(i2c) Now you have access to the attributes .. code-block:: python press = bmp.pressure temp = bmp.temperature """ _device_id = ROUnaryStruct(_REG_WHOAMI, "B") _operation_mode = RWBits(2, _PWR_CTRL, 4) _pressure_mode = RWBit(_PWR_CTRL, 0) _temperature_mode = RWBit(_PWR_CTRL, 1) _filter_coefficients = RWBits(3, _CONFIG, 1) _pressure_oversample = RWBits(3, _OSR_CONFIG, 0) _temperature_oversample = RWBits(3, _OSR_CONFIG, 3) _output_data_rate = RWBits(5, _ODR_CONFIG, 0) _temperature = ROBits(24, 0x07, 0, 3) _pressure = ROBits(24, 0x04, 0, 3) _coeffs = Struct(_REGISTER_CAL_DATA, "<HHbhhbbHHbbhbb") def __init__(self, i2c_bus: I2C, address: int = 0x77) -> None: self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != 0x50: raise RuntimeError("Failed to find BMP384") self._pressure_mode = PRESS_ENABLE self._temperature_mode = TEMP_ENABLE self._operation_mode = NORMAL_MODE self._read_coefficients() @property def power_mode(self) -> str: """ Sensor power_mode +--------------------------------+------------------+ | Mode | Value | +================================+==================+ | :py:const:`bmp384.SLEEP_MODE` | :py:const:`0b00` | +--------------------------------+------------------+ | :py:const:`bmp384.FORCED_MODE` | :py:const:`0b10` | +--------------------------------+------------------+ | :py:const:`bmp384.NORMAL_MODE` | :py:const:`0b11` | +--------------------------------+------------------+ """ values = ( "SLEEP_MODE", "FORCED_MODE", "NORMAL_MODE", ) return values[self._operation_mode] @power_mode.setter def power_mode(self, value: int) -> None: if value not in power_mode_values: raise ValueError("Value must be a valid power_mode setting") self._operation_mode = value @property def temperature_mode(self) -> str: """ Sensor temperature_mode +---------------------------------+------------------+ | Mode | Value | +=================================+==================+ | :py:const:`bmp384.TEMP_DISABLE` | :py:const:`0x00` | +---------------------------------+------------------+ | :py:const:`bmp384.TEMP_ENABLE` | :py:const:`0x01` | +---------------------------------+------------------+ """ values = ( "TEMP_DISABLE", "TEMP_ENABLE", ) return values[self._temperature_mode] @temperature_mode.setter def temperature_mode(self, value: int) -> None: if value not in temperature_mode_values: raise ValueError("Value must be a valid temperature_mode setting") self._temperature_mode = value @property def pressure_mode(self) -> str: """ Sensor pressure_mode +----------------------------------+------------------+ | Mode | Value | +==================================+==================+ | :py:const:`bmp384.PRESS_DISABLE` | :py:const:`0x00` | +----------------------------------+------------------+ | :py:const:`bmp384.PRESS_ENABLE` | :py:const:`0x01` | +----------------------------------+------------------+ """ values = ( "PRESS_DISABLE", "PRESS_ENABLE", ) return values[self._pressure_mode] @pressure_mode.setter def pressure_mode(self, value: int) -> None: if value not in pressure_mode_values: raise ValueError("Value must be a valid pressure_mode setting") self._pressure_mode = value @property def filter_coefficients(self) -> str: """ Sensor filter_coefficients +---------------------------------------+------------------+ | Mode | Value | +=======================================+==================+ | :py:const:`bmp384.IIR_FILTER_DISABLE` | :py:const:`0x00` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X2` | :py:const:`0x01` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X4` | :py:const:`0x02` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X8` | :py:const:`0x03` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X16` | :py:const:`0x04` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X32` | :py:const:`0x05` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X64` | :py:const:`0x06` | +---------------------------------------+------------------+ | :py:const:`bmp384.IIR_FILTER_X128` | :py:const:`0x07` | +---------------------------------------+------------------+ """ values = ( "IIR_FILTER_DISABLE", "IIR_FILTER_X2", "IIR_FILTER_X4", "IIR_FILTER_X8", "IIR_FILTER_X16", "IIR_FILTER_X32", "IIR_FILTER_X64", "IIR_FILTER_X128", ) return values[self._filter_coefficients] @filter_coefficients.setter def filter_coefficients(self, value: int) -> None: if value not in filter_coefficients_values: raise ValueError("Value must be a valid filter_coefficients setting") self._filter_coefficients = value @property def pressure_oversample(self) -> str: """ Sensor pressure_oversample +---------------------------------------+------------------+ | Mode | Value | +=======================================+==================+ | :py:const:`bmp384.OVERSAMPLE_DISABLE` | :py:const:`0x00` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X2` | :py:const:`0x01` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X4` | :py:const:`0x02` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X8` | :py:const:`0x03` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X16` | :py:const:`0x04` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X32` | :py:const:`0x05` | +---------------------------------------+------------------+ """ values = ( "OVERSAMPLE_DISABLE", "OVERSAMPLE_X2", "OVERSAMPLE_X4", "OVERSAMPLE_X8", "OVERSAMPLE_X16", "OVERSAMPLE_X32", ) return values[self._pressure_oversample] @pressure_oversample.setter def pressure_oversample(self, value: int) -> None: if value not in pressure_oversample_values: raise ValueError("Value must be a valid pressure_oversample setting") self._pressure_oversample = value @property def temperature_oversample(self) -> str: """ Sensor temperature_oversample +---------------------------------------+------------------+ | Mode | Value | +=======================================+==================+ | :py:const:`bmp384.OVERSAMPLE_DISABLE` | :py:const:`0x00` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X2` | :py:const:`0x01` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X4` | :py:const:`0x02` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X8` | :py:const:`0x03` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X16` | :py:const:`0x04` | +---------------------------------------+------------------+ | :py:const:`bmp384.OVERSAMPLE_X32` | :py:const:`0x05` | +---------------------------------------+------------------+ """ values = ( "OVERSAMPLE_DISABLE", "OVERSAMPLE_X2", "OVERSAMPLE_X4", "OVERSAMPLE_X8", "OVERSAMPLE_X16", "OVERSAMPLE_X32", ) return values[self._temperature_oversample] @temperature_oversample.setter def temperature_oversample(self, value: int) -> None: if value not in temperature_oversample_values: raise ValueError("Value must be a valid temperature_oversample setting") self._temperature_oversample = value @property def output_data_rate(self) -> int: """ Sensor output_data_rate. for a complete list of values please see the datasheet """ return self._output_data_rate @output_data_rate.setter def output_data_rate(self, value: int) -> None: if value not in range(0, 18, 1): raise ValueError("Value must be a valid output_data_rate setting") self._output_data_rate = value @property def temperature(self) -> float: """ The temperature sensor in Celsius :return: Temperature """ # pylint: disable = invalid-name, too-many-locals raw_temp = self._temperature T1, T2, T3 = self._temp_calib pd1 = raw_temp - T1 pd2 = pd1 * T2 temperature = pd2 + (pd1 * pd1) * T3 return temperature @property def pressure(self) -> float: """ The sensor pressure in hPa :return: Pressure in hPa """ # pylint: disable = invalid-name, too-many-locals temperature = self.temperature raw_pressure = self._pressure P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11 = self._pressure_calib pd1 = P6 * temperature pd2 = P7 * temperature**2.0 pd3 = P8 * temperature**3.0 po1 = P5 + pd1 + pd2 + pd3 pd1 = P2 * temperature pd2 = P3 * temperature**2.0 pd3 = P4 * temperature**3.0 po2 = raw_pressure * (P1 + pd1 + pd2 + pd3) pd1 = raw_pressure**2.0 pd2 = P9 + P10 * temperature pd3 = pd1 * pd2 pd4 = pd3 + P11 * raw_pressure**3.0 pressure = po1 + po2 + pd4 return pressure / 100 def _read_coefficients(self) -> None: """Read & save the calibration coefficients This is from adafruit_bmp3xx library""" coeff = self._coeffs self._temp_calib = ( coeff[0] / 2**-8.0, coeff[1] / 2**30.0, coeff[2] / 2**48.0, ) self._pressure_calib = ( (coeff[3] - 2**14.0) / 2**20.0, (coeff[4] - 2**14.0) / 2**29.0, coeff[5] / 2**32.0, coeff[6] / 2**37.0, coeff[7] / 2**-3.0, coeff[8] / 2**6.0, coeff[9] / 2**8.0, coeff[10] / 2**15.0, coeff[11] / 2**48.0, coeff[12] / 2**48.0, coeff[13] / 2**65.0, )