Source code for bma423

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

BMA423 Bosch Accelerometer CircuitPython Driver included in the Lilygo Watch V3


* Author(s): Jose D. Montoya


"""
import time
from micropython import const
from adafruit_bus_device import i2c_device
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct
from adafruit_register.i2c_bits import RWBits
from adafruit_register.i2c_bit import RWBit

try:
    from busio import I2C
    from typing import Tuple
except ImportError:
    pass

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

_REG_WHOAMI = const(0x00)
_PWR_CTRL = const(0x7D)
_ACC_RANGE = const(0x41)
_ACC_CONF = const(0x40)


# Acceleration range
ACC_RANGE_2 = const(0x00)
ACC_RANGE_4 = const(0x01)
ACC_RANGE_8 = const(0x02)
ACC_RANGE_16 = const(0x03)
acc_range_values = (ACC_RANGE_2, ACC_RANGE_4, ACC_RANGE_8, ACC_RANGE_16)
acc_range_factor = {0x00: 1024, 0x01: 512, 0x02: 256, 0x03: 128}

# Output Data Rate
BANDWIDTH_25_32 = const(0b0001)
BANDWIDTH_25_16 = const(0b0010)
BANDWIDTH_25_8 = const(0b0011)
BANDWIDTH_25_4 = const(0b0100)
BANDWIDTH_25_2 = const(0b0101)
BANDWIDTH_25 = const(0b0110)
BANDWIDTH_50 = const(0b0111)
BANDWIDTH_100 = const(0b1000)
BANDWIDTH_200 = const(0b1001)
BANDWIDTH_400 = const(0b1010)
BANDWIDTH_800 = const(0b1011)
BANDWIDTH_1600 = const(0b1100)
output_data_rate_values = (
    BANDWIDTH_25_32,
    BANDWIDTH_25_16,
    BANDWIDTH_25_8,
    BANDWIDTH_25_4,
    BANDWIDTH_25_2,
    BANDWIDTH_25,
    BANDWIDTH_50,
    BANDWIDTH_100,
    BANDWIDTH_200,
    BANDWIDTH_400,
    BANDWIDTH_800,
    BANDWIDTH_1600,
)

# 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)
oversample_rate_values = (OSR1, OSR2, OSR4, OSR8, OSR16, OSR32, OSR64, OSR128)

CIC_AVG = const(0x00)
CONT = const(0x01)
filter_performance_values = (CIC_AVG, CONT)


[docs]class BMA423: """Driver for the BMA400 Sensor connected over I2C. :param ~busio.I2C i2c_bus: The I2C bus the BMA423 is connected to. :param int address: The I2C device address. Defaults to :const:`0x19` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`BMA423` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import bma423 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 bma = bma423.BMA423(i2c) Now you have access to the attributes .. code-block:: python accx, accy, accz = bma.acceleration """ _device_id = ROUnaryStruct(_REG_WHOAMI, "B") _acc_on = RWBit(_PWR_CTRL, 2) # ACC_CONF (0x40) # | acc_perf_mode | acc_bwp(2) | acc_bwp(1) | acc_bwp(0) | odr(3) | odr(2) | odr(1) | odr(0) | _output_data_rate = RWBits(4, _ACC_CONF, 0) _oversample_rate = RWBits(3, _ACC_CONF, 4) _filter_performance = RWBit(_ACC_CONF, 7) # ACC_RANGE (0x41) # | ---- | ---- | ---- | ---- | ---- | ---- | acc_range(1) | acc_range(0) | _acc_range = RWBits(2, _ACC_RANGE, 0) # Acceleration Data _accx_value_LSB = UnaryStruct(0x12, "B") _accx_value_MSB = UnaryStruct(0x13, "B") _accy_value_LSB = UnaryStruct(0x14, "B") _accy_value_MSB = UnaryStruct(0x15, "B") _accz_value_LSB = UnaryStruct(0x16, "B") _accz_value_MSB = UnaryStruct(0x17, "B") _temperature = ROUnaryStruct(0x22, "B") def __init__(self, i2c_bus: I2C, address: int = 0x19) -> None: self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != 0x13: raise RuntimeError("Failed to find BMA423") self._acc_on = True self._acc_range_mem = self._acc_range @property def acceleration(self) -> Tuple[float, float, float]: """ Acceleration :return: acceleration """ alx = self._accx_value_LSB & 0xF0 ahx = self._accx_value_MSB << 8 totx = (alx | ahx) >> 4 totalx = self._twos_comp(totx, 12) aly = self._accy_value_LSB & 0xF0 ahy = self._accy_value_MSB << 8 toty = (aly + ahy) >> 4 totaly = self._twos_comp(toty, 12) alz = self._accz_value_LSB & 0xF0 ahz = self._accz_value_MSB << 8 totz = (alz + ahz) >> 4 totalz = self._twos_comp(totz, 12) factor = acc_range_factor[self._acc_range_mem] return totalx / factor, totaly / factor, totalz / factor @property def acc_range(self) -> str: """ Sensor acc_range +---------------------------------+------------------+ | Mode | Value | +=================================+==================+ | :py:const:`bma423.ACC_RANGE_2` | :py:const:`0x00` | +---------------------------------+------------------+ | :py:const:`bma423.ACC_RANGE_4` | :py:const:`0x01` | +---------------------------------+------------------+ | :py:const:`bma423.ACC_RANGE_8` | :py:const:`0x02` | +---------------------------------+------------------+ | :py:const:`bma423.ACC_RANGE_16` | :py:const:`0x03` | +---------------------------------+------------------+ """ values = ( "ACC_RANGE_2", "ACC_RANGE_4", "ACC_RANGE_8", "ACC_RANGE_16", ) return values[self._acc_range_mem] @acc_range.setter def acc_range(self, value: int) -> None: if value not in acc_range_values: raise ValueError("Value must be a valid acc_range setting") self._acc_range = value self._acc_range_mem = value @property def temperature(self) -> float: """ The temperature sensor is calibrated with a precision of +/-5°C. :return: Temperature """ raw_temp = self._temperature time.sleep(0.16) temp = self._twos_comp(raw_temp, 8) return temp + 23 @property def output_data_rate(self) -> str: """ Sensor output_data_rate +------------------------------------+--------------------+ | Mode | Value | +====================================+====================+ | :py:const:`bma423.BANDWIDTH_25_32` | :py:const:`0b0001` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_25_16` | :py:const:`0b0010` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_25_8` | :py:const:`0b0011` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_25_4` | :py:const:`0b0100` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_25_2` | :py:const:`0b0101` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_25` | :py:const:`0b0110` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_50` | :py:const:`0b0111` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_100` | :py:const:`0b1000` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_200` | :py:const:`0b1001` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_400` | :py:const:`0b1010` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_800` | :py:const:`0b1011` | +------------------------------------+--------------------+ | :py:const:`bma423.BANDWIDTH_1600` | :py:const:`0b1100` | +------------------------------------+--------------------+ """ values = ( "BANDWIDTH_25_32", "BANDWIDTH_25_16", "BANDWIDTH_25_8", "BANDWIDTH_25_4", "BANDWIDTH_25_2", "BANDWIDTH_25", "BANDWIDTH_50", "BANDWIDTH_100", "BANDWIDTH_200", "BANDWIDTH_400", "BANDWIDTH_800", "BANDWIDTH_1600", ) return values[self._output_data_rate] @output_data_rate.setter def output_data_rate(self, value: int) -> None: if value not in output_data_rate_values: raise ValueError("Value must be a valid output_data_rate setting") self._output_data_rate = value @property def oversample_rate(self) -> str: """ Sensor oversample_rate. Bandwidth parameter, determines filter configuration (acc_perf_mode=1) and averaging for undersampling mode (acc_perf_mode=0) +---------------------------+------------------+ | Mode | Value | +===========================+==================+ | :py:const:`bma423.OSR1` | :py:const:`0x00` | +---------------------------+------------------+ | :py:const:`bma423.OSR2` | :py:const:`0x01` | +---------------------------+------------------+ | :py:const:`bma423.OSR4` | :py:const:`0x02` | +---------------------------+------------------+ | :py:const:`bma423.OSR8` | :py:const:`0x03` | +---------------------------+------------------+ | :py:const:`bma423.OSR16` | :py:const:`0x04` | +---------------------------+------------------+ | :py:const:`bma423.OSR32` | :py:const:`0x05` | +---------------------------+------------------+ | :py:const:`bma423.OSR64` | :py:const:`0x06` | +---------------------------+------------------+ | :py:const:`bma423.OSR128` | :py:const:`0x07` | +---------------------------+------------------+ """ values = ( "OSR1", "OSR2", "OSR4", "OSR8", "OSR16", "OSR32", "OSR64", "OSR128", ) return values[self._oversample_rate] @oversample_rate.setter def oversample_rate(self, value: int) -> None: if value not in oversample_rate_values: raise ValueError("Value must be a valid oversample_rate setting") self._filter_performance = True self._oversample_rate = value @property def filter_performance(self) -> str: """ Sensor filter_performance +----------------------------+------------------+ | Mode | Value | +============================+==================+ | :py:const:`bma423.CIC_AVG` | :py:const:`0x00` | +----------------------------+------------------+ | :py:const:`bma423.CONT` | :py:const:`0x01` | +----------------------------+------------------+ """ values = ( "CIC_AVG", "CONT", ) return values[self._filter_performance] @filter_performance.setter def filter_performance(self, value: int) -> None: if value not in filter_performance_values: raise ValueError("Value must be a valid filter_performance setting") self._filter_performance = value @staticmethod def _twos_comp(val: int, bits: int) -> int: if val & (1 << (bits - 1)) != 0: return val - (1 << bits) return val