# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`kx132`
================================================================================
CircuitPython Driver for the Kionix KX132 Accelerometer
* 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, UnaryStruct, 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_KX132.git"
_ADP = const(0x02)
_ACC = const(0x08)
_REG_WHOAMI = const(0x13)
_TILT_POSITION = const(0x14)
_PREVIOUS_TILT_POSITION = const(0x15)
_INS1 = const(0x16)
_ODCNTL = const(0x21)
_INT_REL = const(0x1A)
_CNTL1 = const(0x1B)
_CNTL2 = const(0x1C)
_CNTL5 = const(0x1F)
_FFTH = const(0x32)
_FFCNTL = const(0x34)
STANDBY_MODE = const(0b0)
NORMAL_MODE = const(0b1)
# 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 = {ACC_RANGE_2: 2, ACC_RANGE_4: 4, ACC_RANGE_8: 8, ACC_RANGE_16: 16}
TILT_DISABLED = const(0b0)
TILT_ENABLED = const(0b1)
tilt_position_enable_values = (TILT_DISABLED, TILT_ENABLED)
# Tap/Double Tap
TDTE_DISABLED = const(0b0)
TDTE_ENABLED = const(0b1)
tap_doubletap_enable_values = (TDTE_DISABLED, TDTE_ENABLED)
LOW_POWER_MODE = const(0b0)
HIGH_PERFORMANCE_MODE = const(0b1)
performance_mode_values = (LOW_POWER_MODE, HIGH_PERFORMANCE_MODE)
ADP_DISABLED = const(0b0)
ADP_ENABLED = const(0b1)
adp_enabled_values = (ADP_DISABLED, ADP_ENABLED)
FF_DISABLED = const(0b0)
FF_ENABLED = const(0b1)
free_fall_enabled_values = (FF_DISABLED, FF_ENABLED)
# pylint: disable=too-many-instance-attributes
[docs]
class KX132:
"""Driver for the KX132 Sensor connected over I2C.
:param ~busio.I2C i2c_bus: The I2C bus the KX132 is connected to.
:param int address: The I2C device address. Defaults to :const:`0x1F`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`KX132` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
import board
import kx132
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
kx = kx132.KX132(i2c)
Now you have access to the attributes
.. code-block:: python
accx, accy, accz = kx.acceleration
"""
_device_id = ROUnaryStruct(_REG_WHOAMI, "B")
_control_register1 = UnaryStruct(_CNTL1, "B")
_interrupt1 = UnaryStruct(_INS1, "B")
_interrupt_release = UnaryStruct(_INT_REL, "B")
_acceleration_data = Struct(_ACC, "hhh")
_adp_data = Struct(_ADP, "hhh")
_tilt_position = UnaryStruct(_TILT_POSITION, "B")
_previous_tilt_position = UnaryStruct(_PREVIOUS_TILT_POSITION, "B")
_free_fall_threshold = UnaryStruct(_FFCNTL, "B")
# Register CNTL1 (0x1B)
# |PC1|RES|DRDYE|GSEL1|GSEL0|TDTE|----|TPE|
_operating_mode = RWBit(_CNTL1, 7)
_performance_mode = RWBit(_CNTL1, 6)
_acc_range = RWBits(2, _CNTL1, 3)
_tap_doubletap_enable = RWBit(_CNTL1, 2)
_tilt_position_enable = RWBit(_CNTL1, 0)
_soft_reset = RWBit(_CNTL2, 7)
_adp_enabled = RWBit(_CNTL5, 4)
_free_fall_enabled = RWBit(_FFCNTL, 7)
# Register ODCNTL (0x21)
# |IIR_BYPASS|LPRO|FSTUP|----|OSA3|OSA2|OSA1|OSA0|
_output_data_rate = RWBits(4, _ODCNTL, 0)
def __init__(self, i2c_bus: I2C, address: int = 0x1F) -> None:
self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
if self._device_id != 0x3D:
raise RuntimeError("Failed to find KX132")
self._operating_mode = NORMAL_MODE
self.acc_range = ACC_RANGE_2
[docs]
def soft_reset(self):
"""
The Software Reset bit initiates software reset, which performs
the RAM reboot routine. This bit will remain 1 until the RAM
reboot routine is finished.
"""
self._operating_mode = STANDBY_MODE
self._soft_reset = 1
time.sleep(0.05)
self._operating_mode = NORMAL_MODE
@property
def acc_range(self) -> str:
"""
Acceleration range of the accelerometer outputs per Table.
This range is also called a full-scale range of the accelerometer.
+---------------------------------+------------------+
| Mode | Value |
+=================================+==================+
| :py:const:`kx132.ACC_RANGE_2` | :py:const:`0b00` |
+---------------------------------+------------------+
| :py:const:`kx132.ACC_RANGE_4` | :py:const:`0b01` |
+---------------------------------+------------------+
| :py:const:`kx132.ACC_RANGE_8` | :py:const:`0b10` |
+---------------------------------+------------------+
| :py:const:`kx132.ACC_RANGE_16` | :py:const:`0b11` |
+---------------------------------+------------------+
"""
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._operating_mode = STANDBY_MODE
self._acc_range = value
self._acc_range_mem = value
self._operating_mode = NORMAL_MODE
@property
def acceleration(self) -> Tuple[float, float, float]:
"""
Acceleration
When accelerometer is enabled the 16-bits of valid acceleration data for each
axis is routed to :attr:`acceleration`. The data is updated every user-defined
ODR period at the rate set by OSA<3:0> bits in ODCNTL register.
:return: acceleration
"""
bufx, bufy, bufz = self._acceleration_data
factor = acc_range_factor[self._acc_range_mem]
return (
bufx / 2**15 * factor,
bufy / 2**15 * factor,
bufz / 2**15 * factor,
)
@property
def tilt_position(self):
"""
Current Sensor tilt position. Data that is updated at the user-defined
ODR frequency determined by OTP<1:0> in CNTL3. Data is protected during
register read
"""
states = {
1: "Face-Up State (Z+)",
2: "Face-Down State (Z-)",
4: "Up State (Y+)",
8: "Down State (Y-)",
16: "Right State (X+)",
32: "Left State (X-)",
}
return states[self._tilt_position]
@property
def previous_tilt_position(self):
"""
Previous Sensor tilt position. Data that is updated at the user-defined
ODR frequency determined by OTP<1:0> in CNTL3. Data is protected during
register read
"""
states = {
1: "Face-Up State (Z+)",
2: "Face-Down State (Z-)",
4: "Up State (Y+)",
8: "Down State (Y-)",
16: "Right State (X+)",
32: "Left State (X-)",
}
return states[self._previous_tilt_position]
@property
def tilt_position_enable(self) -> str:
"""
Sensor tilt_position_enable
+---------------------------------+-----------------+
| Mode | Value |
+=================================+=================+
| :py:const:`kx132.TILT_DISABLED` | :py:const:`0b0` |
+---------------------------------+-----------------+
| :py:const:`kx132.TILT_ENABLED` | :py:const:`0b1` |
+---------------------------------+-----------------+
"""
values = (
"TILT_DISABLED",
"TILT_ENABLED",
)
return values[self._tilt_position_enable]
@tilt_position_enable.setter
def tilt_position_enable(self, value: int) -> None:
if value not in tilt_position_enable_values:
raise ValueError("Value must be a valid tilt_position_enable setting")
self._operating_mode = STANDBY_MODE
self._tilt_position_enable = value
self._operating_mode = NORMAL_MODE
@property
def tap_doubletap_enable(self) -> str:
"""
Sensor tap_doubletap_enable
+---------------------------------+-----------------+
| Mode | Value |
+=================================+=================+
| :py:const:`kx132.TDTE_DISABLED` | :py:const:`0b0` |
+---------------------------------+-----------------+
| :py:const:`kx132.TDTE_ENABLED` | :py:const:`0b1` |
+---------------------------------+-----------------+
"""
values = ("TDTE_DISABLED", "TDTE_ENABLED")
return values[self._tap_doubletap_enable]
@tap_doubletap_enable.setter
def tap_doubletap_enable(self, value: int) -> None:
if value not in tap_doubletap_enable_values:
raise ValueError("Value must be a valid tap_doubletap_enable setting")
self._operating_mode = STANDBY_MODE
self._tap_doubletap_enable = value
self._operating_mode = NORMAL_MODE
@property
def tap_doubletap_report(self):
"""
Tap/Double Tap report. Data is updated at the ODR settings determined
by OTDT<2:0> in CNTL3. These bits are cleared when interrupt_release function
is called.
"""
states = {
0: "No Tap/Double Tap reported",
1: "Z Positive (Z+) Reported",
2: "Z Negative (Z-) Reported",
4: "Y Positive (Y+) Reported",
8: "Y Negative (Y-) Reported",
16: "X Positive (X+) Reported",
32: "X Negative (X-) Reported",
}
return states[self._interrupt1]
[docs]
def interrupt_release(self):
"""
Clear the interrupt register
"""
_ = self._interrupt_release
@property
def output_data_rate(self) -> int:
"""
There are 16 different configuration for the Output Data Rate.
These rate are divided in two groups.
1. Low Power and High Performance Output Data Rates <= 400 Hz
2. High Performance Output Data Rates only >= 800 Hz
Please verify the data sheet for corresponding values
The default ODR is 50Hz (0b110|6).
"""
return self._output_data_rate
@output_data_rate.setter
def output_data_rate(self, value: int) -> None:
if self.performance_mode == "HIGH_PERFORMANCE_MODE":
valid_range = range(10, 16)
else:
valid_range = range(0, 10)
if value not in valid_range:
raise ValueError(
"Value must be a valid setting in relation with the performance mode"
)
self._operating_mode = STANDBY_MODE
self._output_data_rate = value
self._operating_mode = NORMAL_MODE
@property
def performance_mode(self) -> str:
"""
Sensor performance_mode
+-----------------------------------------+-----------------+
| Mode | Value |
+=========================================+=================+
| :py:const:`kx132.LOW_POWER_MODE` | :py:const:`0b0` |
+-----------------------------------------+-----------------+
| :py:const:`kx132.HIGH_PERFORMANCE_MODE` | :py:const:`0b1` |
+-----------------------------------------+-----------------+
"""
values = ("LOW_POWER_MODE", "HIGH_PERFORMANCE_MODE")
return values[self._performance_mode]
@performance_mode.setter
def performance_mode(self, value: int) -> None:
if value not in performance_mode_values:
raise ValueError("Value must be a valid performance_mode setting")
self._operating_mode = STANDBY_MODE
self._performance_mode = value
self._operating_mode = NORMAL_MODE
@property
def advanced_data_path(self) -> Tuple[float, float, float]:
"""
This will nor return any information unless the :attr:`adp_enabled` is set.
Data is updated at the rate set by OADP<3:0> bits in ADP_CNTL1 register. However, if data
is routed via RMS block first (ADP_RMS_OSEL bit is set to 1 in ADP_CNTL2 register), the
rate is also scaled down by RMS_AVC<2:0> bits in ADP_CNTL1 register.
Values will remain the same until sensor is reset
"""
bufx, bufy, bufz = self._adp_data
factor = acc_range_factor[self._acc_range_mem]
return (
bufx / 2**15 * factor,
bufy / 2**15 * factor,
bufz / 2**15 * factor,
)
@property
def adp_enabled(self) -> str:
"""
Sensor adp_enabled
+--------------------------------+-----------------+
| Mode | Value |
+================================+=================+
| :py:const:`kx132.ADP_DISABLED` | :py:const:`0b0` |
+--------------------------------+-----------------+
| :py:const:`kx132.ADP_ENABLED` | :py:const:`0b1` |
+--------------------------------+-----------------+
"""
values = (
"ADP_DISABLED",
"ADP_ENABLED",
)
return values[self._adp_enabled]
@adp_enabled.setter
def adp_enabled(self, value: int) -> None:
if value not in adp_enabled_values:
raise ValueError("Value must be a valid adp_enabled setting")
self._adp_enabled = value
@property
def free_fall_enabled(self) -> str:
"""
Sensor free_fall_enabled
+-------------------------------+-----------------+
| Mode | Value |
+===============================+=================+
| :py:const:`kx132.FF_DISABLED` | :py:const:`0b0` |
+-------------------------------+-----------------+
| :py:const:`kx132.FF_ENABLED` | :py:const:`0b1` |
+-------------------------------+-----------------+
"""
values = (
"FF_DISABLED",
"FF_ENABLED",
)
return values[self._free_fall_enabled]
@free_fall_enabled.setter
def free_fall_enabled(self, value: int) -> None:
if value not in free_fall_enabled_values:
raise ValueError("Value must be a valid free_fall_enabled setting")
self._operating_mode = STANDBY_MODE
self._free_fall_enabled = value
self._operating_mode = NORMAL_MODE
@property
def free_fall_threshold(self) -> int:
"""
Free Fall Threshold. This value is compared to the top 8
bits of the accelerometer 8g output (independent of the
actual g-range setting of the device).
"""
return self._free_fall_threshold
@free_fall_threshold.setter
def free_fall_threshold(self, value: int) -> None:
self._free_fall_threshold = value