Skip to content

Commit d13a2ca

Browse files
authored
Merge pull request #22 from caternuson/iss20
Add resolution, filter, and oversampling
2 parents ad1e761 + 39d0917 commit d13a2ca

File tree

1 file changed

+182
-21
lines changed

1 file changed

+182
-21
lines changed

adafruit_mlx90393.py

+182-21
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
_CMD_REG_CONF3 = const(0x02) # Oversampling, Filter, Resolution
7878
_CMD_REG_CONF4 = const(0x03) # Sensitivity drift
7979

80+
# Gain settings
8081
GAIN_5X = 0x0
8182
GAIN_4X = 0x1
8283
GAIN_3X = 0x2
@@ -87,11 +88,27 @@
8788
GAIN_1X = 0x7
8889
_GAIN_SHIFT = const(4)
8990

90-
_RES_2_15 = const(0) # +/- 2^15
91-
_RES_2_15B = const(1) # +/- 2^15
92-
_RES_22000 = const(2) # +/- 22000
93-
_RES_11000 = const(3) # +/- 11000
94-
_RES_SHIFT = const(5)
91+
# Resolution settings
92+
RESOLUTION_16 = 0x0
93+
RESOLUTION_17 = 0x1
94+
RESOLUTION_18 = 0x2
95+
RESOLUTION_19 = 0x3
96+
97+
# Filter settings
98+
FILTER_0 = 0x0
99+
FILTER_1 = 0x1
100+
FILTER_2 = 0x2
101+
FILTER_3 = 0x3
102+
FILTER_4 = 0x4
103+
FILTER_5 = 0x5
104+
FILTER_6 = 0x6
105+
FILTER_7 = 0x7
106+
107+
# Oversampling settings
108+
OSR_0 = 0x0
109+
OSR_1 = 0x1
110+
OSR_2 = 0x2
111+
OSR_3 = 0x3
95112

96113
_HALLCONF = const(0x0C) # Hall plate spinning rate adjust.
97114

@@ -120,8 +137,22 @@
120137
((0.150, 0.242), (0.300, 0.484), (0.601, 0.968), (1.202, 1.936)),
121138
)
122139

140+
# Lookup table for conversion times for different filter and
141+
# oversampling settings. Values taken from datasheet.
142+
_TCONV_LOOKUP = (
143+
# OSR = 0 1 2 3
144+
(1.27, 1.84, 3.00, 5.30), # DIG_FILT = 0
145+
(1.46, 2.23, 3.76, 6.84), # DIG_FILT = 1
146+
(1.84, 3.00, 5.30, 9.91), # DIG_FILT = 2
147+
(2.61, 4.53, 8.37, 16.05), # DIG_FILT = 3
148+
(4.15, 7.60, 14.52, 28.34), # DIG_FILT = 4
149+
(7.22, 13.75, 26.80, 52.92), # DIG_FILT = 5
150+
(13.36, 26.04, 51.38, 102.07), # DIG_FILT = 6
151+
(25.65, 50.61, 100.53, 200.37), # DIF_FILT = 7
152+
)
153+
123154

124-
class MLX90393:
155+
class MLX90393: # pylint: disable=too-many-instance-attributes
125156
"""
126157
Driver for the MLX90393 magnetometer.
127158
:param i2c_bus: The `busio.I2C` object to use. This is the only
@@ -131,16 +162,38 @@ class MLX90393:
131162
:param bool debug: (optional) Enable debug output.
132163
"""
133164

134-
def __init__(self, i2c_bus, address=0x0C, gain=GAIN_1X, debug=False):
165+
def __init__(
166+
self,
167+
i2c_bus,
168+
address=0x0C,
169+
gain=GAIN_1X,
170+
resolution=RESOLUTION_16,
171+
filt=FILTER_7,
172+
oversampling=OSR_3,
173+
debug=False,
174+
): # pylint: disable=too-many-arguments
135175
self.i2c_device = I2CDevice(i2c_bus, address)
136176
self._debug = debug
137177
self._status_last = 0
138-
self._res_current = _RES_2_15
178+
self._res_x = self._res_y = self._res_z = resolution
179+
self._filter = filt
180+
self._osr = oversampling
139181
self._gain_current = gain
140182

