Source code for bma220.bma220

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

BMA220 Bosch Circuitpython Driver library


* Author(s): 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
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_BMA220.git"


_REG_WHOAMI = const(0x00)
_FILTER_CONF = const(0x20)
_ACC_RANGE = const(0x22)
_SLEEP_CONF = const(0x0F)
_LATCH_CONF = const(0x1C)

# Acceleration range
ACC_RANGE_2 = const(0b00)
ACC_RANGE_4 = const(0b01)
ACC_RANGE_8 = const(0b10)
ACC_RANGE_16 = const(0b11)
acc_range_values = (ACC_RANGE_2, ACC_RANGE_4, ACC_RANGE_8, ACC_RANGE_16)
acc_range_factor = {0b00: 16, 0b01: 8, 0b10: 4, 0b11: 2}

SLEEP_DISABLED = const(0b0)
SLEEP_ENABLED = const(0b1)
sleep_enabled_values = (SLEEP_DISABLED, SLEEP_ENABLED)

# Sleep Duration
SLEEP_2MS = const(0b000)
SLEEP_10MS = const(0b001)
SLEEP_25MS = const(0b010)
SLEEP_50MS = const(0b011)
SLEEP_100MS = const(0b100)
SLEEP_500MS = const(0b101)
SLEEP_1S = const(0b110)
SLEEP_2S = const(0b111)
sleep_duration_values = (
    SLEEP_2MS,
    SLEEP_10MS,
    SLEEP_25MS,
    SLEEP_50MS,
    SLEEP_100MS,
    SLEEP_500MS,
    SLEEP_1S,
    SLEEP_2S,
)

# Axis Enabled Values
X_DISABLED = const(0b0)
X_ENABLED = const(0b1)
Y_DISABLED = const(0b0)
Y_ENABLED = const(0b1)
Z_DISABLED = const(0b0)
Z_ENABLED = const(0b1)
axis_enabled_values = (X_DISABLED, X_ENABLED)

# Filter Bandwidth
ACCEL_32HZ = const(0x05)
ACCEL_64HZ = const(0x04)
ACCEL_125HZ = const(0x03)
ACCEL_250HZ = const(0x02)
ACCEL_500HZ = const(0x01)
ACCEL_1000HZ = const(0x00)
filter_bandwidth_values = (
    ACCEL_32HZ,
    ACCEL_64HZ,
    ACCEL_125HZ,
    ACCEL_250HZ,
    ACCEL_500HZ,
    ACCEL_1000HZ,
)

UNLATCHED = const(0b000)
LATCH_FOR_025S = const(0b001)
LATCH_FOR_050S = const(0b010)
LATCH_FOR_1S = const(0b011)
LATCH_FOR_2S = const(0b100)
LATCH_FOR_4S = const(0b101)
LATCH_FOR_8S = const(0b110)
LATCHED = const(0b111)
latched_mode_values = (
    UNLATCHED,
    LATCH_FOR_025S,
    LATCH_FOR_050S,
    LATCH_FOR_1S,
    LATCH_FOR_2S,
    LATCH_FOR_4S,
    LATCH_FOR_8S,
    LATCHED,
)


