# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`bmp581`
================================================================================
MicroPython Driver for the Bosch BMP581 pressure sensor
* Author: Jose D. Montoya
"""
from micropython import const
from micropython_bmp581.i2c_helpers import CBits, RegisterStruct
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/MicroPython_BMP581.git"
_REG_WHOAMI = const(0x01)
_OSR_CONF = const(0x36)
_ODR_CONFIG = const(0x37)
# Power Modes
STANDBY = const(0x00)
NORMAL = const(0x01)
FORCED = const(0x02)
NON_STOP = const(0x03)
power_mode_values = (STANDBY, NORMAL, FORCED, NON_STOP)
# Oversample Rate
OSR1 = const(0x00)
OSR2 = const(0x01)
OSR4 = const(0x02)
OSR8 = const(0x03)
OSR16 = const(0x04)
OSR32 = const(0x05)
OSR64 = const(0x06)
OSR128 = const(0x07)
pressure_oversample_rate_values = (OSR1, OSR2, OSR4, OSR8, OSR16, OSR32, OSR64, OSR128)
temperature_oversample_rate_values = (
OSR1,
OSR2,
OSR4,
OSR8,
OSR16,
OSR32,
OSR64,
OSR128,
)
[docs]
class BMP581:
"""Driver for the BMP581 Sensor connected over I2C.
:param ~machine.I2C i2c: The I2C bus the BMP581 is connected to.
:param int address: The I2C device address. Defaults to :const:`0x47`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`BMP581` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
from machine import Pin, I2C
from micropython_bmp581 import bmp581
Once this is done you can define your `machine.I2C` object and define your sensor object
.. code-block:: python
i2c = I2C(1, sda=Pin(2), scl=Pin(3))
bmp581 = bmp581.BMP581(i2c)
Now you have access to the attributes
.. code-block:: python
press = bmp.pressure
"""
_device_id = RegisterStruct(_REG_WHOAMI, "B")
_power_mode = CBits(2, _ODR_CONFIG, 0)
_temperature_oversample_rate = CBits(3, _OSR_CONF, 0)
_pressure_oversample_rate = CBits(3, _OSR_CONF, 3)
_output_data_rate = CBits(5, _ODR_CONFIG, 2)
_pressure_enabled = CBits(1, _OSR_CONF, 6)
_temperature = CBits(24, 0x1D, 0, 3)
_pressure = CBits(24, 0x20, 0, 3)
def __init__(self, i2c, address: int = 0x47) -> None:
self._i2c = i2c
self._address = address
if self._device_id != 0x50:
raise RuntimeError("Failed to find the BMP581 sensor")
self._power_mode = NORMAL
self._pressure_enabled = True
self.sea_level_pressure = 101.325
@property
def power_mode(self) -> str:
"""
Sensor power_mode
+-----------------------------+------------------+
| Mode | Value |
+=============================+==================+
| :py:const:`bmp581.STANDBY` | :py:const:`0x00` |
+-----------------------------+------------------+
| :py:const:`bmp581.NORMAL` | :py:const:`0x01` |
+-----------------------------+------------------+
| :py:const:`bmp581.FORCED` | :py:const:`0x02` |
+-----------------------------+------------------+
| :py:const:`bmp581.NON_STOP` | :py:const:`0X03` |
+-----------------------------+------------------+
"""
values = (
"STANDBY",
"NORMAL",
"FORCED",
"NON_STOP",
)
return values[self._power_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._power_mode = value
@property
def pressure_oversample_rate(self) -> str:
"""
Sensor pressure_oversample_rate
Oversampling extends the measurement time per measurement by the oversampling
factor. Higher oversampling factors offer decreased noise at the cost of
higher power consumption.
+---------------------------+------------------+
| Mode | Value |
+===========================+==================+
| :py:const:`bmp581.OSR1` | :py:const:`0x00` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR2` | :py:const:`0x01` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR4` | :py:const:`0x02` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR8` | :py:const:`0x03` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR16` | :py:const:`0x04` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR32` | :py:const:`0x05` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR64` | :py:const:`0x06` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR128` | :py:const:`0x07` |
+---------------------------+------------------+
"""
values = (
"OSR1",
"OSR2",
"OSR4",
"OSR8",
"OSR16",
"OSR32",
"OSR64",
"OSR128",
)
return values[self._pressure_oversample_rate]
@pressure_oversample_rate.setter
def pressure_oversample_rate(self, value: int) -> None:
if value not in pressure_oversample_rate_values:
raise ValueError("Value must be a valid pressure_oversample_rate setting")
self._pressure_oversample_rate = value
@property
def temperature_oversample_rate(self) -> str:
"""
Sensor temperature_oversample_rate
+---------------------------+------------------+
| Mode | Value |
+===========================+==================+
| :py:const:`bmp581.OSR1` | :py:const:`0x00` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR2` | :py:const:`0x01` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR4` | :py:const:`0x02` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR8` | :py:const:`0x03` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR16` | :py:const:`0x04` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR32` | :py:const:`0x05` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR64` | :py:const:`0x06` |
+---------------------------+------------------+
| :py:const:`bmp581.OSR128` | :py:const:`0x07` |
+---------------------------+------------------+
"""
values = (
"OSR1",
"OSR2",
"OSR4",
"OSR8",
"OSR16",
"OSR32",
"OSR64",
"OSR128",
)
return values[self._temperature_oversample_rate]
@temperature_oversample_rate.setter
def temperature_oversample_rate(self, value: int) -> None:
if value not in temperature_oversample_rate_values:
raise ValueError(
"Value must be a valid temperature_oversample_rate setting"
)
self._temperature_oversample_rate = 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, 32, 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 in Celsius
"""
raw_temp = self._temperature
return self._twos_comp(raw_temp, 24) / 2**16.0
@property
def pressure(self) -> float:
"""
The sensor pressure in kPa
:return: Pressure in kPa
"""
raw_pressure = self._pressure
return self._twos_comp(raw_pressure, 24) / 2**6.0 / 1000.0
@property
def altitude(self) -> float:
"""
With the measured pressure p and the pressure at sea level p0 e.g. 1013.25hPa,
the altitude in meters can be calculated with the international barometric formula
"""
# was ** 0.190284, should be ** 0.190295 or better ** (1.0 / 5.255))
altitude = 44330.0 * (
1.0 - ((self.pressure / self.sea_level_pressure) ** (1.0 / 5.255))
)
# was round(altitude, 1), rounding limits accuracy to 100cm, sensor +/- cm
return altitude
@altitude.setter
def altitude(self, value: float) -> None:
self.sea_level_pressure = self.pressure / (1.0 - value / 44330.0) ** 5.255
@staticmethod
def _twos_comp(val: int, bits: int) -> int:
if val & (1 << (bits - 1)) != 0:
return val - (1 << bits)
return val