Skip to content

Swap the dead MTi-3 for a BNO085 and anchor heading to it - #65

Merged
georgesleen merged 7 commits into
mainfrom
dev/georgesleen/bno085-imu-swap
Jul 27, 2026
Merged

Swap the dead MTi-3 for a BNO085 and anchor heading to it#65
georgesleen merged 7 commits into
mainfrom
dev/georgesleen/bno085-imu-swap

Conversation

@georgesleen

Copy link
Copy Markdown
Collaborator

The MTi-3 IMU died on the bench, so this swaps in an Adafruit BNO085 (on hand) as a drop-in over I2C, and fixes an EKF heading gap the swap exposed.

Driver

  • New Arduino-free, host-testable lib/bno08x_imu/: shtp_protocol.h (stateless SHTP framing), sh2_reports.h (SH-2 report decode + Set-Feature), covered by a native test_bno08x suite. Mirrors the xbus spine.
  • Poll-driven I2C transport (bno08x_transport.h, software reset, no INT/RST) plus a bus scan at bring-up. The BNO's Rotation Vector quaternion and calibrated accel/gyro/mag map straight onto fusion::ImuSample, so fusion, the rudder link, sim, and telemetry are untouched. lib/mti_imu and test_xbus stay for if the MTi returns.
  • Telemetry now tees to USB CDC, a hardware UART, and an RTT up-channel, so the bench can read it over the same debug probe used to flash. pico2/feather_rp2350 upload over CMSIS-DAP. Wiring and readback in docs/bno085.md.

Heading anchor

Bench finding: with the board still, the BNO's own heading is flat (~0.017 deg/s) but the shipped EKF drifted ~75 deg with no GNSS, because the mag yaw only observes heading + mag_offset and the free offset lets the good heading leak away.

  • Add an opt-in MtiYawConfig::offset_seed_deg: at the first predict it seeds the offset and heading so the mag anchors heading from boot (first innovation ~0, no cold-start gate lockout); the offset pin engages pre-GNSS when seeded. GNSS still owns absolute heading and refines the offset.
  • Enabled in the shipped tuning.toml (trusts a clean, characterized mag; trades the mag-snap fail-safe, which the tests still cover via the unpinned opt-out). Also fixes a codegen gap where q_offset_outage_deg2 was never emitted to firmware.
  • On the real capture, shipped tuning holds heading to +1.9 deg (was +87.5 unseeded).

Verification: 198 native + 208 sim tests green; pico/pico2/feather_rp2350 build; confirmed live on the Pico 2 over SWD (# I2C scan: 0x4A, # IMU: ready, live telemetry).

Built on the merged no-gps-coast-hardening work (#64), which this branch is rebased onto.

georgesleen and others added 7 commits July 26, 2026 18:22
Decode the BNO085's SHTP framing and SH-2 sensor reports in a new Arduino-free
lib/bno08x_imu, mirroring the xbus spine: shtp_protocol.h is stateless
parse/encode free functions (I2C delivers one cargo per read, so there is no
half-decoded frame to defend), and sh2_reports.h reads the accel/gyro/mag
triads and the Rotation Vector quaternion out of a report batch and builds
Set-Feature commands. Covered by a native test_bno08x suite that static_asserts
the 100 Hz Rotation-Vector command bytes and round-trips the decoders against
hand-built cargos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the dead MTi-3 with an Adafruit BNO085: a poll-driven I2C transport
(software reset, no INT/RST) and an imu_task that resets, enables the Rotation
Vector plus the calibrated triads at 100 Hz, and maps them onto the existing
fusion::ImuSample, so fusion, the rudder link, and telemetry are untouched. The
mount uses Wire, with pins picked per board (i2c0 GP16/GP17 on the Pico, i2c1
A0/A1 on the Feather). A bus scan runs at bring-up to flag power/wiring faults.

Telemetry now tees to USB CDC, a hardware UART, and an RTT up-channel, so the
bench reads it over the same debug probe used to flash, no extra cable. The
pico2 and feather envs upload over CMSIS-DAP. Wiring and readback are in
docs/bno085.md; lib/mti_imu and test_xbus stay for if the MTi returns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Without GNSS the mag yaw only observes heading + mag_offset, so a free offset
lets a good absolute heading leak away and heading coasts on gyro bias (a clean,
still BNO085 drifts ~75 deg over 2 min). Add an opt-in MtiYawConfig offset seed:
at the first predict, seed the offset (declination + frame constant) and
heading = -yaw - seed, so heading + offset matches the mag yaw from sample one
and the mag aids heading immediately instead of starting 90+ deg off and
tripping the yaw gate. The offset pin now also engages before the first fix when
a seed is set, so the seeded offset holds instead of floating; GNSS still owns
absolute heading and refines the offset once present. Unset, behaviour is
unchanged. Covered by host tests for the boot anchor, the no-GNSS hold against a
gyro bias, the opt-in default, and GNSS refining a wrong seed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn on the boot anchor for the BNO085: seed the offset (0.0, trusting the
magnetic yaw directly until GNSS refines it) and pin it (q_offset_outage_deg2 =
1e-4) so a clean mag holds heading with no GNSS. This trades the mag-snap
fail-safe for a heading that survives a GNSS gap, sound only with a
characterized mag; the BNO measured stable to ~0.017 deg/s. Also fix a codegen
gap: gen_tuning.py never emitted q_offset_outage_deg2, so the outage pin was
sim-only; it now emits both optional mti_yaw fields, so firmware and sim run the
same filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose MtiYawConfig::offset_seed_deg (and the previously-unbound
q_offset_outage_deg2) through the nanobind binding, the stub, and the
dataclass/loader/converter, so the sim replays the same boot-anchored filter the
firmware runs. Add test_boot_anchor for heading holding from boot with no GNSS
versus drifting unseeded. The shipped tuning now trusts a clean mag by default,
so rework the two mag fail-safe regressions (snap-does-not-steer, outage loses
confidence) to guard that fail-safe via the explicit unpinned opt-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apply the project clang-format / ruff format to the files added and touched
across this branch, satisfying the pre-push format checks. Whitespace and
line-wrap only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@georgesleen
georgesleen merged commit bc8d053 into main Jul 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant