Swap the dead MTi-3 for a BNO085 and anchor heading to it - #65
Merged
Conversation
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>
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
lib/bno08x_imu/:shtp_protocol.h(stateless SHTP framing),sh2_reports.h(SH-2 report decode + Set-Feature), covered by a nativetest_bno08xsuite. Mirrors the xbus spine.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 ontofusion::ImuSample, so fusion, the rudder link, sim, and telemetry are untouched.lib/mti_imuandtest_xbusstay for if the MTi returns.pico2/feather_rp2350upload over CMSIS-DAP. Wiring and readback indocs/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_offsetand the free offset lets the good heading leak away.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.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 whereq_offset_outage_deg2was never emitted to firmware.Verification: 198 native + 208 sim tests green;
pico/pico2/feather_rp2350build; 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.