55offsets and soft-iron scale factors are stored in the config zone and
66survive power cycles.
77
8- Instructions are displayed on the SSD1327 OLED screen when available.
9-
10- Usage:
11- mpremote mount lib/ run lib/steami_config/examples/calibrate_magnetometer.py
12-
13- When prompted, slowly rotate the board in all directions (tilt, roll,
14- yaw) for about 12 seconds. The script then saves the calibration and
15- verifies it by displaying corrected heading readings.
8+ Instructions and a countdown are displayed on the SSD1327 OLED screen.
9+ Press MENU to start the calibration.
1610"""
1711
1812import gc
19- import sys
2013from time import sleep_ms
2114
22- from machine import I2C
15+ from daplink_flash import DaplinkFlash
16+ from lis2mdl import LIS2MDL
17+ from machine import I2C , SPI , Pin
18+ from ssd1327 import WS_OLED_128X128_SPI
19+ from steami_config import SteamiConfig
2320
24- # Add driver paths when running via mpremote mount lib/
25- for p in ("daplink_flash" , "steami_config" , "lis2mdl" , "ssd1327" ):
26- path = "/remote/" + p
27- if path not in sys .path :
28- sys .path .insert (0 , path )
21+ # --- Hardware init ---
2922
23+ i2c = I2C (1 )
24+ oled = WS_OLED_128X128_SPI (
25+ SPI (1 ),
26+ Pin ("DATA_COMMAND_DISPLAY" ),
27+ Pin ("RST_DISPLAY" ),
28+ Pin ("CS_DISPLAY" ),
29+ )
30+ btn_menu = Pin ("MENU_BUTTON" , Pin .IN , Pin .PULL_UP )
3031
31- def show_screen (i2c , lines ):
32- """Display text lines on the OLED screen, then free the driver."""
33- try :
34- from ssd1327 .device import WS_OLED_128X128_I2C
32+ flash = DaplinkFlash (i2c )
33+ config = SteamiConfig (flash )
34+ config .load ()
35+ mag = LIS2MDL (i2c )
36+ config .apply_magnetometer_calibration (mag )
3537
36- oled = WS_OLED_128X128_I2C (i2c )
37- oled .fill (0 )
38- for i , line in enumerate (lines ):
39- oled .text (line , 0 , i * 12 , 15 )
40- oled .show ()
41- del oled
42- except Exception :
43- pass
44- sys .modules .pop ("ssd1327.device" , None )
45- gc .collect ()
4638
39+ # --- Helper functions ---
4740
48- i2c = I2C (1 )
4941
50- # --- Step 1: Load config and magnetometer ---
42+ def show (lines ):
43+ """Display centered text lines on the round OLED screen."""
44+ oled .fill (0 )
45+ th = len (lines ) * 12
46+ ys = max (0 , (128 - th ) // 2 )
47+ for i , line in enumerate (lines ):
48+ x = max (0 , (128 - len (line ) * 8 ) // 2 )
49+ oled .text (line , x , ys + i * 12 , 15 )
50+ oled .show ()
5151
52- from daplink_flash .device import DaplinkFlash # noqa: E402
53- from steami_config .device import SteamiConfig # noqa: E402
5452
55- flash = DaplinkFlash (i2c )
56- config = SteamiConfig (flash )
57- config .load ()
53+ def draw_degree (x , y , col = 15 ):
54+ """Draw a tiny degree symbol (3x3 circle) at pixel position."""
55+ oled .pixel (x + 1 , y , col )
56+ oled .pixel (x , y + 1 , col )
57+ oled .pixel (x + 2 , y + 1 , col )
58+ oled .pixel (x + 1 , y + 2 , col )
5859
59- from lis2mdl .device import LIS2MDL # noqa: E402
6060
61- mag = LIS2MDL (i2c )
61+ def wait_menu ():
62+ """Wait for MENU button press then release."""
63+ while btn_menu .value () == 1 :
64+ sleep_ms (10 )
65+ while btn_menu .value () == 0 :
66+ sleep_ms (10 )
67+
68+
69+ # --- Step 1: Display instructions and wait for MENU ---
6270
63- # Show current state
6471print ("=== Magnetometer Calibration ===\n " )
6572print ("Current offsets: x={:.1f} y={:.1f} z={:.1f}" .format (
6673 mag .x_off , mag .y_off , mag .z_off ))
6774print ("Current scales: x={:.3f} y={:.3f} z={:.3f}\n " .format (
6875 mag .x_scale , mag .y_scale , mag .z_scale ))
6976
70- # --- Step 2: Display instructions on screen ---
71-
72- show_screen (i2c , [
73- "=== COMPAS ===" ,
74- "" ,
75- "Calibration du" ,
76- "magnetometre" ,
77- "" ,
78- "Tournez la carte" ,
79- "dans toutes les" ,
80- "directions..." ,
81- "" ,
82- "12 secondes" ,
83- ])
84-
85- print ("Rotate the board slowly in ALL directions for 12 seconds..." )
86- print ("(tilt, roll, turn upside down, spin...)\n " )
87- sleep_ms (2000 )
88-
89- # --- Step 3: Run 3D calibration ---
90-
91- show_screen (i2c , [
92- "=== COMPAS ===" ,
77+ show ([
78+ "COMPAS" ,
9379 "" ,
94- "Acquisition..." ,
80+ "Tournez la" ,
81+ "carte dans" ,
82+ "toutes les" ,
83+ "directions" ,
9584 "" ,
96- "Continuez a" ,
97- "tourner la carte" ,
85+ "MENU = demarrer" ,
9886])
9987
100- mag .calibrate_minmax_3d (samples = 600 , delay_ms = 20 )
88+ print ("Press MENU to start calibration..." )
89+ wait_menu ()
90+ print ("Starting calibration...\n " )
91+
92+ # --- Step 2: Acquisition with countdown ---
93+
94+ samples = 600
95+ delay = 20
96+ total_sec = (samples * delay ) // 1000
97+ xmin = ymin = zmin = 1e9
98+ xmax = ymax = zmax = - 1e9
99+
100+ for s in range (samples ):
101+ x , y , z = mag .magnetic_field ()
102+ xmin = min (xmin , x )
103+ xmax = max (xmax , x )
104+ ymin = min (ymin , y )
105+ ymax = max (ymax , y )
106+ zmin = min (zmin , z )
107+ zmax = max (zmax , z )
108+ if s % 50 == 0 :
109+ remain = total_sec - (s * delay ) // 1000
110+ show ([
111+ "COMPAS" ,
112+ "" ,
113+ "Acquisition..." ,
114+ "" ,
115+ "Continuez a" ,
116+ "tourner" ,
117+ "" ,
118+ "{} sec" .format (remain ),
119+ ])
120+ sleep_ms (delay )
121+
122+ mag .x_off = (xmax + xmin ) / 2.0
123+ mag .y_off = (ymax + ymin ) / 2.0
124+ mag .z_off = (zmax + zmin ) / 2.0
125+ mag .x_scale = (xmax - xmin ) / 2.0 or 1.0
126+ mag .y_scale = (ymax - ymin ) / 2.0 or 1.0
127+ mag .z_scale = (zmax - zmin ) / 2.0 or 1.0
101128
102129print ("Calibration complete!" )
103130print (" Hard-iron offsets: x={:.1f} y={:.1f} z={:.1f}" .format (
104131 mag .x_off , mag .y_off , mag .z_off ))
105132print (" Soft-iron scales: x={:.3f} y={:.3f} z={:.3f}\n " .format (
106133 mag .x_scale , mag .y_scale , mag .z_scale ))
107134
108- # --- Step 4: Save to config zone ---
135+ # --- Step 3: Save to config zone ---
136+
137+ show (["COMPAS" , "" , "Sauvegarde..." ])
109138
110139config .set_magnetometer_calibration (
111140 hard_iron_x = mag .x_off ,
@@ -117,17 +146,11 @@ def show_screen(i2c, lines):
117146)
118147config .save ()
119148print ("Calibration saved to config zone.\n " )
149+ sleep_ms (500 )
120150
121- show_screen (i2c , [
122- "=== COMPAS ===" ,
123- "" ,
124- "Calibration" ,
125- "sauvegardee !" ,
126- "" ,
127- "Verification..." ,
128- ])
151+ # --- Step 4: Verify ---
129152
130- # --- Step 5: Verify ---
153+ show ([ "COMPAS" , "" , "Sauvegarde OK" , "" , "Verification..." ])
131154
132155gc .collect ()
133156config2 = SteamiConfig (flash )
@@ -137,15 +160,26 @@ def show_screen(i2c, lines):
137160config2 .apply_magnetometer_calibration (mag2 )
138161
139162print ("Verification (5 heading readings after reload):" )
140- lines = ["=== COMPAS === " , "" , "Verification :" ]
163+ result_lines = ["COMPAS" , "" , "Resultats :" ]
141164for i in range (5 ):
142165 heading = mag2 .heading_flat_only ()
143- norm = mag2 .calibrated_field ()
144- line = " {}: cap={:.0f} deg" .format (i + 1 , heading )
145- print (" Reading {}: heading={:.1f} deg norm=({:.3f}, {:.3f}, {:.3f})" .format (
146- i + 1 , heading , norm [0 ], norm [1 ], norm [2 ]))
147- lines .append (line )
166+ line = " {}: cap={:.0f}" .format (i + 1 , heading )
167+ print (" Reading {}: heading={:.1f} deg" .format (i + 1 , heading ))
168+ result_lines .append (line )
148169 sleep_ms (500 )
149170
150- show_screen (i2c , lines + ["" , "Termine !" ]) # noqa: RUF005
171+ result_lines .append ("" )
172+ result_lines .append ("Termine !" )
173+
174+ # Draw results with degree symbols
175+ oled .fill (0 )
176+ th = len (result_lines ) * 12
177+ ys = max (0 , (128 - th ) // 2 )
178+ for i , line in enumerate (result_lines ):
179+ x = max (0 , (128 - len (line ) * 8 ) // 2 )
180+ oled .text (line , x , ys + i * 12 , 15 )
181+ if "cap=" in line :
182+ draw_degree (x + len (line ) * 8 + 1 , ys + i * 12 )
183+ oled .show ()
184+
151185print ("\n Done! Calibration is stored and will be restored at next boot." )
0 commit comments