Skip to content

Automatic compass mounting-orientation detection during calibration#11708

Draft
sensei-hacker wants to merge 3 commits into
iNavFlight:maintenance-10.xfrom
sensei-hacker:fix-mag-calibration-yaw-fusion
Draft

Automatic compass mounting-orientation detection during calibration#11708
sensei-hacker wants to merge 3 commits into
iNavFlight:maintenance-10.xfrom
sensei-hacker:fix-mag-calibration-yaw-fusion

Conversation

@sensei-hacker

Copy link
Copy Markdown
Member

Summary

Adds automatic detection of compass mounting orientation, running during the
existing magnetometer calibration spin. Based on ArduPilot's
CompassCalibrator::calculate_orientation() technique (minimum variance of
the "implied earth field" across candidate mounting rotations), adapted for
INAV's calibration flow. Also fixes a real, independently-valuable bug found
while implementing this: the AHRS filter was fusing raw, hard-iron-biased
magnetometer samples into the yaw estimate for the entire duration of a
calibration spin.

This is a draft PR — hardware testing (real calibration spins at known
mounting orientations, compared against get align_mag_roll/pitch/yaw) is
planned as a follow-up before this is ready for review/merge.

Changes

  • Stop fusing raw mag data into AHRS during calibration (flight/imu.c):
    canUseMAG now also requires !compassIsCalibrating(). Offsets aren't
    known yet during a spin, so the uncorrected samples were leaking hard-iron
    bias into the attitude estimate — the same attitude reference the new
    orientation-detection feature depends on being bias-free.
  • New compass_orientation.c/.h module: buffers up to 128
    (rawMag, attitude-quaternion) sample pairs during a calibration spin,
    then searches 16 discrete candidate mounting rotations (8 cardinal +
    8 diagonal, upright/inverted) for the one that minimizes variance of the
    implied earth field. Returns a confidence ratio (second-best/best
    variance); auto-applies only above a fixed threshold.
  • Wired into compassUpdate() (sensors/compass.c): auto-detection
    only runs when neither the compass nor the accelerometer has ever been
    calibrated before (!STATE(COMPASS_CALIBRATED) && !STATE(ACCELEROMETER_CALIBRATED)) — the clearest available "genuinely
    first-time setup" signal. No new settings were needed: the confidence
    threshold is a fixed compile-time constant (average users have no
    principled basis to tune it better than the computed default), and the
    precondition above replaces what would otherwise have been an opt-in
    setting.
  • Detected identity rotation (0,0,0) is routed through mag_align=CW0_DEG
    rather than the rollDeciDegrees/etc. fields, since those fields use
    all-zero as an "unconfigured" sentinel and would silently discard a
    genuine identity-rotation result.
  • Unit tests (compass_orientation_unittest.cc): recovery of all 16
    candidates from synthetic data, rejection of an ambiguous "flat spin"
    (yaw-only, the classic calibration mistake), insufficient-sample
    rejection, noise tolerance, buffer-overflow safety, and a zero-magGain
    regression test (guards against a stuck/disconnected axis producing NaN
    that would otherwise bypass the confidence-threshold check).

Testing

  • compass_orientation_unittest: 6/6 passing.
  • Full host unit test suite (make check): 136/136 passing (confirms
    no regression in unrelated tests).
  • Clean rebuilds verified: SITL, MATEKF722 (USE_MAG hardware target),
    AIKONF7 (mag-less target, confirms the #ifdef USE_MAG guarding doesn't
    break non-MAG targets).
  • Monte Carlo simulation (40,000 trials, wide randomized sensor noise and
    attitude error, including an off-grid tilted-mast case up to 30°) to
    characterize the confidence threshold: confidence >= 1.2 corresponds to
    ~100% correctness; the shipped threshold of 2.0 leaves a comfortable
    margin.
  • Verified no double-precision math was introduced (all supported STM32
    targets have single-precision-only FPUs — see Use single-precision math to save ~17 KB of flash on F722 targets #11402 for prior art on
    this class of bug): source-level audit plus an object-code symbol check
    (arm-none-eabi-nm on the compiled MATEKF722 object) confirmed zero
    double-precision runtime helper symbols.
  • Not yet tested: real hardware calibration spins at known mounting
    orientations. This is the planned next step before removing draft status.

