Source code for micropython_h3lis200dl.h3lis200dl

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

Micropython Driver for the ST H3LIS200DL Accelerometer


* Author(s): Jose D. Montoya


"""

from collections import namedtuple
from micropython import const
from micropython_h3lis200dl.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_H3LIS200DL.git"

_REG_WHOAMI = const(0x0F)
_CTRL_REG1 = const(0x20)
_CTRL_REG2 = const(0x21)
_CTRL_REG3 = const(0x22)
_CTRL_REG4 = const(0x23)
_INT1_CFG = const(0x30)
_INT1_SRC = const(0x31)
_INT1_THS = const(0x32)
_INT1_DURATION = const(0x33)
_INT2_CFG = const(0x34)
_INT2_SRC = const(0x35)
_INT2_THS = const(0x36)
_INT2_DURATION = const(0x37)


_ACC_X = const(0x29)
_ACC_Y = const(0x2B)
_ACC_Z = const(0x2D)

_G_TO_ACCEL = 9.80665

POWER_DOWN = const(0b000)
NORMAL_MODE = const(0b001)
LOW_POWER_ODR0_5 = const(0b010)
LOW_POWER_ODR1 = const(0b011)
LOW_POWER_ODR2 = const(0b100)
LOW_POWER_ODR5 = const(0b101)
LOW_POWER_ODR10 = const(0b110)
operation_mode_values = (
    POWER_DOWN,
    NORMAL_MODE,
    LOW_POWER_ODR0_5,
    LOW_POWER_ODR1,
    LOW_POWER_ODR2,
    LOW_POWER_ODR5,
    LOW_POWER_ODR10,
)

RATE_50HZ = const(0b00)
RATE_100HZ = const(0b01)
RATE_400HZ = const(0b10)
RATE_1000HZ = const(0b11)
data_rate_values = (RATE_50HZ, RATE_100HZ, RATE_400HZ, RATE_1000HZ)

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

SCALE_100G = const(0b0)
SCALE_200G = const(0b1)
full_scale_selection_values = (SCALE_100G, SCALE_200G)
full_scale = {SCALE_100G: 100, SCALE_200G: 200}

FILTER_NORMAL_MODE = const(0b00)
FILTER_SIGNAL_FILTERING = const(0b01)
high_pass_filter_mode_values = (FILTER_NORMAL_MODE, FILTER_SIGNAL_FILTERING)

HPCF8 = const(0b00)
HPCF16 = const(0b01)
HPCF32 = const(0b10)
HPCF64 = const(0b11)
high_pass_filter_cutoff_values = (HPCF8, HPCF16, HPCF32, HPCF64)

AlertStatus = namedtuple("AlertStatus", ["high_g", "low_g"])


# pylint: disable=too-many-instance-attributes
[docs] class H3LIS200DL: """Driver for the H3LIS200DL Sensor connected over I2C. The H3LIS200DL is a low-power high-performance 3-axis linear accelerometer The H3LIS200DL has scales of ±100g/±200g and is capable of measuring accelerations with output data rates from 0.5 Hz to 1 kHz. :param ~machine.I2C i2c: The I2C bus the H3LIS200DL 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:`H3LIS200DL` class. First you will need to import the libraries to use the sensor .. code-block:: python from machine import Pin, I2C from micropython_h3lis200dl import h3lis200dl 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)) h3lis200dl = h3lis200dl.H3LIS200DL(i2c) Now you have access to the attributes .. code-block:: python accx, accy, accz = h3lis200dl.acceleration """ _device_id = RegisterStruct(_REG_WHOAMI, "B") _int1_configuration = RegisterStruct(_INT1_CFG, "B") _int1_source_register = RegisterStruct(_INT1_SRC, "B") _int1_threshold = RegisterStruct(_INT1_THS, "B") _int1_duration = RegisterStruct(_INT1_DURATION, "B") _int1_latched = CBits(1, _CTRL_REG3, 2) _int2_configuration = RegisterStruct(_INT2_CFG, "B") _int2_source_register = RegisterStruct(_INT2_SRC, "B") _int2_threshold = RegisterStruct(_INT2_THS, "B") _int2_duration = RegisterStruct(_INT2_DURATION, "B") _int2_latched = CBits(1, _CTRL_REG3, 5) # Acceleration Data _acc_data_x = RegisterStruct(_ACC_X, "B") _acc_data_y = RegisterStruct(_ACC_Y, "B") _acc_data_z = RegisterStruct(_ACC_Z, "B") _full_scale_selection = CBits(1, _CTRL_REG4, 4) # Register CTRL_REG1 (0x20) # |PM2|PM1|PM0|DR1|DR0|Zen|Yen|Xen| _operation_mode = CBits(3, _CTRL_REG1, 5) _data_rate = CBits(2, _CTRL_REG1, 3) _z_enabled = CBits(1, _CTRL_REG1, 2) _y_enabled = CBits(1, _CTRL_REG1, 1) _x_enabled = CBits(1, _CTRL_REG1, 0) # Register CTRL_REG2 (0x21) # |BOOT|HPM1|HPM0|FDS|HPen2|HPen1|HPCF1|HPCF0| _high_pass_filter_mode = CBits(2, _CTRL_REG2, 5) _high_pass_filter_cutoff = CBits(2, _CTRL_REG2, 0) def __init__(self, i2c, address: int = 0x19) -> None: self._i2c = i2c self._address = address if self._device_id != 0x32: raise RuntimeError("Failed to find the H3LIS200DL sensor") self._operation_mode = NORMAL_MODE self._memory_full_scale_selection = self._full_scale_selection @property def operation_mode(self) -> str: """ Sensor operation_mode allow the user to select between power-down and two operating active modes. The device is in power-down mode when the PD bits are set to “000” (default value after boot). Table shows all the possible power mode configurations and respective output data rates. Output data in the low-power mode are computed with the low-pass filter cutoff frequency defined by the :attr:`data_rate` +-----------------------------------------+-------------------+ | Mode | Value | +=========================================+===================+ | :py:const:`h3lis200dl.POWER_DOWN` | :py:const:`0b000` | +-----------------------------------------+-------------------+ | :py:const:`h3lis200dl.NORMAL_MODE` | :py:const:`0b001` | +-----------------------------------------+-------------------+ | :py:const:`h3lis200dl.LOW_POWER_ODR0_5` | :py:const:`0b010` | +-----------------------------------------+-------------------+ | :py:const:`h3lis200dl.LOW_POWER_ODR1` | :py:const:`0b011` | +-----------------------------------------+-------------------+ | :py:const:`h3lis200dl.LOW_POWER_ODR2` | :py:const:`0b100` | +-----------------------------------------+-------------------+ | :py:const:`h3lis200dl.LOW_POWER_ODR5` | :py:const:`0b101` | +-----------------------------------------+-------------------+ | :py:const:`h3lis200dl.LOW_POWER_ODR10` | :py:const:`0b110` | +-----------------------------------------+-------------------+ """ values = ( "POWER_DOWN", "NORMAL_MODE", "LOW_POWER_ODR0_5", "LOW_POWER_ODR1", "LOW_POWER_ODR2", "LOW_POWER_ODR5", "LOW_POWER_ODR10", ) return values[self._operation_mode] @operation_mode.setter def operation_mode(self, value: int) -> None: if value not in operation_mode_values: raise ValueError("Value must be a valid operation_mode setting") self._operation_mode = value @property def acceleration(self) -> Tuple[float, float, float]: """ Acceleration property :return: Acceleration data """ x = ( self._twos_comp(self._acc_data_x, 8) * full_scale[self._memory_full_scale_selection] / 128 ) y = ( self._twos_comp(self._acc_data_y, 8) * full_scale[self._memory_full_scale_selection] / 128 ) z = ( self._twos_comp(self._acc_data_z, 8) * full_scale[self._memory_full_scale_selection] / 128 ) return x, y, z @property def full_scale_selection(self) -> str: """ Sensor full_scale_selection +-----------------------------------+-----------------+ | Mode | Value | +===================================+=================+ | :py:const:`h3lis200dl.SCALE_100G` | :py:const:`0b0` | +-----------------------------------+-----------------+ | :py:const:`h3lis200dl.SCALE_200G` | :py:const:`0b1` | +-----------------------------------+-----------------+ """ values = ( "SCALE_100G", "SCALE_200G", ) return values[self._full_scale_selection] @full_scale_selection.setter def full_scale_selection(self, value: int) -> None: if value not in full_scale_selection_values: raise ValueError("Value must be a valid full_scale_selection setting") self._full_scale_selection = value self._memory_full_scale_selection = value @staticmethod def _twos_comp(val: int, bits: int) -> int: if val & (1 << (bits - 1)) != 0: return val - (1 << bits) return val @property def x_enabled(self) -> str: """ Sensor x_enabled In order to optimize further power consumption of the h3lis200dl, data evaluation of individual axes can be deactivated. Per default, all three axes are active. +-----------------------------------+-----------------+ | Mode | Value | +===================================+=================+ | :py:const:`h3lis200dl.X_DISABLED` | :py:const:`0b0` | +-----------------------------------+-----------------+ | :py:const:`h3lis200dl.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 h3lis200dl, data evaluation of individual axes can be deactivated. Per default, all three axes are active. +-----------------------------------+-----------------+ | Mode | Value | +===================================+=================+ | :py:const:`h3lis200dl.Y_DISABLED` | :py:const:`0b0` | +-----------------------------------+-----------------+ | :py:const:`h3lis200dl.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 h3lis200dl, data evaluation of individual axes can be deactivated. Per default, all three axes are active. +-----------------------------------+-----------------+ | Mode | Value | +===================================+=================+ | :py:const:`h3lis200dl.Z_DISABLED` | :py:const:`0b0` | +-----------------------------------+-----------------+ | :py:const:`h3lis200dl.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 data_rate(self) -> str: """ Sensor data_rate selects the data rate at which acceleration samples are produced. In low-power modes they define the output data resolution. Table shows all the possible configurations +------------------------------------+------------------+ | Mode | Value | +====================================+==================+ | :py:const:`h3lis200dl.RATE_50HZ` | :py:const:`0b00` | +------------------------------------+------------------+ | :py:const:`h3lis200dl.RATE_100HZ` | :py:const:`0b01` | +------------------------------------+------------------+ | :py:const:`h3lis200dl.RATE_400HZ` | :py:const:`0b10` | +------------------------------------+------------------+ | :py:const:`h3lis200dl.RATE_1000HZ` | :py:const:`0b11` | +------------------------------------+------------------+ """ values = ("RATE_50HZ", "RATE_100HZ", "RATE_400HZ", "RATE_1000HZ") return values[self._data_rate] @data_rate.setter def data_rate(self, value: int) -> None: if value not in data_rate_values: raise ValueError("Value must be a valid data_rate setting") self._data_rate = value @property def high_pass_filter_mode(self) -> str: """ Sensor high_pass_filter_mode used to configure the high-pass filter cutoff frequency ft which is given by: .. math:: f_{t}= \\frac{f_{s}}{6*HP_{c}} +------------------------------------------------+------------------+ | Mode | Value | +================================================+==================+ | :py:const:`h3lis200dl.FILTER_NORMAL_MODE` | :py:const:`0b00` | +------------------------------------------------+------------------+ | :py:const:`h3lis200dl.FILTER_SIGNAL_FILTERING` | :py:const:`0b01` | +------------------------------------------------+------------------+ """ values = ("FILTER_NORMAL_MODE", "FILTER_SIGNAL_FILTERING") return values[self._high_pass_filter_mode] @high_pass_filter_mode.setter def high_pass_filter_mode(self, value: int) -> None: if value not in high_pass_filter_mode_values: raise ValueError("Value must be a valid high_pass_filter_mode setting") self._high_pass_filter_mode = value # pylint: disable=line-too-long @property def high_pass_filter_cutoff(self) -> str: """ Sensor high_pass_filter_cutoff +-------------------------------+------------------+----------------+-----------------+-----------------+------------------+ | Mode | Value |Data Rate=50 Hz |Data Rate=100 Hz |Data Rate=400 Hz |Data Rate=1000 Hz | +===============================+==================+================+=================+=================+==================+ | :py:const:`h3lis200dl.HPCF8` | :py:const:`0b00` | 1 | 2 | 8 | 20 | +-------------------------------+------------------+----------------+-----------------+-----------------+------------------+ | :py:const:`h3lis200dl.HPCF16` | :py:const:`0b01` | 0.5 | 1 | 4 | 10 | +-------------------------------+------------------+----------------+-----------------+-----------------+------------------+ | :py:const:`h3lis200dl.HPCF32` | :py:const:`0b10` | 0.25 | 0.50 | 2 | 5 | +-------------------------------+------------------+----------------+-----------------+-----------------+------------------+ | :py:const:`h3lis200dl.HPCF64` | :py:const:`0b11` | 0.125 | 0.25 | 1 | 2.5 | +-------------------------------+------------------+----------------+-----------------+-----------------+------------------+ """ values = ("HPCF8", "HPCF16", "HPCF32", "HPCF64") return values[self._high_pass_filter_cutoff] # pylint: enable=line-too-long @high_pass_filter_cutoff.setter def high_pass_filter_cutoff(self, value: int) -> None: if value not in high_pass_filter_cutoff_values: raise ValueError("Value must be a valid high_pass_filter_cutoff setting") self._high_pass_filter_cutoff = value @property def interrupt1_configuration(self): """ interrupt 1 configuration :return: interrupt 1 configuration """ return self._int1_configuration @interrupt1_configuration.setter def interrupt1_configuration(self, value: int): if value > 255: raise ValueError("value must be a valid setting") self._int1_configuration = value @property def interrupt1_threshold(self): """ interrupt 1 threshold :return: threshold """ return self._int1_threshold @interrupt1_threshold.setter def interrupt1_threshold(self, value: int): if value > 128: raise ValueError("value must be a valid setting") self._int1_threshold = value @property def interrupt1_duration(self): """ interrupt 1 duration set the minimum duration of the interrupt 1 event to be recognized. Duration steps and maximum values depend on the ODR chosen :return: interrupt 1 duration """ return self._int1_duration @interrupt1_duration.setter def interrupt1_duration(self, value: int): if value > 128: raise ValueError("value must be a valid setting") self._int1_duration = value @property def interrupt1_source_register(self): """ interrupt 1 source register. Gives Interrupt 1 Information """ dummy = self._int1_source_register highx = dummy & 0x03 == 2 highy = (dummy & 0xC) >> 2 == 2 highz = (dummy & 0x30) >> 4 == 2 return ( AlertStatus(high_g=highx, low_g=not highx), AlertStatus(high_g=highy, low_g=not highy), AlertStatus(high_g=highz, low_g=not highz), ) @property def interrupt1_latched(self): """ Latch interrupt request on the INT1_SRC register, with the INT1_SRC register cleared by reading the INT1_SRC register. Default value: 0. (0: interrupt request not latched; 1: interrupt request latched) """ return self._int1_latched @interrupt1_latched.setter def interrupt1_latched(self, value): self._int1_latched = value @property def interrupt2_configuration(self): """ interrupt 2 configuration :return: interrupt 2 configuration """ return self._int2_configuration @interrupt2_configuration.setter def interrupt2_configuration(self, value: int): if value > 255: raise ValueError("value must be a valid setting") self._int2_configuration = value @property def interrupt2_threshold(self): """ interrupt 2 threshold :return: threshold """ return self._int2_threshold @interrupt2_threshold.setter def interrupt2_threshold(self, value: int): if value > 128: raise ValueError("value must be a valid setting") self._int2_threshold = value @property def interrupt2_duration(self): """ interrupt 2 duration set the minimum duration of the interrupt 2 event to be recognized. Duration steps and maximum values depend on the ODR chosen :return: interrupt 2 duration """ return self._int2_duration @interrupt2_duration.setter def interrupt2_duration(self, value: int): if value > 128: raise ValueError("value must be a valid setting") self._int2_duration = value @property def interrupt2_source_register(self): """ interrupt 2 source register. Gives Interrupt 2 Information """ dummy = self._int2_source_register highx = dummy & 0x03 == 2 highy = (dummy & 0xC) >> 2 == 2 highz = (dummy & 0x30) >> 4 == 2 return ( AlertStatus(high_g=highx, low_g=not highx), AlertStatus(high_g=highy, low_g=not highy), AlertStatus(high_g=highz, low_g=not highz), ) @property def interrupt2_latched(self): """ Latch interrupt request on the INT2_SRC register, with the INT2_SRC register cleared by reading the INT2_SRC register. Default value: 0. (0: interrupt request not latched; 2: interrupt request latched) """ return self._int2_latched @interrupt2_latched.setter def interrupt2_latched(self, value): """ interrupt 2 duration """ self._int2_latched = value