Section of circuit board

T-Beam Magnetometer Investigation

5/1/2026 – Update: over 100k hits on my Forgejo server, presumably bots, so I now require sign-in to explore.  Contact me if you want an account.

Introduction

This is a deep technical dive, possibly only of interest to people programming the LilyGO T-Beam SUPREME.  Much of the working below are snippets taken from Wikipedia.  What’s important is that if you want the T-Beam to serve as a compass and compute bearings, you need to have an accurate heading (like a compass would give you).

Executive Summary

Calibration must be performed per device if you plan to using bearings derived from the magnetometer. In testing across seven T-Beam units, inter-device variation far exceeded environmental variation, demonstrating that factory or shared calibration constants are not viable.

T-Beam’s Magnetometer

The LilyGO T-Beam SUPREME Specifications states that there is a “Magnetometer: QMC6310” sensor.  “A magnetometer is a device that measures magnetic field (B) [see below “The B Field”] or magnetic dipole moment. Different types of magnetometers measure the direction, strength, or relative change of the magnetic B-field at a particular location. …A compass is one such device, one that measures the direction of an ambient magnetic field, in this case, the Earth’s magnetic field.”  QMC’s data sheet states that the 6310 measures gauss units (Symbol G for gauss) which is a unit of measurement of magnetic flux density,   Gauss units are the older system of measurement which was superseded in 1961 by the International System of Units (SI), the use of the gauss has been deprecated by the standards bodies, but is still regularly used in various subfields of science, and preferred in astrophysics.[1]   Case in point: the QMC6310 uses gauss (G) units.  The SI unit for magnetic flux density is the tesla (symbol T),[2] which corresponds to 10,000gauss.

Therefore, one must bear in mind that there are two kinds of units, gauss, B,  and tesla, T, used in this arena.

The B Field

The magnetic B field is the “magnetic field” responsible for magnetic forces, magnetic torques and electromagnetic induction; it is also known as magnetic flux density.  The SI unit of B is tesla (symbol: T).[note 4] The Gaussian-cgs unit of B is the gauss (symbol: G).[8] (The conversion is 1 T ≘ 10000 G.[9][10])

The QMC6310

The QMC6310 has a required “mode” setting that sets the limits of its ability to determine gauss counts.  There are several, but only two modes are pertinent to the T-Beam.  The first mode is 8G which encompasses measuring +8 to -8 G [gauss]  QMC’s data sheet specifies that for 8G mode, it has a sensitivity of 3,750.  For 2G, the sensitivity is 15,000.

A manufacturers table.
QMC630 Data Sheet, Table 2. Specifications

Note: “Typ” = “Typical”

So, to determine the gauss, for a specified mode, e.g. 8G, you take the “raw” count a particular axis, i.e. “X”, reports and divide 3,750.  So:

\[
B_{\text{Gauss}} = \frac{N_{\text{counts}}}{S_{\text{LSB/G}}}
\]

Where:

  • B = magnetic field
  • N = raw ADC counts
  • S = sensitivity

Example in 2G mode, where X produces a value of 580:

\[
.03866666_{\text{Gauss}} = \frac{580_{\text{counts}}}{15,000_{\text{Sensitivity}}}
\]

This conversion yields uncalibrated field strength; offset and soft-iron corrections must be applied for accurate results.

Raw Readings From 7 T-Beams

I was concerned about possible distortions of the magnetic field in my office where I have lots of metal and electronic equipment, so I performed a test measuring in my office on my workstation, jp, and then on my laptop, eos, on the kitchen table.  The results show very little difference on readings between locations.  There are significant differences between the units themselves, however.

Unit Office (jp) Kitchen (eos)
AMY
BOB
CY
DAN
ED
FLO
GUY

