Files
wadamesh/variants/thinknode_m9/M9Compass.h
T
Christopher Van HooseandClaude Fable 5 5a4a5a8ab6 Park the magnetometer when idle, and stop Lua apps ticking into a dark screen
Two answers to "does the compass app matter for battery life", both yes-ish
and both now fixed.

The driver put the QMC6309 into normal mode at boot and left it there, so it
converted continuously from power-on whether or not anything read it: ~1 mA at
100 Hz / OSR 8, forever, on a 2300 mAh battery. It is now parked in suspend
after configuration and woken on demand, with m9CompassIdleTick() (called from
the M9's existing per-loop branch) suspending it again two seconds after the
last read. Waking is a single register write since suspend preserves the
configuration; the waking call reports "nothing fresh" and the caller's next
poll gets data, which callers already handle because the chip may be absent.

Separately, lv_timer_handler() runs unconditionally, so a Lua app's timer kept
firing while the display slept -- GPS Compass polled the magnetometer at 10 Hz
into a dark screen, which would have held the sensor awake even after the fix
above. The host now skips the tick while the screen is off and resumes on wake;
dt comes from millis(), so an app sees one long frame rather than a broken
clock. Documented on the SDK page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 09:01:19 -04:00

53 lines
2.7 KiB
C++

#pragma once
// ThinkNode M9 magnetometer — QST QMC6309 on the peripheral I2C bus (Wire,
// SDA=7 / SCL=6) at 7-bit address 0x7C. The chip sits on the GPIO18 peripheral
// rail with the LCD/GPS, which ThinkNodeM9Board::begin() claims for the whole
// runtime, so it is powered whenever the firmware runs.
//
// This is deliberately a dumb sensor reader: it hands out the raw field vector
// in Gauss, sensor frame, uncalibrated. Hard-iron calibration, the sensor-to-
// screen axis mapping (not documented anywhere — Meshtastic's own M9 driver
// marks its heading offset "must be verified on real hardware" and never uses
// it) and the heading maths live in the consumer (the GPS Compass Lua app),
// where they can be adjusted and persisted per user without a firmware cut.
//
// Register map (QMC6309 datasheet Rev A, cross-checked against SlimeVR,
// madflight and the emfcamp Tildagon drivers — NOT against SensorLib, whose
// setOutputDataRate() writes the ODR into the wrong register):
// 0x00 chip id (0x90) 0x01..0x06 X/Y/Z int16 little-endian
// 0x09 status: bit0 DRDY (cleared by reading 0x09), bit1 OVFL
// 0x0A CTRL1: OSR2[7:5] OSR1[4:3] MODE[1:0] (00 suspend, 01 normal, 11 cont.)
// 0x0B CTRL2: SOFT_RST[7] ODR[6:4] RNG[3:2] SET/RESET[1:0]
// Sensitivity at the ±32 G range used here: 1000 LSB/G (1 mG per count).
#if defined(HAS_M9_COMPASS) && defined(ESP32)
#include <stdint.h>
class TwoWire;
/** Probe + configure the chip on `w`. Safe to call when the chip is absent or
* not yet out of power-on reset: the read path re-probes on its own. Logs one
* line to Serial either way (matches the keyboard bring-up style). */
void m9CompassBegin(TwoWire& w);
/** True once the chip has answered with its id and taken the configuration. */
bool m9CompassPresent();
/** Latest field vector in Gauss, sensor frame, uncalibrated. Reads the chip
* synchronously when a new sample is ready (three short I2C transactions,
* well under 1 ms at 100 kHz on a healthy bus), otherwise returns the cached
* sample while it is younger than a second. False = no chip, bus error, or
* nothing fresh. `overflow` (optional) is set when the chip flagged the
* sample as saturated (an axis beyond ±32000 counts): the values are still
* returned so a consumer can show "away from magnets" rather than "no
* compass", but they are not a usable heading. */
bool m9CompassRead(float* x_gauss, float* y_gauss, float* z_gauss, bool* overflow = nullptr);
/** Call periodically (cheap: one comparison until it acts). Suspends the chip
* a couple of seconds after the last read, so it only draws its ~1 mA while
* something is actually using the compass. The next read wakes it. */
void m9CompassIdleTick();
#endif