Source code for bma400

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

BMA400 Bosch Accelerometer CircuitPython Driver


* 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, Struct
from adafruit_register.i2c_bits import RWBits

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


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


_REG_WHOAMI = const(0x00)
_ACC_CONFIG0 = const(0x19)
_ACC_CONFIG1 = const(0x1A)
_ACC_CONFIG2 = const(0x1B)

# Power Modes
SLEEP_MODE = const(0x00)
LOW_POWER_MODE = const(0x01)
NORMAL_MODE = const(0x02)
SWITCH_TO_SLEEP = const(0x03)
power_mode_values = (SLEEP_MODE, LOW_POWER_MODE, NORMAL_MODE, SWITCH_TO_SLEEP)

# Output Data rate
ACCEL_12_5HZ = const(0x05)
ACCEL_25HZ = const(0x06)
ACCEL_50HZ = const(0x07)
ACCEL_100HZ = const(0x08)
ACCEL_200HZ = const(0x09)
ACCEL_400HZ = const(0xA4)
ACCEL_800HZ = const(0xB8)
output_data_rate_values = (
    ACCEL_12_5HZ,
    ACCEL_25HZ,
    ACCEL_50HZ,
    ACCEL_100HZ,
    ACCEL_200HZ,
    ACCEL_400HZ,
    ACCEL_800HZ,
)

# Filter Bandwidth
ACC_FILT_BW0 = const(0x00)
ACC_FILT_BW1 = const(0x01)
filter_bandwidth_values = (ACC_FILT_BW0, ACC_FILT_BW1)

# Oversampling
OVERSAMPLING_0 = const(0x00)
OVERSAMPLING_1 = const(0x01)
OVERSAMPLING_2 = const(0x02)
OVERSAMPLING_3 = const(0x03)
oversampling_rate_values = (
    OVERSAMPLING_0,
    OVERSAMPLING_1,
    OVERSAMPLING_2,
    OVERSAMPLING_3,
)

# 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}

# Source Data registers
ACC_FILT1 = const(0x00)
ACC_FILT2 = const(0x01)
ACC_FILT_LP = const(0x02)
source_data_registers_values = (ACC_FILT1, ACC_FILT2, ACC_FILT_LP)