[docs]class BMA220: """Driver for the BMA220 Sensor connected over I2C. :param ~busio.I2C i2c_bus: The I2C bus the BMA220 is connected to. :param int address: The I2C device address. Defaults to :const:`0x0A` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`BMA220` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import bma220 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 bma220 = bma220.BMA220(i2c) Now you have access to the attributes .. code-block:: python accx, accy, accz = bma.acceleration """ _device_id = ROUnaryStruct(_REG_WHOAMI, "B") _acc_range = RWBits(2, _ACC_RANGE, 0) _filter_bandwidth = RWBits(4, _FILTER_CONF, 0) _latched_mode = RWBits(3, _LATCH_CONF, 4) # Acceleration Data _acceleration = Struct(0x04, "BBB") # Register (0x0F) # |----|sleep_en|sleep_dur(2)|sleep_dur(1)|sleep_dur(0)|en_x_channel|en_y_channel|en_z_channel| _z_enabled = RWBit(_SLEEP_CONF, 0) _y_enabled = RWBit(_SLEEP_CONF, 1) _x_enabled = RWBit(_SLEEP_CONF, 2) _sleep_duration = RWBits(3, _SLEEP_CONF, 3) _sleep_enabled = RWBit(_SLEEP_CONF, 6) def __init__(self, i2c_bus: I2C, address: int = 0x0A) -> None: self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != 0xDD: raise RuntimeError("Failed to find BMA220") self._acc_range_mem = self._acc_range @property def acc_range(self) -> str: """ The BMA220 has four different range settings for the full scale acceleration range. In dependence of the use case always the lowest full scale range with the maximum resolution should be selected. Please refer to literature to find out, which full scale acceleration range, which sensitivity or which resolution is the ideal one. +---------------------------------+------------------+ | Mode | Value | +=================================+==================+ | :py:const:`bma220.ACC_RANGE_2` | :py:const:`0b00` | +---------------------------------+------------------+ | :py:const:`bma220.ACC_RANGE_4` | :py:const:`0b01` | +---------------------------------+------------------+ | :py:const:`bma220.ACC_RANGE_8` | :py:const:`0b10` | +---------------------------------+------------------+ | :py:const:`bma220.ACC_RANGE_16` | :py:const:`0b11` | +---------------------------------+------------------+ """ values = ( "ACC_RANGE_2", "ACC_RANGE_4", "ACC_RANGE_8", "ACC_RANGE_16", ) return values[self._acc_range] @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 sleep_enabled(self) -> str: """ The BMA220 supports a low-power mode. In this low-power mode, the chip wakes up periodically, enables the interrupt controller and goes back to sleep if no interrupt has occurred. The low-power mode can be enabled by setting :attr:`sleep_enabled` and by enabling the data ready interrupt (or any other interrupt, see chapter 5 in the datasheet) +-----------------------------------+-----------------+ | Mode | Value | +===================================+=================+ | :py:const:`bma220.SLEEP_DISABLED` | :py:const:`0b0` | +-----------------------------------+-----------------+ | :py:const:`bma220.SLEEP_ENABLED` | :py:const:`0b1` | +-----------------------------------+-----------------+ """ values = ("SLEEP_DISABLED", "SLEEP_ENABLED") return values[self._sleep_enabled] @sleep_enabled.setter def sleep_enabled(self, value: int) -> None: if value not in sleep_enabled_values: raise ValueError("Value must be a valid sleep_enabled setting") self._sleep_enabled = value @property def sleep_duration(self) -> str: """ Sensor sleep_duration. +--------------------------------+-------------------+ | Mode | Value | +================================+===================+ | :py:const:`bma220.SLEEP_2MS` | :py:const:`0b000` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_10MS` | :py:const:`0b001` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_25MS` | :py:const:`0b010` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_50MS` | :py:const:`0b011` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_100MS` | :py:const:`0b100` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_500MS` | :py:const:`0b101` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_1S` | :py:const:`0b110` | +--------------------------------+-------------------+ | :py:const:`bma220.SLEEP_2S` | :py:const:`0b111` | +--------------------------------+-------------------+ """ values = ( "SLEEP_2MS", "SLEEP_10MS", "SLEEP_25MS", "SLEEP_50MS", "SLEEP_100MS", "SLEEP_500MS", "SLEEP_1S", "SLEEP_2S", ) return values[self._sleep_duration] @sleep_duration.setter def sleep_duration(self, value: int) -> None: if value not in sleep_duration_values: raise ValueError("Value must be a valid sleep_duration setting") self._sleep_duration = value @property def x_enabled(self) -> str: """ Sensor x_enabled In order to optimize further power consumption of the BMA220, data evaluation of individual axes can be deactivated. Per default, all three axes are active. +-------------------------------+-----------------+ | Mode | Value | +===============================+=================+ | :py:const:`bma220.X_DISABLED` | :py:const:`0b0` | +-------------------------------+-----------------+ | :py:const:`bma220.X_ENABLED` | :py:const:`0b1` | +-------------------------------+-----------------+ """ values = ( "X_DISABLED", "X_ENABLED", ) return values[self._x_enabled] @x_enabled.setter def x_enabled(self, value: int) -> None: if value not in axis_enabled_values: raise ValueError("Value must be a valid x_enabled setting") self._x_enabled = value @property def y_enabled(self) -> str: """ Sensor y_enabled In order to optimize further power consumption of the BMA220, data evaluation of individual axes can be deactivated. Per default, all three axes are active. +-------------------------------+-----------------+ | Mode | Value | +===============================+=================+ | :py:const:`bma220.Y_DISABLED` | :py:const:`0b0` | +-------------------------------+-----------------+ | :py:const:`bma220.Y_ENABLED` | :py:const:`0b1` | +-------------------------------+-----------------+ """ values = ( "Y_DISABLED", "Y_ENABLED", ) return values[self._y_enabled] @y_enabled.setter def y_enabled(self, value: int) -> None: if value not in axis_enabled_values: raise ValueError("Value must be a valid y_enabled setting") self._y_enabled = value @property def z_enabled(self) -> str: """ Sensor z_enabled In order to optimize further power consumption of the BMA220, data evaluation of individual axes can be deactivated. Per default, all three axes are active. +-------------------------------+-----------------+ | Mode | Value | +===============================+=================+ | :py:const:`bma220.Z_DISABLED` | :py:const:`0b0` | +-------------------------------+-----------------+ | :py:const:`bma220.Z_ENABLED` | :py:const:`0b1` | +-------------------------------+-----------------+ """ values = ( "Z_DISABLED", "Z_ENABLED", ) return values[self._z_enabled] @z_enabled.setter def z_enabled(self, value: int) -> None: if value not in axis_enabled_values: raise ValueError("Value must be a valid z_enabled setting") self._z_enabled = value @property def filter_bandwidth(self) -> str: """ The BMA220 has a digital filter that can be configured. To always ensure an ideal cut off frequency of the filter the BMA220 is adjusting the sample rate automatically. +---------------------------------+------------------+ | Mode | Value | +=================================+==================+ | :py:const:`bma220.ACCEL_32HZ` | :py:const:`0x05` | +---------------------------------+------------------+ | :py:const:`bma220.ACCEL_64HZ` | :py:const:`0x04` | +---------------------------------+------------------+ | :py:const:`bma220.ACCEL_125HZ` | :py:const:`0x03` | +---------------------------------+------------------+ | :py:const:`bma220.ACCEL_250HZ` | :py:const:`0x02` | +---------------------------------+------------------+ | :py:const:`bma220.ACCEL_500HZ` | :py:const:`0x01` | +---------------------------------+------------------+ | :py:const:`bma220.ACCEL_1000HZ` | :py:const:`0x00` | +---------------------------------+------------------+ """ values = ( "ACCEL_32HZ", "ACCEL_64HZ", "ACCEL_125HZ", "ACCEL_250HZ", "ACCEL_500HZ", "ACCEL_1000HZ", ) 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 latched_mode(self) -> str: """ Sensor latched_mode The interrupt controller can be used in two modes - **Latched mode**: Once one of the configured interrupt conditions applies, the INT pin is asserted and must be reset by the external master through the digital interface. - **Non-Latched mode**: The interrupt controller clears the INT signal once the interrupt condition no longer applies. The interrupt output can be programmed by :attr:`latched_mode` to be either unlatched (‘000’) or latched permanently (‘111’) or have different latching times. +-----------------------------------+--------------------------------+ | Mode | Value | +===================================+================================+ | :py:const:`bma220.UNLATCHED` | :py:const:`0b000` | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCH_FOR_025S` | :py:const:`0b001` 0.25 seconds | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCH_FOR_050S` | :py:const:`0b010` 0.5 seconds | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCH_FOR_1S` | :py:const:`0b011` 1 second | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCH_FOR_2S` | :py:const:`0b100` 2 seconds | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCH_FOR_4S` | :py:const:`0b101` 4 seconds | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCH_FOR_8S` | :py:const:`0b110` 8 seconds | +-----------------------------------+--------------------------------+ | :py:const:`bma220.LATCHED` | :py:const:`0b111` | +-----------------------------------+--------------------------------+ """ values = ( "UNLATCHED", "LATCH_FOR_025S", "LATCH_FOR_050S", "LATCH_FOR_1S", "LATCH_FOR_2S", "LATCH_FOR_4S", "LATCH_FOR_8S", "LATCHED", ) return values[self._latched_mode] @latched_mode.setter def latched_mode(self, value: int) -> None: if value not in latched_mode_values: raise ValueError("Value must be a valid latched_mode setting") self._latched_mode = value @property def acceleration(self) -> Tuple[float, float, float]: """ Acceleration :return: acceleration """ bufx, bufy, bufz = self._acceleration factor = acc_range_factor[self._acc_range_mem] return ( self._twos_comp(bufx >> 2, 6) / factor, self._twos_comp(bufy >> 2, 6) / factor, self._twos_comp(bufz >> 2, 6) / factor, ) @staticmethod def _twos_comp(val: int, bits: int) -> int: if val & (1 << (bits - 1)) != 0: return val - (1 << bits) return val