141183
# Put the device in a known state to start
142184
self.reset()
143185

186+
# Set resolution to the supplied level
187+
self.resolution_x = self._res_x
188+
self.resolution_y = self._res_y
189+
self.resolution_z = self._res_z
190+
191+
# Set filter to the supplied level
192+
self.filter = self._filter
193+
194+
# Set oversampling to the supplied level
195+
self.oversampling = self._osr
196+
144197
# Set gain to the supplied level
145198
self.gain = self._gain_current
146199

@@ -162,6 +215,7 @@ def _transceive(self, payload, rxlen=0):
162215
# Write 'value' to the specified register
163216
# TODO: Check this. It's weird that the write is accepted but the read is naked.
164217
with self.i2c_device as i2c:
218+
# pylint: disable=unexpected-keyword-arg
165219
i2c.write(payload, stop=False)
166220

167221
while True:
@@ -185,22 +239,19 @@ def _transceive(self, payload, rxlen=0):
185239
@property
186240
def last_status(self):
187241
"""
188-
Returns the last status byte received from the sensor.
242+
The last status byte received from the sensor.
189243
"""
190244
return self._status_last
191245

192246
@property
193247
def gain(self):
194248
"""
195-
Gets the current gain setting for the device.
249+
The gain setting for the device.
196250
"""
197251
return self._gain_current
198252

199253
@gain.setter
200254
def gain(self, value):
201-
"""
202-
Sets the gain for the device.
203-
"""
204255
if value > GAIN_1X or value < GAIN_5X:
205256
raise ValueError("Invalid GAIN setting")
206257
if self._debug:
@@ -217,6 +268,81 @@ def gain(self, value):
217268
)
218269
)
219270

271+
@property
272+
def resolution_x(self):
273+
"""The X axis resolution."""
274+
return self._res_x
275+
276+
@resolution_x.setter
277+
def resolution_x(self, resolution):
278+
self._set_resolution(0, resolution)
279+
self._res_x = resolution
280+
281+
@property
282+
def resolution_y(self):
283+
"""The Y axis resolution."""
284+
return self._res_y
285+
286+
@resolution_y.setter
287+
def resolution_y(self, resolution):
288+
self._set_resolution(1, resolution)
289+
self._res_y = resolution
290+
291+
@property
292+
def resolution_z(self):
293+
"""The Z axis resolution."""
294+
return self._res_z
295+
296+
@resolution_z.setter
297+
def resolution_z(self, resolution):
298+
self._set_resolution(2, resolution)
299+
self._res_z = resolution
300+
301+
def _set_resolution(self, axis, resolution):
302+
if resolution not in (
303+
RESOLUTION_16,
304+
RESOLUTION_17,
305+
RESOLUTION_18,
306+
RESOLUTION_19,
307+
):
308+
raise ValueError("Incorrect resolution setting.")
309+
shift = (5, 7, 9)[axis]
310+
mask = (0xFF9F, 0xFE7F, 0xF9FF)[axis]
311+
reg = self.read_reg(_CMD_REG_CONF3)
312+
reg &= mask
313+
reg |= (resolution & 0x3) << shift
314+
self.write_reg(_CMD_REG_CONF3, reg)
315+
316+
@property
317+
def filter(self):
318+
"""The filter level."""
319+
return self._filter
320+
321+
@filter.setter
322+
def filter(self, level):
323+
if level not in range(8):
324+
raise ValueError("Incorrect filter level.")
325+
reg = self.read_reg(_CMD_REG_CONF3)
326+
reg &= 0xFFE3
327+
reg |= (level & 0x7) << 2
328+
self.write_reg(_CMD_REG_CONF3, reg)
329+
self._filter = level
330+
331+
@property
332+
def oversampling(self):
333+
"""The oversampling level."""
334+
return self._osr
335+
336+
@oversampling.setter
337+
def oversampling(self, level):
338+
if level not in range(4):
339+
raise ValueError("Incorrect oversampling level.")
340+
reg = self.read_reg(_CMD_REG_CONF3)
341+
reg &= 0xFFFC
342+
reg |= level & 0x3
343+
self.write_reg(_CMD_REG_CONF3, reg)
344+
self._osr = level
345+
220346
def display_status(self):
221347
"""
222348
Prints out the content of the last status byte in a human-readble
@@ -248,14 +374,29 @@ def read_reg(self, reg):
248374
with self.i2c_device as i2c:
249375
i2c.readinto(data)
250376
# Unpack data (status byte, big-endian 16-bit register value)
251-
self._status_last, val = struct.unpack(">Bh", data)
377+
self._status_last, val = struct.unpack(">BH", data)
252378
if self._debug:
253379
print("\t[{}]".format(time.monotonic()))
254380
print("\t Writing :", [hex(b) for b in payload])
255381
print("\tResponse :", [hex(b) for b in data])
256382
print("\t Status :", hex(data[0]))
257383
return val
258384

385+
def write_reg(self, reg, value):
386+
"""
387+
Writes the 16-bit value to the supplied register.
388+
"""
389+
self._transceive(
390+
bytes(
391+
[
392+
_CMD_WR,
393+
value >> 8, # high byte
394+
value & 0xFF, # low byte
395+
reg << 2, # the register
396+
]
397+
)
398+
)
399+
259400
def reset(self):
260401
"""
261402
Performs a software reset of the sensor.
@@ -276,20 +417,40 @@ def read_data(self):
276417
"""
277418
Reads a single X/Y/Z sample from the magnetometer.
278419
"""
279-
delay = 0.01
420+
# Set conversion delay based on filter and oversampling
421+
delay = _TCONV_LOOKUP[self._filter][self._osr] / 1000 # per datasheet
422+
delay *= 1.1 # plus a little
280423