# pylint: disable=too-few-public-methods
[docs]class BMA400: """Driver for the BMA400 Sensor connected over I2C. :param ~busio.I2C i2c_bus: The I2C bus the BMA400 is connected to. :param int address: The I2C device address. Defaults to :const:`0x14` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`BMA400` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import bma400 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 = bma400.BMA400(i2c) Now you have access to the attributes .. code-block:: python accx, accy, accz = bma.acceleration """ _device_id = ROUnaryStruct(_REG_WHOAMI, "B") # ACC_CONFIG0 (0x19) # | filt1_bw | osr_lp(1) | osr_lp(0) | ---- | ---- | ---- | power_mode(1) | power_mode(0) | _power_mode = RWBits(2, _ACC_CONFIG0, 0) _filter_bandwidth = RWBits(1, _ACC_CONFIG0, 7) # ACC_CONFIG1 (0x1A) # | acc_range(1) | acc_range(0) | osr(1) | osr(0) | odr(3) | odr(2) | odr(1) | odr(0) | _output_data_rate = RWBits(4, _ACC_CONFIG1, 0) _oversampling_rate = RWBits(2, _ACC_CONFIG1, 4) _acc_range = RWBits(2, _ACC_CONFIG1, 6) # ACC_CONFIG2 (0x1A) # | ---- | ---- | ---- | ---- | data_src_reg(1) | data_src_reg(0) | ---- | ---- | _source_data_registers = RWBits(2, _ACC_CONFIG2, 2) # Acceleration Data _acceleration = Struct(0x04, "<hhh") _temperature = ROUnaryStruct(0x11, "B") def __init__(self, i2c_bus: I2C, address: int = 0x14) -> None: self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != 0x90: raise RuntimeError("Failed to find BMA400") self._power_mode = NORMAL_MODE self._acc_range_mem = self._acc_range @property def power_mode(self) -> str: """ Sensor power_mode In sleep mode, data conversions are stopped as well as sensortime functionality. In low power mode, data conversion runs with a fixed rate of 25Hz. The low power mode should be mainly used in combination with activity detection as self wake-up mode. In normal mode, output data rates between 800Hz and 12.5Hz. +------------------------------------+------------------+ | Mode | Value | +====================================+==================+ | :py:const:`bma400.SLEEP_MODE` | :py:const:`0x00` | +------------------------------------+------------------+ | :py:const:`bma400.LOW_POWER_MODE` | :py:const:`0x01` | +------------------------------------+------------------+ | :py:const:`bma400.NORMAL_MODE` | :py:const:`0x02` | +------------------------------------+------------------+ | :py:const:`bma400.SWITCH_TO_SLEEP` | :py:const:`0x03` | +------------------------------------+------------------+ """ values = ( "SLEEP_MODE", "LOW_POWER_MODE", "NORMAL_MODE", "SWITCH_TO_SLEEP", ) 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 output_data_rate(self) -> str: """ Sensor output_data_rate +---------------------------------+------------------+ | Mode | Value | +=================================+==================+ | :py:const:`bma400.ACCEL_12_5HZ` | :py:const:`0x05` | +---------------------------------+------------------+ | :py:const:`bma400.ACCEL_25HZ` | :py:const:`0x06` | +---------------------------------+------------------+ | :py:const:`bma400.ACCEL_50HZ` | :py:const:`0x07` | +---------------------------------+------------------+ | :py:const:`bma400.ACCEL_100HZ` | :py:const:`0x08` | +---------------------------------+------------------+ | :py:const:`bma400.ACCEL_200HZ` | :py:const:`0x09` | +---------------------------------+------------------+ | :py:const:`bma400.ACCEL_400HZ` | :py:const:`0xA4` | +---------------------------------+------------------+ | :py:const:`bma400.ACCEL_800HZ` | :py:const:`0xB8` | +---------------------------------+------------------+ """ values = { 0x05: "ACCEL_12_5HZ", 0x06: "ACCEL_25HZ", 0x07: "ACCEL_50HZ", 0x08: "ACCEL_100HZ", 0x09: "ACCEL_200HZ", 0xA4: "ACCEL_400HZ", 0xB8: "ACCEL_800HZ", } 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 oversampling_rate(self) -> str: """ Sensor oversampling_rate oversampling rate 0/1/2/3 for normal mode osr=0: lowest power, lowest oversampling rate, lowest accuracy osr=3: highest accuracy, highest oversampling rate, highest power settings 0, 1, 2 and 3 allow linearly trading power versus accuracy(noise) +-----------------------------------+------------------+ | Mode | Value | +===================================+==================+ | :py:const:`bma400.OVERSAMPLING_0` | :py:const:`0x00` | +-----------------------------------+------------------+ | :py:const:`bma400.OVERSAMPLING_1` | :py:const:`0x01` | +-----------------------------------+------------------+ | :py:const:`bma400.OVERSAMPLING_2` | :py:const:`0x02` | +-----------------------------------+------------------+ | :py:const:`bma400.OVERSAMPLING_3` | :py:const:`0x03` | +-----------------------------------+------------------+ """ values = ( "OVERSAMPLING_0", "OVERSAMPLING_1", "OVERSAMPLING_2", "OVERSAMPLING_3", ) return values[self._oversampling_rate] @oversampling_rate.setter def oversampling_rate(self, value: int) -> None: if value not in oversampling_rate_values: raise ValueError("Value must be a valid oversampling_rate setting") self._oversampling_rate = value @property def acc_range(self) -> str: """ Sensor acc_range +---------------------------------+------------------+ | Mode | Value | +=================================+==================+ | :py:const:`bma400.ACC_RANGE_2` | :py:const:`0x00` | +---------------------------------+------------------+ | :py:const:`bma400.ACC_RANGE_4` | :py:const:`0x01` | +---------------------------------+------------------+ | :py:const:`bma400.ACC_RANGE_8` | :py:const:`0x02` | +---------------------------------+------------------+ | :py:const:`bma400.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 filter_bandwidth(self) -> str: """ Sensor filter_bandwidth Data rate between 800Hz and 12.5Hz, controlled by :attr:`output_data_rate`. Its bandwidth can be configured additionally by :attr:`filter_bandwidth`: +---------------------------------+-----------------------------+ | Mode | Value | +=================================+=============================+ | :py:const:`bma400.ACC_FILT_BW0` | :py:const:`0x00` 0.48 x ODR | +---------------------------------+-----------------------------+ | :py:const:`bma400.ACC_FILT_BW1` | :py:const:`0x01` 0.24 x ODR | +---------------------------------+-----------------------------+ """ values = ( "ACC_FILT_BW0", "ACC_FILT_BW1", ) return values[self._filter_bandwidth] @filter_bandwidth.setter def filter_bandwidth(self, value: int) -> None: if value not in filter_bandwidth_values: raise ValueError("Value must be a valid filter_bandwidth setting") self._filter_bandwidth = value @property def source_data_registers(self) -> str: """ Sensor source_data_registers +--------------------------------+------------------+ | Mode | Value | +================================+==================+ | :py:const:`bma400.ACC_FILT1` | :py:const:`0x00` | +--------------------------------+------------------+ | :py:const:`bma400.ACC_FILT2` | :py:const:`0x01` | +--------------------------------+------------------+ | :py:const:`bma400.ACC_FILT_LP` | :py:const:`0x02` | +--------------------------------+------------------+ """ values = ( "ACC_FILT1", "ACC_FILT2", "ACC_FILT_LP", ) return values[self._source_data_registers] @source_data_registers.setter def source_data_registers(self, value: int) -> None: if value not in source_data_registers_values: raise ValueError("Value must be a valid source_data_registers setting") self._source_data_registers = value @property def acceleration(self) -> Tuple[int, int, int]: """ Acceleration :return: acceleration """ rawx, rawy, rawz = self._acceleration if rawx > 2047: rawx = rawx - 4096 if rawy > 2047: rawy = rawy - 4096 if rawz > 2047: rawz = rawz - 4096 factor = acc_range_factor[self._acc_range_mem] return rawx / factor, rawy / factor, rawz / factor @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 * 0.5) + 23 @staticmethod def _twos_comp(val: int, bits: int) -> int: if val & (1 << (bits - 1)) != 0: return val - (1 << bits) return val