Skip to content

Commit 0f2bbd5

Browse files
Fri3d 2024 Board: add support for WSEN ISDS IMU
1 parent 56b7cc1 commit 0f2bbd5

File tree

5 files changed

+251
-78
lines changed

5 files changed

+251
-78
lines changed

CLAUDE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,33 @@ The `c_mpos/src/webcam.c` module provides webcam support for desktop builds usin
114114

115115
## Build System
116116

117+
### Development Workflow (IMPORTANT)
118+
119+
**For most development, you do NOT need to rebuild the firmware!**
120+
121+
When you run `scripts/install.sh`, it copies files from `internal_filesystem/` to the device storage. These files override the frozen filesystem because the storage paths are first in `sys.path`. This means:
122+
123+
```bash
124+
# Fast development cycle (recommended):
125+
# 1. Edit Python files in internal_filesystem/
126+
# 2. Install to device:
127+
./scripts/install.sh waveshare-esp32-s3-touch-lcd-2
128+
129+
# That's it! Your changes are live on the device.
130+
```
131+
132+
**You only need to rebuild firmware (`./scripts/build_mpos.sh esp32`) when:**
133+
- Testing the frozen `lib/` for production releases
134+
- Modifying C extension modules (`c_mpos/`, `secp256k1-embedded-ecdh/`)
135+
- Changing MicroPython core or LVGL bindings
136+
- Creating a fresh firmware image for distribution
137+
138+
**Desktop development** always uses the unfrozen files, so you never need to rebuild for Python changes:
139+
```bash
140+
# Edit internal_filesystem/ files
141+
./scripts/run_desktop.sh # Changes are immediately active
142+
```
143+
117144
### Building Firmware
118145

119146
The main build script is `scripts/build_mpos.sh`:

internal_filesystem/builtin/apps/com.micropythonos.settings/assets/calibrate_imu.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,57 +256,78 @@ def start_calibration_process(self):
256256
def calibration_thread_func(self):
257257
"""Background thread for calibration process."""
258258
try:
259+
print("[CalibrateIMU] === Calibration thread started ===")
260+
259261
# Step 1: Check stationarity
262+
print("[CalibrateIMU] Step 1: Checking stationarity...")
260263
if self.is_desktop:
261264
stationarity = {'is_stationary': True, 'message': 'Mock: Stationary'}
262265
else:
266+
print("[CalibrateIMU] Calling SensorManager.check_stationarity(samples=30)...")
263267
stationarity = SensorManager.check_stationarity(samples=30)
268+
print(f"[CalibrateIMU] Stationarity result: {stationarity}")
264269

265270
if stationarity is None or not stationarity['is_stationary']:
266271
msg = stationarity['message'] if stationarity else "Stationarity check failed"
272+
print(f"[CalibrateIMU] Device not stationary: {msg}")
267273
self.update_ui_threadsafe_if_foreground(self.handle_calibration_error,
268274
f"Device not stationary!\n\n{msg}\n\nPlace on flat surface and try again.")
269275
return
270276

277+
print("[CalibrateIMU] Device is stationary, proceeding to calibration")
278+
271279
# Step 2: Perform calibration
280+
print("[CalibrateIMU] Step 2: Performing calibration...")
272281
self.update_ui_threadsafe_if_foreground(lambda: self.set_state(CalibrationState.CALIBRATING))
273282
time.sleep(0.5) # Brief pause for user to see status change
274283

275284
if self.is_desktop:
276285
# Mock calibration
286+
print("[CalibrateIMU] Mock calibration (desktop)")
277287
time.sleep(2)
278288
accel_offsets = (0.1, -0.05, 0.15)
279289
gyro_offsets = (0.2, -0.1, 0.05)
280290
else:
281291
# Real calibration
292+
print("[CalibrateIMU] Real calibration (hardware)")
282293
accel = SensorManager.get_default_sensor(SensorManager.TYPE_ACCELEROMETER)
283294
gyro = SensorManager.get_default_sensor(SensorManager.TYPE_GYROSCOPE)
295+
print(f"[CalibrateIMU] Accel sensor: {accel}, Gyro sensor: {gyro}")
284296

285297
if accel:
298+
print("[CalibrateIMU] Calibrating accelerometer (100 samples)...")
286299
accel_offsets = SensorManager.calibrate_sensor(accel, samples=100)
300+
print(f"[CalibrateIMU] Accel offsets: {accel_offsets}")
287301
else:
288302
accel_offsets = None
289303

290304
if gyro:
305+
print("[CalibrateIMU] Calibrating gyroscope (100 samples)...")
291306
gyro_offsets = SensorManager.calibrate_sensor(gyro, samples=100)
307+
print(f"[CalibrateIMU] Gyro offsets: {gyro_offsets}")
292308
else:
293309
gyro_offsets = None
294310

