Hi all,
I’d like to propose a small improvement to the compass calibrator that would
save users a lot of frustration in a specific but common failure scenario.
The problem
I was calibrating the compass on my ZOHD Drift (ArduPlane 4.6.3, Matek
F405-WMN, external QMC5883L). I rotated the aircraft for over 3 minutes in
Mission Planner — the progress bar kept advancing slowly but calibration never
completed. I tried multiple times.
It turned out the foam hatch lid had small neodymium magnets embedded in it
(factory hatch closure magnets). These produced a field of ~3800 mGauss at the
compass location. The valid range for Earth’s field is roughly 185–875 mGauss,
so the reading was 4× too high.
Once I removed the magnets, the field dropped to ~425 mGauss and calibration
succeeded on the first attempt.
Why it took so long to diagnose
The calibrator gave zero feedback during the 3+ minutes of spinning. The
progress bar moved normally. There was no message saying “hey, the field is way
too high, something is wrong.” The failure only came at the very end, with no
hint about magnets.
The pre-arm check does catch this ("Check mag field: 3800, max 875, min 185"), but that only runs at arming — long after you’ve already spent minutes
trying to calibrate.
What happens in the code
Looking at CompassCalibrator.cpp:
pull_sample()accepts all samples regardless of magnitude- Samples are collected until the buffer is full (~300 samples)
- Sphere fit runs (STEP_ONE), then more samples, then ellipsoid fit (STEP_TWO)
- Only at the very end does
fit_acceptable()check:
_params.radius > FIELD_RADIUS_MIN && _params.radius < FIELD_RADIUS_MAX
(where MIN=150, MAX=950) - With ~3800 mGauss, the fitted radius is ~3800, which fails — but only after
the entire pipeline has run
There is no early sanity check on the incoming field magnitude.
Proposed fix
Add a field-magnitude check in pull_sample() after 10 samples have been
collected. If the average magnitude is wildly out of range, abort immediately
with a clear message:
// In pull_sample(), after _samples_collected is incremented:
if (_samples_collected == 10) {
float total_magnitude = 0;
for (uint16_t i = 0; i < 10; i++) {
total_magnitude += _sample_buffer[i].get().length();
}
float avg_magnitude = total_magnitude / 10;
if (avg_magnitude > FIELD_RADIUS_MAX * 2) {
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL,
"Mag(%u) field too high: %.0f mGauss (max %d). Check for magnets near compass",
_compass_idx, avg_magnitude, FIELD_RADIUS_MAX);
set_status(Status::BAD_RADIUS);
return;
}
if (avg_magnitude < FIELD_RADIUS_MIN / 2) {
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL,
"Mag(%u) field too low: %.0f mGauss (min %d). Check compass connection",
_compass_idx, avg_magnitude, FIELD_RADIUS_MIN);
set_status(Status::BAD_RADIUS);
return;
}
}
Thresholds:
- Too high: >1900 mGauss (2× FIELD_RADIUS_MAX). Earth’s field peaks at ~650
at the poles. 1900 is clearly not Earth’s field. - Too low: <75 mGauss (½ FIELD_RADIUS_MIN). Suggests sensor fault or
disconnected compass.
Why 10 samples: Available within the first 1–2 seconds. Enough to average
out noise. Tiny fraction of the ~300 total needed.
Impact
- User gets actionable feedback within ~1 second instead of 3+ minutes
- Message is delivered via STATUSTEXT so all GCS apps display it automatically
(no Mission Planner / QGC changes needed) - BAD_RADIUS status already exists and is handled by all GCS
- No behavior change for normal calibrations (threshold is extremely generous)
- No new parameters
User experience comparison
Before: Start cal → spin 3 min → fail → no idea why → repeat → give up
After: Start cal → 1 sec → “Mag(0) field too high: 3800 mGauss (max 950).
Check for magnets near compass” → remove magnets → recalibrate → success
This isn’t a new problem
The lack of early feedback during compass calibration has been a pain point for
years. A few related reports:
-
Onboard Mag calibration doesn’t complete #4734 (43 comments, 2016–2019)
One user reported getting blisters from rotating a hex for hours. The QGC lead
dev commented: “People are bailing out of ArduPilot because of it.” Another
contributor asked the question this proposal answers: “Is there a way to know
if there is EM noise making the calibration from happening? If so, that should
be shown to the user somehow.” -
Error: Mag(0) bad radius xxx expected 222 #14393 (2020)
Multiple users hitting the “bad radius” error after calibration runs to
completion — same end symptom, different root cause (low field in Brazil vs
high field from magnets), but in both cases the calibrator runs the full
pipeline before telling you something was wrong from the start. -
Onboard Compascal stuck if in certain configuration #9316 (2018)
Calibration gets stuck with no progress and no feedback. Different root cause
(compass ordering), but same user frustration pattern — zero indication of
what’s wrong. -
Compass Interference Causing EKF Crash on copter (Feb 2023, Discourse)
Recurring compass interference causing EKF crashes and flyaways — the kind of
scenario that starts with a calibration that “succeeded” in a bad environment.
The common thread: when the magnetic environment is fundamentally wrong,
the calibrator gives no early warning. It either runs to completion and fails
cryptically, or sits there collecting useless samples forever.
Summary
I think this is a fairly small, safe change that could save users a lot of
confusion. Magnets in foam hatches/canopies are surprisingly common in RC
aircraft — it’s not an exotic failure mode.
Happy to submit a PR if the approach seems reasonable. Would appreciate any
feedback on the thresholds or implementation location.
Thanks,
Christian