| name | calibrate |
| description | BNO085 sensor calibration and install-time boat-frame alignment (mount_quat). TRIGGER when the user asks about calibration, mount orientation, "which way is forward", drift, roll/pitch showing wrong values, or installing the sensor on the boat. DO NOT trigger for general BNO085 datasheet questions unrelated to calibration. |
Calibration — sensor and boat-frame alignment
Two completely separate concerns live under "calibration":
- Sensor calibration — BNO085's internal gyro/accel bias estimation
- Boat-frame alignment — the
mount_quat that rotates sensor axes into
bow-forward/port-left/up
Conflating them is the #1 source of bad data. Treat them independently.
1. Sensor calibration
The BNO085 self-calibrates gyro and accelerometer continuously. Do not
enable magnetometer — we use the Game Rotation Vector report, which is
mag-free. A steel hull, engine, or nearby stanchion destroys mag cal; any
yaw reference we derive will be wrong by tens of degrees and drift. Heading
relative to water/wind is read from the existing B&G instruments via helmlog;
the IMU's job is motion, not compass.
Procedure (one-time, at the dock):
- Power the device on a flat, stable surface — not the boat. The workbench.
- Leave it stationary for 30 seconds — gyro bias converges.
- Tilt through all six faces slowly, ~2 s per face — accel bias converges.
- Do not rotate in a figure-8 (that's mag cal, which we explicitly avoid).
- Check the status byte on the Game Rotation Vector report — the accuracy
flag should read
HIGH (3) before proceeding.
- Save calibration to the BNO085's FRS via
sh2_saveDcdNow() — otherwise
it's re-learned from scratch on every power cycle.
Re-run sensor cal if: the device is dropped, stored below freezing, or
lib/imu/ logs accuracy dropping to LOW persistently.
2. Boat-frame alignment (mount_quat)
The sensor is bolted into the boat at some arbitrary orientation. We want
all downstream data expressed in boat frame:
- X = forward (bow)
- Y = port
- Z = up
- Right-handed
mount_quat rotates sensor → boat. It is computed once at install and
stored in NVS. Every sample published passes through:
q_boat_world = mount_quat * q_sensor_world_from_bno * mount_quat^-1
(and similarly, linear accel vectors are rotated by mount_quat).
Install procedure
- Bolt the device down in its final location and orientation. It must
not move relative to the boat. Use threadlocker.
- Tie the boat to the dock in calm water. No wake, no heel, no pitch.
- Run
scripts/calibrate_mount.py --device imu4.local from the Mac.
The script:
- Averages 10 s of Game Rotation Vector samples → "dock" quaternion
representing sensor orientation when boat is level
- Computes
mount_quat = the rotation that takes "dock" to identity
in the boat frame (bow = X, port = Y, up = Z)
- You still have to tell it which way is forward: the script prompts
"push the bow button" — you physically tap the end of the sensor that's
pointing at the bow. The accelerometer impulse direction fixes the
X-axis; Z comes from gravity; Y is X × Z
- Writes
mount_quat to NVS and prints it
- Verify: pitch the bow up by hand —
pitch should read positive. Heel
to port — roll should read positive (right-hand rule around X).
If signs are inverted, the "bow button" tap was registered wrong — re-run.
Why not do this in software with an IMU-on-table cal?
Because the sensor might be mounted upside-down under a deck, behind
a bulkhead, with axes at 37° to bow. Any "assume X is forward" shortcut
lies. The physical tap is the only source of truth for "which way is
forward" that doesn't require GPS COG (which requires sailing) or a
theodolite.
Sanity checks that live in code
lib/packetizer/ should refuse to publish if:
mount_quat is unset (zeros or all-NaN) — pre-install firmware
- Game Rotation Vector accuracy status is
UNRELIABLE (0) for > 5 s
- Linear acceleration magnitude > 50 m/s² sustained (sensor fault or
catastrophic impact — alert, don't silently drop)
Log every rejection at WARN with counts.
Known gotchas
- BNO085 "tare" feature — there's an on-chip tare that sets the current
orientation as identity. Do not use it. We store
mount_quat in our
own NVS so it's versioned and auditable; relying on chip-internal state
loses that on a reflash
- Z-axis gyro bias drifts with temperature — Game Rotation Vector
compensates internally, but if you derive heading from raw gyro integration
(don't), it will walk several degrees per minute
- The "dock quaternion" is meaningless if the dock is on a current-heeled
mooring — redo calibration if roll/pitch show more than 1° at "rest"