295311
# Step 3: Verify results
312+
print("[CalibrateIMU] Step 3: Verifying calibration...")
296313
self.update_ui_threadsafe_if_foreground(lambda: self.set_state(CalibrationState.VERIFYING))
297314
time.sleep(0.5)
298315

299316
if self.is_desktop:
300317
verify_quality = self.get_mock_quality(good=True)
301318
else:
319+
print("[CalibrateIMU] Checking calibration quality (50 samples)...")
302320
verify_quality = SensorManager.check_calibration_quality(samples=50)
321+
print(f"[CalibrateIMU] Verification quality: {verify_quality}")
303322

304323
if verify_quality is None:
324+
print("[CalibrateIMU] Verification failed")
305325
self.update_ui_threadsafe_if_foreground(self.handle_calibration_error,
306326
"Calibration completed but verification failed")
307327
return
308328

309329
# Step 4: Show results
330+
print("[CalibrateIMU] Step 4: Showing results...")
310331
rating = verify_quality['quality_rating']
311332
score = verify_quality['quality_score']
312333

@@ -316,10 +337,14 @@ def calibration_thread_func(self):
316337
if gyro_offsets:
317338
result_msg += f"\n\nGyro offsets:\nX:{gyro_offsets[0]:.3f} Y:{gyro_offsets[1]:.3f} Z:{gyro_offsets[2]:.3f}"
318339

340+
print(f"[CalibrateIMU] Calibration complete! Result: {result_msg[:80]}")
319341
self.update_ui_threadsafe_if_foreground(self.show_calibration_complete, result_msg)
342+
print("[CalibrateIMU] === Calibration thread finished ===")
320343

321344
except Exception as e:
322345
print(f"[CalibrateIMU] Calibration error: {e}")
346+
import sys
347+
sys.print_exception(e)
323348
self.update_ui_threadsafe_if_foreground(self.handle_calibration_error, str(e))
324349

325350
def show_calibration_complete(self, result_msg):

internal_filesystem/builtin/apps/com.micropythonos.settings/assets/check_imu_calibration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ def update_display(self, timer=None):
151151
if self.is_desktop:
152152
quality = self.get_mock_quality()
153153
else:
154-
quality = SensorManager.check_calibration_quality(samples=30)
154+
# Use only 5 samples for real-time display (faster, less blocking)
155+
quality = SensorManager.check_calibration_quality(samples=5)
155156

156157
if quality is None:
157158
self.status_label.set_text("Error reading IMU")

