Skip to content

Commit 8212fad

Browse files
examples(lis2mdl): Replace test scripts with practical examples. (#251)
* fix(lis2mdl): Remove test from example * feat(lis2mdl): add basic read example * feat(lis2mdl): add calibrate 2D example * feat(lis2mdl): add door sensor example * feat(lis2mdl): add field_map example * feat(lis2mdl): add low power one shot example * feat(lis2mdl): add metal detector example * feat(lis2mdl): add fiel logger example * feat(lis2mdl): add tilt compensation example * docs(lis2mdl): add example to readme * fix(lis2mdl): Remove useless accel read in tilt compensated example * fix(lis2mdl): correct tone timing in metal detector example Use millisecond-based timing in tone() and adjust frequency range to 150–500 Hz. Previous implementation used microsecond calculations with sleep_ms(), causing incorrect and capped frequencies (~500 Hz max regardless of input). This ensures consistent and predictable buzzer behavior without requiring sleep_us() or PWM. * fix(lis2mdl): Use hardware PWM for metal detector buzzer tone. * fix(lis2mdl): Use pyb to import timer in metal detector --------- Co-authored-by: Sébastien NEDJAR <sebastien@nedjar.com>
1 parent dad64ba commit 8212fad

10 files changed

Lines changed: 486 additions & 691 deletions

lib/lis2mdl/README.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -215,22 +215,20 @@ print("Register dump:", regs)
215215

216216
---
217217

218-
## Example: Continuous Compass Loop
219-
220-
```python
221-
from machine import I2C, Pin
222-
from lis2mdl import LIS2MDL
223-
import time
224-
225-
i2c = I2C(1, scl=Pin(22), sda=Pin(21))
226-
mag = LIS2MDL(i2c)
227-
mag.set_declination(2.3)
228-
229-
while True:
230-
heading = mag.heading_flat_only()
231-
print("Heading: {:.1f}° {}".format(heading, mag.direction_label(heading)))
232-
time.sleep(0.5)
233-
```
218+
## Examples
219+
220+
| Example | Description |
221+
|-----------------------------|-------------|
222+
| basic_read.py | Read raw magnetic field (X, Y, Z) in microtesla and temperature in a loop. Simplest entry point to the LIS2MDL driver. |
223+
| calibrate_2d.py | Interactive 2D hard-iron calibration. The user rotates the board flat to compute offsets using `calibrate_minmax_2d()`, then evaluates calibration quality with `calibrate_quality()`. |
224+
| tilt_compensated_heading.py | Tilt-compensated heading using both magnetometer and accelerometer. Combines `heading_with_tilt_compensation()` from LIS2MDL with acceleration data from the ISM330DL driver. **Dependency: `ism330dl` driver required.** |
225+
| metal_detector.py | Detect nearby metal objects by monitoring magnetic field magnitude changes. Displays an intensity bar and triggers a buzzer with stronger beeps for stronger disturbances. **Dependency: board PWM/buzzer support required, no extra driver needed.** |
226+
| door_sensor.py | Detect door open/close using a magnet. Compares live magnetic field magnitude to a closed-door baseline and prints state changes with timestamps. |
227+
| field_logger.py | Log magnetic field (X, Y, Z) and temperature to a CSV file every second for 60 seconds. The file is written to DAPLink flash and can be read later over USB mass storage. **Dependency: `daplink_flash` driver/module required (`set_filename`, `write_line`).** |
228+
| field_map.py | Real-time spatial magnetic field mapping. Displays X, Y, Z, field magnitude, and min/max tracking for each axis while the board is moved around. |
229+
| low_power_one_shot.py | Energy-efficient sampling example. Uses `power_off()` between readings and `read_one_shot()` every 10 seconds, then prints values and free memory. |
230+
| magnet_compass.py | Flat compass example that computes heading and cardinal direction from the LIS2MDL magnetic field. Useful for basic orientation demos. |
231+
| magnet_fieldForce.py | Magnetic field magnitude example that shows total field strength in microtesla. Useful for observing magnetic disturbances and relative field changes. |
234232

235233
---
236234

lib/lis2mdl/examples/basic_read.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
Read raw magnetic field (X, Y, Z) in microtesla and temperature in a loop.
3+
Simplest possible example — good entry point for beginners.
4+
"""
5+
6+
from time import sleep_ms
7+
8+
from lis2mdl import LIS2MDL
9+
from machine import I2C
10+
11+
i2c = I2C(1)
12+
mag = LIS2MDL(i2c)
13+
14+
print("LIS2MDL basic read example")
15+
print("Press Ctrl+C to stop.")
16+
print()
17+
18+
while True:
19+
x_ut, y_ut, z_ut = mag.magnetic_field_ut()
20+
temp_c = mag.temperature()
21+
22+
print(
23+
"Magnetic field: X={:.2f} uT Y={:.2f} uT Z={:.2f} uT Temp={:.2f} C".format(
24+
x_ut, y_ut, z_ut, temp_c
25+
)
26+
)
27+
28+
sleep_ms(500)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
Interactive hard-iron calibration.
3+
Ask the user to slowly rotate the board flat on a table.
4+
Uses calibrate_minmax_2d() to compute offsets, then displays before/after heading quality with calibrate_quality().
5+
Demonstrates the full calibration workflow.
6+
"""
7+
from time import sleep_ms
8+
9+
from lis2mdl import LIS2MDL
10+
from machine import I2C
11+
12+
13+
def print_quality(title, quality):
14+
print(title)
15+
print(" mean_xy = ({:.3f}, {:.3f})".format(quality["mean_xy"][0], quality["mean_xy"][1]))
16+
print(" mean_z = {:.3f}".format(quality["mean_z"]))
17+
print(" std_xy = ({:.3f}, {:.3f})".format(quality["std_xy"][0], quality["std_xy"][1]))
18+
print(" std_z = {:.3f}".format(quality["std_z"]))
19+
print(" r_mean_xy = {:.3f}".format(quality["r_mean_xy"]))
20+
print(" r_std_xy = {:.3f}".format(quality["r_std_xy"]))
21+
print(" anisotropy_xy = {:.3f}".format(quality["anisotropy_xy"]))
22+
print()
23+
24+
25+
i2c = I2C(1)
26+
mag = LIS2MDL(i2c)
27+
28+
print("2D hard-iron calibration example")
29+
print("Keep the board flat on a table.")
30+
print("Slowly rotate it through full circles.")
31+
print()
32+
33+
print("Checking heading quality before calibration...")
34+
quality_before = mag.calibrate_quality(samples_check=120, delay_ms=15)
35+
print_quality("Before calibration:", quality_before)
36+
37+
print("Starting 2D calibration now...")
38+
mag.calibrate_minmax_2d(samples=300, delay_ms=20)
39+
40+
print("Calibration values:")
41+
print(
42+
" x_off={:.2f} y_off={:.2f} x_scale={:.2f} y_scale={:.2f}".format(
43+
mag.x_off, mag.y_off, mag.x_scale, mag.y_scale
44+
)
45+
)
46+
print()
47+
48+
print("Checking heading quality after calibration...")
49+
quality_after = mag.calibrate_quality(samples_check=120, delay_ms=15)
50+
print_quality("After calibration:", quality_after)
51+
52+
print("Live heading preview after calibration")
53+
print("Press Ctrl+C to stop.")
54+
print()
55+
56+
while True:
57+
heading = mag.heading_flat_only()
58+
direction = mag.direction_label(heading)
59+
print("Heading: {:7.2f} deg Direction: {}".format(heading, direction))
60+
sleep_ms(200)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Detect door open/close using a magnet.
3+
Measure baseline field with door closed (magnet near sensor).
4+
Loop and detect large magnitude drop = door opened. Print state changes with timestamp.
5+
Demonstrates a practical IoT use case.
6+
"""
7+
8+
from time import sleep_ms, ticks_diff, ticks_ms
9+
10+
from lis2mdl import LIS2MDL
11+
from machine import I2C
12+
13+
BASELINE_SAMPLES = 60
14+
OPEN_DROP_UT = 10.0
15+
CLOSE_RECOVER_UT = 6.0
16+
17+
18+
def elapsed_seconds(start_ms):
19+
return ticks_diff(ticks_ms(), start_ms) / 1000.0
20+
21+
22+
i2c = I2C(1)
23+
mag = LIS2MDL(i2c)
24+
25+
print("Door sensor example")
26+
print("Place the board with the door closed and the magnet in its normal closed position.")
27+
print("Measuring closed-door baseline...")
28+
print()
29+
30+
baseline = 0.0
31+
for _ in range(BASELINE_SAMPLES):
32+
baseline += mag.magnitude_ut()
33+
sleep_ms(100)
34+
35+
baseline /= BASELINE_SAMPLES
36+
start_ms = ticks_ms()
37+
state = "CLOSED"
38+
39+
print("Closed baseline: {:.2f} uT".format(baseline))
40+
print("Monitoring door state changes...")
41+
print()
42+
43+
while True:
44+
magnitude = mag.magnitude_ut()
45+
drop = baseline - magnitude
46+
47+
if state == "CLOSED" and drop >= OPEN_DROP_UT:
48+
state = "OPEN"
49+
print("[t+{:.1f}s] Door opened |B|={:.2f} uT drop={:.2f} uT".format(
50+
elapsed_seconds(start_ms), magnitude, drop
51+
))
52+
53+
elif state == "OPEN" and drop <= CLOSE_RECOVER_UT:
54+
state = "CLOSED"
55+
print("[t+{:.1f}s] Door closed |B|={:.2f} uT drop={:.2f} uT".format(
56+
elapsed_seconds(start_ms), magnitude, drop
57+
))
58+
59+
sleep_ms(200)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Log magnetic field X, Y, Z and temperature to DAPLink flash as CSV every second for 60 seconds.
3+
Uses daplink_flash (set_filename, write_line).
4+
File is then accessible via USB mass storage for analysis in a spreadsheet.
5+
"""
6+
7+
from time import sleep_ms, ticks_diff, ticks_ms
8+
9+
from daplink_flash import DaplinkFlash
10+
from lis2mdl import LIS2MDL
11+
from machine import I2C
12+
13+
# Set to True to erase the DAPLink flash on startup (DESTRUCTIVE).
14+
ERASE_FLASH_ON_START = False
15+
16+
LOG_DURATION_S = 60
17+
SAMPLE_PERIOD_MS = 1000
18+
19+
20+
i2c = I2C(1)
21+
sensor = LIS2MDL(i2c)
22+
flash = DaplinkFlash(i2c)
23+
24+
flash.set_filename("LIS2MDL", "CSV")
25+
if ERASE_FLASH_ON_START:
26+
flash.clear_flash()
27+
sleep_ms(500)
28+
print("Flash erased.")
29+
30+
start_ms = ticks_ms()
31+
32+
header = "timestamp_s,x_ut,y_ut,z_ut,magnitude_ut,temperature_c"
33+
print(header)
34+
flash.write_line(header)
35+
36+
while True:
37+
elapsed_s = ticks_diff(ticks_ms(), start_ms) // 1000
38+
x_ut, y_ut, z_ut = sensor.magnetic_field_ut()
39+
magnitude_ut = sensor.magnitude_ut()
40+
temperature_c = sensor.temperature()
41+
42+
line = "{},{:.2f},{:.2f},{:.2f},{:.2f},{:.2f}".format(
43+
elapsed_s,
44+
x_ut,
45+
y_ut,
46+
z_ut,
47+
magnitude_ut,
48+
temperature_c,
49+
)
50+
51+
print(line)
52+
flash.write_line(line)
53+
54+
if elapsed_s >= LOG_DURATION_S:
55+
break
56+
57+
sleep_ms(SAMPLE_PERIOD_MS)
58+
59+
print()
60+
print("Logging complete.")
61+
print("The CSV file is available from the DAPLink USB mass storage.")

lib/lis2mdl/examples/field_map.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Spatial field mapping.
3+
Print X, Y, Z field values and magnitude as a formatted table, updating every 500ms.
4+
Move the board around to see how the field changes.
5+
Includes min/max tracking for each axis to show the range explored.
6+
"""
7+
8+
from math import sqrt
9+
from time import sleep_ms
10+
11+
from lis2mdl import LIS2MDL
12+
from machine import I2C
13+
14+
15+
def update_minmax(value, current_min, current_max):
16+
current_min = min(current_min, value)
17+
current_max = max(current_max, value)
18+
return current_min, current_max
19+
20+
21+
i2c = I2C(1)
22+
mag = LIS2MDL(i2c)
23+
24+
x_min = y_min = z_min = 1e9
25+
x_max = y_max = z_max = -1e9
26+
27+
print("Field map example")
28+
print("Move the board around and watch how the field changes.")
29+
print()
30+
31+
header = "{:>8} {:>8} {:>8} {:>10} {:>17} {:>17} {:>17}".format(
32+
"X(uT)", "Y(uT)", "Z(uT)", "|B|(uT)",
33+
"X range", "Y range", "Z range"
34+
)
35+
print(header)
36+
print("-" * len(header))
37+
38+
while True:
39+
x_ut, y_ut, z_ut = mag.magnetic_field_ut()
40+
magnitude = sqrt(x_ut * x_ut + y_ut * y_ut + z_ut * z_ut)
41+
42+
x_min, x_max = update_minmax(x_ut, x_min, x_max)
43+
y_min, y_max = update_minmax(y_ut, y_min, y_max)
44+
z_min, z_max = update_minmax(z_ut, z_min, z_max)
45+
46+
print(
47+
"{:8.2f} {:8.2f} {:8.2f} {:10.2f} {:>17} {:>17} {:>17}".format(
48+
x_ut,
49+
y_ut,
50+
z_ut,
51+
magnitude,
52+
"[{:.2f}, {:.2f}]".format(x_min, x_max),
53+
"[{:.2f}, {:.2f}]".format(y_min, y_max),
54+
"[{:.2f}, {:.2f}]".format(z_min, z_max),
55+
)
56+
)
57+
58+
sleep_ms(500)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
Energy-efficient sampling.
3+
Use power_off() between readings, trigger read_one_shot() every 10s.
4+
Print values and free memory.
5+
Demonstrates idle mode for battery-powered deployments.
6+
"""
7+
8+
import gc
9+
from math import sqrt
10+
from time import sleep_ms
11+
12+
from lis2mdl import LIS2MDL
13+
from machine import I2C
14+
15+
MAG_LSB_TO_UT = 0.15
16+
SAMPLE_INTERVAL_MS = 10000
17+
18+
19+
i2c = I2C(1)
20+
mag = LIS2MDL(i2c)
21+
22+
print("Low-power one-shot example")
23+
print("The sensor stays in idle mode between readings.")
24+
print("One sample every 10 seconds.")
25+
print()
26+
27+
while True:
28+
mag.power_off()
29+
30+
# Sleep in small steps so Ctrl+C remains responsive.
31+
for _ in range(SAMPLE_INTERVAL_MS // 100):
32+
sleep_ms(100)
33+
34+
raw_x, raw_y, raw_z = mag.read_one_shot()
35+
temp_c = mag.temperature()
36+
37+
x_ut = raw_x * MAG_LSB_TO_UT
38+
y_ut = raw_y * MAG_LSB_TO_UT
39+
z_ut = raw_z * MAG_LSB_TO_UT
40+
magnitude_ut = sqrt(x_ut * x_ut + y_ut * y_ut + z_ut * z_ut)
41+
42+
gc.collect()
43+
free_mem = gc.mem_free()
44+
45+
print(
46+
"One-shot read: X={:.2f} uT Y={:.2f} uT Z={:.2f} uT |B|={:.2f} uT Temp={:.2f} C Free mem={} bytes".format(
47+
x_ut, y_ut, z_ut, magnitude_ut, temp_c, free_mem
48+
)
49+
)

0 commit comments

Comments
 (0)