# Exercise 25: MotionCal T-Beam Bridge

This PlatformIO project turns a LilyGO T-Beam Supreme into a serial bridge for Paul Stoffregen's MotionCal desktop calibration tool.

The firmware reads the onboard QMC magnetometer through SensorLib and streams MotionCal-compatible ASCII `Raw:` lines over USB serial. MotionCal can then estimate both hard-iron offsets and the soft-iron correction matrix for the board.

![](MotionCal_2026-04-25_11-22.png)
## Hardware

Target board:

```text
LilyGO T-Beam Supreme / ESP32-S3
QMC6310/QMC5883-family magnetometer
SH1106 OLED display
```

The firmware probes these magnetometer I2C addresses:

```text
0x1C  QMC6310U
0x3C  QMC6310N
0x2C  QMC5883P
```

The default PlatformIO environment is `cy`, with additional board labels available in `platformio.ini`:

```text
amy bob cy dan ed flo guy
```

## Serial Format

MotionCal expects lines in this format:

```text
Raw:accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z,mag_x,mag_y,mag_z
```

This bridge only has a magnetometer, so it sends fixed placeholder accel/gyro values:

```text
Raw:0,0,8192,0,0,0,mag_x,mag_y,mag_z
```

The placeholder values mean:

```text
accel = 0,0,8192   approximately stationary upright in MotionCal's count convention
gyro  = 0,0,0      no gyro data supplied
mag   = live QMC magnetometer data
```

## Magnetometer Units

SensorLib returns QMC magnetometer readings in Gauss. The firmware converts them like this:

```text
1 gauss = 100 microtesla
MotionCal count = microtesla * 10
1 MotionCal count = 0.1 microtesla
```

So a streamed value like this:

```text
Raw:0,0,8192,0,0,0,-1735,428,793
```

means:

```text
X = -1735 counts = -173.5 uT = -1.735 gauss
Y =   428 counts =   42.8 uT =  0.428 gauss
Z =   793 counts =   79.3 uT =  0.793 gauss
```

These are MotionCal-compatible integer counts, not raw QMC register values.

## Build

Activate the Python environment that contains PlatformIO:

```sh
source /home/jlpoole/pioenv/bin/activate
```

Build the default environment:

```sh
cd /usr/local/src/microReticulumTbeam/exercises/25_motioncal_tbeam
pio run
```

Build a specific board label:

```sh
pio run -e dan
```

## Upload

Upload the default environment:

```sh
source /home/jlpoole/pioenv/bin/activate
cd /usr/local/src/microReticulumTbeam/exercises/25_motioncal_tbeam
pio run -t upload
```

Upload a specific board label:

```sh
pio run -e dan -t upload
```

## Serial Monitor

After flashing, monitor the USB serial stream at 115200 baud:

```sh
pio device monitor -b 115200 --port /dev/ttytDAN
```

Expected boot/status output includes lines like:

```text
exercise=Exercise 25 MotionCal T-Beam bridge
serial_format=Raw:accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z,mag_x,mag_y,mag_z
mag_units=MotionCal integer counts, 1 count = 0.1 uT
magnetometer_init=ok label=QMC6310U addr=0x1C chip=0x..
```

Expected streaming output looks like:

```text
Raw:0,0,8192,0,0,0,-1578,447,1266
Raw:0,0,8192,0,0,0,-1583,438,1258
Raw:0,0,8192,0,0,0,-1585,442,1250
```

If the firmware prints `read_fail` or `overflow` status lines, check I2C wiring/power and the configured magnetometer full-scale range.

## Running MotionCal

Build MotionCal if needed:

```sh
cd /usr/local/src/MotionCal
make WXCONFIG=wx-config LDFLAGS="-lglut -lGLU -lGL -lm"
```

Run it:

```sh
cd /usr/local/src/MotionCal
GDK_BACKEND=x11 ./MotionCal
```

Then select the T-Beam USB serial port in MotionCal.

Move and rotate the T-Beam through as many orientations as possible. The goal is to cover the sphere well, not just wave it flat on the table. Better 3D coverage improves both hard-iron and soft-iron calibration.

## Saving Calibration

MotionCal computes:

```text
magnetic_offset_uT       hard-iron offset, in microtesla
magnetic_mapping_matrix  inverse soft-iron correction matrix
magnetic_field_uT        fitted local field magnitude
```

Example:

```text
magnetic_offset_uT=-172.96843,43.0260162,78.8941956
magnetic_field_uT=52.4668198
magnetic_mapping_matrix=
  0.943139076 0.0439298451 0.0595370531
  0.0439298451 1.04979992 -0.0347476006
  0.0595370531 -0.0347476006 1.01706612
```

The firmware also accepts MotionCal's 68-byte calibration packet and echoes `Cal1:` and `Cal2:` lines so MotionCal can confirm the send.

## Applying Calibration

MotionCal's calibration model is:

```text
mag_uT = raw_motioncal_counts * 0.1
centered = mag_uT - magnetic_offset_uT
corrected = magnetic_mapping_matrix * centered
```

Hard iron moves the center of the magnetometer cloud back to zero. Soft iron transforms the ellipsoid-shaped cloud back toward a sphere.

## Troubleshooting

If MotionCal does not show points:

```text
Confirm the serial port is correct.
Confirm baud is 115200.
Confirm monitor output contains Raw: lines.
Close any serial monitor before opening the port in MotionCal.
```

If the values look 10x different from a notebook or script:

```text
The notebook/script may be using MotionCal counts.
MotionCal's saved magnetic_offset_uT is already in microtesla.
Convert counts to uT with: uT = counts * 0.1
```

If the fit is poor:

```text
Collect more orientations.
Rotate around all axes.
Keep the board away from steel, speakers, motors, magnets, and high-current wiring.
Try a different location and repeat the calibration.
```
