mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-10-06 22:57:23 +00:00
A two-axis magnetic heading assumes the device is level. At this latitude the
field dips ~60 degrees, so the vertical component is 1.6x the horizontal one
and tipping the device leaks it into the pair the heading is made from: about
1.5 degrees of heading per degree of tilt. That is what "it drifts" was once
the calibration was sound -- it was the hand holding it.
Driver: variants/thinknode_m9/M9Imu.{h,cpp}, QMI8658 at 0x6B on the peripheral
bus, accelerometer only (the gyro is most of the power budget and nothing here
needs it): +/-2 g at 62.5 Hz with the low-pass on, soft reset with a 160 ms
wait that covers both die variants, and the same idle-suspend as the compass so
it costs nothing when unused. CTRL1's ADDR_AI bit is set and read back -- with
it clear the burst read silently returns six copies of one byte, which looks
like a working sensor reporting nonsense. HAS_M9_IMU -> CAP_IMU ->
wada.sys.accel(), caps().accel.
Axes MEASURED, not guessed, by holding three attitudes and logging:
flat, screen up z = -1.02 -> +Z into the screen (down)
on bottom edge, top up x = +0.97 -> +X at the top edge (forward)
on left edge, right up y = +1.08 -> +Y at the right edge
So the IMU is already in the aerospace body frame, and it agrees with the
magnetometer's independently measured +Z-into-screen. (Meshtastic's M9 driver
passes both sensors through untransformed and mirrors the heading on the sign
of accel Z, so its compass flips when the device is turned over. Not copied.)
Heading now rotates the field back into the horizontal plane using gravity
(NXP AN4248 / ST AN3192) before taking the angle, and reports the tilt angle;
past 55 degrees it says "too steep to read" rather than lying. Calibration
returns to a 3D fit because tilt compensation needs the vertical offset too --
but the instruction is now "turn it every way ON ONE SPOT", and the
accelerometer VERIFIES it: coverage is measured by how far gravity swung, so a
flat spin is refused by name ("turn it nose over tail") instead of silently
fitting a degenerate sphere. Simulated in the harness against a modelled M9 in
a known attitude: offsets recovered exactly, and the heading holds within 3
degrees through 20 degrees of roll and pitch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
49 lines
2.2 KiB
C++
49 lines
2.2 KiB
C++
#pragma once
|
|
|
|
// ThinkNode M9 IMU — QST QMI8658 on the peripheral I2C bus (Wire, SDA=7 /
|
|
// SCL=6) at 7-bit address 0x6B (SA0 grounded). Accelerometer only: it exists
|
|
// here to tell the compass which way is DOWN, so a heading can be corrected
|
|
// for tilt. Nothing needs the gyroscope, and leaving it off is most of the
|
|
// part's power budget.
|
|
//
|
|
// Register map (QMI8658A datasheet Rev D; the QMI8658C shares all of it,
|
|
// differing only in the post-reset delay and some tolerances):
|
|
// 0x00 WHO_AM_I (0x05) 0x01 REVISION_ID (0x7C on the A, other on the C)
|
|
// 0x02 CTRL1: bit6 ADDR_AI (burst reads need it), bit5 BE, bit0 SensorDisable
|
|
// 0x03 CTRL2: aFS[6:4] full scale, aODR[3:0] rate
|
|
// 0x05 CTRL5: accel low-pass 0x08 CTRL7: bit0 aEN
|
|
// 0x2E STATUS0: bit0 aDA 0x35..0x3A AX/AY/AZ int16 little-endian
|
|
// 0x60 RESET (write 0xB0) 0x4D reset-success flag (0x80)
|
|
// At ±2 g the scale is 16384 LSB/g.
|
|
//
|
|
// The part's own axes are right-handed and documented; how the PACKAGE is
|
|
// rotated on the M9's board is not documented anywhere, and no firmware has
|
|
// verified it (Meshtastic passes all three axes through untransformed with a
|
|
// comment saying the handedness is unverified). So this driver reports the
|
|
// sensor frame as-is and the mapping to the device is measured and applied by
|
|
// the consumer, exactly as was done for the magnetometer.
|
|
#if defined(HAS_M9_IMU) && defined(ESP32)
|
|
|
|
#include <stdint.h>
|
|
|
|
class TwoWire;
|
|
|
|
/** Probe + configure on `w`. Safe when the chip is absent or still in reset:
|
|
* the read path re-probes on its own. Logs one line to Serial either way. */
|
|
void m9ImuBegin(TwoWire& w);
|
|
|
|
/** True once the chip has answered with its id and taken the configuration. */
|
|
bool m9ImuPresent();
|
|
|
|
/** Latest acceleration in g, sensor frame. Held level and still, one axis
|
|
* reads about ±1 and the others about 0 — which is what identifies the axes.
|
|
* False = no chip, bus error, or nothing fresh. */
|
|
bool m9ImuRead(float* x_g, float* y_g, float* z_g);
|
|
|
|
/** Call periodically (cheap until it acts). Puts the part in power-down a
|
|
* couple of seconds after the last read, so it costs nothing while no app
|
|
* wants tilt; the next read wakes it. */
|
|
void m9ImuIdleTick();
|
|
|
|
#endif
|