Skip to content

Commit 38479f2

Browse files
committed
initial library and example
1 parent db75f35 commit 38479f2

File tree

5 files changed

+330
-16
lines changed

5 files changed

+330
-16
lines changed

README.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,22 @@ Usage Example
9494

9595
.. code-block:: python
9696
97+
import time
9798
import board
9899
import adafruit_qmc5883p
99100
101+
i2c = board.I2C()
102+
103+
sensor = adafruit_qmc5883p.QMC5883P(i2c)
104+
105+
while True:
106+
mag_x, mag_y, mag_z = sensor.magnetic
107+
108+
print(f"X:{mag_x:10.2f}, Y:{mag_y:10.2f}, Z:{mag_z:10.2f} uT")
109+
print("")
110+
111+
time.sleep(1)
112+
100113
Documentation
101114
=============
102115
API documentation for this library can be found on `Read the Docs <https://docs.circuitpython.org/projects/qmc5883p/en/latest/>`_.

adafruit_qmc5883p.py

Lines changed: 284 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
21
# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
32
#
43
# SPDX-License-Identifier: MIT
@@ -16,22 +15,298 @@
1615
1716
**Hardware:**
1817
19-
.. todo:: Add links to any specific hardware product page(s), or category page(s).
20-
Use unordered list & hyperlink rST inline format: "* `Link Text <url>`_"
18+
* `Adafruit QMC5883P - Triple Axis Magnetometer <https://www.adafruit.com/product/6388>`_"
2119
2220
**Software and Dependencies:**
2321
2422
* Adafruit CircuitPython firmware for the supported boards:
2523
https://circuitpython.org/downloads
2624
27-
.. todo:: Uncomment or remove the Bus Device and/or the Register library dependencies
28-
based on the library's use of either.
29-
30-
# * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
31-
# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
25+
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
26+
* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
3227
"""
3328

34-
# imports
29+
import struct
30+
import time
31+
32+
from adafruit_bus_device.i2c_device import I2CDevice
33+
from adafruit_register.i2c_bit import ROBit, RWBit
34+
from adafruit_register.i2c_bits import RWBits
35+
from adafruit_register.i2c_struct import ROUnaryStruct
36+
from micropython import const
37+
38+
try:
39+
from typing import Tuple
40+
41+
import busio
42+
except ImportError:
43+
pass
3544

3645
__version__ = "0.0.0+auto.0"
3746
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_QMC5883P.git"
47+
48+
# I2C Address
49+
_DEFAULT_ADDR = const(0x2C)
50+
51+
# Registers
52+
_CHIPID = const(0x00)
53+
_XOUT_LSB = const(0x01)
54+
_XOUT_MSB = const(0x02)
55+
_YOUT_LSB = const(0x03)
56+
_YOUT_MSB = const(0x04)
57+
_ZOUT_LSB = const(0x05)
58+
_ZOUT_MSB = const(0x06)
59+
_STATUS = const(0x09)
60+
_CONTROL1 = const(0x0A)
61+
_CONTROL2 = const(0x0B)
62+
63+
# Operating modes
64+
MODE_SUSPEND = const(0x00)
65+
MODE_NORMAL = const(0x01)
66+
MODE_SINGLE = const(0x02)
67+
MODE_CONTINUOUS = const(0x03)
68+
69+
# Output data rates
70+
ODR_10HZ = const(0x00)
71+
ODR_50HZ = const(0x01)
72+
ODR_100HZ = const(0x02)
73+
ODR_200HZ = const(0x03)
74+
75+
# Over sample ratios
76+
OSR_8 = const(0x00)
77+
OSR_4 = const(0x01)
78+
OSR_2 = const(0x02)
79+
OSR_1 = const(0x03)
80+
81+
# Downsample ratios
82+
DSR_1 = const(0x00)
83+
DSR_2 = const(0x01)
84+
DSR_4 = const(0x02)
85+
DSR_8 = const(0x03)
86+
87+
# Field ranges
88+
RANGE_30G = const(0x00)
89+
RANGE_12G = const(0x01)
90+
RANGE_8G = const(0x02)
91+
RANGE_2G = const(0x03)
92+
93+
# Set/Reset modes
94+
SETRESET_ON = const(0x00)
95+
SETRESET_SETONLY = const(0x01)
96+
SETRESET_OFF = const(0x02)
97+
98+
# LSB per Gauss for each range
99+
_LSB_PER_GAUSS = {RANGE_30G: 1000.0, RANGE_12G: 2500.0, RANGE_8G: 3750.0, RANGE_2G: 15000.0}
100+
101+
102+
class QMC5883P:
103+
"""Driver for the QMC5883P 3-axis magnetometer."""
104+
105+
# Register definitions using adafruit_register
106+
_chip_id = ROUnaryStruct(_CHIPID, "<B")
107+
108+
# Status register bits
109+
data_ready = ROBit(_STATUS, 0)
110+
"""Check if new magnetic data is ready."""
111+
overflow = ROBit(_STATUS, 1)
112+
"""Check if data overflow has occurred."""
113+
114+
# Control register 1 bits
115+
_mode = RWBits(2, _CONTROL1, 0)
116+
_odr = RWBits(2, _CONTROL1, 2)
117+
_osr = RWBits(2, _CONTROL1, 4)
118+
_dsr = RWBits(2, _CONTROL1, 6)
119+
120+
# Control register 2 bits
121+
_setreset = RWBits(2, _CONTROL2, 0)
122+
_range = RWBits(2, _CONTROL2, 2)
123+
_selftest = RWBit(_CONTROL2, 6)
124+
_reset = RWBit(_CONTROL2, 7)
125+
126+
def __init__(self, i2c_bus: busio.I2C, address: int = _DEFAULT_ADDR) -> None:
127+
self.i2c_device = I2CDevice(i2c_bus, address)
128+
129+
# Check chip ID
130+
if self._chip_id != 0x80:
131+
raise RuntimeError("Failed to find QMC5883P chip")
132+
133+
# Initialize with default settings
134+
self.mode = MODE_CONTINUOUS
135+
self.data_rate = ODR_10HZ
136+
self.oversample_ratio = OSR_8
137+
self.downsample_ratio = DSR_1
138+
self.range = RANGE_2G
139+
self.setreset_mode = SETRESET_ON
140+
141+
@property
142+
def magnetic(self) -> Tuple[float, float, float]:
143+
"""The magnetic field measured in microteslas (uT).
144+
145+
:return: A 3-tuple of X, Y, Z axis values in microteslas
146+
"""
147+
# Wait for data ready
148+
while not self.data_ready:
149+
time.sleep(0.001)
150+
151+
# Read all 6 bytes at once
152+
buf = bytearray(6)
153+
with self.i2c_device as i2c:
154+
i2c.write_then_readinto(bytes([_XOUT_LSB]), buf)
155+
156+
# Unpack as signed 16-bit integers
157+
raw_x, raw_y, raw_z = struct.unpack("<hhh", buf)
158+
159+
# Get conversion factor based on current range
160+
lsb_per_gauss = _LSB_PER_GAUSS[self._range]
161+
162+
# Convert to Gauss then to microteslas (1 Gauss = 100 uT)
163+
x = (raw_x / lsb_per_gauss) * 100.0
164+
y = (raw_y / lsb_per_gauss) * 100.0
165+
z = (raw_z / lsb_per_gauss) * 100.0
166+
167+
return (x, y, z)
168+
169+
@property
170+
def magnetic_raw(self) -> Tuple[int, int, int]:
171+
"""The raw magnetic field sensor values as signed 16-bit integers.
172+
173+
:return: A 3-tuple of X, Y, Z axis raw values
174+
"""
175+
# Wait for data ready
176+
while not self._data_ready:
177+
time.sleep(0.001)
178+
179+
# Read all 6 bytes at once
180+
buf = bytearray(6)
181+
with self.i2c_device as i2c:
182+
i2c.write_then_readinto(bytes([_XOUT_LSB]), buf)
183+
184+
# Unpack as signed 16-bit integers
185+
return struct.unpack("<hhh", buf)
186+
187+
@property
188+
def mode(self) -> int:
189+
"""The operating mode of the sensor.
190+
191+
Options are:
192+
- MODE_SUSPEND (0x00): Suspend mode
193+
- MODE_NORMAL (0x01): Normal mode
194+
- MODE_SINGLE (0x02): Single measurement mode
195+
- MODE_CONTINUOUS (0x03): Continuous mode
196+
"""
197+
return self._mode
198+
199+
@mode.setter
200+
def mode(self, value: int) -> None:
201+
if value not in {MODE_SUSPEND, MODE_NORMAL, MODE_SINGLE, MODE_CONTINUOUS}:
202+
raise ValueError("Invalid mode")
203+
self._mode = value
204+
205+
@property
206+
def data_rate(self) -> int:
207+
"""The output data rate in Hz.
208+
209+
Options are:
210+
- ODR_10HZ (0x00): 10 Hz
211+
- ODR_50HZ (0x01): 50 Hz
212+
- ODR_100HZ (0x02): 100 Hz
213+
- ODR_200HZ (0x03): 200 Hz
214+
"""
215+
return self._odr
216+
217+
@data_rate.setter
218+
def data_rate(self, value: int) -> None:
219+
if value not in {ODR_10HZ, ODR_50HZ, ODR_100HZ, ODR_200HZ}:
220+
raise ValueError("Invalid output data rate")
221+
self._odr = value
222+
223+
@property
224+
def oversample_ratio(self) -> int:
225+
"""The over sample ratio.
226+
227+
Options are:
228+
- OSR_8 (0x00): Over sample ratio = 8
229+
- OSR_4 (0x01): Over sample ratio = 4
230+
- OSR_2 (0x02): Over sample ratio = 2
231+
- OSR_1 (0x03): Over sample ratio = 1
232+
"""
233+
return self._osr
234+
235+
@oversample_ratio.setter
236+
def oversample_ratio(self, value: int) -> None:
237+
if value not in {OSR_8, OSR_4, OSR_2, OSR_1}:
238+
raise ValueError("Invalid oversample ratio")
239+
self._osr = value
240+
241+
@property
242+
def downsample_ratio(self) -> int:
243+
"""The downsample ratio.
244+
245+
Options are:
246+
- DSR_1 (0x00): Downsample ratio = 1
247+
- DSR_2 (0x01): Downsample ratio = 2
248+
- DSR_4 (0x02): Downsample ratio = 4
249+
- DSR_8 (0x03): Downsample ratio = 8
250+
"""
251+
return self._dsr
252+
253+
@downsample_ratio.setter
254+
def downsample_ratio(self, value: int) -> None:
255+
if value not in {DSR_1, DSR_2, DSR_4, DSR_8}:
256+
raise ValueError("Invalid downsample ratio")
257+
self._dsr = value
258+
259+
@property
260+
def range(self) -> int:
261+
"""The magnetic field range.
262+
263+
Options are:
264+
- RANGE_30G (0x00): ±30 Gauss range
265+
- RANGE_12G (0x01): ±12 Gauss range
266+
- RANGE_8G (0x02): ±8 Gauss range
267+
- RANGE_2G (0x03): ±2 Gauss range
268+
"""
269+
return self._range
270+
271+
@range.setter
272+
def range(self, value: int) -> None:
273+
if value not in {RANGE_30G, RANGE_12G, RANGE_8G, RANGE_2G}:
274+
raise ValueError("Invalid range")
275+
self._range = value
276+
277+
@property
278+
def setreset_mode(self) -> int:
279+
"""The set/reset mode.
280+
281+
Options are:
282+
- SETRESET_ON (0x00): Set and reset on
283+
- SETRESET_SETONLY (0x01): Set only on
284+
- SETRESET_OFF (0x02): Set and reset off
285+
"""
286+
return self._setreset
287+
288+
@setreset_mode.setter
289+
def setreset_mode(self, value: int) -> None:
290+
if value not in {SETRESET_ON, SETRESET_SETONLY, SETRESET_OFF}:
291+
raise ValueError("Invalid set/reset mode")
292+
self._setreset = value
293+
294+
def soft_reset(self) -> None:
295+
"""Perform a soft reset of the chip."""
296+
self._reset = True
297+
time.sleep(0.05) # Wait 50ms for reset to complete
298+
299+
# Verify chip ID after reset
300+
if self._chip_id != 0x80:
301+
raise RuntimeError("Chip ID invalid after reset")
302+
303+
def self_test(self) -> bool:
304+
"""Perform self-test of the chip.
305+
306+
:return: True if self-test passed, False otherwise
307+
"""
308+
self._selftest = True
309+
time.sleep(0.005) # Wait 5ms for self-test to complete
310+
311+
# Check if self-test bit auto-cleared (indicates completion)
312+
return not self._selftest

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# Uncomment the below if you use native CircuitPython modules such as
2626
# digitalio, micropython and busio. List the modules you use. Without it, the
2727
# autodoc module docs will fail to generate with a warning.
28-
# autodoc_mock_imports = ["digitalio", "busio"]
28+
autodoc_mock_imports = ["digitalio", "busio"]
2929

3030
autodoc_preserve_defaults = True
3131

docs/index.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,12 @@ Table of Contents
2424
.. toctree::
2525
:caption: Tutorials
2626

27-
.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave
28-
the toctree above for use later.
27+
Adafruit QMC5883P - Triple Axis Magnetometer Learn Guide <https://learn.adafruit.com/adafruit-qmc5883p-triple-axis-magnetometer>
2928

3029
.. toctree::
3130
:caption: Related Products
3231

33-
.. todo:: Add any product links here. If there are none, then simply delete this todo and leave
34-
the toctree above for use later.
32+
Adafruit QMC5883P - Triple Axis Magnetometer - STEMMA QT <https://www.adafruit.com/product/6388>
3533

3634
.. toctree::
3735
:caption: Other Links

examples/qmc5883p_simpletest.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
1-
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
21
# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
32
#
4-
# SPDX-License-Identifier: Unlicense
3+
# SPDX-License-Identifier: MIT
4+
5+
"""QMC5333P Simple Test"""
6+
7+
import time
8+
9+
import board
10+
11+
import adafruit_qmc5883p
12+
13+
i2c = board.I2C() # uses board.SCL and board.SDA
14+
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
15+
16+
sensor = adafruit_qmc5883p.QMC5883P(i2c)
17+
18+
# Optional: Configure sensor settings
19+
# sensor.mode = adafruit_qmc5883p.MODE_CONTINUOUS
20+
# sensor.data_rate = adafruit_qmc5883p.ODR_10HZ
21+
# sensor.range = adafruit_qmc5883p.RANGE_2G
22+
23+
print("QMC5883P Magnetometer Test")
24+
print("-" * 40)
25+
26+
while True:
27+
mag_x, mag_y, mag_z = sensor.magnetic
28+
29+
print(f"X:{mag_x:10.2f}, Y:{mag_y:10.2f}, Z:{mag_z:10.2f} uT")
30+
print("")
31+
32+
time.sleep(1)

0 commit comments

Comments
 (0)