# SPDX-FileCopyrightText: 2019 Bryan Siepert for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_lis2mdl`
====================================================
CircuitPython driver for the LIS2MDL 3-axis magnetometer.
* Author(s): Bryan Siepert
Implementation Notes
--------------------
**Hardware:**
* Adafruit `Triple-axis Accelerometer+Magnetometer (Compass) Board - LSM303
<https://www.adafruit.com/product/1120>`_ (Product ID: 1120)
* Adafruit `FLORA Accelerometer/Compass Sensor - LSM303 - v1.0
<https://www.adafruit.com/product/1247>`_ (Product ID: 1247)
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://circuitpython.org/downloads
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
"""
from time import sleep
from micropython import const
from adafruit_bus_device.i2c_device import I2CDevice
from adafruit_register.i2c_struct import UnaryStruct, ROUnaryStruct
from adafruit_register.i2c_bit import RWBit
from adafruit_register.i2c_bits import RWBits
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LIS2MDL.git"
_ADDRESS_MAG = const(0x1E) # (0x3C >> 1) // 0011110x
MAG_DEVICE_ID = 0b01000000
[docs]class DataRate: # pylint: disable=too-few-public-methods
"""Data rate choices to set using `data_rate`"""
Rate_10_HZ = const(0x00)
"""10 Hz"""
Rate_20_HZ = const(0x01)
"""20 Hz"""
Rate_50_HZ = const(0x02)
"""50 Hz"""
Rate_100_HZ = const(0x03)
"""100 Hz"""
# Magnetometer registers
OFFSET_X_REG_L = 0x45
OFFSET_X_REG_H = 0x46
OFFSET_Y_REG_L = 0x47
OFFSET_Y_REG_H = 0x48
OFFSET_Z_REG_L = 0x49
OFFSET_Z_REG_H = 0x4A
WHO_AM_I = 0x4F
CFG_REG_A = 0x60
CFG_REG_B = 0x61
CFG_REG_C = 0x62
INT_CRTL_REG = 0x63
INT_SOURCE_REG = 0x64
INT_THS_L_REG = 0x65
STATUS_REG = 0x67
OUTX_L_REG = 0x68
OUTX_H_REG = 0x69
OUTY_L_REG = 0x6A
OUTY_H_REG = 0x6B
OUTZ_L_REG = 0x6C
OUTZ_H_REG = 0x6D
_MAG_SCALE = 0.15 # 1.5 milligauss/LSB * 0.1 microtesla/milligauss
[docs]class LIS2MDL: # pylint: disable=too-many-instance-attributes
"""
Driver for the LIS2MDL 3-axis magnetometer.
:param ~busio.I2C i2c: The I2C bus the device is connected to
**Quickstart: Importing and using the device**
Here is an example of using the :class:`LIS2MDL` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
import board
import adafruit_lis2mdl
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
sensor = adafruit_lis2mdl.LIS2MDL(i2c)
Now you have access to the :attr:`magnetic` attribute
.. code-block:: python
mag_x, mag_y, mag_z = sensor.magnetic
"""
_BUFFER = bytearray(6)
_device_id = ROUnaryStruct(WHO_AM_I, "B")
_int_control = UnaryStruct(INT_CRTL_REG, "B")
_mode = RWBits(2, CFG_REG_A, 0, 1)
_data_rate = RWBits(2, CFG_REG_A, 2, 1)
_temp_comp = RWBit(CFG_REG_A, 7, 1)
_reboot = RWBit(CFG_REG_A, 6, 1)
_soft_reset = RWBit(CFG_REG_A, 5, 1)
_bdu = RWBit(CFG_REG_C, 4, 1)
_int_iron_off = RWBit(CFG_REG_B, 3, 1)
_x_offset = UnaryStruct(OFFSET_X_REG_L, "<h")
_y_offset = UnaryStruct(OFFSET_Y_REG_L, "<h")
_z_offset = UnaryStruct(OFFSET_Z_REG_L, "<h")
_interrupt_pin_putput = RWBit(CFG_REG_C, 6, 1)
_interrupt_threshold = UnaryStruct(INT_THS_L_REG, "<h")
_x_int_enable = RWBit(INT_CRTL_REG, 7, 1)
_y_int_enable = RWBit(INT_CRTL_REG, 6, 1)
_z_int_enable = RWBit(INT_CRTL_REG, 5, 1)
_int_reg_polarity = RWBit(INT_CRTL_REG, 2, 1)
_int_latched = RWBit(INT_CRTL_REG, 1, 1)
_int_enable = RWBit(INT_CRTL_REG, 0, 1)
_int_source = ROUnaryStruct(INT_SOURCE_REG, "B")
low_power = RWBit(CFG_REG_A, 4, 1)
"""Enables and disables low power mode"""
_raw_x = ROUnaryStruct(OUTX_L_REG, "<h")
_raw_y = ROUnaryStruct(OUTY_L_REG, "<h")
_raw_z = ROUnaryStruct(OUTZ_L_REG, "<h")
_x_offset = UnaryStruct(OFFSET_X_REG_L, "<h")
_y_offset = UnaryStruct(OFFSET_Y_REG_L, "<h")
_z_offset = UnaryStruct(OFFSET_Z_REG_L, "<h")
def __init__(self, i2c):
self.i2c_device = I2CDevice(i2c, _ADDRESS_MAG)
if self._device_id != 0x40:
raise AttributeError("Cannot find an LIS2MDL")
self.reset()
[docs] def reset(self):
"""Reset the sensor to the default state set by the library"""
self._soft_reset = True
sleep(0.100)
self._reboot = True
sleep(0.100)
self._mode = 0x00
self._bdu = True # Make sure high and low bytes are set together
self._int_latched = True
self._int_reg_polarity = True
self._int_iron_off = False
self._interrupt_pin_putput = True
self._temp_comp = True
sleep(0.030) # sleep 20ms to allow measurements to stabilize
@property
def magnetic(self):
"""The processed magnetometer sensor values.
A 3-tuple of X, Y, Z axis values in microteslas that are signed floats.
"""
return (
self._raw_x * _MAG_SCALE,
self._raw_y * _MAG_SCALE,
self._raw_z * _MAG_SCALE,
)
@property
def data_rate(self):
"""The magnetometer update rate."""
return self._data_rate
@data_rate.setter
def data_rate(self, value):
if not value in (
DataRate.Rate_10_HZ,
DataRate.Rate_20_HZ,
DataRate.Rate_50_HZ,
DataRate.Rate_100_HZ,
):
raise ValueError("data_rate must be a `DataRate`")
self._data_rate = value
@property
def interrupt_threshold(self):
"""The threshold (in microteslas) for magnetometer interrupt generation. Given value is
compared against all axes in both the positive and negative direction"""
return self._interrupt_threshold * _MAG_SCALE
@interrupt_threshold.setter
def interrupt_threshold(self, value):
if value < 0:
value = -value
self._interrupt_threshold = int(value / _MAG_SCALE)
@property
def interrupt_enabled(self):
"""Enable or disable the magnetometer interrupt"""
return self._int_enable
@interrupt_enabled.setter
def interrupt_enabled(self, val):
self._x_int_enable = val
self._y_int_enable = val
self._z_int_enable = val
self._int_enable = val
@property
def faults(self):
"""A tuple representing interrupts on each axis in a positive and negative direction
``(x_hi, y_hi, z_hi, x_low, y_low, z_low, int_triggered)``"""
int_status = self._int_source
x_hi = (int_status & 0b10000000) > 0
y_hi = int_status & 0b01000000 > 0
z_hi = int_status & 0b00100000 > 0
x_low = int_status & 0b00010000 > 0
y_low = int_status & 0b00001000 > 0
z_low = int_status & 0b00000100 > 0
int_triggered = int_status & 0b1 > 0
return (x_hi, y_hi, z_hi, x_low, y_low, z_low, int_triggered)
@property
def x_offset(self):
"""An offset for the X-Axis to subtract from the measured value to correct
for magnetic interference"""
return self._x_offset * _MAG_SCALE
@x_offset.setter
def x_offset(self, value):
self._x_offset = int(value / _MAG_SCALE)
@property
def y_offset(self):
"""An offset for the Y-Axis to subtract from the measured value to correct
for magnetic interference"""
return self._y_offset * _MAG_SCALE
@y_offset.setter
def y_offset(self, value):
self._y_offset = int(value / _MAG_SCALE)
@property
def z_offset(self):
"""An offset for the Z-Axis to subtract from the measured value to correct
for magnetic interference"""
return self._z_offset * _MAG_SCALE
@z_offset.setter
def z_offset(self, value):
self._z_offset = int(value / _MAG_SCALE)