# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`qmc5883l`
================================================================================
MicroPython Driver for the QMC5883L Accelerometer
* Author(s): Jose D. Montoya
"""
import time
from micropython import const
from micropython_qmc5883l.i2c_helpers import CBits, RegisterStruct
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/MicroPython_QMC5883L.git"
_REG_WHOAMI = const(0x0D)
_REG_SET_RESET = const(0x0B)
_REG_OPERATION_MODE = const(0x09)
_REG_STATUS = const(0x06)
OVERSAMPLE_64 = const(0b11)
OVERSAMPLE_128 = const(0b10)
OVERSAMPLE_256 = const(0b01)
OVERSAMPLE_512 = const(0b00)
oversample_values = (OVERSAMPLE_64, OVERSAMPLE_128, OVERSAMPLE_256, OVERSAMPLE_512)
FIELDRANGE_2G = const(0b00)
FIELDRANGE_8G = const(0b01)
field_range_values = (FIELDRANGE_2G, FIELDRANGE_8G)
OUTPUT_DATA_RATE_10 = const(0b00)
OUTPUT_DATA_RATE_50 = const(0b01)
OUTPUT_DATA_RATE_100 = const(0b10)
OUTPUT_DATA_RATE_200 = const(0b11)
data_rate_values = (
OUTPUT_DATA_RATE_10,
OUTPUT_DATA_RATE_50,
OUTPUT_DATA_RATE_100,
OUTPUT_DATA_RATE_200,
)
MODE_STANDBY = const(0b00)
MODE_CONTINUOUS = const(0b01)
mode_values = (MODE_STANDBY, MODE_CONTINUOUS)
RESET_VALUE = const(0b01)
[docs]
class QMC5883L:
"""Driver for the QMC5883L Sensor connected over I2C.
:param ~machine.I2C i2c: The I2C bus the QMC5883L is connected to.
:param int address: The I2C device address. Defaults to :const:`0xD`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`QMC5883L` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
from machine import Pin, I2C
from micropython_qmc5883l import qmc5883l
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))
qmc5883l = qmc5883l.QMC5883L(i2c)
Now you have access to the attributes
.. code-block:: python
mag_x, mag_y, mag_z = qmc.magnetic
"""
_device_id = RegisterStruct(_REG_WHOAMI, "H")
_reset = RegisterStruct(_REG_SET_RESET, "H")
_conf_reg = RegisterStruct(_REG_OPERATION_MODE, "H")
_oversample = CBits(2, _REG_OPERATION_MODE, 6)
_field_range = CBits(2, _REG_OPERATION_MODE, 4)
_output_data_rate = CBits(2, _REG_OPERATION_MODE, 2)
_mode_control = CBits(2, _REG_OPERATION_MODE, 0)
_data_ready_register = CBits(1, _REG_STATUS, 2)
_measures = RegisterStruct(0x00, "<hhhBh")
def __init__(self, i2c, address: int = 0xD) -> None:
self._i2c = i2c
self._address = address
if self._device_id != 0xFF:
raise RuntimeError("Failed to find the QMC5883L!")
self._reset = 0x01
self.oversample = OVERSAMPLE_128
self.field_range = FIELDRANGE_2G
self.output_data_rate = OUTPUT_DATA_RATE_200
self.mode_control = MODE_CONTINUOUS
@property
def oversample(self) -> int:
"""
Over sample Rate (OSR) registers are used to control bandwidth of an
internal digital filter. Larger OSR value leads to smaller filter bandwidth,
less in-band noise and higher power consumption. It could be used to reach a
good balance between noise and power.
Four oversample ratios can be selected, 64, 128, 256 or 512. With the following
global variables.
+----------------------------------------+-------------------------+
| Mode | Value |
+========================================+=========================+
| :py:const:`qmc5883l.OVERSAMPLE_64` | :py:const:`0b11` |
+----------------------------------------+-------------------------+
| :py:const:`qmc5883l.OVERSAMPLE_128` | :py:const:`0b10` |
+----------------------------------------+-------------------------+
| :py:const:`qmc5883l.OVERSAMPLE_256` | :py:const:`0b01` |
+----------------------------------------+-------------------------+
| :py:const:`qmc5883l.OVERSAMPLE_512` | :py:const:`0b00` |
+----------------------------------------+-------------------------+
Example
---------------------
.. code-block:: python
i2c = board.I2C()
qmc = qmc5883l.QMC5883L(i2c)
qmc.oversample = qmc5883l.OVERSAMPLE_64
"""
oversamples = (
"OVERSAMPLE_512",
"OVERSAMPLE_256",
"OVERSAMPLE_128",
"OVERSAMPLE_64",
)
return oversamples[self._oversample]
@oversample.setter
def oversample(self, value: int) -> None:
if value not in oversample_values:
raise ValueError("Value must be a valid oversample setting")
self._oversample = value
@property
def field_range(self) -> int:
"""Field ranges of the magnetic sensor can be selected through the register RNG.
The full scale field range is determined by the application environments.
For magnetic clear environment, low field range such as +/- 2gauss
can be used. The field range goes hand in hand with the sensitivity of the
magnetic sensor. The lowest field range has the highest sensitivity, therefore,
higher resolution.
Two field range values can be selected, 2G and 8G. With the following
global variables.
+----------------------------------------+-------------------------+
| Mode | Value |
+========================================+=========================+
| :py:const:`qmc5883l.FIELDRANGE_2G` | :py:const:`0b00` |
+----------------------------------------+-------------------------+
| :py:const:`qmc5883l.FIELDRANGE_8G` | :py:const:`0b01` |
+----------------------------------------+-------------------------+
Example
---------------------
.. code-block:: python
i2c = board.I2C()
qmc = qmc5883l.QMC5883L(i2c)
qmc.field_range = qmc5883l.FIELDRANGE_2G
"""
ranges = ("FIELDRANGE_2G", "FIELDRANGE_8G")
return ranges[self._field_range]
@field_range.setter
def field_range(self, value: int) -> None:
if value not in field_range_values:
raise ValueError("Value must be a valid field range setting")
if value == 1:
self.resolution = 3000
else:
self.resolution = 12000
self._field_range = value
@property
def output_data_rate(self) -> int:
"""Output data rate is controlled by ODR registers. Four data update
frequencies can be selected: 10Hz, 50Hz, 100Hz and 200Hz.
For most compassing applications, 10 Hz for low
power consumption is recommended. For gaming, the high update rate such as
100Hz or 200Hz can be used.
Four oversample ratios can be selected, 10, 50, 100 or 200. With the following
global variables.
+-------------------------------------------+-------------------------+
| Mode | Value |
+===========================================+=========================+
| :py:const:`qmc5883l.OUTPUT_DATA_RATE_10` | :py:const:`0b00` |
+-------------------------------------------+-------------------------+
| :py:const:`qmc5883l.OUTPUT_DATA_RATE_50` | :py:const:`0b01` |
+-------------------------------------------+-------------------------+
| :py:const:`qmc5883l.OUTPUT_DATA_RATE_100` | :py:const:`0b10` |
+-------------------------------------------+-------------------------+
| :py:const:`qmc5883l.OUTPUT_DATA_RATE_200` | :py:const:`0b11` |
+-------------------------------------------+-------------------------+
Example
---------------------
.. code-block:: python
i2c = board.I2C()
qmc = qmc5883l.QMC5883L(i2c)
qmc.output_data_rate = qmc5883l.OUTPUT_DATA_RATE_200
"""
rates = (
"OUTPUT_DATA_RATE_10",
"OUTPUT_DATA_RATE_50",
"OUTPUT_DATA_RATE_100",
"OUTPUT_DATA_RATE_200",
)
return rates[self._output_data_rate]
@output_data_rate.setter
def output_data_rate(self, value: int) -> None:
if value not in data_rate_values:
raise ValueError("Value must be a valid data rate setting")
self._output_data_rate = value
@property
def mode_control(self) -> int:
"""Two bits of MODE registers can transfer mode of operations in the device,
the two modes are Standby, and Continuous measurements. The default mode
after Power-on-Reset (POR) is standby. There is no any restriction
in the transferring between the modes.
Two modes can be selected Standby and Continuous With the following
global variables.
+-------------------------------------------+-------------------------+
| Mode | Value |
+===========================================+=========================+
| :py:const:`qmc5883l.MODE_CONTINUOUS` | :py:const:`0b01` |
+-------------------------------------------+-------------------------+
| :py:const:`qmc5883l.MODE_STANDBY` | :py:const:`0b00` |
+-------------------------------------------+-------------------------+
Example
---------------------
.. code-block:: python
i2c = board.I2C()
qmc = qmc5883l.QMC5883L(i2c)
qmc.output_data_rate = qmc5883l.MODE_STANDBY
"""
modes = ("MODE_STANDBY", "MODE_CONTINUOUS")
return modes[self._mode_control]
@mode_control.setter
def mode_control(self, value: int) -> None:
if value not in mode_values:
raise ValueError("Value must be a valid mode setting")
self._mode_control = value
@property
def magnetic(self):
"""Magnetic property"""
while self._data_ready_register != 1:
time.sleep(0.001)
x, y, z, _, _ = self._measures
return x / self.resolution, y / self.resolution, z / self.resolution