# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_fxos8700`
====================================================
CircuitPython module for the NXP FXOS8700 accelerometer and magnetometer.
Based on the driver from: https://github.com/adafruit/Adafruit_FXOS8700
See examples/simpletest.py for a demo of the usage.
* Author(s): Tony DiCola
Implementation Notes
--------------------
**Hardware:**
* `Adafruit Precision NXP 9-DOF Breakout Board - FXOS8700 + FXAS21002
<https://www.adafruit.com/product/3463>`_ (Product ID: 3463)
**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
"""
try:
import ustruct as struct
except ImportError:
import struct
import adafruit_bus_device.i2c_device as i2c_dev
from micropython import const
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_FXOS8700.git"
# Register addresses and other constants:
_FXOS8700_ADDRESS = const(0x1F) # 0011111
_FXOS8700_ID = const(0xC7) # 1100 0111
_FXOS8700_REGISTER_STATUS = const(0x00)
_FXOS8700_REGISTER_OUT_X_MSB = const(0x01)
_FXOS8700_REGISTER_OUT_X_LSB = const(0x02)
_FXOS8700_REGISTER_OUT_Y_MSB = const(0x03)
_FXOS8700_REGISTER_OUT_Y_LSB = const(0x04)
_FXOS8700_REGISTER_OUT_Z_MSB = const(0x05)
_FXOS8700_REGISTER_OUT_Z_LSB = const(0x06)
_FXOS8700_REGISTER_WHO_AM_I = const(0x0D) # 11000111 r
_FXOS8700_REGISTER_XYZ_DATA_CFG = const(0x0E)
_FXOS8700_REGISTER_CTRL_REG1 = const(0x2A) # 00000000 r/w
_FXOS8700_REGISTER_CTRL_REG2 = const(0x2B) # 00000000 r/w
_FXOS8700_REGISTER_CTRL_REG3 = const(0x2C) # 00000000 r/w
_FXOS8700_REGISTER_CTRL_REG4 = const(0x2D) # 00000000 r/w
_FXOS8700_REGISTER_CTRL_REG5 = const(0x2E) # 00000000 r/w
_FXOS8700_REGISTER_MSTATUS = const(0x32)
_FXOS8700_REGISTER_MOUT_X_MSB = const(0x33)
_FXOS8700_REGISTER_MOUT_X_LSB = const(0x34)
_FXOS8700_REGISTER_MOUT_Y_MSB = const(0x35)
_FXOS8700_REGISTER_MOUT_Y_LSB = const(0x36)
_FXOS8700_REGISTER_MOUT_Z_MSB = const(0x37)
_FXOS8700_REGISTER_MOUT_Z_LSB = const(0x38)
_FXOS8700_REGISTER_MCTRL_REG1 = const(0x5B) # 00000000 r/w
_FXOS8700_REGISTER_MCTRL_REG2 = const(0x5C) # 00000000 r/w
_FXOS8700_REGISTER_MCTRL_REG3 = const(0x5D) # 00000000 r/w
_ACCEL_MG_LSB_2G = 0.000244
_ACCEL_MG_LSB_4G = 0.000488
_ACCEL_MG_LSB_8G = 0.000976
_MAG_UT_LSB = 0.1
_SENSORS_GRAVITY_STANDARD = 9.80665
# User-facing constants/module-level globals:
ACCEL_RANGE_2G = 0x00
ACCEL_RANGE_4G = 0x01
ACCEL_RANGE_8G = 0x02
def _twos_comp(val, bits):
# Convert an unsigned integer in 2's compliment form of the specified bit
# length to its signed integer value and return it.
if val & (1 << (bits - 1)) != 0:
return val - (1 << bits)
return val
[docs]class FXOS8700:
"""Driver for the NXP FXOS8700 accelerometer and magnetometer.
:param ~busio.I2C i2c: The I2C bus the device is connected to
:param int address: The I2C device address. Defaults to :const:`0x1F`
:param int accel_range: Device range. Defaults to :const:`0x00`.
**Quickstart: Importing and using the device**
Here is an example of using the :class:`FXOS8700` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
import board
import adafruit_fxos8700
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_fxos8700.FXOS8700(i2c)
Now you have access to the :attr:`accelerometer` and :attr:`magnetometer` attributes
.. code-block:: python
accel_x, accel_y, accel_z = sensor.accelerometer
mag_x, mag_y, mag_z = sensor.magnetometer
"""
# Class-level buffer for reading and writing data with the sensor.
# This reduces memory allocations but means the code is not re-entrant or
# thread safe!
_BUFFER = bytearray(13)
def __init__(self, i2c, address=_FXOS8700_ADDRESS, accel_range=ACCEL_RANGE_2G):
if accel_range not in (ACCEL_RANGE_2G, ACCEL_RANGE_4G, ACCEL_RANGE_8G):
raise Exception("accel_range selected is not a valid option")
self._accel_range = accel_range
self._device = i2c_dev.I2CDevice(i2c, address)
# Check for chip ID value.
if self._read_u8(_FXOS8700_REGISTER_WHO_AM_I) != _FXOS8700_ID:
raise RuntimeError("Failed to find FXOS8700, check wiring!")
# Set to standby mode (required to make changes to this register)
self._write_u8(_FXOS8700_REGISTER_CTRL_REG1, 0)
if accel_range == ACCEL_RANGE_2G:
self._write_u8(_FXOS8700_REGISTER_XYZ_DATA_CFG, 0x00)
elif accel_range == ACCEL_RANGE_4G:
self._write_u8(_FXOS8700_REGISTER_XYZ_DATA_CFG, 0x01)
elif accel_range == ACCEL_RANGE_8G:
self._write_u8(_FXOS8700_REGISTER_XYZ_DATA_CFG, 0x02)
# High resolution
self._write_u8(_FXOS8700_REGISTER_CTRL_REG2, 0x02)
# Active, Normal Mode, Low Noise, 100Hz in Hybrid Mode
self._write_u8(_FXOS8700_REGISTER_CTRL_REG1, 0x15)
# Configure the magnetometer
# Hybrid Mode, Over Sampling Rate = 16
self._write_u8(_FXOS8700_REGISTER_MCTRL_REG1, 0x1F)
# Jump to reg 0x33 after reading 0x06
self._write_u8(_FXOS8700_REGISTER_MCTRL_REG2, 0x20)
def _read_u8(self, address):
# Read an 8-bit unsigned value from the specified 8-bit address.
with self._device as i2c:
self._BUFFER[0] = address & 0xFF
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=1)
return self._BUFFER[0]
def _write_u8(self, address, val):
# Write an 8-bit unsigned value to the specified 8-bit address.
with self._device as i2c:
self._BUFFER[0] = address & 0xFF
self._BUFFER[1] = val & 0xFF
i2c.write(self._BUFFER, end=2)
[docs] def read_raw_accel_mag(self):
"""Read the raw accelerometer and magnetometer readings. Returns a
2-tuple of 3-tuples:
- Accelerometer X, Y, Z axis 14-bit signed raw values
- Magnetometer X, Y, Z axis 16-bit signed raw values
If you want the acceleration or magnetometer values in friendly units
consider using the accelerometer and magnetometer properties!
"""
# Read accelerometer data from sensor.
with self._device as i2c:
self._BUFFER[0] = _FXOS8700_REGISTER_OUT_X_MSB
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=6)
accel_raw_x = struct.unpack_from(">H", self._BUFFER[0:2])[0]
accel_raw_y = struct.unpack_from(">H", self._BUFFER[2:4])[0]
accel_raw_z = struct.unpack_from(">H", self._BUFFER[4:6])[0]
# Convert accelerometer data to signed 14-bit value from 16-bit
# left aligned 2's compliment value.
accel_raw_x = _twos_comp(accel_raw_x >> 2, 14)
accel_raw_y = _twos_comp(accel_raw_y >> 2, 14)
accel_raw_z = _twos_comp(accel_raw_z >> 2, 14)
# Read magnetometer data from sensor. No need to convert as this is
# 16-bit signed data so struct parsing can handle it directly.
with self._device as i2c:
self._BUFFER[0] = _FXOS8700_REGISTER_MOUT_X_MSB
i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=6)
mag_raw_x = struct.unpack_from(">h", self._BUFFER[0:2])[0]
mag_raw_y = struct.unpack_from(">h", self._BUFFER[2:4])[0]
mag_raw_z = struct.unpack_from(">h", self._BUFFER[4:6])[0]
return (
(accel_raw_x, accel_raw_y, accel_raw_z),
(mag_raw_x, mag_raw_y, mag_raw_z),
)
@property
def accelerometer(self):
"""Read the acceleration from the accelerometer and return its X, Y, Z axis values as a
3-tuple in :math:`m/s^2`.
"""
accel_raw, _ = self.read_raw_accel_mag()
# Convert accel values to m/s^2
factor = 0
if self._accel_range == ACCEL_RANGE_2G:
factor = _ACCEL_MG_LSB_2G
elif self._accel_range == ACCEL_RANGE_4G:
factor = _ACCEL_MG_LSB_4G
elif self._accel_range == ACCEL_RANGE_8G:
factor = _ACCEL_MG_LSB_8G
return [x * factor * _SENSORS_GRAVITY_STANDARD for x in accel_raw]
@property
def magnetometer(self):
"""
Read the magnetometer values and return its X, Y, Z axis values as a 3-tuple in μTeslas.
"""
_, mag_raw = self.read_raw_accel_mag()
# Convert mag values to uTesla
return [x * _MAG_UT_LSB for x in mag_raw]