281424
# Set the device to single measurement mode
282425
self._transceive(bytes([_CMD_SM | _CMD_AXIS_ALL]))
283426

284427
# Insert a delay since we aren't using INTs for DRDY
285428
time.sleep(delay)
286429

287-
# Read the 'XYZ' data as three signed 16-bit integers
430+
# Read the 'XYZ' data
288431
data = self._transceive(bytes([_CMD_RM | _CMD_AXIS_ALL]), 6)
289-
self._status_last, m_x, m_y, m_z = struct.unpack(">Bhhh", data)
432+
433+
# Unpack status and raw int values
434+
self._status_last = data[0]
435+
m_x = self._unpack_axis_data(self._res_x, data[1:3])
436+
m_y = self._unpack_axis_data(self._res_y, data[3:5])
437+
m_z = self._unpack_axis_data(self._res_z, data[5:7])
290438

291439
# Return the raw int values if requested
292-
return (m_x, m_y, m_z)
440+
return m_x, m_y, m_z
441+
442+
# pylint: disable=no-self-use
443+
def _unpack_axis_data(self, resolution, data):
444+
# see datasheet
445+
if resolution == RESOLUTION_19:
446+
(value,) = struct.unpack(">H", data)
447+
value -= 0x4000
448+
elif resolution == RESOLUTION_18:
449+
(value,) = struct.unpack(">H", data)
450+
value -= 0x8000
451+
else:
452+
value = struct.unpack(">h", data)[0]
453+
return value
293454

294455
@property
295456
def magnetic(self):
@@ -300,8 +461,8 @@ def magnetic(self):
300461
x, y, z = self.read_data
301462

302463
# Convert the raw integer values to uT based on gain and resolution
303-
x *= _LSB_LOOKUP[self._gain_current][self._res_current][0]
304-
y *= _LSB_LOOKUP[self._gain_current][self._res_current][0]
305-
z *= _LSB_LOOKUP[self._gain_current][self._res_current][1]
464+
x *= _LSB_LOOKUP[self._gain_current][self._res_x][0]
465+
y *= _LSB_LOOKUP[self._gain_current][self._res_y][0]
466+
z *= _LSB_LOOKUP[self._gain_current][self._res_z][1]
306467

307468
return x, y, z

0 commit comments

Comments
 (0)