Source code for micropython_mc3479.mc3479

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

MC3479 Accelerometer MicroPython Driver


* Author: Jose D. Montoya


"""

from micropython import const
from micropython_mc3479.i2c_helpers import CBits, RegisterStruct

try:
    from typing import Tuple
except ImportError:
    pass

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

_REG_WHOAMI = const(0x98)
_SENSOR_STATUS_REG = const(0x05)
_MODE_REG = const(0x07)
_ACC_RANGE = const(0x20)
_ACC_DATA_RATE = const(0x08)

# Acceleration Data
ACC_X_LSB = const(0x0D)
ACC_X_MSB = const(0x0E)
ACC_Y_LSB = const(0x0F)
ACC_Y_MSB = const(0x10)
ACC_Z_LSB = const(0x11)
ACC_Z_MSB = const(0x12)

# Sensor Power
STANDBY = const(0)
NORMAL = const(1)

# Acceleration Range
ACCEL_RANGE_2G = const(0b000)
ACCEL_RANGE_4G = const(0b001)
ACCEL_RANGE_8G = const(0b010)
ACCEL_RANGE_16G = const(0b011)
ACCEL_RANGE_12G = const(0b100)
accel_range_values = (
    ACCEL_RANGE_2G,
    ACCEL_RANGE_4G,
    ACCEL_RANGE_8G,
    ACCEL_RANGE_16G,
    ACCEL_RANGE_12G,
)

LPF_ENABLE = const(1)
LPF_DISABLE = const(0)

BANDWIDTH_1 = const(0b001)
BANDWIDTH_2 = const(0b010)
BANDWIDTH_3 = const(0b011)
BANDWIDTH_5 = const(0b101)
lpf_setting_values = (BANDWIDTH_1, BANDWIDTH_2, BANDWIDTH_3, BANDWIDTH_5)

# Acceleration Output Rate HZ

BANDWIDTH_50 = const(0x08)  # 50 Hz
BANDWIDTH_100 = const(0x09)  # 100 Hz
BANDWIDTH_125 = const(0xA)  # 125 Hz
BANDWIDTH_200 = const(0xB)  # 200 Hz
BANDWIDTH_250 = const(0xC)  # 250 Hz
BANDWIDTH_500 = const(0xD)  # 500 Hz
BANDWIDTH_1000 = const(0xE)  # 1000 Hz
BANDWIDTH_2000 = const(0xF)  # 2000 Hz
acceleration_output_data_rate_values = (
    BANDWIDTH_50,
    BANDWIDTH_100,
    BANDWIDTH_125,
    BANDWIDTH_200,
    BANDWIDTH_250,
    BANDWIDTH_500,
    BANDWIDTH_1000,
    BANDWIDTH_2000,
)


[docs] class MC3479: """Driver for the MC3479 Sensor connected over I2C. :param ~machine.I2C i2c: The I2C bus the MC3479 is connected to. :param int address: The I2C device address. Defaults to :const:`0x4C` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`micropython_mc3479.MC3479` class. First you will need to import the libraries to use the sensor .. code-block:: python from machine import Pin, I2C import micropython_mc3479 as MC3479 Once this is done you can define your `machine.I2C` object and define your sensor object .. code-block:: python i2c = I2C(sda=Pin(8), scl=Pin(9)) # Correct I2C pins for UM FeatherS2 mc3479 = MC3479.MC3479(i2c) Now you have access to the attributes .. code-block:: python accx, accy, accz = mc3479.acceleration """ _device_id = RegisterStruct(_REG_WHOAMI, "B") _status = RegisterStruct(_SENSOR_STATUS_REG, "B") _mode_reg = RegisterStruct(_MODE_REG, "B") _range_scale_control = RegisterStruct(_ACC_RANGE, "B") _data_rate = RegisterStruct(_ACC_DATA_RATE, "B") # Acceleration Data _acc_data_x_msb = RegisterStruct(ACC_X_MSB, "B") _acc_data_x_lsb = RegisterStruct(ACC_X_LSB, "B") _acc_data_y_msb = RegisterStruct(ACC_Y_MSB, "B") _acc_data_y_lsb = RegisterStruct(ACC_Y_LSB, "B") _acc_data_z_msb = RegisterStruct(ACC_Z_MSB, "B") _acc_data_z_lsb = RegisterStruct(ACC_Z_LSB, "B") _mode = CBits(2, _MODE_REG, 0) # Acceleration Range Conf (0x20) _acc_range = CBits(3, _ACC_RANGE, 4) _acc_lpf_en = CBits(1, _ACC_RANGE, 3) _acc_lpf_setting = CBits(3, _ACC_RANGE, 0) acceleration_scale = { "ACCEL_RANGE_2G": 16384, "ACCEL_RANGE_4G": 8192, "ACCEL_RANGE_8G": 4096, "ACCEL_RANGE_16G": 2048, "ACCEL_RANGE_12G": 2730, } def __init__(self, i2c, address: int = 0x4C) -> None: self._i2c = i2c self._address = address if self._device_id != 0xA4: raise RuntimeError("Failed to find the MC3479 sensor") self._mode = NORMAL @property def acceleration(self) -> Tuple[float, float, float]: """ The device has the ability to read all sampled readings in a continuous sampling fashion. The device always updates the XOUT, YOUT, and ZOUT registers at the chosen output data rate X, Y, and Z-axis accelerometer measurements are in 16-bit, signed 2's complement format. Register addresses 0x0D to 0x12 hold the latest sampled data from the X, Y, and Z accelerometers. """ factor = self.acceleration_scale[self.acceleration_range] x = (self._acc_data_x_msb * 256 + self._acc_data_x_lsb) / factor y = (self._acc_data_y_msb * 256 + self._acc_data_y_lsb) / factor z = (self._acc_data_z_msb * 256 + self._acc_data_z_lsb) / factor return x, y, z @property def sensor_mode(self) -> str: """ Standby ******** * Lowest power consumption * Internal clocking is halted * No motion detection, sampling, or calibration * The I2C/SPI bus can read and write to registers (resolution, range, thresholds and other settings can be changed) * Reset not allowed * Default state after a power-up Normal ******* * Highest power consumption * Internal clocking is enabled * Continuous motion detection and sampling; automatic calibration is available * The I2C/SPI bus can only write to the mode register and read all other registers * Reset allowed +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`MC3479.STANDBY` | :py:const:`0` | +----------------------------------------+-------------------------+ | :py:const:`MC3479.NORMAL` | :py:const:`1` | +----------------------------------------+-------------------------+ """ values = ("STANDBY", "NORMAL") return values[self._mode] @sensor_mode.setter def sensor_mode(self, value: int) -> None: if value not in (STANDBY, NORMAL): raise ValueError("Invalid Sensor Mode") self._mode = value @property def acceleration_range(self) -> str: """ The range and scale control register sets the resolution, range, and filtering options for the accelerometer. All values are in sign-extended 2's complement format. Values are reported in registers 0x0D - 0x12 (the hardware formats the output) +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`MC3479.ACCEL_RANGE_2G` | :py:const:`0b000` | +----------------------------------------+-------------------------+ | :py:const:`MC3479.ACCEL_RANGE_4G` | :py:const:`0b001` | +----------------------------------------+-------------------------+ | :py:const:`MC3479.ACCEL_RANGE_8G` | :py:const:`0b010` | +----------------------------------------+-------------------------+ | :py:const:`MC3479.ACCEL_RANGE_16G` | :py:const:`0b011` | +----------------------------------------+-------------------------+ | :py:const:`MC3479.ACCEL_RANGE_12G` | :py:const:`0b100` | +----------------------------------------+-------------------------+ Example ######## .. code-block:: python i2c = I2C(sda=Pin(8), scl=Pin(9)) # Correct I2C pins for UM FeatherS2 mc3479 = MC3479.MC3479(i2c) mc3479.acceleration_range = MC3479.ACCEL_RANGE_12G """ values = ( "ACCEL_RANGE_2G", "ACCEL_RANGE_4G", "ACCEL_RANGE_8G", "ACCEL_RANGE_16G", "ACCEL_RANGE_12G", ) return values[self._acc_range] @acceleration_range.setter def acceleration_range(self, value: int) -> None: if value not in accel_range_values: raise ValueError("Invalid Acceleration Range") self._mode = STANDBY self._acc_range = value self._mode = NORMAL @property def lpf_enabled(self) -> str: """ Low Power Filter Enabler +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`MC3479.LPF_ENABLE` | :py:const:`0b0` | +----------------------------------------+-------------------------+ | :py:const:`MC3479.LPF_DISABLE` | :py:const:`0b1` | +----------------------------------------+-------------------------+ Example --------------------- .. code-block:: python i2c = I2C(sda=Pin(8), scl=Pin(9)) # Correct I2C pins for UM FeatherS2 mc3479 = MC3479.MC3479(i2c) mc3479.lpf_enabled = MC3479.LPF_ENABLE """ values = ("LPF_DISABLE", "LPF_ENABLE") return values[self._acc_lpf_en] @lpf_enabled.setter def lpf_enabled(self, value: int) -> None: if value not in (LPF_ENABLE, LPF_DISABLE): raise ValueError("Invalid Low Pass Filter Setting") self._mode = STANDBY self._acc_lpf_en = value self._mode = NORMAL @property def lpf_setting(self) -> str: """ Selects the Bandwidth for the Low Power Filter. Depends on the selection of the ODR/IDR +--------------------------------+------------------------------------+ | Mode | Value | +================================+====================================+ | :py:const:`MC3479.BANDWIDTH_1` | :py:const:`0b001` Fc = IDR / 4.255 | +--------------------------------+------------------------------------+ | :py:const:`MC3479.BANDWIDTH_2` | :py:const:`0b010` Fc = IDR / 6 | +--------------------------------+------------------------------------+ | :py:const:`MC3479.BANDWIDTH_3` | :py:const:`0b011` Fc = IDR / 12 | +--------------------------------+------------------------------------+ | :py:const:`MC3479.BANDWIDTH_5` | :py:const:`0b101` Fc = IDR / 16 | +--------------------------------+------------------------------------+ Example --------------------- .. code-block:: python i2c = I2C(sda=Pin(8), scl=Pin(9)) # Correct I2C pins for UM FeatherS2 mc3479 = MC3479.MC3479(i2c) mc3479.lpf_setting = MC3479.BANDWIDTH_5 """ values = { 1: "BANDWIDTH_1", 2: "BANDWIDTH_2", 3: "BANDWIDTH_3", 5: "BANDWIDTH_5", } return values[self._acc_lpf_setting] @lpf_setting.setter def lpf_setting(self, value: int) -> None: if value not in lpf_setting_values: raise ValueError("Invalid Low Pass Filter Setting") self._mode = STANDBY self._acc_lpf_setting = value self._mode = NORMAL @property def acceleration_output_data_rate(self) -> str: """ Define the output data rate in Hz The output data rate is dependent of the power mode setting for the sensor +----------------------------------------+---------------------------------+ | Mode | Value | +========================================+=================================+ | :py:const:`MC3479.BANDWIDTH_50` | :py:const:`0x08` 50 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_100` | :py:const:`0x09` 100 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_125` | :py:const:`0xA` 125 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_200` | :py:const:`0xB` 200 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_250` | :py:const:`0xC` 250 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_500` | :py:const:`0xD` 500 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_1000` | :py:const:`0xE` 1000 Hz | +----------------------------------------+---------------------------------+ | :py:const:`MC3479.BANDWIDTH_2000` | :py:const:`0xF` 2000 Hz | +----------------------------------------+---------------------------------+ Example ######## .. code-block:: python i2c = I2C(sda=Pin(8), scl=Pin(9)) # Correct I2C pins for UM FeatherS2 mc3479 = MC3479.MC3479(i2c) mc3479.acceleration_output_data_rate = MC3479.BANDWIDTH_500 """ values = { 0x08: "BANDWIDTH_50", 0x09: "BANDWIDTH_100", 0xA: "BANDWIDTH_125", 0xB: "BANDWIDTH_200", 0xC: "BANDWIDTH_250", 0xD: "BANDWIDTH_500", 0xE: "BANDWIDTH_1000", 0xF: "BANDWIDTH_2000", } return values[self._data_rate] @acceleration_output_data_rate.setter def acceleration_output_data_rate(self, value: int) -> None: if value not in acceleration_output_data_rate_values: raise ValueError("Invalid Output Data Rate") self._mode = STANDBY self._data_rate = value self._mode = NORMAL