internal_filesystem/lib/mpos/hardware/drivers/wsen_isds.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ def __init__(self, i2c, address=0x6B, acc_range="2g", acc_data_rate="1.6Hz",
126126
acc_range: Accelerometer range ("2g", "4g", "8g", "16g")
127127
acc_data_rate: Accelerometer data rate ("0", "1.6Hz", "12.5Hz", ...)
128128
gyro_range: Gyroscope range ("125dps", "250dps", "500dps", "1000dps", "2000dps")
129-
gyro_data_rate: Gyroscope data rate ("0", "12.5Hz", "26Hz", ...)
129+
gyro_data_rate: Gyroscope data rate ("0", "12.5Hz", "26Hz", ...")
130130
"""
131+
print(f"[WSEN_ISDS] __init__ called with address={hex(address)}, acc_range={acc_range}, acc_data_rate={acc_data_rate}, gyro_range={gyro_range}, gyro_data_rate={gyro_data_rate}")
131132
self.i2c = i2c
132133
self.address = address
133134

@@ -149,10 +150,31 @@ def __init__(self, i2c, address=0x6B, acc_range="2g", acc_data_rate="1.6Hz",
149150
self.GYRO_NUM_SAMPLES_CALIBRATION = 5
150151
self.GYRO_CALIBRATION_DELAY_MS = 10
151152

153+
print("[WSEN_ISDS] Configuring accelerometer...")
152154
self.set_acc_range(acc_range)
153155
self.set_acc_data_rate(acc_data_rate)
156+
print("[WSEN_ISDS] Accelerometer configured")
157+
158+
print("[WSEN_ISDS] Configuring gyroscope...")
154159
self.set_gyro_range(gyro_range)
155160
self.set_gyro_data_rate(gyro_data_rate)
161+
print("[WSEN_ISDS] Gyroscope configured")
162+
163+
# Give sensors time to stabilize and start producing data
164+
# Especially important for gyroscope which may need warmup time
165+
print("[WSEN_ISDS] Waiting 100ms for sensors to stabilize...")
166+
time.sleep_ms(100)
167+
168+
# Debug: Read all control registers to see full sensor state
169+
print("[WSEN_ISDS] === Sensor State After Initialization ===")
170+
for reg_addr in [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19]:
171+
try:
172+
reg_val = self.i2c.readfrom_mem(self.address, reg_addr, 1)[0]
173+
print(f"[WSEN_ISDS] Reg 0x{reg_addr:02x} (CTRL{reg_addr-0x0f}): 0x{reg_val:02x} = 0b{reg_val:08b}")
174+
except:
175+
pass
176+
177+
print("[WSEN_ISDS] Initialization complete")
156178

157179
def get_chip_id(self):
158180
"""Get chip ID for detection. Returns WHO_AM_I register value."""
@@ -166,10 +188,12 @@ def _write_option(self, option, value):
166188
opt = Wsen_Isds._options[option]
167189
try:
168190
bits = opt["val_to_bits"][value]
169-
config_value = self.i2c.readfrom_mem(self.address, opt["reg"], 1)[0]
191+
old_value = self.i2c.readfrom_mem(self.address, opt["reg"], 1)[0]
192+
config_value = old_value
170193
config_value &= opt["mask"]
171194
config_value |= (bits << opt["shift_left"])
172195
self.i2c.writeto_mem(self.address, opt["reg"], bytes([config_value]))
196+
print(f"[WSEN_ISDS] _write_option: {option}={value} → reg {hex(opt['reg'])}: {hex(old_value)}{hex(config_value)}")
173197
except KeyError as err:
174198
print(f"Invalid option: {option}, or invalid option value: {value}.", err)
175199

@@ -300,15 +324,19 @@ def read_accelerations(self):
300324

301325
def _read_raw_accelerations(self):
302326
"""Read raw accelerometer data."""
327+
print("[WSEN_ISDS] _read_raw_accelerations: checking data ready...")
303328
if not self._acc_data_ready():
329+
print("[WSEN_ISDS] _read_raw_accelerations: DATA NOT READY!")
304330
raise Exception("sensor data not ready")
305331

332+
print("[WSEN_ISDS] _read_raw_accelerations: data ready, reading registers...")
306333
raw = self.i2c.readfrom_mem(self.address, Wsen_Isds._REG_A_X_OUT_L, 6)
307334

308335
raw_a_x = self._convert_from_raw(raw[0], raw[1])
309336
raw_a_y = self._convert_from_raw(raw[2], raw[3])
310337
raw_a_z = self._convert_from_raw(raw[4], raw[5])
311338

339+
print(f"[WSEN_ISDS] _read_raw_accelerations: raw values = ({raw_a_x}, {raw_a_y}, {raw_a_z})")
312340
return raw_a_x, raw_a_y, raw_a_z
313341

314342
def gyro_calibrate(self, samples=None):
@@ -351,15 +379,19 @@ def read_angular_velocities(self):
351379

352380
def _read_raw_angular_velocities(self):
353381
"""Read raw gyroscope data."""
382+
print("[WSEN_ISDS] _read_raw_angular_velocities: checking data ready...")
354383
if not self._gyro_data_ready():
384+
print("[WSEN_ISDS] _read_raw_angular_velocities: DATA NOT READY!")
355385
raise Exception("sensor data not ready")
356386

387+
print("[WSEN_ISDS] _read_raw_angular_velocities: data ready, reading registers...")
357388
raw = self.i2c.readfrom_mem(self.address, Wsen_Isds._REG_G_X_OUT_L, 6)
358389

359390
raw_g_x = self._convert_from_raw(raw[0], raw[1])
360391
raw_g_y = self._convert_from_raw(raw[2], raw[3])
361392
raw_g_z = self._convert_from_raw(raw[4], raw[5])
362393

394+
print(f"[WSEN_ISDS] _read_raw_angular_velocities: raw values = ({raw_g_x}, {raw_g_y}, {raw_g_z})")
363395
return raw_g_x, raw_g_y, raw_g_z
364396

365397
def read_angular_velocities_accelerations(self):
@@ -426,10 +458,15 @@ def _get_status_reg(self):
426458
Returns:
427459
Tuple (acc_data_ready, gyro_data_ready, temp_data_ready)
428460
"""
429-
raw = self.i2c.readfrom_mem(self.address, Wsen_Isds._ISDS_STATUS_REG, 4)
461+
# STATUS_REG (0x1E) is a single byte with bit flags:
462+
# Bit 0: XLDA (accelerometer data available)
463+
# Bit 1: GDA (gyroscope data available)
464+
# Bit 2: TDA (temperature data available)
465+
status = self.i2c.readfrom_mem(self.address, Wsen_Isds._ISDS_STATUS_REG, 1)[0]
430466

431-
acc_data_ready = True if raw[0] == 1 else False
432-
gyro_data_ready = True if raw[1] == 1 else False
433-
temp_data_ready = True if raw[2] == 1 else False
467+
acc_data_ready = bool(status & 0x01) # Bit 0
468+
gyro_data_ready = bool(status & 0x02) # Bit 1
469+
temp_data_ready = bool(status & 0x04) # Bit 2
434470

471+
print(f"[WSEN_ISDS] Status register: 0x{status:02x} = 0b{status:08b}, acc_ready={acc_data_ready}, gyro_ready={gyro_data_ready}, temp_ready={temp_data_ready}")
435472
return acc_data_ready, gyro_data_ready, temp_data_ready

0 commit comments

Comments
 (0)