1- """Calibrate the LIS2MDL magnetometer and save to persistent config.
2-
3- This example runs a 3D min/max calibration by collecting samples while
4- the user rotates the board in all directions. The computed hard-iron
5- offsets and soft-iron scale factors are stored in the config zone and
6- survive power cycles.
7-
8- Instructions and a countdown are displayed on the SSD1327 OLED screen.
9- Press MENU to start the calibration.
10- """
1+ """Calibrate the LIS2MDL magnetometer using steami_screen UI."""
112
123import gc
134from time import sleep_ms
145
6+ import ssd1327
157from daplink_bridge import DaplinkBridge
168from lis2mdl import LIS2MDL
179from machine import I2C , SPI , Pin
18- from ssd1327 import WS_OLED_128X128_SPI
1910from steami_config import SteamiConfig
11+ from steami_screen import Screen , SSD1327Display
2012
2113# --- Hardware init ---
2214
2315i2c = I2C (1 )
24- oled = WS_OLED_128X128_SPI (
25- SPI (1 ),
26- Pin ("DATA_COMMAND_DISPLAY" ),
27- Pin ("RST_DISPLAY" ),
28- Pin ("CS_DISPLAY" ),
16+
17+ spi = SPI (1 )
18+ dc = Pin ("DATA_COMMAND_DISPLAY" )
19+ res = Pin ("RST_DISPLAY" )
20+ cs = Pin ("CS_DISPLAY" )
21+
22+ display = SSD1327Display (
23+ ssd1327 .WS_OLED_128X128_SPI (spi , dc , res , cs )
2924)
25+
26+ screen = Screen (display )
27+
3028btn_menu = Pin ("MENU_BUTTON" , Pin .IN , Pin .PULL_UP )
3129
3230bridge = DaplinkBridge (i2c )
3331config = SteamiConfig (bridge )
3432config .load ()
33+
3534mag = LIS2MDL (i2c )
3635config .apply_magnetometer_calibration (mag )
3736
3837
39- # --- Helper functions ---
40-
41-
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 ()
51-
52-
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 )
59-
38+ # --- Helpers ---
6039
6140def wait_menu ():
62- """Wait for MENU button press then release."""
6341 while btn_menu .value () == 1 :
6442 sleep_ms (10 )
6543 while btn_menu .value () == 0 :
6644 sleep_ms (10 )
6745
6846
69- # --- Step 1: Display instructions and wait for MENU ---
47+ def show_intro ():
48+ screen .clear ()
49+ screen .title ("COMPAS" )
50+
51+ screen .text ("Tournez la" , at = (22 , 38 ))
52+ screen .text ("carte dans" , at = (22 , 50 ))
53+ screen .text ("toutes les" , at = (22 , 62 ))
54+ screen .text ("directions" , at = (24 , 74 ))
55+ screen .text ("Menu=demarrer" , at = (10 , 90 ))
56+
57+ screen .show ()
58+
59+
60+ def show_progress (remaining ):
61+ screen .clear ()
62+ screen .title ("COMPAS" )
63+
64+ screen .text ("Acquisition..." , at = (12 , 44 ))
65+ screen .value (remaining )
66+ screen .text ("Tournez" , at = (30 , 80 ))
67+ screen .text ("la carte" , at = (28 , 92 ))
68+
69+ screen .show ()
70+
71+
72+ def show_message (* lines ):
73+ screen .clear ()
74+ screen .title ("COMPAS" )
75+ screen .subtitle (* lines )
76+ screen .show ()
77+
78+
79+ def show_results (readings ):
80+ screen .clear ()
81+ screen .title ("COMPAS" )
82+
83+ screen .text ("Resultats:" , at = (24 , 34 ))
84+
85+ y = 48
86+ for i , heading in enumerate (readings ):
87+ line = "{}: {} deg" .format (i + 1 , int (heading ))
88+ screen .text (line , at = (16 , y ))
89+ y += 12
90+
91+ screen .text ("Termine !" , at = (28 , 112 ))
92+ screen .show ()
93+
94+
95+ # --- Step 1: Instructions ---
7096
7197print ("=== Magnetometer Calibration ===\n " )
72- print ("Current offsets: x={:.1f} y={:.1f} z={:.1f}" .format (
73- mag .x_off , mag .y_off , mag .z_off ))
74- print ("Current scales: x={:.3f} y={:.3f} z={:.3f}\n " .format (
75- mag .x_scale , mag .y_scale , mag .z_scale ))
76-
77- show ([
78- "COMPAS" ,
79- "" ,
80- "Tournez la" ,
81- "carte dans" ,
82- "toutes les" ,
83- "directions" ,
84- "" ,
85- "MENU = demarrer" ,
86- ])
98+
99+ show_intro ()
87100
88101print ("Press MENU to start calibration..." )
89102wait_menu ()
90103print ("Starting calibration...\n " )
91104
92- # --- Step 2: Acquisition with countdown ---
105+
106+ # --- Step 2: Acquisition ---
93107
94108samples = 600
95109delay = 20
96110total_sec = (samples * delay ) // 1000
111+
97112xmin = ymin = zmin = 1e9
98113xmax = ymax = zmax = - 1e9
99114
100115for s in range (samples ):
101116 x , y , z = mag .magnetic_field ()
117+
102118 xmin = min (xmin , x )
103119 xmax = max (xmax , x )
104120 ymin = min (ymin , y )
105121 ymax = max (ymax , y )
106122 zmin = min (zmin , z )
107123 zmax = max (zmax , z )
124+
108125 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- ])
126+ remaining = total_sec - (s * delay ) // 1000
127+ show_progress (remaining )
128+
120129 sleep_ms (delay )
121130
131+
132+ # --- Compute calibration ---
133+
122134mag .x_off = (xmax + xmin ) / 2.0
123135mag .y_off = (ymax + ymin ) / 2.0
124136mag .z_off = (zmax + zmin ) / 2.0
137+
125138mag .x_scale = (xmax - xmin ) / 2.0 or 1.0
126139mag .y_scale = (ymax - ymin ) / 2.0 or 1.0
127140mag .z_scale = (zmax - zmin ) / 2.0 or 1.0
128141
129142print ("Calibration complete!" )
130- print (" Hard-iron offsets: x={:.1f} y={:.1f} z={:.1f}" .format (
131- mag .x_off , mag .y_off , mag .z_off ))
132- print (" Soft-iron scales: x={:.3f} y={:.3f} z={:.3f}\n " .format (
133- mag .x_scale , mag .y_scale , mag .z_scale ))
134143
135- # --- Step 3: Save to config zone ---
136144
137- show (["COMPAS" , "" , "Sauvegarde..." ])
145+ # --- Step 3: Save ---
146+
147+ show_message ("Sauvegarde..." )
138148
139149config .set_magnetometer_calibration (
140150 hard_iron_x = mag .x_off ,
@@ -144,42 +154,42 @@ def wait_menu():
144154 soft_iron_y = mag .y_scale ,
145155 soft_iron_z = mag .z_scale ,
146156)
157+
147158config .save ()
148- print ("Calibration saved to config zone.\n " )
149159sleep_ms (500 )
150160
161+
151162# --- Step 4: Verify ---
152163
153- show ([ "COMPAS" , "" , " Sauvegarde OK" , "" , "Verification..." ] )
164+ show_message ( " Sauvegarde OK" , "" , "Verification..." )
154165
155166gc .collect ()
167+
156168config2 = SteamiConfig (bridge )
157169config2 .load ()
158170
159171mag2 = LIS2MDL (i2c )
160172config2 .apply_magnetometer_calibration (mag2 )
161173
162- print ("Verification (5 heading readings after reload):" )
163- result_lines = ["COMPAS" , "" , "Resultats:" ]
174+ print ("Verification (5 readings):" )
175+
176+ readings = []
177+
164178for i in range (5 ):
165179 heading = mag2 .heading_flat_only ()
166- line = " {}: cap={:.0f}" .format (i + 1 , heading )
167- print (" Reading {}: heading={:.1f} deg" .format (i + 1 , heading ))
168- result_lines .append (line )
180+ readings .append (heading )
181+
182+ screen .clear ()
183+ screen .title ("COMPAS" )
184+ screen .value (int (heading ), unit = "deg" , label = "Mesure {}" .format (i + 1 ))
185+ screen .show ()
186+
187+ print ("Reading {}: {:.1f} deg" .format (i + 1 , heading ))
169188 sleep_ms (500 )
170189
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-
185- print ("\n Done! Calibration is stored and will be restored at next boot." )
190+
191+ # --- Done ---
192+
193+ show_results (readings )
194+
195+ print ("\n Done! Calibration stored." )
0 commit comments