The Jupyter notebook and a concurrent stand-alone Python script mimicking the notebook are posted at: https://salemdata.net/repo/jlpoole/Magnetometer_Calibration
Caveat: What I post on my Forgejo repository is not polished code and explanations.  I feel I am working at a rapid rate and I doubt many people would take interest, so I do not spend a lot of time making the code and project neat and tidy.  What is posted works for me and lets me move forward with my research and validation and preserves what I need so when I return to this project after completely forgetting its nuances, there’s enough for me to resume work, if needed.

MotionCal

A superior calibration tool in MotionCal.

Note on taking readings.  You do not have to carefully rotate the unit slowly.  The important thing to do is keep the top of the unit in the same spherical realm as you rotate so that you are measuring the same field surrounding it.  When you have a sphere which has many samples in all of its quadrants, you should have enough.

I created a custom firmware to be loaded into the T-Beam just for purposes of testing and calibrating the magnetometer.  This project is Exercise 25 MotionCal TBeam.  As of April 29, 2026, the project is in my branch: fieldtest-beacon-sd-provision.  You must load the T-Beam with the Exercise 25 firmware before you can use MotionCal.

Here are the results of calibrating each unit.  A perfectly calibrated magnetometer produces a centered sphere.
Off-center spheres indicate hard-iron bias.  Elliptical distortion indicates soft-iron effects. Not all units are equally calibratable; some exhibit significant distortion even after calibration.

Unit MotionCal Calibrations
AMY
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 19:01:33
valid_points=650
fit_error_percent=1.954331
surface_gap_error_percent=6.660000
magnitude_variance_error_percent=6.289725
wobble_error_percent=2.118480

magnetic_offset_uT=38.0446205,15.5483704,-36.5659103
magnetic_field_uT=47.0147285
magnetic_mapping_matrix=
  0.942833066 0.0306609273 0.0751264095
  0.0306609273 1.05493665 -0.0529854298
  0.0751264095 -0.0529854298 1.01525247
BOB
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 19:04:29
valid_points=650
fit_error_percent=1.688673
surface_gap_error_percent=4.040000
magnitude_variance_error_percent=4.089704
wobble_error_percent=0.480344

magnetic_offset_uT=-113.187836,30.1211472,34.6742783
magnetic_field_uT=46.1403847
magnetic_mapping_matrix=
  0.950518668 0.0357225537 0.071059376
  0.0357225537 1.05537307 -0.0355574489
  0.071059376 -0.0355574489 1.00482666
CY
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 19:07:45
valid_points=650
fit_error_percent=2.589669
surface_gap_error_percent=57.410004
magnitude_variance_error_percent=7.070223
wobble_error_percent=5.633270

magnetic_offset_uT=-199.562531,107.011795,94.7919998
magnetic_field_uT=46.0281258
magnetic_mapping_matrix=
  0.922755837 0.0477279425 0.0638417602
  0.0477279425 1.05570126 -0.03140679
  0.0638417602 -0.03140679 1.03449798
DAN
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 19:10:17
valid_points=650
fit_error_percent=1.891944
surface_gap_error_percent=20.080002
magnitude_variance_error_percent=7.532951
wobble_error_percent=1.719216

magnetic_offset_uT=-15.968605,8.26969051,-1.2073102
magnetic_field_uT=46.4549446
magnetic_mapping_matrix=
  0.955098867 0.0472385511 0.0669671297
  0.0472385511 1.06611001 -0.0453635007
  0.0669671297 -0.0453635007 0.991166353
ED
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 18:55:57
valid_points=650
fit_error_percent=1.538095
surface_gap_error_percent=15.660001
magnitude_variance_error_percent=1.532305
wobble_error_percent=2.341300

magnetic_offset_uT=117.21711,-44.1901093,-82.8643723
magnetic_field_uT=52.0245972
magnetic_mapping_matrix=
  0.952153444 0.0437445045 0.111379355
  0.0437445045 1.07807684 -0.0304355621
  0.111379355 -0.0304355621 0.990212023