Related Issues

None filed; this implements a firmware-side improvement identified during
research into ArduPilot's orientation-detection approach.

compassUpdate() intentionally skips offset/gain correction while a
calibration spin is in progress (the offsets aren't known yet), but
imuCalculateEstimatedAttitude()'s canUseMAG check had no way to know
calibration was active, so it fused the raw, hard-iron-biased samples
into the yaw estimate for the whole spin.

Promote compass.c's calStartedAt to file scope and expose it via a new
compassIsCalibrating(), then gate canUseMAG on it in imu.c. During a
spin, yaw now comes from gyro integration only, same as it already does
when there's no compass at all.

Also extract compassInit()'s alignment-cache setup (mag.dev.magAlign
useExternal/externalRotation/onBoard) into compassRefreshAlignment(),
callable on its own so alignment changes made after boot (e.g. by a
future auto-detected orientation) can take effect immediately instead
of only after a reboot. Pure refactor, no behavior change.
…ration

Adds sensors/compass_orientation.c/.h: during the existing magnetometer
calibration spin, buffer magnetometer samples paired with the attitude
quaternion at the moment each was taken, then compare the implied earth
magnetic field across 16 candidate mounting rotations (8 cardinal yaw x
upright/inverted, 8 diagonal-45deg yaw x upright/inverted). The correct
rotation is the one that makes the implied earth field most consistent
across samples taken at different attitudes - no true-north or GPS
reference needed. Confidence is the ratio of the second-best candidate's
variance to the best one's; a Monte Carlo sweep across wide synthetic
noise/attitude-error ranges showed accuracy is ~100% for any accepted
result at confidence >= 1.2, so the fixed 2.0 threshold used here has a
comfortable margin.

Detection only runs, and only auto-applies, when neither the compass nor
the accelerometer has ever been calibrated on this board - the clearest
available signal this is a genuinely first-time setup, not a routine
recalibration of an already-mounted compass. This required no new
settings.yaml entries: the enable condition is computed from existing
calibration state rather than a persistent flag (which would risk
silently re-triggering on some future unrelated recalibration), and the
confidence threshold is a fixed constant rather than user-tunable, since
nobody picking a value for a statistical variance ratio has a principled
basis to choose better than the analyzed default.

On success, the detected rotation is written directly to the existing
align_mag_roll/pitch/yaw settings and applied immediately via
compassRefreshAlignment(), using the same auto-save call calibration
already performs - no separate "apply" step.

Also fixes an unrelated but adjacent bug found while wiring this up:
compass.c intentionally skips offset/gain correction while a spin is in
progress, but imu.c's canUseMAG check had no way to know that, so it was
fusing the raw, hard-iron-biased samples into the yaw estimate for the
whole spin - the same attitude estimate this feature depends on being
bias-free. Fixed by exposing compassIsCalibrating() and gating canUseMAG
on it.

Adds DEBUG_MAG_CALIB for field diagnosis of the computed confidence
value, and a compass_orientation_unittest.cc covering recovery of all 16
candidates from synthetic data, rejection of an ambiguous "spun flat on a
table" spin, insufficient-sample rejection, noise tolerance, and buffer
overflow handling.
…nce bypass

A detected identity rotation (0,0,0) is indistinguishable from "unconfigured"
in rollDeciDegrees/pitchDeciDegrees/yawDeciDegrees, so it was silently
discarded; route that case through mag_align=CW0_DEG instead, which can
represent "explicitly no rotation" unambiguously. Also guard against a
stuck/disconnected axis leaving magGain at 0: dividing by it produced NaN,
which bypassed the confidence-threshold rejection since NaN < threshold is
always false. Add #ifdef USE_MAG guarding (matching platform.h availability)
so the module compiles to nothing on mag-less targets instead of always
being built. Add a regression test for the zero-gain/NaN case.
@github-actions

Copy link
Copy Markdown

Test firmware build ready — commit 6ef39a0

Download firmware for PR #11708

240 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant