# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`adxl343`
================================================================================
MicroPython Driver for the Analog Devices ADXL343 Accelerometer
* Author(s): Jose D. Montoya
"""
from micropython import const
from micropython_adxl343.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_ADXL343.git"
_STANDARD_GRAVITY = 9.80665
_REG_WHOAMI = const(0x00)
_POWER_CTL = const(0x2D)
_DATA_FORMAT = const(0x31)
_ACC = const(0x32)
_THRESH_TAP = const(0x1D)
_INT_ENABLE = const(0x2E)
_INT_SOURCE = const(0x30)
_TAP_AXES = const(0x2A)
_DUR = const(0x21)
_LATENT = const(0x22)
_WINDOW = const(0x23)
_THRESH_ACT = const(0x24)
_THRESH_INACT = const(0x25)
_TIME_INACT = const(0x26)
_ACT_INACT_CTL = const(0x27)
STANDBY = const(0b0)
READY = const(0b1)
measurement_mode_values = (STANDBY, READY)
LOW_RES = const(0b0)
HIGH_RES = const(0b1)
resolution_mode_values = (LOW_RES, HIGH_RES)
RANGE_2 = const(0b00)
RANGE_4 = const(0b01)
RANGE_8 = const(0b10)
RANGE_16 = const(0b11)
acceleration_range_values = (RANGE_2, RANGE_4, RANGE_8, RANGE_16)
ST_DISABLED = const(0b0)
ST_ENABLED = const(0b1)
single_tap_mode_values = (ST_DISABLED, ST_ENABLED)
DT_DISABLED = const(0b0)
DT_ENABLED = const(0b1)
double_tap_mode_values = (DT_DISABLED, DT_ENABLED)
ACTIVITY_DISABLED = const(0b0)
ACTIVITY_ENABLED = const(0b1)
activity_mode_values = (ACTIVITY_DISABLED, ACTIVITY_ENABLED)
[docs]
class ADXL343:
"""Driver for the ADXL343 Sensor connected over I2C.
:param ~machine.I2C i2c: The I2C bus the ADXL343 is connected to.
:param int address: The I2C device address. Defaults to :const:`0x53`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`ADXL343` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
from machine import Pin, I2C
from micropython_adxl343 import adxl343
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))
adxl = adxl343.ADXL343(i2c)
Now you have access to the attributes
.. code-block:: python
accx, accy, accz = adx.acceleration
"""
# Device Data
_device_id = RegisterStruct(_REG_WHOAMI, "B")
# Acceleration Data
_acceleration_data = RegisterStruct(_ACC, "<hhh")
# Tap Information
_tap_threshold = RegisterStruct(_THRESH_TAP, "B")
_tap_duration = RegisterStruct(_DUR, "B")
_tap_latent = RegisterStruct(_LATENT, "B")
_tap_window = RegisterStruct(_WINDOW, "B")
# Activity Information
_activity_threshold = RegisterStruct(_THRESH_ACT, "B")
# Inactivity Information
_inactivity_threshold = RegisterStruct(_THRESH_INACT, "B")
_inactivity_duration = RegisterStruct(_TIME_INACT, "B")
# Acceleration Config
_measurement_mode = CBits(1, _POWER_CTL, 3)
_resolution_mode = CBits(1, _DATA_FORMAT, 3)
_acceleration_range = CBits(2, _DATA_FORMAT, 0)
# Tap Configuration
_single_tap_mode = CBits(1, _INT_ENABLE, 6)
_single_tap_mode_interrupt = CBits(1, _INT_SOURCE, 6)
_single_tap_enable_axes = CBits(3, _TAP_AXES, 0)
# Double Tap Configuration
_double_tap_mode_interrupt = CBits(1, _INT_SOURCE, 5)
_double_tap_mode = CBits(1, _INT_ENABLE, 5)
_double_tap_enable_axes = CBits(3, _TAP_AXES, 0)
# Activity Configuration
_activity_mode = CBits(1, _INT_ENABLE, 4)
_activity_interrupt = CBits(1, _INT_SOURCE, 4)
_activity_enable_axes = CBits(3, _ACT_INACT_CTL, 4)
# Inactivity Configuration
_inactivity_mode = CBits(1, _INT_ENABLE, 3)
_inactivity_interrupt = CBits(1, _INT_SOURCE, 3)
_inactivity_enable_axes = CBits(3, _ACT_INACT_CTL, 0)
def __init__(self, i2c, address: int = 0x53) -> None:
self._i2c = i2c
self._address = address
if self._device_id != 0xE5:
raise RuntimeError("Failed to find the ADXL343 sensor")
self._measurement_mode = True
self._resolution_mode = True
self._cached_resolution = 0.004
@property
def measurement_mode(self) -> str:
"""
Sensor measurement_mode. Selecting 0 or `False` places the part
into standby mode, and a setting of 1 or `True` places the part
into measurement mode.
The ADXL343 powers up in standby mode with minimum power
consumption.
+-----------------------------+-----------------+
| Mode | Value |
+=============================+=================+
| :py:const:`adxl343.STANDBY` | :py:const:`0b0` |
+-----------------------------+-----------------+
| :py:const:`adxl343.READY` | :py:const:`0b1` |
+-----------------------------+-----------------+
"""
values = (
"STANDBY",
"READY",
)
return values[self._measurement_mode]
@measurement_mode.setter
def measurement_mode(self, value: int) -> None:
if value not in measurement_mode_values:
raise ValueError("Value must be a valid measurement_mode setting")
self._measurement_mode = value
@property
def acceleration(self) -> Tuple[float, float, float]:
"""
Acceleration Data in :math:`m / s ^ 2`
"""
x, y, z = self._acceleration_data
x = x * _STANDARD_GRAVITY * self._cached_resolution
y = y * _STANDARD_GRAVITY * self._cached_resolution
z = z * _STANDARD_GRAVITY * self._cached_resolution
return x, y, z
@property
def acceleration_range(self) -> str:
"""
Sensor acceleration_range
+------------------------------+------------------+
| Mode | Value |
+==============================+==================+
| :py:const:`adxl343.RANGE_2` | :py:const:`0b00` |
+------------------------------+------------------+
| :py:const:`adxl343.RANGE_4` | :py:const:`0b01` |
+------------------------------+------------------+
| :py:const:`adxl343.RANGE_8` | :py:const:`0b10` |
+------------------------------+------------------+
| :py:const:`adxl343.RANGE_16` | :py:const:`0b11` |
+------------------------------+------------------+
"""
values = ("RANGE_2", "RANGE_4", "RANGE_8", "RANGE_16")
return values[self._acceleration_range]
@acceleration_range.setter
def acceleration_range(self, value: int) -> None:
if value not in acceleration_range_values:
raise ValueError("Value must be a valid acceleration_range setting")
if self._resolution_mode == 0:
res_values = {0: 0.004, 1: 0.008, 2: 0.016, 3: 0.031}
self._cached_resolution = res_values[value]
else:
self._cached_resolution = 0.004
self._acceleration_range = value
@property
def resolution_mode(self) -> str:
"""
Sensor resolution_mode. When :attr:`resolution_mode` is set to `True`,
the device is in full resolution mode, where the output resolution
increases with the g range set by the range bits to maintain a 4
mg/LSB scale factor. When is set to `False`, the device is in 10-bit
mode, and the range bits determine the maximum g
:attr:`acceleration_range` and scale factor.
+------------------------------+-----------------+
| Mode | Value |
+==============================+=================+
| :py:const:`adxl343.LOW_RES` | :py:const:`0b0` |
+------------------------------+-----------------+
| :py:const:`adxl343.HIGH_RES` | :py:const:`0b1` |
+------------------------------+-----------------+
"""
values = (
"LOW_RES",
"HIGH_RES",
)
return values[self._resolution_mode]
@resolution_mode.setter
def resolution_mode(self, value: int) -> None:
if value not in resolution_mode_values:
raise ValueError("Value must be a valid resolution_mode setting")
self._resolution_mode = value
if value == 0:
res_values = {0: 0.004, 1: 0.008, 2: 0.016, 3: 0.031}
self._cached_resolution = res_values[self._acceleration_range]
else:
self._cached_resolution = 0.004
@property
def tap_threshold(self) -> float:
"""
Tap threshold in :math:`m / s ^ 2`
:return:
"""
return self._tap_threshold * 0.0627451 * _STANDARD_GRAVITY
@tap_threshold.setter
def tap_threshold(self, value: float) -> None:
if 156 < value < 1:
raise ValueError("Value should be a valid tap_threshold setting")
self._tap_threshold = int(value / _STANDARD_GRAVITY / 0.0627451)
@property
def single_tap_mode(self) -> str:
"""
Sensor single_tap_mode
+---------------------------------+-----------------+
| Mode | Value |
+=================================+=================+
| :py:const:`adxl343.ST_DISABLED` | :py:const:`0b0` |
+---------------------------------+-----------------+
| :py:const:`adxl343.ST_ENABLED` | :py:const:`0b1` |
+---------------------------------+-----------------+
"""
values = (
"ST_DISABLED",
"ST_ENABLED",
)
return values[self._single_tap_mode]
@single_tap_mode.setter
def single_tap_mode(self, value: int) -> None:
if value not in single_tap_mode_values:
raise ValueError("Value must be a valid single_tap_mode setting")
self._single_tap_mode = value
if value == 1:
self._single_tap_enable_axes = 0b111
else:
self._single_tap_enable_axes = 0
@property
def single_tap_activated(self) -> bool:
"""
Returns if a single tap event was detected
:return: bool
"""
values = {0: False, 1: True}
return values[self._single_tap_mode_interrupt]
@property
def tap_duration(self) -> float:
"""
Tap threshold in us. Maximum time that an event must be above the
:attr:`tap_threshold` to qualify as a tap event. The scale factor
is 625 μs/LSB.
A value of 0 disables the single tap/ double tap functions
"""
return self._tap_duration * 625
@tap_duration.setter
def tap_duration(self, value: int) -> None:
if 159000 < value < 1:
raise ValueError("Value should be a valid tap_duration setting")
self._tap_duration = int(value / 625)
@property
def tap_latent(self) -> float:
"""
Wait time from the detection of a tap event to the
start of the time window during which a possible second tap event can
be detected. The scale factor is 1.25 ms/LSB.
A value of 0 disables the double tap function.
"""
return self._tap_latent * 1.25
@tap_latent.setter
def tap_latent(self, value: int) -> None:
if 318 < value < 1:
raise ValueError("Value should be a valid tap_latent setting")
self._tap_latent = int(value / 1.25)
@property
def tap_window(self) -> float:
"""
Time after the expiration of the latency time during which a
second valid tap can begin.
The scale factor is 1.25 ms/LSB.
A value of 0 disables the double tap function
"""
return self._tap_window * 1.25
@tap_window.setter
def tap_window(self, value: int) -> None:
if 318 < value < 1:
raise ValueError("Value should be a valid tap_window setting")
self._tap_window = int(value / 1.25)
@property
def double_tap_mode(self) -> str:
"""
Sensor double_tap_mode
Every mechanical system has somewhat different single tap/double
tap responses based on the mechanical characteristics of the
system. Therefore, some experimentation is required. In general,
a good starting point is to set the :attr:`tap_duration` to a value
greater 10 ms, the :attr:`tap_latent` to a value greater than
20 ms, the :attr:`tap_window` to a value greater than 80 ms,
and the :attr:`tap_threshold` to a value greater than 3 g.
Setting a very low values may result in an unpredictable response
due to the accelerometer picking up echoes of the tap inputs.
+---------------------------------+-----------------+
| Mode | Value |
+=================================+=================+
| :py:const:`adxl343.DT_DISABLED` | :py:const:`0b0` |
+---------------------------------+-----------------+
| :py:const:`adxl343.DT_ENABLED` | :py:const:`0b1` |
+---------------------------------+-----------------+
"""
values = (
"DT_DISABLED",
"DT_ENABLED",
)
return values[self._double_tap_mode]
@double_tap_mode.setter
def double_tap_mode(self, value: int) -> None:
if value not in double_tap_mode_values:
raise ValueError("Value must be a valid double_tap_mode setting")
self._double_tap_mode = value
if value == 1:
self._double_tap_enable_axes = 0b111
else:
self._double_tap_enable_axes = 0
@property
def double_tap_activated(self) -> bool:
"""
Returns if a double tap event was detected
:return: bool
"""
values = {0: False, 1: True}
return values[self._double_tap_mode_interrupt]
@property
def activity_threshold(self) -> float:
"""
Activity threshold in :math:`m / s ^ 2`
:return:
"""
return self._activity_threshold * 0.0627451 * _STANDARD_GRAVITY
@activity_threshold.setter
def activity_threshold(self, value: float) -> None:
if 156 < value < 1:
raise ValueError("Value should be a valid activity_threshold setting")
self._activity_threshold = int(value / _STANDARD_GRAVITY / 0.0627451)
@property
def activity_mode(self) -> str:
"""
Sensor activity_mode
+---------------------------------------+-----------------+
| Mode | Value |
+=======================================+=================+
| :py:const:`adxl343.ACTIVITY_DISABLED` | :py:const:`0b0` |
+---------------------------------------+-----------------+
| :py:const:`adxl343.ACTIVITY_ENABLED` | :py:const:`0b1` |
+---------------------------------------+-----------------+
"""
values = (
"ACTIVITY_DISABLED",
"ACTIVITY_ENABLED",
)
return values[self._activity_mode]
@activity_mode.setter
def activity_mode(self, value: int) -> None:
if value not in activity_mode_values:
raise ValueError("Value must be a valid activity_mode setting")
self._activity_mode = value
if value == 1:
self._activity_enable_axes = 0b111
else:
self._activity_enable_axes = 0
@property
def activity_detected(self) -> bool:
"""
Returns if an activity was detected
:return: bool
"""
values = {0: False, 1: True}
return values[self._activity_interrupt]