Source code for micropython_mmc5983.mmc5983

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

MicroPython Library for the Memsic MMC5983 Magnetometer


* Author(s): Jose D. Montoya


"""
import time
from micropython import const
from micropython_mmc5983.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_MMC5983.git"

_REG_WHOAMI = const(0x2F)
_DATA = const(0x00)
_INTERNAL_CONTROL0 = const(0x09)
_INTERNAL_CONTROL1 = const(0x0A)
_INTERNAL_CONTROL2 = const(0x0B)

ONE_SHOT = const(0b0)
CONTINUOUS = const(0b1)
operation_mode_values = (ONE_SHOT, CONTINUOUS)

SCALE_FACTOR = const(16384)

CM_OFF = const(0b000)
CM_1HZ = const(0b001)
CM_10HZ = const(0b010)
CM_20HZ = const(0b011)
CM_50HZ = const(0b100)
CM_100HZ = const(0b101)
CM_200HZ = const(0b110)
CM_1000HZ = const(0b111)
continuous_mode_frequency_values = (
    CM_OFF,
    CM_1HZ,
    CM_10HZ,
    CM_20HZ,
    CM_50HZ,
    CM_100HZ,
    CM_200HZ,
    CM_1000HZ,
)

BW_100HZ = const(0b00)
BW_200HZ = const(0b01)
BW_400HZ = const(0b10)
BW_800HZ = const(0b11)
bandwidth_values = (BW_100HZ, BW_200HZ, BW_400HZ, BW_800HZ)
delay_times = (0.008, 0.004, 0.002, 0.00005)


[docs]class MMC5983: """Driver for the MMC5983 Sensor connected over I2C. :param ~machine.I2C i2c: The I2C bus the MMC5983 is connected to. :param int address: The I2C device address. Defaults to :const:`0x30` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`MMC5983` class. First you will need to import the libraries to use the sensor .. code-block:: python from machine import Pin, I2C from micropython_mmc5983 import mmc5983 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)) mmc5983 = mmc5983.MMC5983(i2c) Now you have access to the attributes .. code-block:: python """ _device_id = RegisterStruct(_REG_WHOAMI, "B") _raw_data = RegisterStruct(_DATA, "HHHB") _temperature = RegisterStruct(0x07, "B") _bandwidth = CBits(2, _INTERNAL_CONTROL1, 0) _continuous_mode_frequency = CBits(3, _INTERNAL_CONTROL2, 0) _operation_mode = CBits(1, _INTERNAL_CONTROL2, 3) _start_magnetic_measure = CBits(1, _INTERNAL_CONTROL0, 0) _start_temperature_measure = CBits(1, _INTERNAL_CONTROL0, 1) def __init__(self, i2c, address: int = 0x30) -> None: self._i2c = i2c self._address = address if self._device_id != 0x30: raise RuntimeError("Failed to find MMC5983") self.operation_mode = CONTINUOUS self.continuous_mode_frequency = CM_1HZ self.bandwidth = BW_100HZ @property def operation_mode(self) -> str: """ Sensor operation_mode. In order to enter the continuous mode, :attr:`continuous_mode_frequency` cannot be :attr:`CM_OFF` +--------------------------------+-----------------+ | Mode | Value | +================================+=================+ | :py:const:`mmc5983.ONE_SHOT` | :py:const:`0b0` | +--------------------------------+-----------------+ | :py:const:`mmc5983.CONTINUOUS` | :py:const:`0b1` | +--------------------------------+-----------------+ """ values = ("ONE_SHOT", "CONTINUOUS") return values[self._om_cached] @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") if self._continuous_mode_frequency == 0: raise ValueError("Please select first a valid continuous mode frequency") self._operation_mode = value self._om_cached = value @property def continuous_mode_frequency(self) -> str: """ Sensor continuous_mode_frequency determine how often the chip will take measurements in Continuous Measurement Mode. The frequency is based on the assumption that :attr:`bandwidth` is :attr:`BW_100HZ` +-------------------------------+-------------------+ | Mode | Value | +===============================+===================+ | :py:const:`mmc5983.CM_OFF` | :py:const:`0b000` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_1HZ` | :py:const:`0b001` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_10HZ` | :py:const:`0b010` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_20HZ` | :py:const:`0b011` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_50HZ` | :py:const:`0b100` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_100HZ` | :py:const:`0b101` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_200HZ` | :py:const:`0b110` | +-------------------------------+-------------------+ | :py:const:`mmc5983.CM_1000HZ` | :py:const:`0b111` | +-------------------------------+-------------------+ """ values = ( "CM_OFF", "CM_1HZ", "CM_10HZ", "CM_20HZ", "CM_50HZ", "CM_100HZ", "CM_200HZ", "CM_1000HZ", ) return values[self._cmfc] @continuous_mode_frequency.setter def continuous_mode_frequency(self, value: int) -> None: if value not in continuous_mode_frequency_values: raise ValueError("Value must be a valid continuous_mode_frequency setting") if value == CM_200HZ and self._bandwidth < BW_200HZ: raise ValueError("Please set a correct bandwidth value for this setting") if value == CM_1000HZ and self._bandwidth < BW_800HZ: raise ValueError("Please set a correct bandwidth value for this setting") self.operation_mode = ONE_SHOT self._continuous_mode_frequency = value self._cmfc = value self.operation_mode = CONTINUOUS @property def bandwidth(self) -> str: """ Sensor bandwidth. These bandwidth selection bits adjust the length of the decimation filter. They control the duration of each measurement .. Note: X/Y/Z channel measurements are taken in parallel. +------------------------------+------------------+ | Mode | Value | +==============================+==================+ | :py:const:`mmc5983.BW_100HZ` | :py:const:`0b00` | +------------------------------+------------------+ | :py:const:`mmc5983.BW_200HZ` | :py:const:`0b01` | +------------------------------+------------------+ | :py:const:`mmc5983.BW_400HZ` | :py:const:`0b10` | +------------------------------+------------------+ | :py:const:`mmc5983.BW_800HZ` | :py:const:`0b11` | +------------------------------+------------------+ """ values = ("BW_100HZ", "BW_200HZ", "BW_400HZ", "BW_800HZ") return values[self._bw_cached] @bandwidth.setter def bandwidth(self, value: int) -> None: if value not in bandwidth_values: raise ValueError("Value must be a valid bandwidth setting") self.operation_mode = ONE_SHOT self._bandwidth = value self._bw_cached = value self.operation_mode = CONTINUOUS @property def magnetic(self) -> Tuple[float, float, float]: """ Returns magnetic data in uT """ x, y, z, extra = self._raw_data time.sleep(0.2) x_raw = (x << 2) | (((extra & 0xC0) >> 6) & 0x3) y_raw = (y << 2) | (((extra & 0x30) >> 4) & 0x3) z_raw = (z << 2) | (((extra & 0x03) >> 2) & 0x3) # https://thecavepearlproject.org/2015/05/22/calibrating-any-compass-or-accelerometer-for-arduino/ x_scale = x_raw - 131072.0 x_scale = (x_scale / 131072.0) * 100 y_scale = y_raw - 131072.0 y_scale = (y_scale / 131072.0) * 100 z_scale = z_raw - 131072.0 z_scale = (z_scale / 131072.0) * 100 return x_scale, y_scale, z_scale @property def temperature(self) -> float: """ Returns Temperature in Celsius """ self.operation_mode = ONE_SHOT self._start_temperature_measure = True t_raw = self._temperature self.operation_mode = CONTINUOUS return t_raw * 200.0 / 256.0 - 75.0