FLO
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 19:12:16
valid_points=650
fit_error_percent=3.273206
surface_gap_error_percent=28.100002
magnitude_variance_error_percent=3.075784
wobble_error_percent=4.869506

magnetic_offset_uT=103.806641,-24.567482,-84.2488556
magnetic_field_uT=45.9784737
magnetic_mapping_matrix=
  0.965178549 0.0238724053 0.0916736871
  0.0238724053 1.05761039 -0.0596620739
  0.0916736871 -0.0596620739 0.992523193
GUY
MotionCal magnetic calibration settings
saved_local_time=2026-04-29 19:14:44
valid_points=650
fit_error_percent=3.055690
surface_gap_error_percent=9.860001
magnitude_variance_error_percent=5.574696
wobble_error_percent=1.545430

magnetic_offset_uT=-21.6121769,36.7250824,-12.2471542
magnetic_field_uT=46.2770653
magnetic_mapping_matrix=
  0.949235439 0.0333992094 0.0756401643
  0.0333992094 1.0552783 -0.0628371835
  0.0756401643 -0.0628371835 1.00950599

Meshtastic & the QMC6310.

I wondered if and how Meshtastic utilizes the T-Beam’s QMC6310.  It appears Meshtastic does not utilize this component.

Prompt to CODEX (ChatGPT)

Prompt
I’ve been studying the QMC6310 magnetometer and determined that among
7 different LilyGO T-Beams, the readings can vary from 600 unit to 1800+
units for each axis. I’d like to know how the meshtastic software
calibrates the QMC6310, if at all. Perhaps current bearing is not
something meshtastic cares about? I have the meshtastic firmware staged
at: /usr/local/src/meshtastic/firmware/. Can you search and determine if
meshastic calibrates the QMC1630 or even uses its readings?

Response

Response
I searched /usr/local/src/meshtastic/firmware/. Short
answer: Meshtastic detects the QMC6310, but I don’t see it reading or
calibrating it for heading.What I found:
  • QMC6310U is only mapped into nodeTelemetrySensorsMap as
    a detected telemetry sensor in main.cpp.
    The nearby comment says these are sensors with “no further logic than to
    be found and stuffed into” that map.
  • QMC6310N is detected in the I2C scanner, but is not even mapped to
    telemetry yet; there is a TODO for adding its telemetry type in main.cpp.
  • QMC6310U detection is a simple address match at 0x1C in
    ScanI2CTwoWire.cpp.
    QMC6310N detection is a screen-probe special case in ScanI2CTwoWire.cpp.
  • firstAccelerometer() does not include QMC6310 or
    QMC5883L. It only considers
    MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150
    in ScanI2C.cpp.
  • The AccelerometerThread switch has no QMC6310 or
    QMC5883L case, so no runtime motion/heading object is created for it in
    AccelerometerThread.h.

The actual compass heading path appears to be:

  • BMX160Sensor reads magnetometer + accelerometer data,
    applies a basic hard-iron offset from min/max values gathered during a
    30-second calibration, then calls
    FusionCompassCalculateHeading() and
    screen->setHeading() in BMX160Sensor.cpp.
  • ICM20948Sensor does the same style of min/max hard-iron
    calibration and heading calculation in ICM20948Sensor.cpp.
  • BMM150Sensor uses the DFRobot driver’s
    getCompassDegree() and sets the screen heading in BMM150Sensor.cpp.

The UI exposes “Compass Calibrate” only when
accelerometerThread exists, and it just calls
accelerometerThread->calibrate(30) in MenuHandler.cpp.
Since QMC6310 never becomes an AccelerometerThread sensor,
that calibration path won’t touch it.

So for LilyGO T-Beams with a QMC6310: Meshtastic seems to acknowledge
the chip exists, but it does not currently use its raw magnetic readings
for current bearing, nor does it calibrate them. Current bearing in the
UI falls back to supported compass IMUs when present, or estimated
movement heading from GPS positions via Screen.cpp.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *